目录
一、信号的概念
在Linux系统中,信号是一种进程间通信的机制,用于通知进程发生了某个事件。信号可以由内核、其他进程或进程自身发送。信号的本质就是用软件来模拟中断的行为。例如,当用户按下Ctrl+C键发送中断信号给一个运行中的进程时,该进程会收到SIGINT信号,通常会导致进程终止。
Linux系统提供了多种信号,每个信号都用一个唯一的整数值标识。有一些常见的信号,比如SIGINT(中断)、SIGKILL(强制终止)、SIGTERM(正常终止)、SIGCHLD(子进程状态改变)等。通过发送不同的信号,可以实现进程的控制、通知和同步。
二、信号的处理
在Linux中,进程可以通过系统调用signal()
或sigaction()
来注册信号处理函数,以处理接收到的信号。处理函数可以是系统提供的默认处理方式,也可以是自定义的处理方式。通过信号的机制,可以实现进程之间的通信、协作和错误处理等功能。
进程可以对信号进行以下几种处理:
默认处理:系统为每种信号定义了默认行为。例如,SIGINT 的默认行为是终止进程。
忽略信号:进程可以选择忽略某些信号(除了 SIGKILL 和 SIGSTOP)。收到信号但不执行
捕获信号:进程可以通过信号处理函数捕获并处理信号。用户捕捉某个或某些信号,并对它们的功能进行修改
信号处理函数是一个用户自定义的函数,当进程接收到信号时,会调用该函数。可以通过 signal 或 sigaction 系统调用来设置信号处理函数。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
void fun(int sig)
{
printf("sig=%d\n",sig);//2
}
int main()
{
signal(SIGINT,fun);//设置信号的响应方式,达成约定,如果接收到信号,就调用fun函数
while(1)
{
printf("hello pid=%d\n",getpid());
sleep(1);
}
}
运行结果:
运行程序会无限循环打印"hello的pid号",按Ctrl+C时,程序不会终止,而是会捕获SIGINT信号,并调用fun函数打印出信号的编号。只有按Ctrl+\ 时才会终止程序。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
void fun(int sig)
{
printf("sig=%d\n",sig);//2
signal(sig,SIG_DFL);
}
int main()
{
signal(SIGINT,fun);//设置信号的响应方式
while(1)
{
printf("hello pid=%d\n",getpid());
sleep(1);
}
}
运行结果:
和上次不同的是调用fun函数后会执行默认的代码,然后接收到默认的信号,按Ctrl+C会打印出信号的编号,在下一次执行的时候按Ctrl+C就会结束程序。
三、信号的发送
使用kill命令,他需要的头文件和函数形式如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
kill函数的两个参数中,第一个参数pid代表的是给哪个进程发送,这个进程的pid号;第二个参数是信号的代号。发送成功与否我们要看它的返回值,如果返回值是-1,那么就发送失败了。
第一种方式:atoi
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
//argc 参数个数
//argv 参数内容
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("argc err\n");
exit(1);
}
int pid=atoi(argv[1]);
int sig=atoi(argv[2]);
if(kill(pid,sig)==-1)
{
printf("kill err\n");
exit(1);
}
exit(0);
//printf("argc=%d\n",argc);
//for(int i=0;i<argc;i++)
//{
// printf("argv[%d]=%s\n",i,argv[i]);
//}
}
运行结果:
第二种方式:sscanf
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
//argc 参数个数
//argv 参数内容
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("argc err\n");
exit(1);
}
int pid=0;
int sig=0;
sscanf(argv[1],"%d",&pid);
sscanf(argv[2],"%d",&sig);
if(kill(pid,sig)==-1)
{
printf("kill err\n");
exit(1);
}
exit(0);
}
运行结果:
四、通过信号处理僵死进程
通过以下代码可以看出子进程先结束,父进程没有获取子进程的退出码,使进程变成僵死进程。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<sys/wait.h>
void fun(int sig)
{
printf("sig=%d\n",sig);
}
int main()
{
int n=0;
char*s=NULL;
signal(SIGCHLD,fun);
pid_t pid=fork();
if(pid==-1)
{
exit(1);
}
if(pid==0)
{
n=2;
s="child";
}
else
{
n=7;
s="parent";
}
for(int i=0;i<n;i++)
{
printf("s=%s\n",s);
sleep(1);
}
exit(0);
}
运行结果:
那么我们就可以利用信号处理僵死进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
// 信号处理函数,用于打印接收到的信号编号,并等待子进程结束
void fun(int sig)
{
wait(NULL);
printf("sig=%d\n",sig);
}
int main()
{
int n = 0;
char* s = NULL;
signal(SIGCHLD,fun); // 注册信号处理函数fun,用于处理SIGCHLD信号
pid_t pid = fork();
if (pid == -1)
{
exit(1);
}
if (pid == 0)
{
n = 3;
s = "child";
}
else
{
n = 7;
s = "parent";
}
// 根据进程类型打印相应次数的标识字符串
for(int i = 0; i < n; i++)
{
printf("%s\n",s);
sleep(1);
}
exit(0);
}
运行结果: