Linux - 信号

[复制链接]
发表于 2025-12-27 21:04:06 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

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

×

一、信号的界说

   信号在Linux中是一种软件制止,它提供了一种处理处罚异步变乱的方法。当某个变乱发生时,利用体系会向目标进程发送一个信号。进程可以辨认这个信号,并根据预设的处理处罚方式做出相应的反应。
  运行下面步伐
  1. int main()
  2. {
  3.     while(1);
  4.     return 0;
  5. }
复制代码

观察上面,通过ctrl + c 就能竣事进程,实在ctrl + c 就是一个信号,ctrl + c被利用体系表明然后向进程发送制止信号。
二、查察信号

1、通过 kill -l 指令可以查察信号。

   可靠信号:
  

  • 又称之为RT信号或及时信号。 信号值位于SIGRTMIN(及时信号最小值)及SIGRTMAX(及时信号最大值)之间。
  • 支持列队机制,确保多个信号可以或许被吸收和处理处罚,不会丢失。
    非可靠信号:
  

  • 信号值小于SIGRTMIN。
  • 不支持列队机制,若一次性来了多个信号,这些信号不会列队,只会保存一个,后续的信号大概会被扬弃。
  下面紧张讨论的是1-31号信号
2、man 7 signal指令可以查察具体的信号信息。

Term和Core的区别
   一、Core(核心转储)
  

  • 界说:当进程吸收到某些信号(如SIGSEGV、SIGABRT等)时,假如利用体系设置了核心转储(CoreDump),那么进程当前的内存映像(包罗代码、数据、堆栈等)会被写入到一个名为“core”的文件中。这个文件通常位于进程的当前工作目次下,文件名大概会附加进程的PID(进程标识符)以区分差别的核心转储文件。
  • 目标:核心转储的紧张目标是为了调试。当进程非常制止时,开发者可以通太过析核心转储文件来相识进程在制止时的状态,包罗内存中的数据、堆栈信息等。这有助于定位标题的根源,并采取相应的修复步伐。
  • 触发条件:通常,只有当进程吸收到某些特定的、与内存访问错误或步伐非常制止干系的信号时,才会触发核心转储。这些信号的具体种类大概因体系而异,但通常包罗SIGSEGV(段错误)、SIGABRT(非常制止)等。
  通过ulimit下令查察利用体系是否开启Core

修改Core
  1. ulimit -c 文件大小
复制代码
产生core文件的条件
利用体系开启Core && 退出信号为Core。
怎么查察退出信号是否为Core

开启Core后再实行下面步伐后就会产生core文件
  1. int test()
  2. {
  3.     int a = 10;
  4.     //整数除零 -- 出异常 -- 信号:8
  5.     int b = a / 0;
  6. }
  7. int main()
  8. {
  9.     pid_t id = fork();
  10.     if(id == 0)
  11.     {
  12.         test();
  13.     }
  14.     else
  15.     {
  16.         int status = 0;
  17.         waitpid(id,&status,0);
  18.         //打印第8位
  19.         std::cout<<((status>>7)&0x1)<<std::endl;
  20.     }
  21.     return 0;
  22. }
复制代码
  二、Term(制止进程)
  

  • 界说:当进程吸收到某些信号(如SIGTERM、SIGINT等)时,利用体系会立即制止该进程的实行。这意味着进程的全部线程都将被制止,进程所占用的资源(如内存、文件形貌符等)将被开释。
  • 目标:制止进程的目标是为了开释体系资源,并确保体系的稳固性和安全性。当进程不再必要运行或其运动大概对体系造成危害时,利用体系会发送制止信号来制止该进程。
  • 触发条件:触发制止信号的条件大概因信号而异。比方,SIGTERM信号通常用于哀求进程正常退出,而SIGINT信号则通常用于制止当前正在运行的进程(如用户按下Ctrl+C时)。其他信号(如SIGKILL)则具有更高的优先级,可以立即制止进程而不给进程留下任何整理资源的时机。
  三、产生信号

1、指令

  1. kill -[信号]  [进程id]
复制代码
如向进程 6666发送9号信号
  1. kill -9 6666
复制代码
2、体系调用

(1)kill函数
功能
   先指定进程发送信号。
  头文件
  1. #include <sys/types.h>
  2. #include <signal.h>
复制代码
函数原型
  1. int kill(pid_t pid, int sig);
复制代码
参数
   pid参数表现要制止的进程或线程的ID。
sig参数表现要发送的信号。
  返回值
   乐成:返回0。
失败:返回-1。
  (2) raise函数
功能
   用于向当进步程发送一个指定的信号。
  头文件
  1. #include <signal.h>
复制代码
函数原型
  1. int raise(int signo);
复制代码
参数
   signo:要发送的信号编号。
  返回值
   乐成:返回0。
失败:返回-1。
  (3) abort函数
功能
   用于非常制止当前步伐的实行。当调用 abort函数时,步伐会立即制止运行,而且会举行一系列的整理利用,包罗革新输出缓冲区(假如它们未被禁用)和天生一个核心转储文件(假如体系设置允许)。核心转储文件是一个包罗步伐制止时内存内容的文件,通常用于调试目标。
  头文件
  1. #include <stdlib.h>
复制代码
函数原型
  1. void abort(void);
复制代码
3、由软件条件产生信号

当软件条件满意时,进程可以自动发送信号给自身或其他进程,以关照它们某些变乱的发生或哀求实行特定的利用。
alarm函数
功能
   用于设置一个定时器,当定时器到期时,会向调用进程发送一个 SIGALRM 信号。这个函数通常用于实现简单的定时功能。
  头文件
  1. #include <unistd.h>
复制代码
函数原型
  1. unsigned int alarm(unsigned int seconds);
复制代码
参数
   seconds:定时器的时间长度,以秒为单位。假如 seconds 是0,那么任何当前已经设置的定时器都会被取消。
  返回值
   假如之前没有设置定时器,alarm 返回0。
假如之前已经设置了定时器,alarm 返回之前定时器剩余的时间(以秒为单位),即间隔定时器到期还剩下的时间。
  4、非常

如除0错误就会发送8号错误,利用野指针。
5、键盘输入

如ctrl + c 产生2号信号,ctrl + \ 产生3号信号。
四、生存信号

1、增补:信号其他干系概念

   

  • 实际实行信号的处理处罚动作称为信号递达(Delivery) 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择壅闭 (Block )某个信号。 被壅闭的信号产生时将保持在未决状态,直到进程清除对此信号的壅闭,才实行递达的动作.
  • 留意:壅闭和忽略是差别的,只要信号被壅闭就不会递达,而忽略是在递达之后可选的一种处理处罚动作。
  2、信号生存在哪,怎么生存?

生存task_struct布局体中,通过位图大概数组生存。

   

  • 每个信号都有两个标志位分别表现壅闭(block)和未决(pending),尚有一个函数指针表现处理处罚动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才扫除该标志。
  • 当继承到信号时会在符适时实行对应的函数。
  3、信号集利用函数

(1)sigset_t
   从上图来看,每个信号只有一个bit的未决标志,非0即1,不记载该信号产生了多少次,壅闭标志也是如许表现的。因此,未决和壅闭标志可以用雷同的数据范例sigset_t来存储,sigset_t称为信号集,这个范例可以表现每个信号的“有用”或“无效”状态,在壅闭信号会合“有用”和“无效”的寄义是该信号是否被壅闭,而在未决信号会合“有用”和“无效”的寄义是该信号是否处于未决状态。下一节将具体先容信号集的各种利用。壅闭信号集也叫做当进步程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该明确为壅闭而不是忽略。
  (2)处理处罚sigset_t干系函数
头文件
  1. #include <signal.h>
复制代码
函数原型与功能
  1. // 初始化信号集,使其不包含任何信号。
  2. int sigemptyset(sigset_t *set);
  3. //初始化信号集,使其包含所有可能的信号。
  4. int sigfillset(sigset_t *set);
  5. // 向信号集中添加指定的信号。
  6. int sigaddset(sigset_t *set, int signum);
  7. //从信号集中删除指定的信号。
  8. int sigdelset(sigset_t *set, int signum);
  9. //检查指定的信号是否属于信号集。
  10. int sigismember(const sigset_t *set, int signum);
复制代码
参数
   set:设置的信号集。
signum : 设置信号集的位置。
  返回值
   这四个函数都是乐成返回0,堕落返回-1。
  (3)sigprocmask函数
功能
   改变进程的当前壅闭信号集,大概检测当进步程的信号掩码的函数。
  头文件
  1. #include <signal.h>
复制代码
函数原型
  1. int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
复制代码
参数
   

  • how:指示怎样修改当前信号屏蔽字。它可以是以下三个值之一: SIG_BLOCK:将 set 中指定的信号添加到当进步程的信号掩码中,即壅闭这些信号。 SIG_UNBLOCK:将 set 中指定的信号从当进步程的信号掩码中移除,即清除对这些信号的壅闭。 SIG_SETMASK:将当进步程的信号掩码设置为 set 中指定的信号掩码,即壅闭 set 中的信号,清除对其他信号的壅闭。
  • set:指向一个信号集的指针,用于指定要壅闭或清除壅闭的信号。这个参数可以是 NULL,在这种环境下,how 参数的运动取决于是否要获取当前的信号屏蔽字(通过 oldset 参数)(输入型参数)。
  • oldset:指向一个信号集的指针,用于生存之前的信号掩码。假如 oldset 不是 NULL 指针,那么当前的信号屏蔽字会由此指针返回。假如 oldset 是 NULL,则不生存旧的信号屏蔽字(输出型参数)。
  返回值
   

  • 假如 sigprocmask 实行乐成,则返回 0。
  • 假如实行失败,则返回 -1,并设置 errno 以指示错误。
  利用
将2号信号壅闭。
  1. int main()
  2. {
  3.     //信号集
  4.     sigset_t set, oldset;
  5.     // 初始化
  6.     sigemptyset(&set);
  7.     sigaddset(&set, 2);
  8.     // 设置阻塞
  9.     sigprocmask(SIG_BLOCK, &set, &oldset);
  10.     //循环,让程序不要结束
  11.     while (1);
  12.     return 0;
  13. }
复制代码
征象:ctrl + c 不能制止进程了,分析2号信号被壅闭了。

(4)sigpending函数
功能
   于获取当前壅闭信号集。
  头文件
  1. #include <signal.h>
复制代码
函数原型
  1. int sigpending(sigset_t *set);
复制代码
参数
   set:这是一个指向 sigset_t 范例的指针,用于存储当前被壅闭的信号集。sigset_t 是一个数据范例,用于表现信号集(输出型参数)。
  返回值
   乐成时,sigpending 返回 0。
失败时,返回 -1,并设置 errno 以指示错误缘故原由。
  五、捕捉信号

1、概念

   进程捕捉信号的过程,即进程在吸收到信号后,根据信号的编号和预设的处理处罚函数举行相应的处理处罚。
  2、捕捉信号的时机

   进程在吸收到信号后,并不是立即处理处罚信号,而是在符合的时间举行处理处罚。这个符合的时间通常指的是进程从内核态切换回用户态过程中。当进程处于内核态时,它无法直接处理处罚用户空间的信号。因此,当进程从内核态返回用户态时,利用体系会查抄进程是否有未处理处罚的信号,假如有,则根据信号的编号和预设的处理处罚函数举行相应的处理处罚。
  3、捕捉信号后的三种处理处罚方式

(1)默认处理处罚
   利用内置的处理处罚函数。
  (2)自界说和忽略
   自界说:用户自界说。
忽略:递达后不做处理处罚。
  signal函数
功能
头文件
  1. #include <signal.h>
复制代码
函数原型
  1. typedef void (*sighandler_t)(int);        //int 参数 为触发信号的编号
  2. sighandler_t signal(int signum, sighandler_t handler);
复制代码
参数
   

  • signum:指定要处理处罚的信号编号。比方,SIGINT(制止信号,通常由 Ctrl+C 产生)、SIGTERM(制止信号)等。
  • handler:指定信号处理处罚步伐,它是一个返回范例为 void 并继承一个 int 参数的函数指针。假如通报 SIG_IGN,则忽略该信号;假如通报 SIG_DFL,则利用默认的信号处理处罚运动。
  返回值
   signal 函数返回之前设置的信号处理处罚步伐(假如有的话),假如出现错误则返回 SIG_ERR。
  利用
  1. void sigcb(int signum)
  2. {
  3.     std::cout << "signum : " << signum << std::endl;
  4. }
  5. int main()
  6. {
  7.     //改变信号 2 - ctrl + c
  8.     signal(2,sigcb);
  9.     //signal(2,SIG_IGN);  //忽略信号
  10.    // signal(2,SIG_DFL);//使用默认处理方法 :注意:不调用函数也是使用默认的方法
  11.    
  12.     //别让程序结束
  13.     while(1);
  14.     return 0;
  15. }
