进程创建
fork函数
功能:从已存在的进程中创建出一个新的进程。新进程叫子进程,原进程叫父进程。
#include<unistd.h>
pid_t fork();
返回值:子进程返回0;父进程返回子进程pid;创建失败返回-1。
进程调用fork(),当控制转移到内核中的fork代码后,内核会做:
- 分配新的内存块和内核数据结构给子进程。
- 将父进程部分数据结构内容拷贝给子进程。
- 添加子进程到系统进程列表中。
- fork返回,开始调度调度器。
当一个进程调用fork后,就有两个二进制代码相同的进程,而且他们都运行到相同的位置,但每个进程都将可以开始他们自己的旅程:
注意:
- fork之前,父进程独立执行,fork之后,父子进程分别执行。
- fork之后,谁先执行完全由调度器决定。
fork函数返回值
- 子进程返回0;
- 父进程返回子进程的pid。
写时拷贝
通常情况下,父子进程代码共享,两者都不写入时,数据也是共享的;当任意一方写入时便以写时拷贝的方式,给子进程开辟一个新空间存储被修改的原数据。
进程终止
进程退出场景
- 代码运行完毕,结果正确:
- 代码运行完毕,结果不正确;
- 代码异常终止。
进程常见退出方式
正常退出:
- 从main(return)退出(退出前刷新缓冲区)
- 调用exit(库函数接口,退出前刷新缓冲区,任意地方退出)
- _exit(系统调用接口,从任意地方退出,释放所有函数)
异常退出:
- ctrl+c,信号终止
return退出
return是一种常见的退出进程的方法。执行return n等同于执行exit(n),因为调用了main的运行时,函数会将main的返回值当作exit的参数。
_exit函数
#include<unistd.h>
void _exit(int status);
参数:status定义了进程的终止状态;父进程通过wait来获取该值
- 说明:虽然status是int,但是只有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值时225。
exit函数
#include<unistd.h>
void exit(int status);
exit最后也会调用_exit,但在调用之前,还做了其他工作:
- 执行用户通过atexit或on_exit定义的清理函数;
- 关闭所有打开的流,所有的缓存数据均被写入:
- 调用_exit。
进程等待
进程等待的必要性
- 之前了解到,子进程退出时如果父进程不管不顾,就会造成“僵尸进程”的问题,进而造成内存泄漏。
- 另外,进程一旦变为僵尸进程,就会刀枪不入,连“kill-9”都无能为力,谁都没有办法杀死一个已经死去的进程。
- 最后,我们需要知道父进程派给子进程的任务完成的如何,如:子进程运行完成,结果对还是不对,或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。
进程等待的方法
wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status)//*status是子进程退出的返回值
//返回值:成功返回被等待进程的pid,失败返回-1.
//参数:输出型参数,获取子进程的退出状态,不关心也可以设置成null.
阻塞接口:等待任意一个子进程退出。若没有子进程退出,则一直等待,直到子进程的返回值放入到参数的status中。
waitpid方法
pid_t waitpid(pid_t pid,int *status,int options)
返回值:
-当正常返回时waitpid返回收集到的子进程的进程ID;
-如果设置了选项WNOHANG,而调用中wait发现没有已退出的子进程可以收集,则返回0;
-如果调用中出错,则返回-1,这是errno会被设置成相应的值以指出错误何在
参数:
pid:
=1,等待任一个子进程。与wait同效。
>0,等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否正常退出)
WEXITSTATUS(status):若WIFEXITED非零,则提取子进程退出码。(查看进程退出码)
options:
WNOHANG:若pid的子进程没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回该子进程的ID。(将waitpid设为非阻塞)
- 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立即出错返回。
获取子进程status
- wait和waitpid都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递null,则表示不关心子进程的退出状态。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- status不能简单地当作整形看待,可以当作位图来看待,如下图:(之研究status的低十六比特位)