0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一篇文章彻底搞定信号!

Linux爱好者 来源:CSDN技术社区 作者:903419 2022-12-06 14:26 次阅读

1.信号是什么?

信号其实就是一个软件中断。

例:

输入命令,在Shell下启动一个前台进程。

用户按下Ctrl-C,键盘输入产生一个硬件中断。

如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行, CPU从用户态切换到内核态处理硬件中断。

终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。

当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

在这个例子中,由ctrl+c产生的硬件中断就是一个信号。Ctrl+C产生的信号只能发送给前台进程,命令后加&就可放到后台运行。Shell可同时运行一个前台进程和任意多个后台进程,只有前台进程才能接受到像CTRL+C这种控制键产生的信号。

2.信号的种类

使用命令查看:

kill-l

非可靠信号:1~31号信号,信号可能会丢失可靠信号:34~64号信号,信号不可能丢失

bf80fb7a-7387-11ed-8abf-dac502259ad0.png

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

3.信号的产生

3.1硬件产生

硬件产生即通过终端按键产生的信号:

ctrl + c:SIGINT(2),发送给前台进程,& 进程放到后台运行,fg 把刚刚放到后台的进程,再放到前台来运行

ctrl + z:SIGTSTP(20),一般不用,除非有特定场景

ctrl + | :SIGQUIT(3),产生core dump文件

产生core dump文件的条件:

当前OS一定不要限制coredump文件的大小,ulimit-a
磁盘空间要足够
如何产生:
3.1解引用空指针,收到11号信号,产生coredump文件
3.2内存访问越界,程序一旦崩溃,就会收到11号信号,也就会产生coredump文件
3.3 double free,收到6号信号,并产生core dump。
3.4free(NULL),不会崩溃

3.2软件产生

软件产生即调用系统函数向进程发信号

kill函数

#include
#include
intkill(pid_tpid,intsig);
参数解释:
pid:进程号
sig:要发送的信号值
返回值:成功返回0,失败返回-1,并设置错误

kill命令:kill -[信号] pid,

abort:void abort(void);,收到6号信号,谁调用该函数,谁就收到信号

alarm:unsigned int alarm(unsigned int seconds);,收到14号信号,告诉内核在seconds秒后给进程发送SIGALRM信号,该信号默认处理动作为终止当前进程。

4.信号的注册

信号注册又分为可靠信号的注册和非可靠信号的注册。信号注册实际上是一个位图和一个sigqueue队列。bfbef272-7387-11ed-8abf-dac502259ad0.png

4.1非可靠信号的注册

当进程收到非可靠信号时:

将非可靠信号对应的比特位置为1

添加sigqueue节点到sigqueue队列当中,但是,在添加sigqueue节点的时候,队列当中已然有了该信号的sigqueue节点,则不添加

4.2可靠信号的注册

当进程所受到可靠信号时:

在sig位图中更改信号对应的比特位为1不论之前sigqueue队列中是否存在该信号的sigqueue节点,都再次添加sigqueue节点到sigqueue队列当中去

5.信号的注销

5.1非可靠信号的注销

信号对应的比特位从1置为0将该信号的sigqueue节点从sigqueue队列当中进行出队操作

5.2可靠信号的注销

将该信号的sigqueue节点从sigqueue队列当中进行出队操作需要判断sigqueue队列当中是否还有相同的sigqueue节点:①没有了:信号比特位从1置为0②还有:不会更改sig位图中的比特位

6.信号阻塞

6.1信号是怎样阻塞的?

bfdf9c20-7387-11ed-8abf-dac502259ad0.png

信号的阻塞,并不会干扰信号的注册。信号能注册,但不能被立即处理,将block位图中对应的信号比特位置为1,表示阻塞该信号进程收到该信号,还是一如既往的注册当进程进入到内核空间,准备返回用户空间的时候,调用do_signal函数,就不会立即去处理该信号了当该信号不被阻塞后,就可以进行处理了

6.2sigprocmask

函数原型: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
#include
#include


