hello,各位读者大大们你们好呀
系列专栏:【Linux的学习】
本篇内容:fork函数初识;写时拷贝;fork常规用法;fork调用失败原因;进程终止;进程常见退出方法;进程等待
⬆⬆⬆⬆上一篇:程序地址空间
作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-
#include
pid_t fork(void);
fork从已存在进程中创建一个新的进程。新进程为子进程,而原进程为父进程
返回值:子进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表中
fork返回,开始调度器调用
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定
#include
2 #include <unistd.h>
3 int main()
4 {
5 int ret=fork();
6 if(ret>0)
7 {
8 //父进程
9 while(1)
10 {
11 printf("I am parent task, my pid is %d,my ppid is %d\n",getpid(),getppid());
12 sleep(1);
13 }
14 }
15
16 if(ret==0)
17 {
18 //子进程
19 while(1)
20 {
21 printf("I am child task,my pid is %d,my ppid is %d\n",getpid(),getppid());
22 sleep(1);
23 }
24 }
25 return 0;
26 }
父子代码共享,父子在不写入时,数据也是共享的,当任何一方试图写入,便以写时拷贝的方式各自一份副本
#include
2 #include <unistd.h>
3 int main()
4 {
5 int ret=fork();
6 int num=100;
7 if(ret>0)
8 {
9 //父进程
10 while(1)
11 {
12 printf("I am parent task, my pid is %d,my ppid is %d,num=%d,&num=%p\n",getpid(),getppid(),num,&num);
13 sleep(1);
14 }
15 }
16
17 if(ret==0)
18 {
19 //子进程
20 num=50;//在执行子进程时修改num的值
21 while(1)
22 {
23 printf("I am child task,my pid is %d,my ppid is %d,num=%d,&num=%p\n",getpid(),getppid(),num,&num);
24 sleep(1);
25 }
26 }
27 return 0;
28 }
可以发现在子进程中修改了num的值,但是父进程却没有变化,并且他们的地址还是相同的,因此这里的地址不是真实的物理地址,而是虚拟地址,这一个我在另一个博客中提到过,不理解的可以去看一下,其实本质上就是发生了写时拷贝,当还没有修改num的值时,父进程和子进程共享同一份代码和数据,但是当子进程修改num的值时,对于子进程而言,页表所指向的物理地址发生了改变,在另一个地方修改了num的值。
①一个父进程希望复制自己,使父子进程同时执行不同的代码段
②一个进程要执行一个不同的程序
①系统中有太多进程
②实际用户的进程超过了限制
在进程退出时,会有三种情况:代码运行完毕,结果正确;代码运行完毕,结果不正确;代码异常终止
代码异常终止本质上就是因为某些原因,导致进程收到了来自操作系统的信号
首先讲一下如果查看进程退出码
使用命令echo $?
上图用了最简单的一个代码展示了如果查看程序退出码,return返回的就是程序的退出码,程序的退出码是为了供用户进行程序退出的健康状态的判断,也就是查看代码运行完毕,结果是否正确
void _exit(int status);
头文件:#include
参数:status定义了进程的终止状态,父进程通过wait来获取该值(这一个我们后面再讲)
#include
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <stdlib.h>
5 void Function()
6 {
7
8 printf("I am a function\n");
9 _exit(10);
10 }
11 int main()
12 {
13 Function();
14 printf("I am a task\n");
15 exit(-1);
16 }
可以看见我们的进程直接结束了,并且通过exit,我们的退出码为10
再观察这个代码,去掉了换行,来看一下他的结果
可以发现它并不会打印出我们想要的内容,但是exit并不会这样,exit会在此前先刷新缓冲区,具体往后看
exit(int code):code代表就是进程的退出码
需要使用头文件#include
exit最后也会调用_exit,但是在调用exit之前,还做了其他工作:
①执行用户通过atexit或on_exit定义的清理函数
②关闭所有打开的流,所有的缓存数据均被写入
③调用_exit
可以发现,进程直接在exit(0)处结束了,并且他的退出码是0
注意观察这个代码,与之前相比,少了换行,但是最后还是打印出了内容,这也就印证了上面的结论
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时,函数会将main的返回值当做exit的参数,这个就不多讲了
①子进程退出,父进程不管不顾,就可能造成“僵尸进程”的问题,进而造成内存泄露,进程一旦变成“僵尸进程”,kill也无能为力
②父进程派给子进程的任务完成的如何,我们需要知道
③父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
wait方法
pid_t wait(int* status);
返回值:成功返回被等待进程的pid,失败返回-1
参数:输出型参数,获取子进程退出状态,不关心则可以设置NULL
可以发现,父进程一直等到子进程被杀掉才继续往后执行,说明wait一直在等待子进程结束,通过查看进程的信息,可以发现没有僵尸进程的存在,这是wait起了作用
waitpid方法
pid_t waitpid(pid_t pid,int* status,int options);
返回值:
①当正常返回时,waitpid返回收集到的子进程的进程pid
②如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
③如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
参数:
pid:
pid=-1:等待任意一个子进程。与wait等效
pid>0:等待其进程pid与pid相等的子进程
status:
不要当做一个完整的整数,而应该看做位图
wait和waitpid都有一个status参数,该参数是一个输出型参数,由操作系统填充,如果传递NULL,表示不关心子进程的退出状态信息
我们暂时先把waitpid的第三个参数写成0,这里主要研究的是status,前面也说了,他是个输出型参数,我们只研究status低16位比特位
当信号为0时表示没有收到信号,正常退出
观察上面的代码和现象,(status>>8)&0xFF表示的是右移8位,然后再按位与,正好得出的结果是status低16位的前8位,得到了退出码,而status&0x7F也是如此,得出了所接收到的信号。在上面的代码中,使用kill -9终止了子进程的运行,所以说子进程收到了来自操作系统的信号,因此打印出的终止信号为9。同时要注意一个点,当终止信号不为0的情况下,无论它的退出码是多少,都是没有意义的。
接下来讲一下waitpid的第三个参数,当他为WNOHANG时,若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回子进程的id,也就是说父进程在没有接收到子进程的退出信息时,也可以先做别的事情,等到子进程结束了,再接收其退出信息。
只要当waitpid的返回值为0时,他就会执行所对应的代码
这里再介绍一种获取程序退出码和终止信号的方法
WIFEXITEND(status):若为正常终止子进程返回的状态则为真。(查看进程是否正常退出)
WEXITSTATUS(status):若WIFEXITED非0,提取子进程的退出码
在之前的代码上稍作了修改
Linux进程控制(一)的知识大概就讲到这里啦,博主后续会继续更新更多Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!