文章目录
进程创建
fork()
fork函数在之前的文章中也已经提到过了。其主要作用是从已存在的进程中创建一个新的进程,也就是新建的进程为子进程,原进程为父进程
当一个进程调用fork函数后,内核会做几件事:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝给子进程
- 添加子进程到系统进程列表中
- fork返回后,开始调度器调度
下面来看看进程创建的一段简单代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
pid_t id = fork();
if(id == 0){
printf("I am child process, pid = %d, ppid = %d\n", getpid(), getppid());
}
printf("I am parent process, pid = %d, ppid = %d\n", getpid(), getppid());
return 0;
}
当其返回值为0时,说明创建出了子进程。
进程退出
进程退出场景
进程退出总共会有三种情况,也就是我们平常写代码执行的时候也是会遇到这三种情况:
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码遇到异常终止执行
进程退出方法
对于进程退出而言,可以有两种方法退出。一种就是正常的程序运行完毕终止执行,另一种就是程序遇到异常信号终止运行。
那么现在有一个问题,我们平常写代码的时候为什么总是会带上一个 return 0 呢?这里就涉及到一个知识点—退出码
退出码
其实return 0这个0并没有什么特殊的意思,返回的是0就代表着程序执行正常退出,非0就是程序有错误,每一个非0的退出码都代表着不同的错误信息。可以通过程序看看
#include<stdio.h>
#include<string.h>
int main(){
for(int i = 0; i < 20; i++)
printf("%d: %s\n", i, strerror(i));
return 0;
}
exit、_exit
那么除了return可以返回退出码退出程序外,exit和**_exit**也是可以的。不过这两者之间还是会有所区别的。
- exit是库函数,_exit是系统调用
- exit会刷新缓冲区,_exit不会
进程等待
进程等待是非常重要的。之前在进程状态里面谈到了一种状态—僵尸状态。这种状态是非常危险的,会造成内存泄漏。并且一旦进程变成了僵尸状态,那么即使使用kill -9都无法将其杀死。
所以父进程想要获取子进程的任务完成的程度如何,就必须通过进程等待的方式,回收子进程资源,获取子进程退出信息
进程等待的方法
进程等待有两种方法:1、阻塞等待;2、非阻塞等待。可以使用两个函数去实现:wait和waitpid
wait
wait等待成功会返回被等待的进程的pid,失败则返回-1,下面来一段代码感受一下
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(){
pid_t id = fork();
if(id == 0){
int cnt = 5;
while(cnt--){
printf("I am child process, pid = %d\n", getpid());
sleep(1);
}
exit(1);
}
pid_t ret = wait(NULL);
printf("%d\n", ret);
return 0;
}
可以看到进程最后打印出的是子进程的pid,说明等待成功了。
waitpid
waitpid 相对于 wait 来说能够获取的信息就更多了,可以获取子进程的退出码和子进程返回的状态。
如果子进程是正常终止,那么返回的状态为0,如果收到了异常信号终止则非0
但是这里还要注意的是,waitpid 返回的子进程的数据是有自己的存储方式的。例如 waitpid 返回了一个变量 status 那么这个变量的**高八位为退出状态,低八位为终止信号。
如果进程是被信号所杀,则退出状态就没有用到,终止信号根据实际。如果进程正常终止,则退出状态根据实际,终止信号为0.
所以当waitpid 返回了一个值,我们想要获取终止信号就得用这个值 & 0x7f;获取退出状态就得用这个变量 向右移动8位再 & 0xff
来一段代码感受一下
#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#