voidsigncallback(intsignumber)
{
printf("changethesignal%d
",signumber);
}

intmain()
{
sigset_tset;
sigset_toldset;
sigfillset(&set);//所有比特位全置为1,则信号全部会被阻塞
sigprocmask(SIG_BLOCK,&set,&oldset);
while(1)
{
sleep(1);
}

return0;
}

结果:此时发送信号是不会有作用的,采用kill -9强杀掉

bffb4074-7387-11ed-8abf-dac502259ad0.png

7.信号未决

7.1 未决概念

实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是、在递达之后可选的一种处理动作。

7.2 sigpending

函数原型:int sigpending(sigset_t *set);读取当前进程的未决信号集,通过set参数传出。调用成功返回0,出错返回-1.

例:

#include
#include
#include

voidsignalcallback(intsignumber)
{
printf("changsignumber%d
",signumber);
}
voidprintsigset(sigset_t*set)
{
inti=0;
for(;i< 32;i++)
  {
    if(sigismember(set,i))
    {
      putchar('1');
    }
    else{
      putchar('0');
    }
  }
}

int main()
{
  signal(2,signalcallback);
  signal(10,signalcallback);
  sigset_t set;
  sigset_t oldset;
  sigset_t pending;
  sigfillset(&set);//所有比特位全部置为1,则信号会全部被阻塞
  sigprocmask(SIG_BLOCK,&set,&oldset);
  while(1)
  {
    sigpending(&pending);
    printsigset(&pending);
    sleep(1);
  }

  return 0;
}

结果:c0119a22-7387-11ed-8abf-dac502259ad0.png

8.信号的处理方式

c044a598-7387-11ed-8abf-dac502259ad0.png

每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。

在上述例子中:

SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。

SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

8.1signal函数

该函数可以更改信号的处理动作。

typedefvoid(*sighandler_t)(int);
sighandler_tsignal(intsignum,sighandler_thandler);
参数解释:

signum:更改的信号值
handler:函数指针,要更改的动作是什么

实际上,该函数内部也调用了sigaction函数。

8.2sigaction函数

读取和修改与指定信号相关联的处理动作。

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
#include
#include

voidsigncallback(intsignumber)
{
printf("changesignumber%d
",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;
}

结果:c06ab7f6-7387-11ed-8abf-dac502259ad0.png

8.3 自定义信号处理的流程

c082b806-7387-11ed-8abf-dac502259ad0.png

task_struct结构体中有一个struct sighand_struct结构体。

struct sighand_struct结构体有一个struct k_sigaction action[_NSIG]结构体数组。

该数组中,其中的_sighandler_t sa_handler保存的是信号的处理方式,通过改变其指向,可以实现我们对自定义信号的处理。

9.信号的捕捉

9.1信号捕捉的条件

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

9.2信号捕捉流程

c097d182-7387-11ed-8abf-dac502259ad0.png

内核态返回用户态会调用do_signal函数,两种情况:

无信号:sys_return函数,返回用户态

有信号:先处理信号,信号返回,再调用do_signal函数例:

程序注册了SIGQUIT信号的处理函数sighandler。

当前正在执行main函数,这时发生中断或异常切换到内核态。

在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。

内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数, sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。

sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。

如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

10.常用信号集操作函数

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位图中的信号

11.SIGCHLD信号

该信号是子进程在结束是发送给父进程的信号,但是该信号的处理方式是默认处理的。父进程对子进程发送过来的SIGCHLD信号进行了忽略处理,就会导致子进程成为僵尸进程。

可以自定义该信号的处理方式:

#include
#include
#include
#include
#include
#include

voidsigncallback(intsignumber)
{
printf("changesignal%d
",signumber);
wait(NULL);
}

intmain()
{
signal(17,signcallback);
pid_tpid=fork();
if(pid< 0)
  {
    perror("fork");
    return -1;
  }
  else if(pid == 0)
  {
    printf("I am child
");
    sleep(1);
    exit(12);
  }
  else{
    while(1)
    {
      sleep(1);
    }
  }
  return 0;
}


指令查看后台:ps aux | grep ./fork

c0b0f310-7387-11ed-8abf-dac502259ad0.png

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10824

    浏览量

    211100
  • 信号
    +关注

    关注

    11

    文章

    2778

    浏览量

    76609
  • 中断
    +关注

    关注

    5

    文章

    895

    浏览量

    41384

原文标题:一篇文章彻底搞定信号!

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    全面认识MOS管,文章就够了

    今天势必要来文章,彻底掌握mos管!
    的头像 发表于 06-13 10:37 1.9w次阅读
    全面认识MOS管,<b class='flag-5'>一</b><b class='flag-5'>篇</b>文章就够了

    彻底掌握MOS管

    基础知识中 MOS 部分迟迟未整理,实际分享的电路中大部分常用电路都用到了MOS管, 今天势必要来文章,彻底掌握mos管!
    发表于 07-05 11:56 2.9w次阅读

    文章彻底搞定Linux信号

    输入命令,在 Shell 下启动个前台进程。
    发表于 11-21 11:26 296次阅读

    彻底搞定指针

    本帖最后由 zgzzlt 于 2012-8-16 13:43 编辑 彻底搞定指针
    发表于 08-07 14:00

    彻底搞定C指针

    本帖最后由 发烧友之麒麟 于 2014-10-6 09:17 编辑 彻底搞定C指针教程,个人觉得写得很好,希望对大家有帮助[attach]214619[/attach下面是个更深入理解C指针
    发表于 09-30 13:47

    如何搞定PCB设计的差分信号

    来源:互联网在高速PCB设计中,差分信号的应用越来越广泛,这主要原因是和普通的单端信号走线相比,差分信号具有抗干扰能力强、能有效抑制EMI、时序定位精确的优势。作为名(准)PCB设计
    发表于 10-23 08:36

    彻底搞定电路设计中的接地问题

    彻底搞定电路设计中的接地问题
    发表于 06-08 06:53

    【178页完整版】轻松搞定C语言(提高)!!

    【178页完整版】轻松搞定C语言(提高)!!需要完整版的朋友可以下载附件保存哦~
    发表于 08-16 10:58

    轻松搞定C语言(提高)

    太大,上传不了附件,以下是网盘链接:轻松搞定C语言(提高)链接:https://pan.baidu.com/s/1epKmrHjVZddkOiRugTSgXQ 提取码:cq6c
    发表于 09-14 17:29

    彻底搞定C指针_姚云飞

    彻底搞定C指针》是互联网上下载次数最多的针对C指针问题的中文资源之。现在,经由修订者的重新修订、编辑与排版,本书的《完全版修订增补版》全新登场。新版本中的技术用
    发表于 02-02 10:58 0次下载
    <b class='flag-5'>彻底</b><b class='flag-5'>搞定</b>C指针_姚云飞

    彻底搞定C指针

    彻底搞定C指针。
    发表于 03-17 09:59 1次下载

    彻底搞定C语言指针详解完整版

    彻底搞定C语言指针详解完整版。
    发表于 05-10 17:04 0次下载

    几招搞定iPhone手机WiFi信号不稳定

    有的时候,我们会发现我们的iPhone WiFi信号总是不满格,用起来还很卡。对此许多果粉都束手无策,其实只要简单几招,就可以搞定这个问题。
    发表于 08-22 18:22 10w次阅读

    多轴伺服,搞定

    多轴伺服,搞定
    的头像 发表于 10-19 17:54 630次阅读
    多轴伺服,<b class='flag-5'>一</b>芯<b class='flag-5'>搞定</b>

    难搞的工业信号调节,零漂移运算放大器是如何搞定的?

    难搞的工业信号调节,零漂移运算放大器是如何搞定的?
    的头像 发表于 12-06 16:59 559次阅读
    难搞的工业<b class='flag-5'>信号</b>调节,零漂移运算放大器是如何<b class='flag-5'>搞定</b>的?