多进程——进程的控制与终止
进程的控制
一般在多进程项目中:
1、子进程是业务进程(干活的进程)
2、父进程负责管理子进程
fork函数创造进程后,有两种情况
情况1:
如果父进程先于子进程退出,则子进程成为孤儿进程,此时将自动被 PID 为 1 的进程
(即 init进程)接管。孤儿进程退出后,它的清理工作有祖先进程 init 自动处理。但在 init 进程清理子进程之前,会一直消耗系统的资源,所以要尽量避免孤儿进程。
情况2:
如果子进程先退出,系统不会自动清理掉子进程的环境,而必须由父进程调用 wait 或
waitpid 函数来完成清理工作,如果父进程不做清理工作,则已经退出的子进程将成为僵尸
(zombie)进程,过多的僵尸会造成系统性能下降。
孤儿进程代码如下:
#include <func.h>
int main()
{
if(!fork())
{
printf("Child Mark2\n");
while(1);
}else
{
printf("Parent Mark1\n");
return 0;
}
}
执行效果如下:
wait函数:
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait 和 waitpid 都将暂停父进程,等待一个已经退出的子进程,并进行清理工作
wait 函数随机地等待一个已经退出的子进程,并返回该子进程的 pid
waitpid 等待指定 pid 的子进程;如果为-1 表示等待所有子进程。
参数解析:
status 参数是传出参数,存放子进程的退出状态;通常用以下的两个宏来获取状态信息:
WIFEXITED(status)
如果子进程正常结束,它就取一个非 0 值。传入整型值,非地址
WEXITSTATUS(status) 如果 WIFEXITED 非零,它返回子进程的退出码.
options 用于改变 waitpid 的行为,其中最常用的是 WNOHANG,它表示无论子进程是否退出都将立即返回,不会将调用者的执行挂起。
例:不带子进程退出状态(status)的wait
代码如下:
#include <func.h>
//不带返回状态(status)的wait
int main()
{
if(!fork())
{
printf("Child Mark2,my pid is %d\n",getpid());
return 0;
}else
{
printf("Parent Mark1\n");
pid_t pid=wait(NULL);//等待子进程
printf("wait success,pid=%d\n",pid);
return 0;
}
}
执行效果如下:
例:带返回状态(status)的wait (重要)
代码如下;
#include <func.h>
//带返回状态的wait
int main()
{
if(!fork())
{
printf("Child Mark2,my pid is %d\n",getpid());
return -1;//return的范围在(0-255),return的返回值即下方的exit code
}else
{
printf("Parent Mark1\n");
int status;
pid_t pid=wait(&status);
if(WIFEXITED(status))//如果子进程正常结束,宏就取一个非0值
{
printf("Child Mark2 exit code=%d\n",WEXITSTATUS(status));
}else
{
printf("Child Mark2 crash\n");
}
printf("wait success,pid=%d\n",pid);
return 0;
}
}
执行效果如下:
例:子进程非正常退出(子进程崩溃)
代码如下:
#include <func.h>
//子进程崩溃举例——除以0或者空指针
int main()
{
if(!fork())
{
printf("Child Mark2,my pid is %d\n",getpid());
//例1
//float f=1/0;//除以0子进程崩溃
//printf("f=%f\n",f);
//例2
char *p=NULL;//空指针
*p=5;
return -1;//return的范围在(0-255),return的返回值即下方的exit code
}else
{
printf("Parent Mark1\n");
int status;
pid_t pid=wait(&status);
if(WIFEXITED(status))//如果子进程正常结束,宏就取一个非0值
{
printf("Child Mark2 exit code=%d\n",WEXITSTATUS(status));
}else
{
printf("Child Mark2 crash\n");
}
printf("wait success,pid=%d\n",pid);
return 0;
}
}
执行效果如下:
例:waitpid,接指定的Pid
代码如下:
#include <func.h>
//不带返回状态的waitpid
int main()
{
pid_t pid=fork();
if(0==pid)
{
printf("Child Mark2,my pid is %d\n",getpid());
return 0;
}else
{
printf("Parent Mark1\n");
//pid=waitpid(pid,NULL,0);
pid=waitpid(pid,NULL,WNOHANG);//一般WNOHANG用于循环内,父进程也在干活,它表示无论子进程是否退出都将立即返回,不会将调用者的执行挂起。
printf("wait success,pid=%d\n",pid);
return 0;
}
}
执行效果如下:
进程的终止
进程有5种终止方式:
- main函数的正常结束
- 调用exit函数(man 3 exit)
- 调用_exit函数
- 调用abort函数
- 发送信号ctrl+c,ctrl+(2号信号SIGINT,3号信号SIGQUIT)
前 3 种方式为正常的终止,后 2 种为非正常终止。
常用1和2的终止方式,3~5是一般用于识别异常的函数。
exit与_exit的区别在于:
exit 函数在退出之前会检查文件的打开情况,把文件缓冲区中的内容写回文件,即清理 I/O 缓冲(刷新标准缓冲区),而_exit不会,因此_exit需要手动输入\n,否则数据会丢失。
由于 linux 的标准函数库中,有一种被称作“缓冲 I/O”操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区中读取;同样,每次写文件的时候,也仅仅是写入内存中的缓冲区,等满足一定的条件(如达到一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也为编程带来了麻烦。比如有一些数据,认为已经写入文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit 函数直接将进程关闭,缓冲区中的数据就会丢失。一般情况下,exit函数较为常用。
例:exit使用
代码如下:
#include <func.h>
//exit如何操作
int print()
{
printf("now is exit\n");
exit(1);//子进程在函数中终止
return 0;
}
int main()
{
if(!fork())
{
printf("Child Mark2,my pid is %d\n",getpid());
print();
printf("After print\n");
return 0;
}else
{
printf("Parent Mark1\n");
int status;
pid_t pid=wait(&status);
if(WIFEXITED(status))//如果子进程正常结束,宏就取一个非0值
{
printf("Child Mark2 exit code=%d\n",WEXITSTATUS(status));
}else
{
printf("Child Mark2 crash\n");
}
printf("wait success,pid=%d\n",pid);
return 0;
}
}
执行效果如下:
例:_exit
代码如下:
#include <func.h>
//_exit和exit有何区别
int print()
{
//printf("now is exit");//不加\n,没有刷新缓冲区,虽然实际已经执行,但不会打印
printf("now is exit\n");//刷新标准输入输出缓冲区,本句会打印
_exit(1);//子进程在函数中终止
//exit和_exit的区别在于是否会刷新标准输入输出缓冲区,为了安全性一般常用exit
}
int main()
{
if(!fork())
{
printf("Child Mark2,my pid is %d\n",getpid());
print();
printf("After print\n");
return 0;
}else
{
printf("Parent Mark1\n");
int status;
pid_t pid=wait(&status);
if(WIFEXITED(status))//如果子进程正常结束,宏就取一个非0值
{
printf("Child Mark2 exit code=%d\n",WEXITSTATUS(status));
}else
{
printf("Child Mark2 crash\n");
}
printf("wait success,pid=%d\n",pid);
return 0;
}
}
执行效果如下:
加 \n 的_exit会刷新标准缓冲区,因此会打印 now is exit
不加 \n 的 _exit 没有刷新标准缓冲区,因此没有打印 now is exit
abort函数
abort常用于中间层,实际是调用6号信号SIGABRT代码如下:
代码如下:
#include <func.h>
//abort常用于中间层
int main(int argc,char *argv[])
{
abort();//abort会发出6号信号,使进程终止
printf("I am Mark3\n");//由于进程终止,本句不会打印
return 0;
}
执行效果如下: