分类: LINUX
2012-01-22 20:23:11
++++++APUE读书笔记-10信号-06可重入函数++++++
6、可重入函数
================================================
进程捕捉到信号时,进程正常执行的指令次序会被信号处理打断。进程会继续执行,但是这时候执行的是信号处理函数中的指令。如果信号处理函数返回(不是调用exit或者调用longjmp等方式返回的),那么在捕捉到信号之前的普通的指令会依次执行(这一点和硬中断发生的情况类似)。但是,在信号处理函数中,我们无法判断当捕捉到信号的时候进程执行到了哪里。如果进程正在调用malloc分配内存的过程中被信号打断,并且在信号处理函数中还调用malloc分配内存,这时候会怎样?如果进程正在执行一个函数的过程中,例如getpwnam函数,这个函数使用了局部静态了变量存储它运行的结果,然后我们在信号处理函数中调用了相同的函数,这时候会怎么样?在malloc的例子中,进程会发生很严重的错误;这是因为,malloc通常会维护一个链表,这个链表存放它分配的区域,很可能在它修改这个链表的时候捕捉到信号并且在信号处理函数中又调用malloc函数,这样修改链表的操作就被打乱了。在getpwnam的例子中,由于getpwnam每次调用都有一个不同的结果,并且把结果返回到一个局部的静态变量中,所以在捕捉到信号并且信号处理的时候调用了getpwnam的时候,很可能捕捉信号之前的getpwnam的结果被后来的getpwnam覆盖,这样先前的getpwnam的结果就是错误的了。
Single UNIX Specification指定了哪些函数是可重入的函数,下面以表格的方式列出了这些函数。
信号处理函数可能会调到的可重入函数
+----------------------------------------------------------------------------------+
| accept | fchmod | lseek | sendto | stat |
|---------------+-------------+-------------------+-------------+------------------|
| access | fchown | lstat | setgid | symlink |
|---------------+-------------+-------------------+-------------+------------------|
| aio_error | fcntl | mkdir | setpgid | sysconf |
|---------------+-------------+-------------------+-------------+------------------|
| aio_return | fdatasync | mkfifo | setsid | tcdrain |
|---------------+-------------+-------------------+-------------+------------------|
| aio_suspend | fork | open | setsockopt | tcflow |
|---------------+-------------+-------------------+-------------+------------------|
| alarm | fpathconf | pathconf | setuid | tcflush |
|---------------+-------------+-------------------+-------------+------------------|
| bind | fstat | pause | shutdown | tcgetattr |
|---------------+-------------+-------------------+-------------+------------------|
| cfgetispeed | fsync | pipe | sigaction | tcgetpgrp |
|---------------+-------------+-------------------+-------------+------------------|
| cfgetospeed | ftruncate | poll | sigaddset | tcsendbreak |
|---------------+-------------+-------------------+-------------+------------------|
| cfsetispeed | getegid | posix_trace_event | sigdelset | tcsetattr |
|---------------+-------------+-------------------+-------------+------------------|
| cfsetospeed | geteuid | pselect | sigemptyset | tcsetpgrp |
|---------------+-------------+-------------------+-------------+------------------|
| chdir | getgid | raise | sigfillset | time |
|---------------+-------------+-------------------+-------------+------------------|
| chmod | getgroups | read | sigismember | timer_getoverrun |
|---------------+-------------+-------------------+-------------+------------------|
| chown | getpeername | readlink | signal | timer_gettime |
|---------------+-------------+-------------------+-------------+------------------|
| clock_gettime | getpgrp | recv | sigpause | timer_settime |
|---------------+-------------+-------------------+-------------+------------------|
| close | getpid | recvfrom | sigpending | times |
|---------------+-------------+-------------------+-------------+------------------|
| connect | getppid | recvmsg | sigprocmask | umask |
|---------------+-------------+-------------------+-------------+------------------|
| creat | getsockname | rename | sigqueue | uname |
|---------------+-------------+-------------------+-------------+------------------|
| dup | getsockopt | rmdir | sigset | unlink |
|---------------+-------------+-------------------+-------------+------------------|
| dup2 | getuid | select | sigsuspend | utime |
|---------------+-------------+-------------------+-------------+------------------|
| execle | kill | sem_post | sleep | wait |
|---------------+-------------+-------------------+-------------+------------------|
| execve | link | send | socket | waitpid |
|---------------+-------------+-------------------+-------------+------------------|
| _Exit & _exit | listen | sendmsg | socketpair | write |
+----------------------------------------------------------------------------------+
有许多函数没有在列出的函数之内,那是因为:(a)它们使用了静态数据结构 (b)它们调用了malloc或free (c)它们是标准I/O库的一部分。大多数标准I/O库实现的时候,都采用一种不可重入的方式,来访问一个全局的数据结构。我们还需注意的是,尽管我们的一些例子在信号处理的时候,使用了printf函数,但是这并不保证会产生正确的输出结果,因为可能产生信号的时候,正是我们的主程序运行printf函数的时候。
(在这里补充一下关于可重入的概念:可重入函数可以被一个以上的任务调用,而不必担心数据被破坏,它在任何时候都可以被中断,一段时间后又可以运行,而应用数据不会丢失.满足条件是:不使用共享资源;在使用共享资源时关中断,使用完毕后再开中断;在使用共享资源时申请信号量,使用完后释放信号量.例如:含静态局部变量的函数是非可重入的).
还有一个需要注意的地方是,尽管我们在信号处理函数中调用的是列出的那些个可重入的函数,但是不要忘记在每个线程中只有一个errno变量,我们可能会把这个errno变量的值改变了。比如说,我们的main函数中由于某些原因导致设置了errno,之后就产生了信号,导致执行信号处理函数;如果这个信号处理函数调用了read,那么可能就会修改errno变量的值,从而覆盖main函数中的errno的值。因此,一般来说,我们在信号处理函数中调用前面列出的“可重入”函数的时候,要确保能够保存和恢复errno的值。(需要注意,一个比较经常发生的信号是SIGCHLD信号,这个信号的处理函数中,经常调用各种wait函数,所有的wait函数都会修改errno的值!).
需要注意的是longjmp函数和siglongjmp函数也没有列在“可重入”函数的里面。因为,可能在main函数正在以不可重入方式修改一个数据结构,有可能在信号处理函数中调用siglongjmp的时候这个数据结构正处于被修改了一半的状态。如果修改的数据结构是一个全局的数据结构,那么,如果捕获一个导致sigsetjmp被调用的信号的时候,这个应用程序应该在修改这个数据结构的时候阻塞那个信号。
参考资料中有一个例子,这里只给出关键的部分:
static void my_alarm(int signo)
{
struct passwd *rootptr;
...
rootptr = getpwnam("root");
...
alarm(1);
}
int main(void)
{
struct passwd *ptr;
signal(SIGALRM, my_alarm);
alarm(1);
for ( ; ; ) {
...
ptr = getpwnam("sar");
...
strcmp(ptr->pw_name, "sar");
...
}
}
这里例子主要说,在信号处理函数my_alarm中,以及main中都调用了getpwnam,由于getpwnam是不可重入的,所以在my_alarm中调用这个函数,就可能导致各种问题,出现的问题或者是结果很不正确,或者是提示free了没有malloc的数据,或者是正确的结果等等,这里省略详细解释,具体参见参考资料。
参考: