【Linux】历程信号

[复制链接]
发表于 2025-11-23 17:52:00 | 显示全部楼层 |阅读模式
  🔥 个人主页:大耳朵土土垚  🔥 所属专栏:Linux体系编程
   这里将会不定期更新有关Linux的内容,欢迎各人点赞,收藏,品评🥳🥳🎉🎉🎉    
  

Linux历程信号是一种历程间通讯的机制,它答应一个历程关照另一个历程某个变乱已经发生。以下是关于Linux历程信号的详细先容:
1. 信号的概念

  信号就像是一个忽然的电话铃声,它会打断正在举行的步调并引起其注意。在Linux体系中,信号是一种软件克制,它通常是异步发生的,用来关照历程某个变乱已经发生。每个信号都有一个唯一的编号和一个宏界说名称,这些宏界说可以在signal.h中找到。


  • 利用kill -l下令检察信号编号:

  • 检察信号宏界说:

2. 信号的分类

  在Linux中,信号被分为尺度信号(也称为传统或不可靠信号)和及时信号。它们的告急区别在于编号范围、处置惩罚方式以及特性。
1) 尺度信号 (Traditional/Standard Signals)

这些信号是早期Unix体系界说的,编号通常从1到31(只管某些体系大概会有所差别)。以下是一些常见的尺度信号:


  • SIGHUP (1): 终端挂起或控制历程竣事。
   1号信号,当用户退出终端时,由该终端开启的全部历程都会吸收到这个信号,默认动作为停止历程。但也可以捕获这个信号,比如wget能捕获SIGHUP信号并忽略它,以便在退出登录后继承下载。
  

  • SIGINT (2): 克制信号,通常是Ctrl+C产生的。
   2号信号,步调停止(interrupt)信号,在用户键入INTR字符(通常是Ctrl+C)时发出,用于关照前台历程组停止历程。
  

  • SIGQUIT (3): 退出信号,产生核心转储。
   3号信号,和SIGINT类似,但由QUIT字符(通常是Ctrl+\)来控制。历程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个步调错误信号。
  

  • SIGILL (4): 非法指令。
  • SIGTRAP (5): 跟踪陷阱(由调试器利用)。
  • SIGABRT (6): 调用abort()函数天生的信号。
  • SIGBUS (7): 总线错误。
  • SIGFPE (8): 浮点非常。
  • SIGKILL (9): 逼迫停止信号(不可被捕获、壅闭或忽略)。
   9号信号,用来立刻竣事步调的运行。本信号不能被壅闭、处置惩罚和忽略。
  

  • SIGSEGV (11): 段违例。
  • SIGPIPE (13): 管道破裂。
  • SIGALRM (14): 定时器到期。
  • SIGTERM (15): 停止哀求。
   15号信号,步调竣事(terminate)信号,与SIGKILL差别的是该信号可以被壅闭和处置惩罚。通常用来要求步调本身正常退出,答应历程做一些须要的清算工作退却出。
  2) 及时信号 (Real-time Signals)

及时信号是在POSIX.1b尺度中引入的,用于提供更可靠的信号机制。它们的编号范围从SIGRTMIN到SIGRTMAX,详细数值取决于利用体系实现。一样平常情况下,这个范围是从34开始直到体系的最大信号数。比方,在很多Linux体系上,SIGRTMIN对应的是34,而SIGRTMAX可以到达64大概更高。
及时信号的特点包罗但不限于:


  • 不会丢失:如果多个类似的及时信号发送给同一个历程,全部信号都会被吸收。
  • 支持列队:每个范例的及时信号可以有一个队列来存储未处置惩罚的信号实例。
  • 有序性:及时信号按照发送序次处置惩罚。
  • 可携带数据:可以通过sigqueue()发送附加的数据(一个整数或指针)。
请注意,现实的信号编号大概根据差别的体系架构和版本有所变革。别的,对于及时信号,应当利用SIGRTMIN + n和SIGRTMAX - n如许的情势来引用,而不是直接利用详细的数字值,以确保兼容性和准确性。
   本章只讨论编号31以下的信号,不讨论及时信号。
  
