一、进程的创建
1.fork函数
fork 函数能够从已存在进程中创建一个新进程,新进程为子进程,而原进程为父进程。进程调用 fork 函数后,操作系统会做一下的事情:
1.创建子进程,操作系统需要为子进程分配新的内核数据结构和内存块
2.拷贝父进程的虚拟地址空间,但映射到物理内存时采用写时拷贝
3.共享父进程fork之后的代码,而数据分离(代码是只读的,代码共享并不影响进程的独立性)
4.添加子进程到系统进程列表中
5.fork返回,开始由调度器调度
2.fork函数的返回值
子进程返回0;
父进程返回子进程的pid;
3.fork失败的原因
1.系统中有太多的进程
2.实际用户的进程数超过了限制
二、进程的终止
1.进程退出的场景
1.代码运行结束,结果正确
2.代码运行结束,结果错误
3.代码异常终止
2.进程的退出码
.我们常在main函数的后面加一句return 0,实际上,0就是这个进程的退出码,return 0就代表的是: 程序结束,且正常退出。非0: 代表程序正常退出且结果有误。
通过调用strerror函数可以查看所有的进程退出码
在Linux中也可以通命令行echo $?查看上一个进程的进程退出码
三、进程的等待
1.为什么要等待
子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏。 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。
2.如何等待
wait
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
//让父进程通过使用wait系统调用阻塞式的等待回收子进程
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int count = 3;
while(count)
{
printf("I am child, 我将在%d秒后结束, 我的pid: %d\n", count, getpid());
count--;
sleep(1);
}
}
else
{
//父进程
printf("I am father, 我开始等待回收子进程\n");
pid_t res = wait(NULL);
if(res > 0)
{
printf("I am father, 等待子进程(pid: %d)成功\n", res);
}
else if(res == -1)
{
printf("I am father, 等待子进程失败\n");
}
}
return 0;
}
运行结果如下
wait的参数只有一个, 是一个输出型参数, 当调用wait回收子进程时, 会将子进程的退出状态填入这个输出型参数中,这个输出型参数status是一个32位的数字, 低七位存放信号部分(第8位先不考虑), 次低八位存放退出码。
当程序正常退出, 则信号部分将不被关心
当程序异常崩溃, 则退出码部分将不被关系(异常崩溃的本质就是操作系统给这个异常进程发送了信号并且强制终止)
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
//让父进程通过使用wait系统调用阻塞式的等待回收子进程
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int count = 3;
while(count)
{
printf("I am child, 我将在%d秒后结束, 我的pid: %d\n", count, getpid());
count--;
sleep(1);
}
exit(55);//子进程结束, 退出码为55
}
else
{
//父进程
int status = 0;
printf("I am father, 我开始等待回收子进程\n");
pid_t res = wait(&status);
if(res > 0)
{
printf("I am father, 等待子进程(pid: %d)成功\n", res);
printf("wait中输出型参数status:%d\n", status);
printf("status中的次低八位(退出码): %d\n", (status >> 8) & 0xFF);
printf("status中的低七位(信号): %d\n", status & 0x7F);
}
else if(res == -1)
{
printf("I am father, 我回收子进程失败\n");
}
}
return 0;
}
子程序正常退出情况
子程序异常退出情况
waitpid
pid_ t waitpid(pid_t pid, int *status, int options);
参数:
第一个参数pid:
pid > 0, 回收为对应pid的子进程
pid == -1, 回收任意一个子进程
第二个参数status:
与wait的参数是同一个, 都是输出型参数
第三个参数options:
options传参为0: 阻塞式的等待回收子进程
options传参为WNOHANG: 非阻塞式的等待回收子进程
(WNOHANG是一个宏, #define WNOHANG 1)
返回值:
回收子进程成功, 返回子进程的pid
回收子进程过程中出错, 返回-1
如果使用非阻塞等待(WNOHANG)式的调用, 子进程还未结束, 返回0
两个宏函数
WIFEXITED(status): 若正常终止子进程返回状态, 则为真 (判断子进程是否是正常退出的)
WEXITSTATUS(status): 若子进程是正常退出的, 则表示正常退出的退出码
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
//让父进程通过使用wait系统调用阻塞式的等待回收子进程
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int count = 3;
while(count)
{
printf("I am child, 我将在%d秒后结束, 我的pid: %d\n", count, getpid());
count--;
sleep(1);
}
//观察进程崩溃(异常退出)
//int *p = NULL;
//*p = 10;
exit(55);
}
else
{
//父进程
int status = 0;
printf("I am father, 我开始等待回收子进程\n");
//阻塞式等待
pid_t res = waitpid(id, &status, 0);
if(res > 0)//子进程回收成功
{
if(WIFEXITED(status))//子进程正常退出
{
printf("I am father, 等待子进程(pid: %d)成功\n", res);
printf("子进程正常退出且退出码为: %d\n", WEXITSTATUS(status));
}
else//子进程异常退出
{
printf("I am father, 等待子进程(pid: %d)成功\n", res);
printf("子进程异常退出且信号为: %d\n", status & 0x7F);
}
}
else if(res == -1)//子进程回收失败
{
printf("子进程回收失败\n");
}
}
return 0;
}
运行结果如下