进程线程 笔记01
进程
一、什么是进程
-
进程是一个程序的一次执行的过程 ./a.out
-
进程是一个独立的可调度的任务
-
进程是程序执行和资源管理的最小单位(进程是资源分配的最小单位)
-
进程和程序的区别
- 程序:静态的, 保存在磁盘上的有序指令的集合,无执行的概念
- 进程:动态的, 一个程序的执行过程,进程有生命周期(创建,运行,消亡),进程(代码段 数据段 堆栈)
-
进程的特性
- 并发性 //允许统一时间同时运行多个进程
- 动态性 //进程有生命周期,创建 运行 消亡
- 交互性 //scanf输入 与用户交互
- 独立性 //每个进程都有自己独立地址空间
-
进程分类
- 交互式进程、批处理进程、守护进程
二、查看所有进程命令
ps aux
显示所有进程的信息
top
动态显示进程信息 ,按q退出
三、进程关系树(init、PID)
pstree
列出进程关系树 最先启动进程 init(PID 1号)
四、在程序中如何获取当前进程的pid:getpid()和getppid()
//头文件
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
// 获取当前进程的PID
pid_t getppid(void);
// 获取当前进程的父进程的PID 中间的p 是parent的缩写(父母)
typedef int pid_t;
// 类型重定义
代码演示
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
pid_t pid = getpid();
pid_t ppid = getppid();
printf("当前进程的PID是%d\n",pid);
printf("当前进程的父进程的PID是%d\n",ppid);
return 0;
}
五、进程说明
- pid号的顺序:顺序增加
- pid的回收:每个进程运行结束后,系统会回收进程ID(PID),但不会立即使用,进程的PID号不是无限的
- 进程与终端:所有在终端上运行的进程其父进程都为终端,终端结束,其终端上的进程也结束
六、进程状态(就绪态 运行态 阻塞态 见图)
七、如何创建一个子进程
1.fork()函数介绍
typedef int pid_t;
// 类型重定义
#include <unistd.h>
头文件
pid_t fork(void);
创建一个子进程
功能:
用于创建一个子进程,当执行完fork 就会出现一个新进程(新进程称为子进程,原来的进程称为父进程)fork 执行完 子进程会复制 父进程几乎所有东西,并且从fork后面语句开始执行,调用一次,返回两次
代码演示
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
fork();//执行完fork函数,就会产生一个子进程,子进程从fork函数的下一行代码开始执行
printf("hello world!\n");
return 0;
}
linux@ubuntu:~$ ./a.out
hello world!
hello world!
linux@ubuntu:~$
两遍hello world 一个是父进程打印的,一个是子进程打印的
一个是父进程 一个是子进程
最先走的是父进程,执行完fork之后,才有了子进程
父子进程谁先执行,不确定,是随机的,由cpu决定
可以使用getppid和getpid来获取父子进程的pid值
为什么子进程里面,父进程的打印是1 ?
执行完fork之后,才有了子进程,父子进程谁先执行,不确定,由cpu决定,随机之后,cpu先处理的是 父进程,父进程先结束了,父进程结束后,子进程由init进程接管(1 福利院)
那么如何让子进程先结束,父进程后结束呢?
可以在父进程中加个延时
2.如何判断,父进程和子进程
通过fork函数的返回值可以区分当前的进程是父进程还是子进程
pid_t pid = fork();
pid == 0 说明当前的进程是子进程
pid > 0 说明当前的进程是父进程, 并且返回值pid 是子进程的PID
pid == -1 说明创建一个子进程失败
代码演示
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();//执行完fork函数,就会产生一个子进程,子进程从fork函数的下一行代码开始执行
if(pid == -1)
{
perror("创建子进程失败");
return -1;
}
else if(pid == 0)//说明当前进程是子进程,子进程会进入该条件分支
{
printf("子进程!!\n");
printf("子进程的PID:%d 父进程的PID:%d\n",getpid(),getppid());
printf("子进程中fork函数的返回值是:%d\n",pid);
}
else if(pid > 0)//说明当前进程是父进程,父进程会进入该条件分支
{
sleep(1);//延时1s,cpu不可能等待1s,cpu会立刻取处理其他的任务,为了让子进程先结束,父进程后结束
printf("父进程!!\n");
printf("父进程的PID:%d 父进程的父进程的PID是%d\n",getpid(),getppid());
printf("父进程中fork函数的返回值是:%d\n",pid);
}
printf("hello world!!\n");
return 0;
}
linux@ubuntu:~$ ./a.out
子进程!!
子进程的PID:15211 父进程的PID:15210
子进程中fork函数的返回值是:0
hello world!!
父进程!!
父进程的PID:15210 父进程的父进程的PID是15102
父进程中fork函数的返回值是:15211
hello world!!
linux@ubuntu:~$
八、for 循环创建多进程实例
#include <stdio.h>
int main()
{
int i;
for(i = 0; i < 3; i++)
{
fork();
}
printf("11111111111\n");
}
思考:11111111111打印了多少次?
九、_exit和exit的区别
exit()函数——在结束程序前,会刷新缓存区
- 功能:
用于结束进程,当程序执行到exit函数时,程序就结束 exit(0); - 头文件与函数原型
#include <stdlib.h>
void exit(int status);
- 参数:
status是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束,其它值表示出现了错误,进程非正常结束
_exit()——在结束程序前,不会刷新缓存区
- 功能:
用于结束进程,当程序执行到_exit函数时,_exit(0); - 头文件与函数原型
#include <stdlib.h>
void _exit(int status);
status结束状态,0,表示正常结束
#include
#include
#include
int main(int argc, const char *argv[])
{
printf("hello world");//注意此处没有\n
_exit(0);//hello world不会被打印,因为_exit函数后,直接结束程序,不刷新缓存区
printf("11111111111111\n");//1111111不会打印,因为执行完_exit程序结束
return 0;
}
十、资源回收
父进程不应该比子进程先结束,父进程要负责回收子进程的资源,
孤儿进程
父进程先结束,子进程未结束,此时子进程会由1号 init进程接管(福利院)此时的子进程称为孤儿进程
僵尸进程
子进程先结束,父进程未结束,但是父进程又没有调用wait族函数来回收子进程的资源,此时的子进程称为 僵尸进程
linux查看进程信息
ps aux | grep a.out
发现子进程的状态是 Z+ ,僵尸进程
如何回收子进程资源
调用wait族函数,回收子进程的资源
wait()函数
-
功能:
wait(当父进程执行此函数时,父进程阻塞等待子进程结束)父进程执行到wait函数就会阻塞,等待子进程的结束,一旦有一个子进程结束,wait将结束阻塞当子进程结束时,父进程会通过wait函数回收子进程的资源 -
头文件及函数原型
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status);
-
函数参数
status是一个整型指针,指向的对象用来保存子进程退出时的状态。 status若为空,表示忽略子进程退出时的状态 status若不为空,表示保存子进程退出时的状态 -
函数返回值
成功:pid_t //返回值是结束的那个子进程的id
失败:-1
//子进程延时仍无效
//对阻塞功能和返回值的验证(延时无效)
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();//执行完fork函数,就会产生一个子进程,子进程从fork函数的下一行代码开始执行
if(pid == -1)
{
perror("创建子进程失败");
return -1;
}
else if(pid == 0)//说明当前进程是子进程,子进程会进入该条件分支
{
sleep(3);//在子进程加上延时,本来想让父进程先结束,但是目的仍未达到
printf("子进程PID是:%d\n",getpid());
}
else if(pid > 0)//说明当前进程是父进程,父进程会进入该条件分支
{
int ret = wait(NULL);//阻塞等待子进程的结束,直到子进程结束,wait函数才会解除阻塞,回收资源,继续向下执行
printf("ret is %d\n",ret);//ret的值,就是结束的那个子进程的PID
printf("父进程PID是:%d\n",getpid());
}
return 0;
}
waitpid()函数
- 功能:指定等待子进程的结束
- 头文件及函数原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options); //等待指定的子进程结束
int options //你可以选择让 waitpid()函数阻塞等待 也可以选择 非阻塞等待
- 参数说明
pid_t pid
pid > 0 回收进程的ID
int *status
同wait
options:
WNOHANG
表示不阻塞,waitpid不阻塞而立即返回,此时值为0,结束后返回值为结束子进程pid
0
阻塞 - 返回值:
大于0: 已经结束运行的子进程进程号
等于0: 使用WNOHANG且没有子进程退出
等于-1:出错 - 应用举例
waitpid(31111, NULL, 0);
//指定阻塞等待PID 31111 子进程结束
wait(NULL);
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();//执行完fork函数,就会产生一个子进程,子进程从fork函数的下一行代码开始执行
if(pid == -1)
{
perror("创建子进程失败");
return -1;
}
else if(pid == 0)//说明当前进程是子进程,子进程会进入该条件分支
{
sleep(3);//在子进程加上延时,本来想让父进程先结束
printf("子进程PID是:%d\n",getpid());
}
else if(pid > 0)//说明当前进程是父进程,父进程会进入该条件分支
{
//父进程里fork函数的返回值>0,值就是子进程PID
int ret = waitpid(pid,NULL,0);//第三个参数0,表示阻塞,阻塞等待子进程的结束,直到子进程结束,waitpid函数才会解除阻塞,继续向下执行
printf("ret is %d\n",ret);//ret的值,就是结束的那个子进程的PID
printf("父进程PID是:%d\n",getpid());
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid == 0) //子进程活了5s
{
sleep(5);
exit(0);
}
else if(pid > 0)
{
int ret;
do
{
//第三个参数是WNOHANG说明waitpid函数,是非阻塞的
//调用waitpid函数后,如果 pid这个子进程没有结束,那么waitpid函数的返回值是0
//当子进程结束后,再调用waitpid()函数时候,返回值 就是结束的哪个子进程的PID
ret = waitpid(pid,NULL,WNOHANG);
if(ret == 0) //如果返回值是0,说明子进程活着
{
printf("子进程活着!!\n");
}
else if(ret == pid)//说明子进程已经结束了
{
printf("子进程死了!1\n");
}
sleep(1);//延时1s,循环,每隔1s,检查一次子进程是否结束
}while(ret != pid); // ret == pid的时候,循环结束
}
return 0;
}
打印输出结果:
子进程活着!!!
子进程活着!!!
子进程活着!!!
子进程活着!!!
子进程活着!!!//打印5次,是因为子进程活了5s,每隔1s检查一次子进程是否结束
子进程跪了!!!
十一、exec函数族
功能
用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
execl
execv
execle
execve
execlp
ececvp
头文件及函数原型
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...); //...表示多个参数
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
l //list 缩写 希望接收以逗号分隔的参数列表,列表以NULL作为结尾标志
p //PATH 系统会按照PATH环境变量进行路径进行查找命令
e //enviroment 缩写 指定当前进程所使用的环境变量
v //vertor 缩写 参数传递为以NULL结尾的字符指针数组
六个函数返回:若出错则为- 1,若成功则不返回
参数说明
char * file
执行程序的名称(可以包含路径"/home/linux/hello")
char *arg, ...
具体执行这个程序,需要在命令行传递的参数
返回值
出错返回-1
成功无返回值
//gcc -o hello hello.c
////hello.c//////////////
#include <stdio.h>
int main(int argc, const char *argv[])
{
if(argc != 3)
{
printf("忘记传递参数了!!\n");
return -1;
}
printf("新的程序hello被执行了!!\n");
printf("argv[1] is %s\n",argv[1]);
printf("argv[2] is %s\n",argv[2]);
return 0;
}
//gcc -o hello hello.c
////////test.c/////////////////////
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
//第一参数,是执行新程序的名称包含路径
//第二参数,开始是这个程序 在命令行的执行过程
//注意,最后的参数一定是NULL,表示参数结束
execlp("/home/linux/hello","./hello","aaaa","bbbb",NULL);
printf("hello world!!\n");//会被打印吗?不会被打印
//没有打印,因为执行完execlp后,当前进程的空间的所有内容由
//新的程序 hello,替换,重新开始从hello程序main函数开始执行
return 0;
}
7 条评论
写得好好哟,我要给你生猴子!
滴!学生卡!打卡时间:下午6:55:42,请上车的乘客系好安全带~
骚年,我怀疑你写了一篇假的文章!
滴!学生卡!打卡时间:下午4:51:59,请上车的乘客系好安全带~
写得好好哟,我要给你生猴子!
滴!学生卡!打卡时间:下午3:44:49,请上车的乘客系好安全带~