3. 信号的处置惩罚


在Linux中,信号处置惩罚是历程对特定变乱相应的一种机制。信号处置惩罚有三种方式:
✨方式一:执⾏该信号的默认处置惩罚动作


  • 利用下令man 7 signal检察信号在什么条件下产⽣,默认的处置惩罚动作是什么:



✨方式二:忽略此信号
可以通过设置信号处置惩罚器(也就是信号处置惩罚函数来实现):


  • 信号处置惩罚器(Signal Handler)
    信号处置惩罚器是一个函数,它在历程吸收到指定信号时被调用。你可以为每个信号设置一个自界说的处置惩罚器,函数如下:
  1. #include <signal.h>
  2. void (*signal(int signum, void (*handler)(int)))(int);
复制代码
  参数signum表现要设置的信号编号,参数handler表现要设置的信号处置惩罚函数。signal函数会返回上一个信号处置惩罚函数的指针,如果堕落则返回SIG_ERR。
  

  • 忽略信号
    可以将信号处置惩罚器设置为 SIG_IGN 来忽略某些信号。但是,不能忽略像 SIGKILL 和 SIGSTOP 如许的不可捕获信号。代码如下:
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<signal.h>
  4. int main()
  5. {
  6.     signal(SIGINT,SIG_IGN);//将2号信号忽略
  7.     while(true)
  8.     {
  9.         std::cout<<"PID:"<<getpid()<<"   I am waiting a signal."<<std::endl;
  10.         sleep(1);
  11.     }
  12.     return 0;
  13. }
复制代码
效果如下:

   2号信号的默认处置惩罚动作是步调停止(interrupt),但是由于我们利用信号处置惩罚器忽略了该信号,以是输入Ctrl+c没有停止步调。
  ✨情况三:设置自界说处置惩罚方式
和忽略信号类似我们也是利用信号处置惩罚器来实现,实例代码如下:
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<signal.h>
  4. void Handler(int signo)
  5. {
  6.     std::cout<<"PID:"<<getpid()<<"  Get a signal:"<<signo<<std::endl;//当接受到SIGINT也就是2号信号会执行打印动作,而非终止进程
  7. }
  8. int main()
  9. {
  10.     signal(SIGINT,Handler);//对2号信号设置自定义处理动作
  11.     while(true)
  12.     {
  13.         std::cout<<"PID:"<<getpid()<<"   I am waiting a signal."<<std::endl;
  14.         sleep(1);
  15.     }
  16.     return 0;
  17. }
复制代码
效果如下:



对于信号默认处置惩罚动作,我们也可以利用信号处置惩罚器来实现:signal(SIGINT/*2*/, SIG_DFL);利用SIG_DFL即可选择默认处置惩罚动作,效果如下:

   注意:signal方法只须要设置一次,那么在这个历程中,只要担当到被设置的信号就会实行Handler方法;别的如果没有产生被设置的信号,Handler方法就永久不会被实行。也就是说signal方法类似的设置了一种机制,当有对应的信号产生时就触发,没有就不触发。
  
4. 明白信号处置惩罚

  对于信号的处置惩罚我们可以分别通过软件和硬件这两个视角来明白,由于硬件比力贫苦,计划利用体系运行原理、时钟克制、死循环等,以是这里不做表明。


  • 软件
   我们以发送2号信号——从键盘输入Ctrl+c为例,当键盘将输入的Ctrl+c信息交给利用体系后,利用体系就会将信号发送给对应的历程,然后历程就会在符合的时间处置惩罚信号。
  注意,历程处置惩罚信号不是立刻处置惩罚,而是在符合的机遇,由于当进步程大概在处置惩罚本身的事。
   当历程吸收到信号时,是怎样记录是哪个信号从而实行相应的信号处置惩罚任务呢?
  

  • 我们发现本次学习的信号是1 ~ 31,连续的数字,那么只需选择位图即可利用最小的空间记录下完备的1 ~ 31个数字;
  • 只需在历程PCB中界说一个无符号整型,利用32个比特位记录31个信号,比特位的位置代表信号的编号,比特位设为1代表收到该位置的信号,为0代表没有。
  也就是说利用体系给历程发送信号就是将历程PCB中记录信号的位图对应位置的信号比特位由0置1,然后历程在符合的时间发现本身收到了信号,实行对应处置惩罚动作。
接下来为了包管条理,将采⽤如下思绪来进⾏叙述:

5. 信号产生


   在Linux体系中,信号(signal)是一种异步关照机制,用于关照历程发生了某些变乱。历程可以吸收到多种范例的信号,而且可以对这些信号举行处置惩罚大概忽略。以下是几种产生信号的常见方法:
1) 键盘输入



  • Ctrl+C:发送SIGINT给前台历程,通常用于停止一个历程。
  • Ctrl+\:发送SIGQUIT给前台历程,类似于SIGINT但会天生核心转储文件。
  • Ctrl+Z:发送SIGTSTP给前台历程,停息历程的实行。
2) kill下令



  • 利用kill下令可以向指定的历程发送信号,默认情况下是发送SIGTERM信号哀求历程正常停止。可以通过-l选项列出全部可用的信号,通过-s或直接跟信号编号来指定发送的信号范例。
    1. kill -9 <pid>  # 发送SIGKILL信号,强制终止进程
    2. kill -15 <pid> # 发送SIGTERM信号,请求进程正常终止
    复制代码
3) kill函数及体系调用



  • kill()函数: 可以直接从步调内部发送信号给其他历程。
  1. #include <sys/types.h>
  2. #include <signal.h>
  3. int kill(pid_t pid, int sig);
复制代码
  参数阐明:
  

  • pid:要发送信号的历程ID
  • sig:要发送的信号编号
  该函数的返回值为0表现乐成,返回-1表现失败。
  以是我们可以根据kill体系调用来封装一个本身的kill下令,代码如下:
  1. #include<iostream>
  2. #include <sys/types.h>
  3. #include <signal.h>
  4. int main(int argc,char* argv[])
  5. {
  6.     if(argc!=3)
  7.     {
  8.         std::cout<<"you should print: ./mykill -signo -pid"<<std::endl;//提示输入格式
  9.         return 1;
  10.     }
  11.     int signo = std::stoi(argv[1]);//字符串转整型
  12.     pid_t pid = std::stoi(argv[2]);
  13.     int n = ::kill(pid,signo);
  14.     if(n < 0)
  15.     {
  16.         perror("kill");
  17.         return 2;
  18.     }
  19.     return 0;
  20. }
复制代码
效果如下:

   以是着实kill下令本质也是通过体系调用来实现的。
  

  • raise函数:用于向调用它的历程自身发送信号。
  1. int raise(int sig);
复制代码
  参数sig为要天生的信号编号。常见的信号编号包罗SIGINT(克制信号,通常由终端键盘输入产生)、SIGABRT(停止信号,由abort函数产生)等。
  

  • abort函数:abort函数用于非常停止步调。当调用该函数时,步调会立刻退出,并天生SIGABRT信号。
  1. void abort(void);
复制代码
4) 软件条件



  • 当特定的软件条件发生时,比如子历程竣事(SIGCHLD),文件形貌符预备停当(SIGIO),大概会触发信号的产生。
  • 比方alarm定时器:用于设置一个定时器,可以在指定的时间后产生一个SIGALRM信号。
  1. #include <unistd.h>
  2. unsigned int alarm(unsigned int seconds);
