信号的引入:
僵尸进程:
进程中的指令已经执行完成,但是进程PCB结构还没有回收;父进程未结束,子进程结束,但是父进程没有处理子进程的退出状态,没有释放子进程占用的资源,该子进程就成为一个僵尸进程
处理僵尸进程的系统调用:
pid_t wait(int *result);//获取调用此方法的进程的子进程的退出码
父进程调用wait方法处理子进程的僵尸状态,调用wait时,
- 如果没有子进程则出错返回;
- 如果有子进程,子进程还没结束,则wait调用会阻塞等待子进程的结束;
- 如果有子进程并且已经结束,则销毁子进程,返回子进程的pid,退出码用result返回
一次wait调用只能处理一个子进程(最先结束的)
wait的调用时机要放在子进程刚刚结束之后,那么父进程如何知道子进程结束了呢?这就要用到信号
子进程结束后给父进程发送信号,当父进程接收到信号,在信号处理函数中调用wait
信号
信号
信号是操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
即系统预先定义好的某些特定的事件。信号可以被发送,也可以被接收,发送和接收的主体都是进程
查看Linux信号
路径:/usr/include/bits/signum.h
信号的响应方式
一个进程接收到信号后有三种处理方式:
- 忽略:即忽略此信号,用SIG_IN(函数指针类型)指定
- 默认:针对大多数信号的系统默认动作是终止进程,用SIG_DFL指定
- 自定义(捕获)由用户自己指定
修改信号响应方式
使用signal系统调用,给信号值绑定一个信号处理函数,绑定之后进程收到此信号,系统自动调用绑定的函数
#include <signal.h>
函数地址 signal(信号值,函数地址);
信号处理函数,参数必须是int类型
void signal_fun(int);
信号处理函数的调用时机:收到信号以后,调用信号处理函数
例:绑定ctrl+c信号与signal_fun函数
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void signal_fun(int sign)
{
printf("signal_fun start\n");
}
int main()
{
signal(SIGINT,signal_fun);//绑定ctrl+c信号与signal_fun函数
int i;
for(i=0;i<10;i++)
{
sleep(2);
printf("pricess is running\n");
}
exit(0);
}
如果进程接收到一个信号,会将当前正在执行的主逻辑暂停,等待信号处理函数执行完毕,主逻辑再接着执行
信号处理函数执行过程中,进程再最多接收一次该信号(信号屏蔽,因为记录信号的发生是通过位来记录的,信号处理函数执行过程中,再来信号,该位由0变1,不管来多少信号,该位只能记录来信号这一事件,而不能具体知道来了多少信号),所以信号处理函数应该尽可能快地执行结束,以防止信号被长时间屏蔽
扩充:为防止信号处理函数执行过程中,再来的信号被长时间屏蔽,可以使用统一事件源机制,即把复杂耗时长的信号处理函数放在主逻辑中去处理,因为主逻辑的执行不会屏蔽信号
用信号机制处理僵尸进程
对于如下代码:父进程未结束,但子进程已经结束,子进程就变为僵尸进程,但用signal绑定了SIGCHILD与sign_fun函数,一旦子进程结束发送给父进程SIGCHILD信号,系统就自动调用signal_fun函数,signal_fun里的wait方法就会处理掉该僵尸进程
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <assert.h>
void signal_fun(int sign)//信号处理函数
{
wait(NULL);
}
int main()
{
signal(SIGCHILD,sign_fun);//绑定信号与信号处理函数
pid_t n = fork();
assert(n!=-1);
if(n == 0)//子进程
{
printf("child start\n");
sleep(2);
printf("child over");
}
else//父进程
{
int i=0;
while(i<100)
{
sleep(1);
i++;
}
}
}
例:信号的二次绑定
运行程序,第一次接收到SIGINT信号,进程打印HelloWorld,第二次接收到SIGINT信号,进程结束
- 程序启动时:signal(SIGINT, fun1);
- 第一次接收到信号,fun
- 将信号的响应方式修改为默认SIG_DFL
- 第二次接收信号,默认退出程序
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void fun(int sign)
{
printf("HelloWorld\n");//第一次接收到信号
signal(SIGINT,SIG_DFL);//将信号响应方式恢复为默认,默认退出程序
}
int main()
{
signal(SIGINT, fun);//程序启动时,绑定
int i=0;
while(i<100)
{
sleep(1);
printf("process running...\n");
i++;
}
}
信号的发送
int kill(pid_t pid, int sign);//sign是要发送的信号
kill方法的pid参数有四种不同的情况
- pid>0,将该信号发送给进程ID为pid的进程
- pid==0,将该信号发送给与发送进程属于同一进程组的所有进程,而且发送进程具有向这些进程发送信号的权限
- pid<0,将该信号发送给其进程组ID等于pid的绝对值的进程,而且发送进程具有向这些进程发送信号的权限
- pid==-1,将该信号发送给发送进程有权限向它们发送信号的系统上的所有进程
返回值:成功返回0,失败返回-1
用kill方法实现kill命令
kill命令:kill pid 或 kill -9 pid
用kill方法实现kill的命令代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int main(int argc, char *argv[])//argv[0]:kill argv[1]: -9/pid
{
if(argc<2)
{
printf("please input pid\n");
}
int i=1;
int sign = SIGTERM;//kill命令的默认信号
if(strncmp(argv[1], "-9", 2) == 0)
{
sign = SIGKILL;
i=2;
}
for(;i<argc;i++)
{
int pid = 0;
sscanf(argv[i],"%d",&pid);//从字符串中取得整数放到pid里
if(-1 == kill(pid,sign))//获取失败
{
char buff[128] = {0};
sprintf(buff, "%d",pid);//把%d形式存入buff
perror(buff);//标准错误输出
}
}
exit(0);
}
测试mykill: