Linux系统编程学习笔记(八)信号管理1第9章 信号1信号是提供处理异步事件机制的软件中断。这些事件可以来自
Linux系统编程学习笔记(八)信号管理1
第9章 信号1
信号是提供处理异步事件机制的软件中断。这些事件可以来自系统外部--例如用户产生中断符(通常是Ctrl+c),
或者来自程序或者内核内部的活动,例如进程执行除以0的代码。作为一种进程间通信的基本形式,进程也可以
给另一个进程发信号。
不光事件的发生是异步的,而且程序对信号的处理也是异步的。信号处理函数在内核中注册,收到信号时,内核
从程序的其它部分异步地调用信号处理函数。
信号很早就是Unix的一部分,但是早期的信号时不可靠的,可能会出现丢失的情况。后来不同的Unix对信号做了
不同的改进,出现了兼容性问题。但后来POSIX标准标准化了信号处理。
1、信号的概念:
信号有一个非常明确的生命周期:首先产生信号,然后内核存储信号直到可以发送它,最后内核适当的处理信号。
一下几种情形可以产生信号:
1)终端形成的信号:当用户按下一定的终端键。比如在终端上按下DELETE键(或者Ctrl+c),可以产生中断信号(SIGINT),
2)硬件异常信号:比如除以0,不合法的内存引用,这些条件通常是由硬件捕获的,然后通知内核,然后内核产生恰当的
信号。比如当一个程序执行一个非法的内存引用,就会产生SIGSEGV。
3)kill给进程发的信号:kill允许一个进程向另一个进程或者进程组发送任一信号。当然我们必须是接受信号进程的拥有者
或者超级用户。
4)软件条件形成的信号:软件条件可以产生信号,当发生了事件要通知进程时。比如SIGURG(当数据从网络连接中
发送过来)、SIGPIPE(一个进程向管道中写数据,但是reader进程已经终止)、SIGALARM(进程设置的alarm clock超时)。
信号是一个经典的异步事件,信号可以在任意时间发生,进程不能简单的测试一个变量来查看一个信号是否已经发生,而是:
进程需要告诉内核“当事件发生时怎么做”。当信号发生时,我们可以做一下三种事之一:(我们叫做信号处理)
1)忽略信号不采取任何操作,但是有两种信号不可以忽略:SIGKILL和SIGSTOP。这样做的原因是系统管理员需要能够
杀死或停止进程。如果进程能够忽略他们,将会破坏这一权利
2)捕获并处理 内核将停止该进程正在执行的代码,并跳转到先前注册过的函数,执行这个函数。一旦进程从该函数返回,
它会跳回捕获到信号的地方继续执行。SIGKILL和SIGSTOP不能被捕获。
3)执行默认操作 该操作取决于被发送的信号。默认操作通常是终止进程。例如SIGKILL就是这样。
2、信号标示符:每一个信号都有一个以SIG为前缀的符号名称,比如SIGINT就是用户按下Ctrl+c时发出的信号,SIGABRT是进程
调用abort函数产生的信号,SIGKILL是进程被强制终止时产生的信号。
这些信号都是在<signal.h>头文件中定义的,每一个信号都有一个整数标识符相关联,具体的映射依赖于实现,尽可能使用
可读的名称,而不是整数值。大约共有31个信号,从1开始编号,没有任何信号的值为0,这个是一个特殊值被称为空信号。
3、基本的信号管理:
最简单古老的信号管理接口是signal函数,这个函数由ISO C89标准定义的,其中只定义了信号支持的最少的共同特征,是个
非常基本的系统调用。Linux通过其他接口提供了更多的信号控制。
1)我们先看看signal函数:
#include <signal.h>#include <unistd.h>static void sig_alarm(int signo){/* noting to do, just return to wake up the pause */}unsigned int sleep1(unsigned in seconds){if(signal(SIGALARM,sig_alarm) == SIG_ERR){return seconds;}alarm(seconds);pause();return alarm(0);}
但是这个实现有三个问题:
1、调用sleep1之前已经有一个alarm时钟,那个alarm会被清除。这个问题可以通过第一次调用signal的时候时查看返回值来修正:
如果前一个alarm剩余的时间小于seconds,我们只需要等待前一个alarm过期,如果大于seconds,我们在返回之前重新设置alarm
到目标时间。
2、我们修改了已经存在的那个alarm时钟的处理方式。我们需要在调用之前保存处理函数,执行完之后再恢复。
3、存在竞争条件:如果在pause之前警告时钟已经到期,信号处理函数以调用。这样pause就会一直等待。
8)可重入的函数:
当内核发送信号时,进程可能执行到代码的任何位置。例如进程可能正在执行一个重要的操作,如果被中断可能会导致不一致
的状态(例如数据结构之更新了一半,或者只计算了一部分)。进程甚至正在处理另一个信号。
当信号到达的时候,信号处理程序不能说明进程正在执行什么代码,处理程序可以在任何情况下运行。因此任何该进程设置的
信号处理函数都应该谨慎的对待他的操作和设计的数据。信号处理函数不要对中断的程序做任何的假设。尤其是修改全局数据结构时。
总之,信号处理函数最好从来不接触全局的数据。
一些函数是不可重入的,如果一个程序正在执行一个不可重入的函数,信号发生了,信号处理程序也执行了这个不可重入的函数,
那么可能造成混乱。可重入函数是指可以安全调用自己的函数。为了使函数可重入,函数不能操作静态数据,必须只操作栈分配
的数据或者调用者提供的数据,不得调用任何不可以重入的函数。
在信号处理函数中我们只能调用可重入的函数,因为中断的程序可能是不可重入的。
引用
abort( ) accept( ) access( )
aio_error( ) aio_return( ) aio_suspend( )
alarm( ) bind( ) cfgetispeed( )
cfgetospeed( ) cfsetispeed( ) cfsetospeed( )
chdir( ) chmod( ) chown( )
clock_gettime( ) close( ) connect( )
creat( ) dup( ) dup2( )
execle( ) execve( ) Exit( )
_exit( ) fchmod( ) fchown( )
fcntl( ) fdatasync( ) fork( )
fpathconf( ) fstat( ) fsync( )
ftruncate( ) getegid( ) geteuid( )
getgid( ) getgroups( ) getpeername( )
getpgrp( ) getpid( ) getppid( )
getsockname( ) getsockopt( ) getuid( )
kill( ) link( ) listen( )
lseek( ) lstat( ) mkdir( )
mkfifo( ) open( ) pathconf( )
pause( ) pipe( ) poll( )
posix_trace_event( ) pselect( ) raise( )
read( ) readlink( ) recv( )
recvfrom( ) recvmsg( ) rename( )
rmdir( ) select( ) sem_post( )
send( ) sendmsg( ) sendto( )
setgid( ) setpgid( ) setsid( )
setsockopt( ) setuid( ) shutdown( )
sigaction( ) sigaddset( ) sigdelset( )
sigemptyset( ) sigfillset( ) sigismember( )
signal( ) sigpause( ) sigpending( )
sigprocmask( ) sigqueue( ) sigset( )
sigsuspend( ) sleep( ) socket( )
socketpair( ) stat( ) symlink( )
sysconf( ) tcdrain( ) tcflow( )
tcflush( ) tcgetattr( ) tcgetpgrp( )
tcsendbreak( ) tcsetattr( ) tcsetpgrp( )
time( ) timer_getoverrun( ) timer_gettime( )
timer_settime( ) times( ) umask( )
uname( ) unlink( ) utime( )
wait( ) waitpid( ) write( )
其他函数不可重入的原因:
1)使用了静态的数据结构 2)掉用了malloc或者free 3)标准IO
在信号处理函数中调用这些函数,我们要注意保存和恢复errno。