复制代码
  

  • 参数seconds指定了定时器的时间隔断,单位为秒。
  • 该函数返回当前的闹钟定时器的剩余秒数。如果没有闹钟定时器正在运行,则返回0。
  利用代码如下:
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<signal.h>
  4. int main()
  5. {
  6.     ::alarm(1);//设置1s的定时器
  7.    
  8.     int count = 0;
  9.     while(true)
  10.     {
  11.         count++;//统计服务器1s可以将计数器累加到多少
  12.         printf("count:%d\n",count);
  13.     }
  14.     return 0;
  15. }
复制代码
效果如下:

   我们发现服务器1s也就盘算4万多次,有点慢,这着实是由于打印count数值时,举行了IO交互,会影响速率;以是我们可以通过signal信号处置惩罚器来优化,代码如下:
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<signal.h>
  4. int gcount = 0;
  5. void Handler(int signo)
  6. {
  7.     printf("count:%d\n",gcount);//只在1s后打印总的计算次数
  8.     exit(1);//直接退出
  9. }
  10. int main()
  11. {
  12.     signal(SIGALRM,Handler);
  13.     ::alarm(1);
  14.     while(true)
  15.     {
  16.         gcount++;
  17.     }
  18.     return 0;
  19. }
复制代码
效果如下:

   可以看到服务器1s大概盘算了6亿多次
  


  • 闹钟返回值:返回当前的闹钟定时器的剩余秒数。如果没有闹钟定时器正在运行,则返回0。测试代码如下:
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<signal.h>
  4. int main()
  5. {
  6.     int n = ::alarm(4);//在此之前没有闹钟在运行,返回0
  7.     std::cout<<n<<std::endl;
  8.     sleep(1);
  9.     int m = ::alarm(0);//0:表示取消闹钟,m表示剩余秒数
  10.     std::cout<<m<<std::endl;
  11.     return 0;
  12. }
复制代码
效果如下:



  • 闹钟原理:
  着实本质是OS必须自身具有定时功能,并能让用户设置这种定时功能,才大概实现闹钟如许的技能。利用体系须要对定时器举行管理,利用布局体对它先形貌再构造,其布局体大概包罗:
  1. struct timer
  2. {
  3.         int who;
  4.         task_struct *t;//表示哪个进程设置的闹钟
  5.         uint64_t expired;//过期时间
  6.         struct timer *next;//定时器可以有多个,通过链表连接起来
  7.         func_t f;//到期执行的函数
  8.         //...
  9. }
复制代码
  当定时器到期后,利用体系就会根据它的func_t f函数,实行对应的方法,比如给目的历程发送SIGALRM信号。别的我们可以明白定时器在利用体系中毗连的数据布局为小堆,每次都是堆顶元素先到期,如许利用体系就不须要每次都遍历定时器查找是否有逾期的定时器。
   也就是说定时器到期时可以明白为软件条件停当,利用体系就会给对应历程发送信号,以是软件条件看成信号产生的方式之一。
  

  • 利用闹钟完成定时器功能
  根据闹钟定时作用,我们可以利用信号处置惩罚器使得闹钟响起时历程自动去完成某些任务,代码如下:
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<signal.h>
  4. #include<stdlib.h>
  5. #include<vector>
  6. #include<functional>
  7. using func_t = std::function<void()>;
  8. int gcount = 0;
  9. std::vector<func_t> gfuncs;//使用vector管理相关任务
  10. void Handler(int signo)
  11. {
  12.     for(auto& f : gfuncs)//遍历任务执行
  13.         f();
  14.     std::cout<<"gcount:"<<++gcount<<std::endl;//记录执行次数
  15.     alarm(1);
  16. }
  17. int main()
  18. {
  19.     gfuncs.push_back([](){std::cout<<"我是一个内核刷新操作..."<<std::endl;});
  20.     gfuncs.push_back([](){std::cout<<"我是一个检测进程时间片的操作..."<<std::endl;});
  21.     gfuncs.push_back([](){std::cout<<"我是一个检测进程时间片的操作..."<<std::endl;});
  22.     signal(SIGALRM,Handler);//信号处理器自定义设置闹钟响起时执行的任务
  23.     ::alarm(1);//设置1s后执行任务
  24.     while(true)//防止进程退出
  25.     {
  26.         pause();//暂停等待信号到来
  27.     }
  28.     return 0;
  29. }
