页面加载中,请稍候
来源:开源Linux发布时间:2022-12-29862浏览
询问 AI
信号其实就是一个软件中断。
例:
在这个例子中,由ctrl+c产生的硬件中断就是一个信号。Ctrl+C产生的信号只能发送给前台进程,命令后加就可放到后台运行。Shell可同时运行一个前台进程和任意多个后台进程,只有前台进程才能接受到像CTRL+C这种控制键产生的信号。
使用命令查看:
kill-l
非可靠信号:1~31号信号,信号可能会丢失可靠信号:34~64号信号,信号不可能丢失

SIGHUP:1号信号,Hangup detected on controlling terminal or death of controlling process(在控制终端上挂起信号,或让进程结束),ation:term
SIGINT:2号信号,Interrupt from keyboard(键盘输入中断,ctrl + c),action:term
SIGQUIT:3号信号,Quit from keyboard(键盘输入退出,ctrl+ |),action:core,产生core dump文件
SIGABRT:6号信号,Abort signal from abort(3)(非正常终止,double free),action:core
SIGKILL:9号信号,Kill signal(杀死进程信号),action:term,该信号不能被阻塞、忽略、自定义处理
SIGSEGV:11号信号,Invalid memory reference(无效的内存引用,解引用空指针、内存越界访问),action:core
SIGPIPE:13号信号,Broken pipe: write to pipe with no readers(管道中止: 写入无人读取的管道,会导致管道破裂),action:term
SIGCHLD:17号信号,Child stopped or terminated(子进程发送给父进程的信号,但该信号为忽略处理的)
SIGSTOP:19号信号,Stop process(停止进程),action:stop
SIGTSTP:20号信号,Stop typed at terminal(终端上发出的停止信号,ctrl + z),action:stop
具体的信号采取的动作和详细信息可查看:man 7 signal
硬件产生即通过终端按键产生的信号:
产生core dump文件的条件:
当前OS一定不要限制coredump文件的大小,ulimit-a
磁盘空间要足够
如何产生:
3.1解引用空指针,收到11号信号,产生coredump文件
3.2内存访问越界,程序一旦崩溃,就会收到11号信号,也就会产生coredump文件
3.3 double free,收到6号信号,并产生core dump。
3.4free(NULL),不会崩溃
软件产生即调用系统函数向进程发信号
#include<sys/types.h>
#include<signal.h>
intkill(pid_tpid,intsig);
参数解释:
pid:进程号
sig:要发送的信号值
返回值:成功返回0,失败返回-1,并设置错误
信号注册又分为可靠信号的注册和非可靠信号的注册。信号注册实际上是一个位图和一个sigqueue队列。
当进程收到非可靠信号时:
当进程所受到可靠信号时:
在sig位图中更改信号对应的比特位为1不论之前sigqueue队列中是否存在该信号的sigqueue节点,都再次添加sigqueue节点到sigqueue队列当中去
信号对应的比特位从1置为0将该信号的sigqueue节点从sigqueue队列当中进行出队操作
将该信号的sigqueue节点从sigqueue队列当中进行出队操作需要判断sigqueue队列当中是否还有相同的sigqueue节点:①没有了:信号比特位从1置为0②还有:不会更改sig位图中的比特位

信号的阻塞,并不会干扰信号的注册。信号能注册,但不能被立即处理,将block位图中对应的信号比特位置为1,表示阻塞该信号进程收到该信号,还是一如既往的注册当进程进入到内核空间,准备返回用户空间的时候,调用do_signal函数,就不会立即去处理该信号了当该信号不被阻塞后,就可以进行处理了
函数原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);参数解释:
how,该做什么样的操作
SIG_BLOCK:设置信号为阻塞
SIG_UNBLOCK:解除信号阻塞
SIG_SETMASK:替换阻塞位图
set:用来设置阻塞位图
SIG_BLOCK:设置某个信号为阻塞,block(new)=block(old)|set
SIG_UNBLOCK:解除某个信号阻塞,block(new)=block(old)(~set)
SIG_SETMASK:替换阻塞位图,block(new)=set
oldset:原来的阻塞位图
例:下述例子,信号全部被阻塞,采用kill -9,将该进程结束掉
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
voidsigncallback(intsignumber)
{
printf("changethesignal%d\n",signumber);
}
intmain()
{
sigset_tset;
sigset_toldset;
sigfillset(set);//所有比特位全置为1,则信号全部会被阻塞
sigprocmask(SIG_BLOCK,set,oldset);
while(1)
{
sleep(1);
}
return0;
}
结果:此时发送信号是不会有作用的,采用kill -9强杀掉

