Linux中进程的信号
我们在Linux的shell下启动一个前台进程,然后按下ctrl+c使进程中断,那有没有想过它的底层原理是什么?
一、进程信号是什么?
进程信号是一种“软件中断”,一种事件通知机制。通知进程发生了某个事件,打断进程当前操作,去处理这个事件。
查看信号种类:kill -l
每个信号表示一个事件,总共有62种信号,1-31号信号是借鉴Unix操作系统的非可靠信号(可能会信号丢失),34-64号是可靠信号。
在路径: /usr/include/bits/signum.h,查看这个头文件,这些信号都被定义为宏。
二、信号的生命周期
产生——注册——注销——处理
1.信号产生
1.硬件产生: ctrl+c:发送SIGINT中断信号;
ctrl+\ :发送SOGQUIT退出信号;
ctrl+z:发送SIGSTOP停止信号
int main(int argc, char *argv[])
{
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}
该进程处于“T”状态,进程由前台进程变为后台进程,
jobs:查看后台停止作业;
fg 作业序号:将该后台停止作业转换到前台。
2.软件产生:
kill(命令):kill -signum pid 杀死一个进程,给进程发送一个信号-默认是终止信号(例如kill -9 就是发送SIGKILL信号),如果不指定信号,则发送默认信号SIGTERM;
kill函数(系统调用):int kill(pid_t pid, int sig);
参数解释:pid:指定要将信号发送给哪个进程;
sig:指定要发送的信号;
返回值:成功返回0,失败返回-1.
int main(int argc, char *argv[])
{
kill(getpid(),SIGINT);
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}
运行程序直接中断。
int raise(int sig):给调用进程自身发送指定信号。
unsigned int alarm(unsigned int seconds):设置一个定时器,多少seconds秒后,给进程发送一个SIGALRM信号(该信号默认处理方式为退出进程)。
int main(int argc, char *argv[])
{
alarm(3);
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}
void abort(void):给进程发送一个SIGABRT信号,abort函数使当前进程接收到信号而异常终止。
2.信号在进程中注册
在进程的pcb中有个未决信号集合(pending)-还没有被处理的信号的集合——位图,还有一个struct sigqueue专门存放信号信息节点的队列(双向链表)。
注册就是在未决信号集合中标记信号将0置为1,然后给双向链表中添加信号的信息节点。
非可靠信号:若信号已经注册,则不作任何操作;
可靠信号:无论信号是否注册,都会进行注册(置1加添加节点)。
3.信号注销
删除在进程pcb中的信号信息节点,重置位图为0。
非可靠信号:删除信息节点,直接位图置0;
可靠信号:删除信息节点之后,确定没有相同节点才会重置位图。
4.信号处理
执行信号的处理回调函数(sigaction-handler)。
如何修改信号的处理流程,暨修改处理函数?
1.sighandler_t signal(int signum, sighandler_t handler);
参数解释:
sigunm:信号值;
handler:信号要新指定的处理方式;
handler:SIG_DFL-默认;SIG_IGN-忽略;自定义函数。
自定义函数的类型:typedef void (*sighandler_t)(int);
返回值:成功返回信号原来的处理方式,失败返回-1(SIG_ERR)。
举例忽略SIGINT,暨忽略ctrl+c:
int main(int argc, char *argv[])
{
signal(SIGINT,SIG_IGN);
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}
自定义处理回调函数:
把SIGINT的处理方式改为打印信号值。
void sigcb(int signo)
{
printf("recv signo : %d\n", signo);
}
int main(int argc, char *argv[])
{
signal(SIGINT,sigcb);
while(1)
{
printf("_____________\n");
sleep(1);
}
return 0;
}
暨运行程序后按一次ctrl+c就会打印一次2.
注意:9号信号和19号信号不能被修改处理操作。
自定义处理方式的信号捕捉流程:
信号的处理是一种异步操作。
如何使程序运行从用户态切换到内核态:
中断、异常、系统调用
信号的阻塞
阻塞一个信号表示收到这个信号之后暂时不去处理,知道解除阻塞之后进行处理。
进程pcb中有一个信号阻塞集合,在这个集合中标记那个信号则阻塞哪个信号,
int sigprocmask(int how, const sigset_t * set, sigset_t * oldset);
参数解释:
how:要对信号阻塞集合进行的操作类型;
oldset:保存原有集合;
set:新集合;
①SIG_BLOCK:将set集合中的信号添加到阻塞集合中;block |= set;(block:101,set:010;——111)
②SIG_UNBLOCK:从阻塞集合中移除set集合中的信号;
③SIG_SETMASK:将set集合中的信号设置为阻塞集合的信号;block = set,覆盖式设置。
返回值:成功返回0,失败返回-1.
#include <signal.h>
int sigemptyset(sigset_t *set);清空set集合
int sigfillset(sigset_t *set);将所有信号添加到set集合中
int sigaddset(sigset_t *set, int signum);将指定信号添加到集合中
int sigdelset(sigset_t *set, int signum);从集合中移除指定信号
int sigismember(const sigset_t *set, int signum);判断信号是否在集合中
举个栗子:自定义修改两种信号(可靠和非可靠)signal。 先阻塞信号sigprocmask SIG_BLOCK,再使进程暂停getchar,暂停期间给进程发送信号,让进程继续运行,解除信号阻塞sigprocmask SIG_UNBLOCK。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void sigcb(int signo)
{
printf("recv sino:%d\n", signo);
}
int main(int argc, char* argv[])
{
signal(SIGINT, sigcb);//非可靠信号
signal(40,sigcb);//可靠信号
sigset_t set;
sigemptyset(&set);
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set,NULL);
printf("回车解除暂停\n");
getchar();
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1)
{
sleep(1);
}
return 0;
}
转载:https://blog.csdn.net/qq_43560037/article/details/116138228