本文共 3734 字,大约阅读时间需要 12 分钟。
基础系列比子系统系列好些,这个系列大部分都是摘抄,很多兄弟在前面做了很好的总结,在此感谢他们。
最近处理一个调试的问题,涉及到linux的信号,在此总结一下,以作备忘
+++++++++++++++++++++++++++++++++++++++++++++++++++++
目录:
1,linux的信号
2,信号处理
3,debug中如何处理信号
4,如何在
一,linux的信号
1,概念
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。
信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。
2,分类
早期Unix系统只定义了32种信号,linux支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,也就是可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
特别注意,不要有这样的误解:由sigqueue()发送、sigaction安装的信号就是可靠的。事实上,可靠信号是指后来添加的新信号(信号值位于SIGRTMIN及SIGRTMAX之间);不可靠信号是信号值小于SIGRTMIN的信号。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
二,信号的处理
如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。
linux主要有两个函数实现信号的安装:signal()、sigaction()。其中signal()在可靠信号系统调用的基础上实现, 是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。
三,debug中如何处理信号
gdb通常可以捕捉到发送给他trace的任务的大多数信号,通过捕捉信号,它就可决定对于正在运行的进程要做些什么工作。
当任务被traced的时候,每次gdb收到信号时,该任务都会被stop,然后gdb决定该信号如何处理(根据信号内容,handle命令设置等),根据结果继续执行任务。这是ptrace的行为,可以从我们 freindly man ptrace中看到。
四,如何在
1,背景
在开发多线程应用时,开发人员一般都会考虑线程安全,会使用 pthread_mutex
去保护全局变量。如果应用中使用了信号,而且信号的产生不是因为程序运行出错,而是程序逻辑需要,譬如 SIGUSR1、SIGRTMIN 等,信号在被处理后应用程序还将正常运行。在编写这类信号处理函数时,应用层面的开发人员却往往忽略了信号处理函数执行的上下文背景,没有考虑编写安全的信号处理函数的一些规则。这里将介绍编写信号处理函数时需要考虑的一些规则:
因为信号是异步事件,即信号处理函数执行的上下文背景是不确定的,譬如一个线程在调用某个库函数时可能会被信号中断,库函数提前出错返回,转而去执行信号处理函数。对于上述第三种信号的产生,信号在产生、处理后,应用程序不会终止,还是会继续正常运行,在编写此类信号处理函数时尤其需要小心,以免破坏应用程序的正常运行。关于编写安全的信号处理函数主要有以下一些规则:
errno
是线程安全,即每个线程有自己的 errno
,但不是异步信号安全。如果信号处理函数比较复杂,且调用了可能会改变 errno
值的库函数,必须考虑在信号处理函数开始时保存、结束的时候恢复被中断线程的 errno
值; malloc(),free()
以及标准 I/O 库函数等; volatile,
以避免编译器不恰当的优化。 从整个 Linux 应用的角度出发,因为应用中使用了异步信号,程序中一些库函数在调用时可能被异步信号中断,此时必须根据errno
的值考虑这些库函数调用被信号中断后的出错恢复处理。
显而易见,编写异步信号处理函数如履薄冰,稍不注意就会忘掉某一原则,照成隐患(编写安全的异步信号处理函数本身有很多的规则束缚;应用中其它地方在调用可被信号中断的库函数时还需考虑被中断后的出错恢复处理)。
2,方案
幸运的是,POSIX.1 规范定义了sigwait()、 sigwaitinfo()
和 pthread_sigmask()
等接口,可以实现:
这种在指定的线程中以同步方式处理信号的模型可以避免因为处理异步信号而给程序运行带来的不确定性和潜在危险。
sigwait()
提供了一种等待信号的到来,以串行的方式从信号队列中取出信号进行处理的机制。sigwait(
)只等待函数参数中指定的信号集,即如果新产生的信号不在指定的信号集内,则 sigwait()
继续等待。
sigwaitinfo()
以及 sigtimedwait()
也提供了与 sigwait()
函数相似的功能。
因此,我们可以这样来搭建我们的模型:
sigwait()
的第一个参数。 3,总结
在基于 Linux 的多线程应用中,对于因为程序逻辑需要而产生的信号,可考虑使用同步模型进行处理;而对会导致程序运行终止的信号如 SIGSEGV 等,必须按照传统的异步方式使用 signal()
、 sigaction()
注册信号处理函数进行处理。这两种信号处理模型可根据所处理的信号的不同同时存在一个 Linux 应用中:
sigwait()
等待的信号集已经被进程中所有的线程阻塞。 kill()
将信号发给整个进程,而不能使用 pthread_kill()
发送某个特定的工作线程,否则信号处理线程无法接收到此信号。 sigwait()
使用了串行的方式处理信号的到来,为避免信号的处理存在滞后,或是非实时信号被丢失的情况,处理每个信号的代码应尽量简洁、快速,避免调用会产生阻塞的库函数。
注意点:
转载地址:http://bdwpi.baihongyu.com/