复制代码
效果如下:

   注意闹钟设置是一次性的,以是在Handler实行方法中要再设置闹钟,否则就会停息在那。
  5) 硬件非常

  硬件非常被硬件以某种⽅式被硬件检测到并关照内核,然后内核向当进步程发送得当的信号。比方当进步程执⾏了除以0的指令, CPU的运算单位会产⽣非常, 内核将这个非常表明为SIGFPE信号发送给历程。再⽐如当进步程访问了⾮法内存地点, MMU会产⽣非常,内核将这个非常表明为SIGSEGV信号发送给历程。


  • 除0非常:
  1. #include <stdio.h>
  2. #include <signal.h>
  3. void handler(int sig)
  4. {
  5.     printf("catch a sig : %d\n", sig);
  6. }
  7. int main()
  8. {
  9.     signal(SIGFPE, handler); // 8) SIGFPE
  10.     sleep(1);
  11.     int a = 10;
  12.     a /= 0;
  13.     while (1)
  14.     ;
  15.     return 0;
  16. }
复制代码



  • 野指针非常:
  1. #include <stdio.h>
  2. #include <signal.h>
  3. void handler(int sig)
  4. {
  5.     printf("catch a sig : %d\n", sig);
  6. }
  7. int main()
  8. {
  9.     signal(SIGSEGV, handler);
  10.     sleep(1);
  11.     int *p = NULL;
  12.     *p = 100;
  13.     while (1)
  14.         ;
  15.    
复制代码

   由此可以确认,我们在C/C++当中除零,内存越界等非常,在体系层⾯上,是被当成信号处置惩罚的。
    但是我们发现⼀直有8/11号信号产⽣被我们捕获,这是为什么呢?上⾯我们只提到CPU运算非常后,如那里理后续的流程,现实上 OS 会查抄应⽤步调的非常情况,着实在CPU中有⼀些控制和状态寄存器,告急⽤于控制处置惩罚器的利用,通常由利用体系代码利用。状态寄存器可以简单明白为⼀个位图,对应着⼀些状态标志位、溢出标志位。OS 会检测是否存在非常状态,有非常存在就会调⽤对应的非常处置惩罚⽅法。除零非常后,我们并没有清算内存,关闭历程打开的⽂件,切换历程等利用,以是CPU中还保存上下⽂数据以及寄存器内容,除零非常会⼀直存在,就有了我们看到的⼀直发出非常信号的征象。访问⾮法内存着实也是云云,⼤家可以⾃⾏实行。
    以上就是信号产生的五种方法,包罗键盘输入、下令行产生、kill函数及体系调用、软件条件及硬件非常;但是无论产生信号是何种方式,发送信号的永久是利用体系!!!这是由于利用体系是历程的管理者。
6. 信号生存


6.1 信号其他相干常见概念



  • 信号递达(Delivery):现实执⾏信号的处置惩罚动作称为信号递达
  • 信号未决(Pending):信号从产⽣到递达之间的状态,称为信号未决
  • 壅闭:历程可以选择壅闭 (Block )某个信号。被壅闭的信号产⽣时将保持在未决状态,直到历程排除对此信号的壅闭,才执⾏递达的动作
   注意,壅闭和忽略是差别的,只要信号被壅闭就不会递达,⽽忽略是在递达之后可选的⼀种处置惩罚动作。
  6.2 信号在内核中的表现

  每个信号都有两个标志位分别表现壅闭(block)和未决(pending),另有⼀个函数指针表现处置惩罚动作。如图所示:

  信号产⽣时,内核在历程控制块中设置该信号的未决标志,表现吸收到信号,直到信号递达(也就是说信号处置惩罚完成)才扫除该标志。
  在上图的例⼦中,SIGHUP信号未产生(pending表SIGHUP信号标志位为0),也未壅闭(block表SIGHUP信号标志位为0)。SIGINT信号产⽣过,但正在被壅闭,以是暂时不能递达。SIGQUIT信号未产⽣过,⼀旦产⽣SIGQUIT信号将被壅闭,无法实行它的处置惩罚动作(也就是无法递达)。
  如果SIGHUP信号产生时,利用体系会将SIGHUP信号发送给历程,历程pending表SIGHUP标志位就会由0置为1,历程在将本身的事变处置惩罚完后检察信号表,发现收到了SIGHUP信号而且没被壅闭,进而实行SIGHUP的handler方法——SIG_DEF表现默认处置惩罚方法。
  如果SIGINT排除壅闭,那么历程在检察信号时就会实行SIGINT的处置惩罚方法——SIG_IGN(忽略)。
   如果在历程排除对某信号的壅闭之前这种信号产⽣过多次,将如那里理?
  

  • POSIX.1答应体系递送该信号⼀次或多次。
  • Linux是如许实现的:通例信号在递达之前产⽣多次只计⼀次,⽽及时信号在递达之前产⽣多次可以依次放在⼀个队列⾥。本章不讨论及时信号。
  6.3 sigset_t

  从上图来看,pending表中每个信号只有⼀个bit的未决标志, ⾮0即1, 不记录该信号产⽣了多少次;壅闭标志(block表)也是如许表现的。因此, 未决和壅闭标志可以⽤类似的数据范例sigset_t来存储, sigset_t称为信号集,这个范例可以表⽰每个信号的“有效”或“⽆效”状态。
  在壅闭信号会合“有效”和“⽆效”的寄义是该信号是否被壅闭, ⽽在未决信号会合“有 效”和“⽆效”的寄义是该信号是否处于未决状态。
   sigset_t的底层实现是一个整数范例,利用位利用来设置和获取各个信号的状态。壅闭信号集也叫做当进步程的信号屏蔽字这⾥的“屏蔽”应该明白为壅闭⽽不是忽略。
  6.4 信号集利用函数

  有了信号的利用范例——信号集sigset_t之后,我们就可以通过它对pending表和block表举行利用,函数如下:
1) 初始化sigset_t信号集函数

  1. int sigemptyset(sigset_t *set);
  2. int sigfillset(sigset_t *set);
  3. int sigaddset(sigset_t *set, int signo);
  4. int sigdelset(sigset_t *set, int signo);
  5. int sigismember(const sigset_t *set, int signo);
