1 可重入函数
进程在捕捉到信号并对其进行处理时,进程正在执行的正常指令就被信号处理程序临时中断。进程首先执行信号处理程序的指令,然后从信号处理程序返回则继续执行在捕捉信号前正在执行的正常指令序列。
例如,如果进程正在执行malloc,在其堆中分配存储空间,而此时捕捉到信号,信号处理程序中又调用malloc,则可能对进程造成破坏,因为malloc通常为它分配的存储区维护一个李链表,而信号处理程序中又调用malloc,可能会破坏该链表。
再例如,若进程在执行getpwnam,因为getpwnam函数将结果存放在静态存储区,而此时捕捉到信号,信号处理程序中又调用getpwnam,则返回给调用者的信息可能被信号处理程序的信息覆盖。
Single UNIX Specification说明了在信号处理函数内保证调用安全的函数。这些函数被称为可重入函数,也被称为异步信号安全函数。
可重入函数如下表:
abort | faccessat | linkat | select | socketpair |
accept | fchmod | listen | sem_post | stat |
access | fchmodat | lseek | send | symlink |
aio_error | fchown | lstat | sendmsg | symlinkat |
aio_return | fchownat | mkdir | sendto | tcdrain |
aio_suspend | fcntl | mkdirat | setgid | tcflow |
alarm | fdatasync | mkfifo | setpgid | tcflush |
bind | fexecve | mkfifoat | setsid | tcgetattr |
cfgetispeed | fork | mknod | setsockopt | tcgetpgrp |
cfgetospeed | fstat | mknodat | setuid | tcsendbreak |
cfsetispeed | fstatat | open | shutdown | tcsetattr |
cfsetospeed | fsync | openat | sigaction | tcsetpgrp |
chdir | ftruncate | pause | sigaddset | time |
chmod | futimens | pipe | sigdelset | timer_getoverrun |
chown | getegid | poll | sigemptyset | timer_gettime |
clock_gettime | geteuid | posix_trace_event | sigfillset | timer_settime |
close | getgid | pselect | sigismember | times |
connect | getgroups | raise | signal | umask |
creat | getpeername | read | sigpause | uname |
dup | getpgrp | readlink | sigpending | unlink |
dup2 | getpid | readlinkat | sigprocmask | ulinkat |
execl | getppid | recv | sigqueue | utime |
execle | getsockname | recvfrom | sigset | utimensat |
execv | getsockopt | recvmsg | sigsuspend | utimes |
execve | getuid | rename | sleep | wait |
_Exit | kill | renameat | socketmark | waitpid |
_exit | link | rmdir | socket | write |
注意,即使信号处理程序里调用了上表里的函数,但是由于每一个线程只有一个errno变量,所以信号处理程序可能会修改其原先的值。例如,main函数设置了errno的值,而信号处理函数调用了read函数,从而可能更改了errno的值。因此,作为一个通用的规则,当信号处理函数调用上表中的函数时,应该在调用前保存errno,在调用后恢复errno。
2 信号处理函数内必须使用可重入函数
如下程序的信号处理程序my_alarm里调用非可重入函数getpwnam,alarm函数每3秒产生一次SIGALRM信号。
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
static void my_alarm(int signo)
{
struct passwd *rootptr;
printf("in signal handler >>> \n");
if ((rootptr = getpwnam("root")) == NULL)
printf("getpwnam(root) error");
printf("signal: pw_name = %s\n", rootptr->pw_name);
printf("in signal handler done <<<\n");
/* 继续设置3s一次SIG_ALARM,否则SIG_ALARM信号不再有 */
alarm(3);
}
int main(void)
{
struct passwd *ptr;
char *loginName = "hyh";
signal(SIGALRM, my_alarm);
/* 设置SIG_ALARM,3s一次 */
alarm(3);
for ( ; ; ) {
/* getpwname:获取loginName登录信息 */
if ((ptr = getpwnam(loginName)) == NULL)
printf("getpwnam error");
sleep(1);
printf("pw_name = %s\n", ptr->pw_name);
if (strcmp(ptr->pw_name, loginName) != 0)
printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);
}
}
测试结果如下:
pw_name = hyh
pw_name = hyh
in signal handler >>>
signal: pw_name = root
in signal handler done <<<
pw_name = root
return value corrupted!, pw_name = root
pw_name = hyh
pw_name = hyh
in signal handler >>>
signal: pw_name = root
in signal handler done <<<
pw_name = root
return value corrupted!, pw_name = root
pw_name = hyh
pw_name = hyh
in signal handler >>>
signal: pw_name = root
in signal handler done <<<
pw_name = root
return value corrupted!, pw_name = root
pw_name = hyh
...
可以看到非可重入函数getpwnam的pw_name在main函数时设置的是hyh,信号处理函数又再次调用getpwnam后设置为root,结果被覆盖了。
所以请注意,信号处理函数应该使用可重入函数,禁止使用非可重入函数。