【Linux】详解进程控制 ( fork函数 | 写时拷贝 | 进程退出 | 进程等待 )

fork函数

fork函数初识

 在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

 当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程

fork返回值问题

  • 子进程返回0,
  • 父进程返回的是子进程的pid
  1. 如何理解fork函数有两个返回值问题?

在这里插入图片描述

在这里插入图片描述
  我们常说在fork函数之后就有两个执行流其实不太严谨,其实在fork函数内部就已经有两个执行流了。

  1. 如何理解fork返回之后,给父进程返回子进程的pid,给子进程返回0?
    我们可以这么理解,在生活中我们父亲与孩子的关系是 1 :n的关系(n>=1),任何一个人只有一个父亲,但是一个父亲可以有多个儿子,所以孩子找父亲具有唯一性。这也是我们要给父进程返回子进程的pid,是为了父进程更好的找到子进程

  2. 如何理解同一个id值,怎么可能会保存两个不同的值,让if 、else if同时执行?

	pid_t id = fork();

  我们返回的本质就是写入,所以说谁先返回,谁就写入id值,因为进程具有独立性,所以此时会发生写时拷贝。此时也就会出现同一个id,它的地址是一样的,但是内容不一样。

fork常规用法

 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子
进程来处理请求。
 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因:

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

写时拷贝

 通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图

在这里插入图片描述

进程退出

进程退出码

查看进程退出码:

echo $?
  • ./Test : 运行一个进程
  • echo $? :永远记录最近一个进程在命令行中执行完毕时对应的退出码
    在这里插入图片描述
     我们运行程序发现第一次退出码是1,然后重复命令echo $? 退出码变成0,是因为echo本身就是一个进程,进程正常运行完以后返回0.

如何设定main函数返回值呢?
 如果不关心进程退出码,return 0就行
 如果未来我们是要关心进程退出码的时候,要返回特定的数据表明特定的错误进程退出的时候,对应的退出码标定进程执行的结果是否正确, 退出码的意义:0: success,!0:标识失败,!0具体是几,标识不同的错误

这个退出码就跟考试成绩一样,你考了100分你爸不会问你为什么,而你考了30分你爸就会问你为什么只考了30分,如果是正确的返回0就行,但是如果你代码出错了,可能有多种错误情况,返回的也就不尽相同。

系统的退出码及各自对应的含义:

	strerror(i);

在这里插入图片描述
在这里插入图片描述

进程退出场景

  1. 代码运行完毕,结果正确 – return 0;
  2. 代码运行完毕,结果不正确 – return !0 (退出码这个时候起作用)
  3. 代码没跑完,程序异常了,退出码无意义
    比如你考试作弊被抓住了,那么你的考试成绩无论是多少分都是无意义的。

进程如何退出

  1. main函数return返回
  2. 任意地方调用exit(i)返回(不管在哪里只要调用了exit就直接终止该进程了)
    在这里插入图片描述
  3. _exit(i);
    在这里插入图片描述
     exit函数这边过2秒才会打印而不是打印了再过2秒,是因为我们printf函数没有写‘\n’数据还在缓冲区。
  • exit是库函数,终止进程,会主动刷新缓冲区
  • _exit是系统调用,终止进程,不会刷新缓存区
    在这里插入图片描述
    缓冲区在哪?在用户级的缓冲区

进程等待

僵尸进程

进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法
    杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,
    或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

进程等待的方法

在这里插入图片描述

1. wait方法:
我们通过下面代码和循环打印pid的脚本来解释wait方法:

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
	成功返回被等待进程pid,失败返回-1。
参数:
	输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
  1 #include <stdio.h>  
  2 #include <stdlib.h>                                                                                
  3 #include <unistd.h>                     
  4 #include <sys/types.h>                  
  5 #include <sys/wait.h>                   
  6 int main()                              
  7 {                                       
  8                                         
  9     pid_t id = fork();                  
 10     if(id == 0)                         
 11     {                                   
 12         //子进程                        
 13         int cnt = 5;                    
 14         while(cnt)                      
 15         {                                                                                 
 16             printf("我是子进程: %d, 父进程: %d, cnt: %d\n", getpid(), getppid(), cnt--);  
 17             sleep(1);                                                                   
 18         }                               
 19         exit(0);//子进程退出            
 20     }                                   
 21     //父进程                            
 22     sleep(8);                           
 23     pid_t ret = wait(NULL);             
 24     if(id>0)                            
 25     {                                    
 26         printf("wait sucess:%d\n",ret);  
 27     }                                   
 28     sleep(5);
 29		return 0;	
 30}
//循环打印进程信息
while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep;sleep 1;done

在这里插入图片描述
 代码主要逻辑:创建子进程,然后子进程先自己打印5秒,然后直接exit退出,这时候子进程就进入了僵尸状态,而父进程从一开始就睡眠8秒,当父进程睡眠完以后通过wait回收子进程资源,成功就打印“wait sucess”,这个时候子进程就退出僵尸状态,最后父进程再睡眠5秒然后父进程终止。

2. waitpid方法:
我们通过下面代码来解释waitpid方法:

在这里插入图片描述

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
	当正常返回的时候waitpid返回收集到的子进程的进程ID;
	如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
	如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
	pid:
	Pid=-1,等待任一个子进程。与wait等效。
	Pid>0.等待其进程ID与pid相等的子进程。
status:
	//宏
	WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
	WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
	WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。

 这样使用是错误的:

printf("wait success: %d, status:%d\n", ret,status);

 打印结果:
在这里插入图片描述
 原因是:
 我们的退出场景为以下三种情况:

  • 运行完
    1.代码完,结果对
    2.代码完,结果不对
  • 异常
    代码没跑完,出异常了

获取子进程status:

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特
    ):

在这里插入图片描述

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 
  7 
  8 int main()
  9 {
 10     pid_t id = fork();
 11     if(id == 0)
 12     {
 13         //子进程
 14         int cnt=5;
 15         while(cnt)
 16         {
 17             printf("我是子进程: %d, 父进程: %d, cnt: %d\n", getpid(), getppid(), cnt--);
 18             sleep(1);
 19         }
 20         exit(15); //进程退出
 21     }
 22     // 父进程
 23     int status = 0; // 不是被整体使用的,有自己的位图结构
 24     pid_t ret = waitpid(id, &status, 0);
 25     if(id > 0)  
 26     {  
 27         printf("wait success: %d, sig number: %d, child exit code: %d\n", ret, (status & 0x7F), (status>>8) & 0xFF);
 28     }  
 29   
 30     sleep(5);
 31     return 0;
 32 }  

 (status & 0x7F) 前7位
 (status>>8) & 0xFF)第8-16位

 运行结果:
在这里插入图片描述
 我们写一个空指针解引用:
在这里插入图片描述

//查看异常信息
kill -l

在这里插入图片描述
 我们将循环调整大一点,使用 kill 命令对子进程:
在这里插入图片描述

僵尸进程:
进程进入僵尸状态后它的代码和数据因为程序结束了,所以都不需要了,但是它的退出码和退出信号都会保留在PCB中,PCB不会被删除
在这里插入图片描述
我们也可以不使用status,使用宏:

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
    if(ret > 0)
    {
        // 是否正常退出
        if(WIFEXITED(status))
        {
            // 判断子进程运行结果是否ok
            printf("exit code: %d\n", WEXITSTATUS(status));
        }
        else{
            //TODO异常
            printf("child exit not normal!\n");
        }

如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侠客cheems

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值