1.如何理解信号
例如在生活中,快递员告诉我们快递到了,那么我们会终止手上的事,去取快递,这就是一种信号。
站在操作系统的角度,再来理解信号:
1.用户输入命令,在Shell下启动一个前台进程。
2. 用户按下 Ctrl-C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程
3. 前台进程因为收到信号,进而退出。
所以,显而易见,信号是操作系统与用户通信的一种方式,也可以是进程间通信的方式。
2. Linux下信号机制
- 信号是进程间时间异步通知的一种方式,属于软中断。
在Linux下我们可以使用kill -l命令查看系统定义的信号列表。
2.1 信号常见的处理方式
- 忽略此信号。
- 执行该信号的默认处理动作。
- 提供一个信号处理函数,要求内核在处理信号时切换到用户态执行这个处理函数,这种处理方式称之为捕捉一个信号。
2.2 产生信号的方式
- 通终端按键产生信号(如常用的Ctrl+z)
- 调用系统函数向进程发信号
- 由软件条件产生信号
- 硬件异常产生信号
3. Linux下信号阻塞
先来了解一些信号相关常见的概念:
信号递达(Delivery):实际执行信号的处理动作
未决信号(Pending):信号从产生到传递之间的状态。
阻塞信号(Blocking):被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
在内核中,每个信号都有两个标志位表示阻塞和未决,还有一个函数表示处理动作,信号产生时,内核在PCB中设置该信号的未决标志,知道信号递达才清除该标志。
3.1sigprocmask
调用sigprocmask函数可以读取或者更改进程信号屏蔽字(阻塞信号集)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
来看下面的demo来体会使用sigprocmask阻塞信号
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void sigcb(int signum)
{
printf("recv a signal:%d\n",signum);
}
int main()
{
signal(SIGINT,sigcb);
signal(40,sigcb);
sigset_t set,old;
sigemptyset(&set); //清空信号集合
sigemptyset(&old);
sigfillset(&set); //将所有信号都添加到set集合
sigprocmask(SIG_BLOCK,&set,&old); //阻塞所有信号
printf("enter to continue\n");
getchar();
sigprocmask(SIG_UNBLOCK,&set,NULL); //解除阻塞
return 0;
}
可以看到执行程序后,按下CTRL+c ,并不能退出程序,因为SIHGINT信号被阻塞。
4.捕捉信号
4.1 内核如何实现信号的捕捉
以SIGQUIT信号为例
- 用户注册了SIGQUIT信号的处理函数sighandler,当前正在执行 main函数,这时发生中断或异常切换到内核态。
- 在中断处理完毕后要返回用户态的main函数之前检查到有信号 SIGQUIT递达。
- 内核执行sighandler函数,其与main函数是两个独立的控制流程。
- sighandler函数返 回后自动执行特殊的系统调用sigreturn再次进入内核态。
- 如果没有新的信号要递达,这次再返回用户态就是恢复 main函数的上下文继续执行。
4.2 sigaction函数的演示
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
// signo --- 指定信号的编号
act --- 自定义动作
oact --- 保存信号原有处理动作
再来看看sigaction结构体:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_fla 5gs;
void (*sa_restorer)(void);
};
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。
以下demo演示sigaction函数的使用
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
void sigcb(int signum)
{
printf("recv signum : %d\n",signum);
}
int main()
{
struct sigaction newact;
struct sigaction oldact;
newact.sa_handler = sigcb;
newact.sa_flags = 0;
sigemptyset(&newact.sa_mask);
sigaction(SIGINT,&newact,&oldact);
//sigaction修改信号的处理动作作为newact,原来的动作使用oldact保存
while(1){
printf("hello\n");
sleep(1);
}
return 0;
}
5.SIGCHLD信号
我们清理僵尸进程的方式有wait和waitpid函数,调用wait时,父进程会阻塞的等待子进程结束,后者调用时,是轮询查询是否有子进程退出。
但其实子进程退出时会向父进程发送SIGCHLD信号,该信号的默认处理方式是忽略,父进程可以定义SIGCHLD信号的处理函数,父进程在处理函数中调用wait函数即可。
下面的demo我们可以体会下SIGCHLD信号的处理及作用:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/wait.h>
void sigcb(int signo)
{
while(waitpid(-1,NULL,0) > 0);
}
int main()
{
signal(SIGCHLD,sigcb);
pid_t pid = fork();
if(pid == 0){
sleep(5);
exit(0);
}
while(1){
printf("-------------\n");
sleep(1);
}
return 0;
}
来源:CSDN
作者:爱喝可乐的loser
链接:https://blog.csdn.net/weixin_42549259/article/details/104340677