实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是、在递达之后可选的一种处理动作。
函数原型:int sigpending(sigset_t *set);读取当前进程的未决信号集,通过set参数传出。调用成功返回0,出错返回-1.
例:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
voidsignalcallback(intsignumber)
{
printf("changsignumber%d\n",signumber);
}
voidprintsigset(sigset_t*set)
{
inti=0;
for(;i<32;i++)
{
if(sigismember(set,i))
{
putchar('1');
}
else{
putchar('0');
}
}
}
intmain()
{
signal(2,signalcallback);
signal(10,signalcallback);
sigset_tset;
sigset_toldset;
sigset_tpending;
sigfillset(set);//所有比特位全部置为1,则信号会全部被阻塞
sigprocmask(SIG_BLOCK,set,oldset);
while(1)
{
sigpending(pending);
printsigset(pending);
sleep(1);
}
return0;
}
结果:

每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。
在上述例子中:
该函数可以更改信号的处理动作。
typedefvoid(*sighandler_t)(int);
sighandler_tsignal(intsignum,sighandler_thandler);
参数解释:
signum:更改的信号值
handler:函数指针,要更改的动作是什么
实际上,该函数内部也调用了sigaction函数。
读取和修改与指定信号相关联的处理动作。
intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact);
参数解释:
signum:待更改的信号值
struct sigaction结构体:
void(*sa_handler)(int);//函数指针,保存了内核对信号的处理方式
void(*sa_sigaction)(int,siginfo_t*,void*);//
sigset_tsa_mask;//保存的是当进程在处理信号的时候,收到的信号
intsa_flags;//SA_SIGINFO,OS在处理信号的时候,调用的就是sa_sigaction函数指针当中
//保存的值0,在处理信号的时候,调用sa_handler保存的函数
void(*sa_restorer)(void);
例:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
voidsigncallback(intsignumber)
{
printf("changesignumber%d\n",signumber);
}
intmain()
{
structsigactionact;//act为入参
sigemptyset(act.sa_mask);
act.sa_flags=0;
act.sa_handler=signcallback;
structsigactionoldact;//oldact为出参
sigaction(3,act,oldact);
while(1)
{
sleep(1);
}
return0;
}
结果:

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这就称为信号捕捉。

内核态返回用户态会调用do_signal函数,两种情况:
无信号:sys_return函数,返回用户态
有信号:先处理信号,信号返回,再调用do_signal函数例:
程序注册了SIGQUIT信号的处理函数sighandler。
当前正在执行main函数,这时发生中断或异常切换到内核态。
在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数, sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
intsigemptyset(sigset_t*set);://将比特位图全置为0
intsigfillset(sigset_t*set);//将比特位图全置为1
intsigaddset(sigset_t*set,intsignum);//将该set位图,多少号信号置为1
intsigdelset(sigset_t*set,intsignum);//将该set位图,多少号信号置为0
intsigismember(constsigset_t*set,intsignum);//信号signum是否是set位图中的信号
该信号是子进程在结束是发送给父进程的信号,但是该信号的处理方式是默认处理的。父进程对子进程发送过来的SIGCHLD信号进行了忽略处理,就会导致子进程成为僵尸进程。
可以自定义该信号的处理方式:
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
#include<sys/wait.h>
#include<stdlib.h>
voidsigncallback(intsignumber)
{
printf("changesignal%d\n",signumber);
wait(NULL);
}
intmain()
{
signal(17,signcallback);
pid_tpid=fork();
if(pid<0)
{
perror("fork");
return-1;
}
elseif(pid==0)
{
printf("Iamchild\n");
sleep(1);
exit(12);
}
else{
while(1)
{
sleep(1);
}
}
return0;
}
指令查看后台:ps aux | grep ./fork

新闻来源:开源Linux,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。
暂无评论哦,快来评论一下吧!
2026-07-03

2026-06-30
2026-07-20