linux 信号处理

前言

Linux中的信号是向进程异步发送的事件通知,通知进程有事件(硬件异常、程序执行异常、外部发出信号)发生。当信号产生时,内核向进程发送信号(在进程所在的进程表项的信号域设置对应于该信号的位)。内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时,当一个进程在内核态运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理,进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。内核为每个进程维护一个(未处理)的信号队列,信号产生后首先被放入到未决队列中,如果进程选择阻塞信号,那么如果某个信号发生多次,未决队列中仅保留相同的信号(不可靠信号类型)中的一个,而可靠信号则会被保留。

 一、进程信号处理  


1

2

3

4

5

6

7


int pause(void);     //将调用进程/线程 挂起sleep,直到有信号产生且在信号处理函数完成后返回

int kill(pid_t pid, int sig);     //将sig信号发送到pid进程

int raise(int sig);   //向调用进程/线程发送sig信号

 

sigemptyset, sigfillset, sigaddset, sigdelset, sigismember用来操作信号集合sigset_t,该信号集合可以用于sigwait、sigaction等操作

 

int sigwait(const sigset_t *set, int *sig);     //阻塞等待set中的信号,sig保存发生的信号;同类函数有sigtimedwait,sigwaitinfo


1

2

3

4

5


sighandler_t signal(int signum, sighandler_t handler);

 

示例: signal(SIGUSR1, myfunc);    //注册SIGUSR1的信号处理函数myfunc

 

//注:该函数在不同的linux、unix版本实现方式不太一样,为了保证程序的通用性,建议使用sigaction


1

2

3

4

5

6

7

8

9

10

11

12

13

14


int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

 

示例:struct sigaction act, oldact;

     //注册的信号处理函数类型为void (*sa_handler)(int);

     //act.sa_handler = show_handler;

 

     //注册的信号处理函数,类型为void (*sa_sigaction)(int, siginfo_t *, void *);这种方法功能与sa_handler相同,但是从siginfo_t结构体参数中获取产生该信号的详细信息,特别是对于错误分析特别有用,建议采用这种。

     act.sa_sigaction = show_handler;

 

     sigaddset(&act.sa_mask, SIGQUIT); //在SIGINT的信号处理函数执行时,阻塞SIGQUIT信号,直到函数执行完成

     act.sa_flags = 0;

     sigaction(SIGINT, &act, &oldact);   //设置SIGINT信号新的处理方法,将老的处理方法保留到oldact中,方便在适当的时候还原之前的信号处理方法

 

//注:siginfo_t结构体的具体参数以及sa_flags的一些标志位的含义,参加man手册 man sigaction


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18


int sigpending(sigset_t *set);     //获取当前阻塞的信号集

 

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

//how包含SIG_BLOCK(将set中包含的信号添加到已有的阻塞信号集合中), SIG_UNBLOCK(将set中信号从阻塞的信号集合中移除), SIG_SETMASK(将阻塞信号集合修改成set中的信号)

 

int sigsuspend(const sigset_t *mask);     //将调用进程的信号集替换成mask指向的信号集,然后挂起,直到有信号(不在mask中)产生且对应的信号处理函数返回,此时将原有的信号集还原

 

//注:sigsuspend通常配合sigprocmask使用,用于保证临界区代码执行。

示例:sigemptyset(&new_mask);

sigemptyset(&zero_mask);      // 清空信号集zero_mask

sigaddset(&new_mask, SIGQUIT);

sigprocmask(SIG_BLOCK, &new_mask, &old_mask);   // 阻塞SIGQUIT

 

while( quitflag == 0 ) {

    sigsuspend(&zero_mask);   // 将信号掩码替换为空,等待SIGQUIT信号处理函数将quitflag置1

}

 

sigprocmask(SIG_SETMASK, &old_mask, NULL);      // 恢复信号掩码

二、多线程信号处理

多线程信号处理跟单线程的程序最大的区别就是所有的线程共享信号处理函数,每个线程对信号处理函数的修改,都会同步到其他线程。linux环境下线程是通过轻量级进程(有兴趣可以查资料)实现的,因此内核为每个线程维护一个未决信号队列。创建新的线程时,新线程继承主线程的信号屏蔽字,但是新线程的未决信号队列被清空(防止同一信号被多个线程处理)。各个线程的信号屏蔽字(sigmask)是独立的,可以通过pthread_sigmask函数来控制线程级别的sigmask。

如果是硬件故障(如SIGBUS/SIGFPE/SIGILL/SIGSEGV)或定时器超时触发的信号,该信号会发往引起该事件的线程;其余的所有情况产生的信号都会发送到主线程。因此要想让特定线程处理信号,需要主线程将这些信号屏蔽。


1

2

3

4


int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);     //线程级别的sigprocmask

int pthread_kill(pthread_t threadint sig);     //线程级别的kill

 

注:进程信号处理中讲到的大部分函数都是可以在多线程程序中使用的

三、踩坑教训

