目录
一、信号基本概念
1.1信号定义
1.2信号的来源
1.3信号种类
1.4信号处理流程
二、信号发送
2.1kill函数
2.2raise函数
三、信号等待
四、信号处理
1、基本概念
2、用户自定义处理
五,介绍两种较为常用的信号
定时器信号
子进程退出信号
信号是一种软件中断,用于告知一个进程发生了某个事件。信号可以由操作系统、由其他进程,或者由进程本身发出。是一种异步通信方式,一般具有如下特点:
(1)进程在运行过程中,随时可能被各种信号打断
(2)进程可以忽略,或者去调用相应的函数去处理信号
(3)进程无法预测到达的精准时间
再补充说明下是异步通信方式。异步通信是一种计算机通信方式,其中数据的发送和接收不需要同时发生。在异步通信模式中,发送方和接收方不需要同步工作;发送方可以在任何时候发送数据,而接收方则在数据到达时处理数据。这种方式与同步通信不同,在同步通信中,发送方和接收方必须同时参与数据交换过程。打个比方,上学时老师说今天要给家长打电话做家访,我们不知道老师会打来电话,家访时间以电话铃声为准。
linux中信号来源如下:
程序执行错误,如内存访问越界,数学运算除0
由其他进程发送
通过控制终端发送 如ctrl + c
等......
在linux中可以用kill -l查看系统中设置的所有信号,常用的信号列举如下:
SIGINT:该信号在用户键入INTR字符(通常是Ctrl+C)时发出,终端驱动程序发送此信号并送到前台进>程中的每一个进程
d 是一个用于Unix和类Unix操作系统的信号,用于暂停一个进程的执行。当进程接收到 SIGSTOP
信号时,它会立即停止执行,并且进程的状态变为停止(stopped)状态。SIGSTOP
信号不能被捕获、阻塞或忽略,这意味着任何进程都不能忽视这个信号,并且总是被迫停止执
等......
信号处理流程:发送、投递、处理,其中进程只需要执行发送操作,而投递和处理都是在内核中完成的
Linux中对信号的常见处理方式有以下几种:
(1)忽略信号
(2)默认处理方式
(3)自定义处理方式
进程中发送信号可以调用kill()函数与raise()函数
定义:kill函数是一个系统调用,用于允许一个进程向另一个进程(或一组进程)发送信号。它提供了一种进程间通信(IPC)的方式。
函数头文件
#include
#include
函数原型
int kill(pid_t pid, int sig);
函数功能
向指定的进程发送一个信号
函数参数
pid : 进程的 id
sig : 信号的 id
函数返回值
成功: 返回 0
失败: 返回 -1,并设置 errno
定义:raise
函数是一个标准C库函数,用于允许进程向自己发送信号。它是自我通信的一种形式,允许进程触发自己的信号处理程序。
函数头文件
#include
#include
函数原型
int raise(int sig);
函数参数
sig : 信号编号
函数返回值
成功 : 返回 0
失败 : 返回 -1,并设置 errno
示例 :
创建一个子进程,子进程通过信号暂停,父进程发送 终止信号
#include
#include
#include
#include
#include
#include
int main(void)
{
pid_t cpid;
cpid = fork(); //创建子进程
if (cpid < 0) {
perror("[ERROR] fork():\n");
exit(EXIT_FAILURE);
}
else if(cpid == 0){
//进入子进程
fprintf(stdout, "child %d is running.\n", getpid());
//SIGSTOP:暂停一个进程,不能被忽略,阻塞,只能执行默认操作
raise(SIGSTOP);
fprintf(stdout, "child %d exit.\n", getpid());
exit(EXIT_SUCCESS);
}
else {
//进入父进程
fprintf(stdout, "father %d is running.\n", getpid());
sleep(1);
int ret;
ret = kill(cpid, SIGKILL);
if (ret == 0) {
fprintf(stdout, "father %d kill child %d.\n", getpid(), cpid);
}
waitpid(cpid, NULL, 0);
fprintf(stdout, "father %d exit.\n", getpid());
exit(EXIT_SUCCESS);
}
return 0;
}
在进程没有结束时,进程在任何时间点都可以接受到信号
需要阻塞等待信号时,则可以调用 pause() 函数,具体如下
函数头文件
#include
函数原型
int pause(void);
函数功能
阻塞进程,直到收到信号后唤醒
函数返回值
成功 : 返回 0
失败 : 返回 -1,并设置 errno
示例
创建创建一个子进程,父进程调用 pause 函数,子进程给父进程发送信号
#include
#include
#include
#include
#include
#include
int main(void)
{
pid_t cpid;
cpid = fork();
if (cpid < 0) {
perror("[ERROR] fork():\n");
exit(EXIT_FAILURE);
}
else if(cpid == 0){
//进入子进程
fprintf(stdout, "child %d is running.\n", getpid());
sleep(3);
int ret;
ret = kill(getppid(), SIGKILL);
if (ret == 0) {
fprintf(stdout, "child %d kill father %d.\n", getpid(), getppid());
}
fprintf(stdout, "child %d exit.\n", getpid());
exit(EXIT_SUCCESS);
}
else {
//进入父进程
fprintf(stdout, "father %d is running.\n", getpid());
sleep(1);
pause();
waitpid(cpid, NULL, 0);
fprintf(stdout, "father %d exit.\n", getpid());
exit(EXIT_SUCCESS);
}
return 0;
}
上文提到信号是由内核发送给指定的程序,进程收到信号后则需要进行处理
处理信号的三种方式反别为:默认、忽略、自定义处理方式
我觉得每种信号的及默认处理方式在使用时查阅使用,这里只列举些常用信号的默认处理方式
进程退出:SIGALRM,SIGINT,SIGKILL
进程忽略:SIGCHLD
进程暂停:SIGSTOP
(1)实现自定义处理函数
定义形式:void 函数名(int)
(2)将处理函数在内核中进行注册,通过signal函数
函数头文件 #include
函数原型 sighandler_t signal(int signum, sighandler_t handler);
函数参数 signum : 信号编号
handler : 信号处理方式
SIG_IGN : 忽略信号
SIG_DFL : 按照默认方式处理
自定义处理函数的地址
SIG_IGN : 忽略信号
SIG_DFL : 按照默认方式处理
自定义处理函数的地址
函数返回值
成功 : 返回信号处理函数地址
失败 : 返回 SIG_ERR ,并设置 errno
示例
创建一个子进程,父进程给子进程发送SIGUSR1信号,并使用自定义的方式处理
#include
#include
#include
#include
#include
#include
#include
void signal_usr1(int sig) {
printf("Receive %s \n", strsignal(sig));
}
int main(void)
{
__sighandler_t handle_flag = signal(SIGUSR1, signal_usr1);
if (handle_flag == SIG_ERR){
perror("[ERROR] signal(): \n");
exit(EXIT_FAILURE);
}
pid_t cpid;
cpid = fork();
if (cpid < 0) {
perror("[ERROR] fork():\n");
exit(EXIT_FAILURE);
}
else if(cpid == 0){
//进入子进程
fprintf(stdout, "child %d is running.\n", getpid());
pause();
exit(EXIT_SUCCESS);
}
else {
//进入父进程
fprintf(stdout, "father %d is running.\n", getpid());
sleep(3);
kill(cpid, SIGUSR1);
wait( NULL);
fprintf(stdout, "father %d exit.\n", getpid());
exit(EXIT_SUCCESS);
}
return 0;
}
在 Linux 系统中提供了 alarm 函数,用于设置定时器,具体信息如下
函数头文件
#include
函数原型
unsigned int alarm(unsigned int seconds);
函数功能 设置定时器的秒数
函数参数
seconds : 定时的时间秒数
函数返回值
返回上一次进程设置定时器剩余的秒数
要点:
定时器的定时任务由内核完成,当定时时间到达后,内核会向进程发出SIGALARM信号
示例一
验证 alram 函数的返回值
#include
#include
#include
int main(void)
{
unsigned int ret;
ret = alarm(5);
sleep(2);
printf("ret = %d\n", ret); //ret的值为0,因为上一次进程没有设置计时器
ret = alarm(5);
sleep(1);
printf("ret = %d\n", ret);//ret的值为3,因为上一次设置计时器,还剩3s(5s - 2s)
return 0;
}
示例二
设置定时器的定时时间为 3s ,并处理 SIGALRM 信号
#include
#include
#include
#include
#include
#include
#include
#include
#include
void process_sigalarm(int sig) {
printf("Receive signal %s\n", strsignal(sig));
}
int main(void)
{
__sighandler_t sigret;
sigret = signal(SIGALRM, process_sigalarm);
if (sigret == SIG_ERR) {
perror("[ERROR] signal():");
exit(EXIT_FAILURE);
}
unsigned int ret;
ret = alarm(5);
pause();
return 0;
}
输出结果
Receive signal Alarm clock
问题
在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源,并且在阻塞情况下,父进程不能执行其他逻辑
解决办法:
子进程是异步事件,利用子进程结束时发出的子进程结束信号解决
#include
#include
#include
#include
#include
#include
#include
#include
#include
void process_sigchld(int sig) {
printf("Receive signal %s\n", strsignal(sig));
wait(NULL);
}
int main(void)
{
__sighandler_t sigret;
sigret = signal(SIGCHLD, process_sigchld);
if (sigret == SIG_ERR) {
perror("[ERROR] signal():");
exit(EXIT_FAILURE);
}
pid_t cpid = fork();
if (cpid < 0) {
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if (cpid == 0) {
printf("child process <%d> start.\n", getpid());
sleep(2);
exit(EXIT_SUCCESS);
}
else {
while(1) {
}
}
return 0;
}
运行结果
child process <47240> start.
Receive signal Child exited