【Linux的进程篇章 - 进程终止和进程等待的理解】

本文详细解析了Linux中的fork函数、进程的终止方式(包括正常退出、异常退出和父进程等待),以及进程间的通信机制,重点介绍了wait和waitpid函数在处理子进程退出时的作用。
摘要由CSDN通过智能技术生成

Linux之fork函数、进程终止和等待的理解

前言:
前篇开始进行了解学习Linux的进程优先级、环境变量、地址空间等知识,这篇学习LInux的fork函数、进程终止和等待的理解等相关内容,深入地了解这个强大的开源操作系统。
/知识点汇总/

1、fork函数

前言:

进程:内核的相关管理数据结构(task_struct + mm_struct(内存分配表) + 页表) + 代码和数据

1.1、什么是fork?

直接man fork指令先在手册页查看以下基本信息:
在这里插入图片描述
补充:
在这里插入图片描述

man fork提示no manual entry for fork,则说明你当前没有安装man手册.

解决方法就是

root权限下:yum -y install man-pages

基本概念

fork() 是 Unix/Linux系统中的一个非常重要的系统调用,用于创建一个新的进程,这个新的进程是当前进程的复制品,称为子进程。这个复制过程包括父进程的代码、数据、堆、栈等内容都会被复制到子进程中。

1.2、fork的功能介绍

头文件:
#include <unistd.h>
原型:
pid_t fork(void)
返回值:
-1 ---- fork创建/调用子进程失败
==0 — 返回0是判断创建子进程成功
!=0 — 返回非负整数,表示返回新创建的子进程的进程ID,即,父进程返回的子进程pid

测试代码
在这里插入图片描述
fork语句之后的程序,父子进程共享代码 —> fork创建一个进程,本质是系统中多一个进程;
而多一个进程,也就是多一个进程控制块(task_struct)
父进程的代码和数据是从磁盘加载来的;子进程的代码数据是哪里来的呢?
子进程会默认继承父进程的代码和数据。

我们为什么要创建一个子进程呢?
通常来说,是想要子进程执行和父进程不一样的代码,常规的程序都是单进程,不可能同时跑不同的代码内容。多进程情况下,则是合理的。

对于普通的单进程程序,只能有一个返回值;
对于多进程,那么为什么同一个程序同一个id,会有两个值,即是0又是!=0?
其次,fork怎么会有两个返回值,即返回两次呢?怎么做到的呢?
(涉及虚拟地址空间和父子写时拷贝知识,见这篇文章)

1.3、fork函数返回值的理解

fork也是一个函数,只不过属于系统调用。
然后平常的普通程序中的return在fork执行语句之后,即当执行完fork后,父子代码数据共享,
子进程代码中自然也有了return语句,所以父子进程各自被调度一次,那么自然就返回两次且值可以不同

引出一个概念:理论上,子进程被创建后是空的,自然其代码和数据就从父进程哪里拷贝获取,
就比如有window的任务管理器,有两个子进程由一个父进程创建,当此时关系结束一个子进程会影响其它进程?
当然不会的,逻辑上是父子等关系,实际上是分裂独立的。

所以进程一定要具备的特点就是

具备独立性,即一个进程出现问题,绝不能影响其它进程。

进程 = 内核task_struct结构体(唯一的pid) + 代码程序(只读/共享的)和数据(父子数据各自独立,原则上数据需要分开)每一个进程都有一个自己唯一的标识符,叫做进程pid

fork()函数返回值:

1.子进程返回0
2.父进程返回子进程pid

为什么父进程返回的是子进程的pid,而子进程返回的是0?
因为父进程返回子进程的pid是对它更好的进程管理,而子进程返回值是判断创建子进程是否创建成功。
即:为了父进程对子进程进行标识,方便管理。

fork的常规用法:

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

fork()调用失败的原因:

1.系统中已有太多的进程。
2.实际用户的进程数超过了限制。

1.4、fork函数的总结

总结:

1.fork()函数在Linux中用于创建一个新的进程,这个新进程是当前进程的复制品,称为子进程。
2.fork()函数的特点在于它只被调用一次,但却能返回两次:一次在父进程中,一次在子进程中。

关于fork()函数的返回值,具体总结如下:

1.在父进程中,fork()函数返回新创建的子进程的进程ID(PID)。
2.这个PID是一个非负整数,用于唯一标识子进程。
3.在子进程中,fork()函数返回0。这是因为在子进程的上下文中,它自己是新创建的进程,所以没有其他的进程ID可以返回。返回0表示子进程成功创建。
4.如果fork()函数执行过程中发生错误,例如由于系统资源不足而无法创建新的进程,那么它会返回一个负值。这个负值通常表示特定的错误码,可以通过检查这个值来确定具体的错误原因。
5.需要注意的是,fork()函数创建的子进程是父进程的副本,包括父进程的代码、数据、堆、栈等内容都会被复制到子进程中。但是,这两个进程有不同的进程ID,并且它们从fork()函数的返回点开始独立执行。
6.因此,通过检查fork()函数的返回值,可以在父进程和子进程中执行不同的代码路径,从而实现并发执行或创建多进程程序。

2、进程的终止

2.1、终止是在做什么?

释放曾经的代码和数据所占据的空间;理解为释放内核的数据结构 ,否则–>僵尸态

2.2、进程终止的3种情况

测试代码
在这里插入图片描述

echo $?
内建命令,打印的都是bash本身内部的变量数据。通常0:成功 非0:标识失败
其中?属于bash维护的一个变量
表示的是,父进程bash获取到的,最近的一个子进程退出的退出码
目的:
告诉父进程(关心结果),我把任务完成得怎么样
0:标识成功
非0:标识失败
非0的数值。一方面可表示失败,另一方面可表示失败的原因。即都有相应的错误描述。

测试代码
在这里插入图片描述
在这里插入图片描述

父进程为什么要得到子进程的退出码呢?
答:要知道子进程的退出情况(成功、失败以及失败的原因),本质退出的情况信息,告诉给用户的。

退出码可以使用默认的,同时也可以自定义。
测试代码
在这里插入图片描述

3、进程的终止

3.1、进程终止的三种情况

a、代码跑完,进程正确;
b、代码跑完,进程不正确。

可以通过进程的退出码决定。反馈原因:系统的 && 自定义退出码

c、代码异常终止,提前退出

操作系统发现了你的进程做了不该做的事情,就会被OS自主杀掉该进程。而且一旦出现异常,退出码就没有意义了。

需要关心的是为什么会出异常呢?

1.进程出异常,本质上是由操作系统(OS)向进程发生了一个信号。
2.我们可以查看进程退出的时候,退出的信号是多少,就可以判断,我的进程为什么异常了。
常见情况有:
a、段错误
b、栈溢出

3.2、判断是否成功的顺序

1.先确认是否是异常。
2.不是异常,就一定是代码跑完了。看退出码是否符合预期就行。

结论

衡量一个进程退出,我们只需要两个数字(反馈给父进程):退出码和退出信号(2^2)。
task_struct()会包含一些字段信息。用于匹配反馈。
比如:exit_code和exit_signal
0 0
0 !0
!0 0
!0 !0
由退出码+退出信号 --》退出进程退出的原因。

3.3、如何终止

a、main()函数return 表示进程终止(非main函数,return,函数结束)
b、代码调用exit()函数,可在代码的任意位置退出,并冲刷缓冲区。
void exit(int status);
c、_exit()–系统调用,与exit的区别不会冲刷缓冲区。
void _exit(int status);
注意:这里所说的缓冲区,不是内核的缓冲区。

4、进程等待

4.1、直接先看结论

任何子进程,在退出的情况下,一般必须都要有父进程进行等待,进程在退出的时候,如果父进程不管不顾(不等待),直到退出进程,导致状态Z(僵尸状态),造成内存泄漏的问题。

为什么?

1.通过父进程等待,解决子进程退出的僵尸问题,回收系统资源(一定要注意)
2.获取子进程的退出信息,知道子进程是因为什么原因退出的(前提是如果需要的话,不是必须的)

4.2、进程等待怎么处理呢?

答:通过两个函数wait()函数和waitpid函数

阻塞等待测试代码
在这里插入图片描述

查看指令

while :; do ps ajx | head -1 && ps ajx | grep wait_process | grep -v
grep;sleep 1;done

说明

头文件:
#include <sys/types.h>
#include <sys/wait.h>
函数原型:
pid_t wait(int* status);
返回值:
pid_t:等待成功时,子进程的pid给返回
参数:
status:等待父进程中,任意一个子进程的退出
功能:
用于解决子进程僵尸进程的。(defunct僵尸态)

如何理解阻塞等待子进程呢?
如果子进程没有退出,父进程就会一直处于进行阻塞等待的状态(死等)
子进程本身就是软件,父进程本身就是等待某种软件的条件就绪。

头文件:
#include <sys/types.h>
#include <sys/wait.h>
函数原型
pid_t waitpid(pid_t pid,int* status,int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程id;
如果设置了选项,而调用waitpid发现没有已退出的子进程收集,则返回0;
如果调用中出错,则返回error会被设置成相应的值以指示错误所在。
1.正常返回时,返回的收集到的子进程id;
2.如果设置了option选项参数为WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
3.如果调用失败,则返回-1,且errno会被设置为相应的值得以指示错误所在。
参数:
pid=-1:等待任意个子进程的pid,与wait相同;
pid>0:等待其进程id与pid相等的子进程;

参数二说明

status具有特定的格式。(低16位)
1.输出型参数,表示的是输出信息
高八位 – 退出状态
低八位 – 终止信号
2.参数二:status
WIFEXITED(status):若为正常终止子进程的状态,则返回真 — 作用是查看进程是否正常退出
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码 ---- 查看进程的退出码
WNOHANG:若pid指定的子进程没有进程,则waitpid()函数返回0,不予以等待,若正常结束,则返回该子进程的ID。

非阻塞等待测试代码
在这里插入图片描述

如果子进程没有退出,而父进程在进行执行waitpid进行等待,阻塞等待(scanf()…的原理)。
所以阻塞就是进程阻塞。
直到等待某种条件的发生(比如子进程的退出)
那么一些非阻塞等待,属于服务器宕机。
阻塞等待和非阻塞的等待本质区别就是判断是否有返回值,是否达成某成条件。

即:pid_t > 0:等待是成功的,子进程退出了,并且父进程回收成功;
pid_t < 0:等待失败了;
pid_t == 0:检测是成功的,只不过子进程还没有退出,需要你下一次进行重复等待。

  • 10
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值