进程创建、终止、等待

一、进程创建

首先,我们需要认识一下fork函数
返回值:子进程返回0,父进程返回子进程的id,出错返回-1.
进程创建、终止、等待
进程调用fork,当控制转移到内核中的fork代码,内核做下面几件事情:
1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝给子进程
3.添加子进程到系统进程列表中
4.fork返回,开始调度器调度
进程创建、终止、等待
一个进程fork后,会有两个相同的二进制代码,都运行到相同的位置,这两个进程的代码共享,父子不再写入的时候,数据也是共享的,当一方试图写入,便以写时拷贝的方式各自存放在一个副本中。

写时拷贝
C++中写时拷贝可以理解为“写的时候才去分配空间”,实际上是一个拖延战术。
进程创建、终止、等待
写时拷贝其实是通过引用计数实现的,在分配内存空间的时候多分配4个字节,用来记录有多少个指针指向这块空间,当有新的指针指向这块空间的时候,引用计数+1,当要释放这块空间的时候,引用计数-1,直到为0才真正释放了这块空间。当有的指针要改变这块空间的值时,为这个指针分配自己的空间。
那么,在linux下的写时拷贝的原理是什么呢?
进程创建、终止、等待
进程创建、终止、等待
vfork函数
和fork函数一样,vfork也是用来创建子进程的,但区别是:
1.vfork创建的子进程和父进程共享地址空间,fork的子进程具有独立的地址空间
2.vfork保证子进程先运行,在它调用exec会exit之后父进程才可能被调度运行
进程创建、终止、等待
进程创建、终止、等待
子进程改变了父进程的变量值,因为子进程在父进程的地址空间运行。

二、进程终止

进程终止的几个情景:
1.代码运行完毕,结果正确
2.代码运行完毕,结果错误
3.代码异常终止

进程常见的退出方法
1.正常退出
a.从main函数中执行return
b.调用exit函数
c.调用_exit函数

2.异常退出
a.调用abort函数
b.进程收到某个信号,而该信号使程序终止(Ctrl-C)

几种退出方法的比较:
1.exit和return的区别
exit是一个函数,含参数,exit执行完后把控制权交给系统
return是函数执行完后的返回。return执行完后把控制权交给调用函数

2.exit和abort的区别
exit是正常终止进程
abort是异常终止

_exit函数&exit函数
进程创建、终止、等待
我们首先需要知道这两个函数的作用,这两个函数都是都是用来终止进程的。status定义了进程的终止状态,父进程通过wait来等待。status为0,表示非正常退出,返回0;为1表示正常退出,返回1。

1.联系
当程序执行到这两个函数时,进程都退出,即关闭进程所打开的文件描述符,释放已占内存和其他资源。

2.区别
_exit函数在头文件unistd.h中,exit在头文件stdlib.h中。
执行_exit函数后,控制权立即返回给内核,而exit函数要先执行一些清除工作,然后才将控制权返回给内核。
_exit函数不会刷新I/O缓冲区,可能造成数据丢失,而exit函数是在_exit函数上的一个封装,在调用_exit之前,会先刷新I/O缓冲区,保证了数据的完整性。

那么什么是缓冲区呢?
在LinuxC标准函数库中,运用了一种叫做“I/O缓冲区”的技术。对于每一个打开的文件,都在内存开辟了读写缓冲区。在读文件时,会连续的从硬盘中读取若干条数据到缓冲区,下次再读文件时直接从缓冲区获取数据;同样,在写文件时,先把数据写到缓冲区,等到缓冲区的数据量达到一定程度或者接收到特殊的指令,再将缓冲区中的数据一次性写到硬盘中。通过这种技术,减少了程序访问硬盘的次数,提高了运行效率。
进程创建、终止、等待
这里的处理函数又是什么呢?
进程创建、终止、等待
其中参数是一个函数指针,指向处理函数,该函数无参无返回值。很多时候我们需要在程序退出的时候做一些诸如释放资源的操作,但程序退出的方式有很多种,比如main函数运行结束、在程序的某个地方用exit函数结束程序、用户通过Ctrl-C终止程序等,因此需要有一种与程序退出方式无关的方法来进程程序退出时的必要处理。

总结:
_exit和exit的最大区别在于exit在调用_exit之前需要检查文件的打开情况,把文件缓冲区的内容写回文件。
我们来举个例子:
进程创建、终止、等待
对比以上三种情景以及输出的结果,我们更加清楚了一点,_exit函数结束程序的时,没有做刷新缓冲区的操作,导致我们想要输出的”world”数据丢失。注:函数遇到\n时会刷新缓冲区。

三、进程等待

我们之前在进程概念中就提到了僵尸进程,大家可以回顾一下僵尸进程是怎么来的。https://blog.csdn.net/zwe7616175/article/details/79519466
父进程通过wait函数或waitpid函数等待子进程,回收子进程的资源,获取子进程的退出信息。
进程创建、终止、等待
1.wait函数
参数:获取子进程的退出状态,不关心则可设置为NULL。
返回值:成功返回被等待进程的pid,失败返回-1。

2.waitpid函数
参数:
pid:
pid=-1,等待任一个子进程,与wait等效
pid>0,等待其进程ID与pid相等的子进程
status:
WIFEXITED(status):查看进程是否正常退出
WEXITSTATUS(status):查看进程的退出码
options:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回该子进程的ID。

返回值:
当正常返回时,返回子进程的pid。
若设置了WNOHANG,而pid指定的子进程没有结束,则返回0。
如果调用出错,则返回-1。

获取子进程status(可将其看做位图,只研究低16位)
wait和waitpid都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态。
如果不为空,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
进程创建、终止、等待

测试代码
进程创建、终止、等待
进程创建、终止、等待
如图所示,正常退出时,打印的是子进程的status的次低8位(进程的退出状态),异常退出时,打印的是子进程的status的低8位(终止信号)。