1、在一个多线程程序中,线程A中会设置定时器,如果超时就会触发SIGALRM的信号处理函数sig_alarm_func,该函数执行了pthread_cancel(A);pthread_create(B);的操作。在测试过程中发现进程中同时存在A, B两个线程。查看pthread_cancel 说明,phtread_cancel是个异步的,需要等到线程A执行到cancellation point才能结束退出。利用gdb查看A的函数调用栈发现,阻塞到了信号处理函数sig_alarm_func中,即发生了“自己取消自己”的问题。根据第二部分讲到的信号通告机制,定时器信号被发往了调用定时器的线程,因而信号处理函数也是在调用线程的上下文中执行,所以出现了异常。

解决方法:单独设置一个信号处理线程,阻塞除该线程外的其他所有线程的信号。在信号处理线程中,利用while+sigwait 对信号进行同步处理代替注册信号处理函数的异步处理方式。

2、在处理一个程序堆栈时,发现程序在malloc函数中发生了死锁。进一步分析发现信号处理函数在保存函数调用堆栈时调用了malloc,而信号产生时正好也在执行malloc操作。通过查看malloc的相关文档发现,malloc在申请内存的时候,有加锁操作。

解决方法:信号处理函数中取消malloc这类不可重入的有锁函数。以后编写信号处理函数的时候,在函数内部尽少做一些耗时处理尽快返回,在调用函数时必须调用可重入(reentrant)函数(即不可以有static、global等全局变量,不可以分配、释放内存,不要修改errno等)。

时间: 2024-12-26 11:29:36

linux 信号处理的相关文章

linux 信号处理 二

[摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核对于信号的处理流程包括信号的触发/注册/执行及注销等.最后介绍了应用层的相关处理,主要包括信号处理函数的安装.信号的发送.屏蔽阻塞等,最后给了几个简单的应用实例. [关键字]软中断信号,signal,sigaction,kill,sigqueue,settimer,sigmask,sigprocmask,sigset_t 1       信

linux信号处理相关知识

因为要处理最近项目中碰上的多个子进程退出信号同时到达,导致程序不当产生core的情况,今天我花了时间看了一些关于linux信号处理的博客. 总结一下:(知识未经实践) 1 1 linux信号分两种,一种实时信号(也叫可靠信号),一种非实时信号(也叫不可靠信号). 2 2 实时信号之所以是可靠的,因为在进程阻塞该信号的时间内,发给该进程的所有实时信号会排队,而非实时信号则会合并为一个信号.(可以这样理解,同一个不可靠信号同时产生时,只有一个信号会被处理,其它的丢弃) 3 3 早期设计的信号都是不可

linux 信号处理 四

一.信号生命周期 从信号发送到信号处理函数的执行完毕 对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:信号诞生:信号在进程中注册完毕:信号在进程中的注销完毕:信号处理函数执行完毕.相邻两个事件的时间间隔构成信号生命周期的一个阶段. 下面阐述四个事件的实际意义: 信号"诞生".信号的诞生指的是触发信号的事件发生(如检测到硬件异常.定时器超时以及调用信号发送函数kill()或sigqueue()等). 信号在目标

linux 信号处理 一

信号是Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念.Linux对信号机制的大致实现方法.如何使用信号,以及有关信号的几个系统调用. 信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断.从它的命名可以看出,它的实质和使用很象中断.所以,信号可以说是进程控制的一部分. 一.信号的基本概念 本节先介绍信号的一些基本概念,然后给出一些基本的信号类型和信号对应的事件.基本概念对于理解和使用信号,对于理解信号机制都特别重要.下面就来看看什么是信号. 1.基本

linux 信号处理 三

一.信号及信号来源 信号本质 信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达. 信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了.信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息. 信号来源 信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它

Linux信号处理1

函数原型 NAME signal - ANSI C signal handling SYNOPSIS #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); signal函数具有注册功能,什么事都不干.只是告诉系统,当来信号signum时,按handler的方式处理.只有来信号时,才会调用这个函数.信号不来,永远不会调用这个函数.

linux信号处理时机

信号号称所谓软中断,事实上,还是没有真正的硬件中断那样能随时改变cpu的执行流 硬件中断之所以能一发生就得到处理是因为处理器在每个指令周期的结尾都会去检查中断,这种粒度是很细的 但是信号的实现只是在进程的task_struct里面有一个成员用于标识当前收到了哪些信号? 而这个成员的检查显然只能在特定时间点:从内核模式返回到用户模式的时候 可以想象,当进程从一个硬件中断中返回.从系统调用中返回或者正在休眠或者刚刚得到了调度,都是从内核态返回用户态的时机 这时候就会检查pending signals

Linux信号处理2

引言 先看以下两个信号量: 13)SIGPIPE     当管道读端关闭,再往管道写东西,会发出SIGPIPE信号 17)SIGCHLD   子进程退出会向父进程发出SIGCHLD信号,系统默认处理是忽略掉该信号 代码 /************************************************************************* > File Name: my_fork.c > Author: KrisChou > Mail:[email prote

Linux信号处理

给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取.这些信息包括子进程的进程ID.终止状态以及资源利用信息(CPU时间,内存使用量等等).如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程).继承这些子进程的init进程将清理它们(init进程将wait它们,从而去除僵尸状态). 但通常情况下,我们是不愿意留存僵尸进程的,它们占用内核中的空间,最终可能导致我们耗尽进程资源.那么为什么会产生僵尸进程以及如何避免产生僵尸