复制代码


  • 函数sigemptyset初始化set所指向的信号集,使此中全部信号的对应bit清零,表现该信号集不包罗任何有效信号。
  • 函数sigfillset初始化set所指向的信号集,使此中全部信号的对应bit置为1,表现该信号集的有效信号包罗系 统⽀持的全部信号。
  • 注意,在使⽤sigset_ t范例的变量之前,⼀定要调⽤sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调⽤sigaddset和sigdelset在该信号会合添加或删除某种有效信号。
  • 这四个函数都是乐成返回0,堕落返回-1。
  • sigismember是⼀个布尔函数,⽤于判定⼀个信号集的有效信号中是否包罗某种信号,若包罗则返回1,不包罗则返回0,堕落返回-1。
   有了上述函数,我们就可以界说完一个信号集之后举行添加或删除等初始化利用。
  2) 读取或更改未决信号集(pending)

  1. #include <signal.h>
  2. int sigpending(sigset_t *set);
复制代码


  • 读取当进步程的未决信号集(pending),通过set参数传出。
  • 调用乐成则返回0,堕落则返回-1
3) 读取或更改壅闭信号集函数(block)

  1. #include <signal.h>
  2. int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
复制代码


  • 返回值:若乐成则为0,若堕落则为-1
  • 如果oset好坏空指针,则读取历程的当前信号屏蔽字(壅闭信号集)通过oset参数传出
  • 如果set好坏空指针,则 更改历程的信号屏蔽字
  • 参数how指⽰怎样更改。
  • 如果oset和set都好坏空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
  • 假设当前的信号屏蔽字为mask,下表阐明白how可选参数及其寄义:
how参数参数利用阐明SIG_BLOCKset包罗了我们渴望添加到当前信号屏蔽字的信号,相当于mask=mask|setSIG_UNBLOCKset包罗了我们渴望从当前信号屏蔽字中排除壅闭的信号,相当于mask=mask&~setSIG_SETMASK设置当前信号屏蔽字为set所指向的值,相当于mask=set  如果调⽤sigprocmask排除了对当前若⼲个未决信号的壅闭,则在sigprocmask返回前,⾄少将此中⼀
个信号递达。
  下⾯是利用上述函数的示例代码:
  1. #include <iostream>
  2. #include <unistd.h>
  3. #include <cstdio>
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. //打印pending表
  7. void PrintPending(sigset_t &pending)
  8. {
  9.     std::cout << "cur process[" << getpid() << "]pending: ";
  10.     for (int signo = 31; signo >= 1; signo--)
  11.     {
  12.         if (sigismember(&pending, signo))//如果pending表中有signo信号返回1
  13.             std::cout << 1;
  14.         else
  15.             std::cout << 0;
  16.     }
  17.     std::cout << "\n";
  18. }
  19. void handler(int signo)
  20. {
  21.     std::cout << signo << " 号信号被递达!!!" << std::endl;
  22.     std::cout << "-------------------------------" << std::endl;
  23.     sigset_t pending;
  24.     sigpending(&pending);//获取当前pending表
  25.     PrintPending(pending);//打印pending表
  26.     std::cout << "-------------------------------" << std::endl;
  27. }
  28. int main()
  29. {                        
  30.     signal(2, handler); // 0.⾃定义捕捉2号信号
  31.    
  32.     //  1. 屏蔽2号信号
  33.     sigset_t block_set, old_set;
  34.     sigemptyset(&block_set);//将自定义block_set设为0
  35.     sigemptyset(&old_set);//将自定义old_set设为0
  36.     sigaddset(&block_set, SIGINT); // 我们有没有修改当前进⾏的内核block表呢???1 0
  37.     // 1.1 设置进⼊进程的Block表中
  38.     sigprocmask(SIG_BLOCK, &block_set, &old_set); //SIG_BLOCK->block_set = mask|oldset 真正的修改当前进⾏的内核block表,完成了对2号信号的屏蔽!
  39.     int cnt = 10;
  40.     while (true)
  41.     {
  42.         // 2. 获取当前进程的pending信号集
  43.         sigset_t pending;
  44.         sigpending(&pending);
  45.         // 3. 打印pending信号集
  46.         PrintPending(pending);
  47.         cnt--;
  48.         // 4. 解除对2号信号的屏蔽
  49.         if (cnt == 0)
  50.         {
  51.             std::cout << "解除对2号信号的屏蔽!!!" << std::endl;
  52.             sigprocmask(SIG_SETMASK, &old_set, &block_set);
  53.         }
  54.         sleep(1);
  55.     }
  56. }
复制代码
效果如下:

   没有看懂的同砚,可以回首一下信号未决与壅闭的概念以及相干利用函数。
  7. 信号捕获


如果信号的处置惩罚动作是用户自界说函数,在信号递达时就调用这个函数,这称为捕获信号。
7.1 信号捕获流程


由于信号处置惩罚函数的代码是在用户空间的,处置惩罚过程⽐较复杂,举比方下:


  • 用户步调注册了 SIGQUIT 信号的处置惩罚函数 sighandler 。
  • 当前正在实行main 函数,这时发生克制或非常切换到内核态。
  • 在克制处置惩罚完毕后要返回用户态的 main 函数之前查抄到有信号 SIGQUIT 递达。
  • 内核决定返回用户态后不是规复 main 函数的上下⽂继承实行,而是实行 sighandler 函数, sighandler 和 main 函数利用差别的堆栈空间,它们之间不存在调⽤和被调⽤的关系,是两个独⽴的控制流程。
  • sighandler 函数返回后自动实行特殊的体系调用 sigreturn 再次进⼊内核态。
  • 如果没有新的信号要递达,这次再返回用户态就是规复 main 函数的上下文继承实行了。
我们之前说过信号处置惩罚不是立刻的,而是选择符合的时间,以是历程在举行信号处置惩罚的时间段如下图:

   也就是说当吸收到信号会被存储在pending表中,此时不会立刻处置惩罚,比及历程从用户态与内核态切换时再查抄pending表看是否有信号要处置惩罚。
  我们可以简单明白:


  • 用户态:实行我本身写的代码
  • 内核态:实行利用体系的代码
7.2 sigaction函数

  1. #include <signal.h>
  2. int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
复制代码


  • sigaction函数可以读取和修改与指定信号相干联的处置惩罚动作。调⽤乐成则返回0,堕落则返回- 1。
  • signo:指定要利用的信号编号,比方 SIGINT、SIGTERM 等。
  • act:指向 sigaction 布局体的指针,根据act修改该信号的处置惩罚动作。如果不须要改变当前的信号处置惩罚方式,则可以设置为 NULL。
  • oact:指向 sigaction 布局体的指针,通过oact传出该信号原来的处置惩罚动作。如果不须要生存当前的信号处置惩罚方式,则可以设置为 NULL。
  • sigaction 布局体:
  1. struct sigaction {
  2.     void     (*sa_handler)(int); // 指定信号处理函数或特殊值 SIG_IGN 或 SIG_DFL
  3.     void     (*sa_sigaction)(int, siginfo_t *, void *); // 用于替代 sa_handler 的扩展信号处理器
  4.     sigset_t   sa_mask; // 额外的信号屏蔽字,在执行信号处理器期间阻止其他信号
  5.     int        sa_flags; // 特殊标志,影响信号传递行为
  6.     void     (*sa_restorer)(void); // 不再使用,应设为 NULL
  7. };
复制代码


  • 将sa_handler赋值为常数SIG_IGN传给sigaction表现忽略信号;
  • 赋值为常数SIG_DFL表⽰执⾏体系默认动作;
  • 赋值为⼀个函数指针表现用自界说函数捕获信号,大概说向内核注册了⼀个信号处置惩罚函数,该函数返回值为void,可以带⼀个int参数——通过参数可以得知当前信号的编号,如许就可以用同一个函数处置惩罚多种信号。显然,这也是⼀个回调函数,不是被main函数调用,而是被体系所调用。
代码示比方下:
  1. #include<iostream>
  2. #include<signal.h>
  3. void handler(int signo)//自定义处理动作
  4. {
  5.     std::cout<<"get a signal:"<<signo<<std::endl;
  6. }
  7. int main()
  8. {
  9.     struct sigaction act,oact;
  10.     act.sa_handler=handler;//将sa_handler赋值为一个函数指针,自定义2号信号处理动作
  11.     ::sigaction(2,&act,&oact);//oact保存的是2号信号原来的处理动作
  12.     while(true)
  13.     {
  14.         ::pause();//等待
  15.     }
  16.     return 0;
  17. }
复制代码
效果如下:

   着实和信号处置惩罚器功能一样,只是sigaction函数提供了对信号处置惩罚更准确的控制,相比于 signal 函数来说更为安全和机动。
  8. 结语

  我们从信号界说、分类、处置惩罚谈到信号产生、信号生存末了到信号捕获,关键在于信号处置惩罚的明白、相干的信号处置惩罚函数、信号生存的三张表——pending表、block表和handler表以及信号捕获的明白与运用。总之,Linux历程信号是一种强盛且机动的历程间通讯机制。通过公道地利用信号,可以实现历程间的异步关照、同步和通讯等功能。以上就是Linux历程信号有关的内容啦~ 完结撒花~ 🥳🎉🎉

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表