复制代码
征象:
当通报2号信号时不再制止步伐,而是实行我们传入的函数。

本质:通过signal函数修改handler数组的函数指针。
sigaction函数
功能
   sigaction 函数是 POSIX 尺度中用于设置信号处理处罚步伐的函数,它提供了比 signal 函数更强大和机动的功能。sigaction 允许你具体指定信号处理处罚的各个方面,包罗信号处理处罚步伐、信号屏蔽集和标志。
  头文件
  1. #include <signal.h>
复制代码
函数原型
  1. struct sigaction {
  2.     void (*sa_handler)(int);      // 或者使用 sa_sigaction
  3.     void (*sa_sigaction)(int, siginfo_t *, void *);
  4.     sigset_t sa_mask;             // 调用处理程序时要阻塞的信号集
  5.     int sa_flags;                 // 标志位,控制行为的选项
  6.     void (*sa_restorer)(void);    // 已废弃,不应使用
  7. };
  8. /*
  9. sa_handler 或 sa_sigaction:指定信号处理程序。sa_handler 是一个简单的函数指针,而 sa_sigaction 提供了一个更复杂的接口,允许访问更多关于信号的信息。
  10. sa_mask:在调用信号处理程序时,要阻塞的信号集。这可以防止在信号处理程序运行时接收到其他信号。
  11. sa_flags:标志位,用于控制 sigaction 的行为。常用的标志包括 SA_RESTART(如果信号处理程序被中断的系统调用应该自动重启)、SA_SIGINFO(使用 sa_sigaction 而不是 sa_handler)等。
  12. sa_restorer:这是一个已废弃的字段,不应在新代码中使用。
  13. */
  14. int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
复制代码
参数
   

  • signum:指定要处理处罚的信号编号。
  • act:指向一个 struct sigaction 布局的指针,该布局包罗了新的信号处理处罚步伐的设置。
  • oldact:假如 oldact 不为 NULL,则 sigaction 函数会将之前的信号处理处罚设置存储在这个指向 struct
    sigaction 布局的指针中。
  返回值
   假如 sigaction 乐成,则返回 0。
假如失败,则返回 -1,并设置 errno 以指示错误。
  利用
  1. void sigcb(int signum)
  2. {
  3.     std::cout << "signum : " << signum << std::endl;
  4. }
  5. int main()
  6. {
  7.     struct sigaction act,oldact;
  8.     //初始化
  9.     sigemptyset(&act.sa_mask);
  10.     act.sa_handler = sigcb;
  11.     //act - 输入型参数  oldact - 输出型参数
  12.     sigaction(2, &act, &oldact);
  13.     while(1);
  14.     return 0;
  15. }
复制代码
4、对于自界说动作的捕捉信号过程


   

  • 进程在用户态实行:比方实行 main() 函数的代码。
  • 信号到达,进入内核态:通过制止、非常或体系调用进入内核。
  • 内核处理处罚信号:查抄信号队列,准备实行信号处理处罚函数。
  • 返回到用户态实行处理处罚函数:内核修改用户态堆栈,设置 EIP/RIP 指向处理处罚函数,切换回用户态。
  • 处理处罚函数实行完毕:用户态代码实行完毕后,调用 sigreturn 体系调用再次进入内核态。
  • 内核规复上下文:内核规复原始寄存器、堆栈、信号屏蔽字等。
  • 返回用户态继承实行:进程继承实行被信号制止的代码(如 main() 函数)。
    为什么必要从内核态切换到用户态处理处罚自界说信号?
由于有安全风险,必要举行权限切换。
    内核态切换到用户态处理处罚自界说信号之后为什么要再进入内核态末了再回到用户态?
从内核态切换到用户态实行信号处理处罚函数后,必须再次进入内核态,是为了让内核规复被制止的上下文、整理暂时数据,并确保进程状态的同等性。这一计划包管了信号处理处罚的安全性和透明性,使得用户态代码无需关心底层制止细节。
回复

使用道具 举报

登录后关闭弹窗

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