信号的影响
前面介绍的信号是软中断,主要是由于信号的发送和中断一样具有异步性和随机性的特点。
系统调用中断
如果线程在某些慢速系统调用的阻塞期间捕捉到一个信号,那么此时的系统调用就会被中断,并且返回错误号和设置 errno 为 EINTR。属于这一类的系统调用包括:
- POSIX 消息队列调用:mq_receive 函数、mq_send 函数。
- POSIX AIO 调用:aio_suspend 函数。
- 信号调用:sigsuspend 函数、pause 函数、sigtimedwait 函数、sigwaitinfo 函数。
- 定时器调用:nanosleep 函数、sleep 函数。
信号对上述系统调用的作用,可能正是设计所期望的,也可能是设计必须避免的。无论哪种情况,完善的系统应该充分考虑这种影响。如果要避免信号对系统调用的影响,就要采取一定的措施来重新启动系统调用,在 4.2BSD 中,程序能够选择自动恢复被信号中断的系统调用,SylixOS 支持这一特点,只要在安装信号处理函数时设置 SA_RESTART 标志,系统将会自动判断并恢复被中断的系统调用。
如下表给出了部分 SylixOS 中能被信号中断的系统调用:
函数名 | 描述 |
---|---|
nanosleep | 使线程睡眠一个指定的时间(纳秒级) |
usleep | 使线程睡眠一个指定的时间(微秒级) |
sleep | 使线程睡眠一个指定的时间(秒级) |
mq_send | POSIX 消息队列发送函数 |
mq_timedsend | POSIX 消息队列发送函数,带超时(时间为绝对时间) |
mq_reltimedsend_np | POSIX 消息队列发送函数,带超时(时间为相对时间) |
mq_receive | POSIX 消息队列接收函数 |
mq_timedreceive | POSIX 消息队列接收函数,带超时(时间为绝对时间) |
mq_reltimedreceive_np | POSIX 消息队列接收函数,带超时(时间为相对时间) |
sem_wait | POSIX 信号量阻塞函数 |
sem_timedwait | POSIX 信号量阻塞函数,带超时(时间为绝对时间) |
sem_reltimedwait_np | POSIX 信号量阻塞函数,带超时(时间为相对时间) |
函数可重入影响
线程捕捉到信号并对其进行处理时,正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理函数中的指令。如果从信号处理程序返回,则继续执行在捕捉到信号时正在执行的正常指令序列(这类似于发生硬件中断时所做的)。但在信号处理函数中,不能判断捕捉到信号时线程执行到何处。如果正在执行 malloc 在其堆中分配另外的存储空间,而此时由于捕捉到信号而插入执行该信号处理程序,其中又调用 malloc 函数,这时可能会对正在执行的上下文造成破坏。
Single UNIX Specification 说明了在信号处理程序中保证调用安全的函数,这些函数是可重入的。除了可重入以外,在信号处理操作期间,它会阻塞任何会引起一致的信号发送,下表列出了这些异步信号安全函数:
函数名 | 函数名 | 函数名 | 函数名 | 函数名 | 函数名 |
---|---|---|---|---|---|
abort | dup2 | getpid | recv | sigfillset | times |
accept | execl | getppid | recvfrom | sigismember | umask |
access | execle | getsockname | recvmsg | signal | uname |
aio_error | execv | getsockopt | rename | sigpending | unlink |
aio_return | execve | getuid | rmdir | sigprocmask | utime |
aio_suspend | _Exit | kill | select | sigqueue | utimes |
alarm | _exit | listen | sem_post | sigsuspend | wait |
bind | fchmod | lseek | send | sleep | waitpid |
cfgetispeed | fchown | lstat | sendmsg | socket | write |
cfggetospeed | fcntl | mkdir | sendto | socketpair | |
cfsetispeed | fdatasync | mkfifo | setgid | stat | |
cfsetospeed | fstat | mknod | setpgid | symlink | |
chdir | fsync | open | setsid | tcdrain | |
chmod | ftruncate | pause | setsockopt | tcflush | |
chown | getegid | pipe | setuid | tcgetattr | |
clock_gettime | geteuid | poll | shutdown | tcsetattr | |
close | getgid | pselect | sigaction | time | |
connect | getgroups | raise | sigaddset | timer_getoverrun | |
creat | getpeername | read | sigdelset | timer_gettime | |
dup | getpgrp | readlink | sigemptyset | timer_settime |
下面我们看一个实例,在信号处理函数 int_handler 中调用 getpwnam 函数来获得用户名,int_handler 每一秒被调用一次。
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pwd.h>
void int_handler (int signum)
{
struct passwd *ptr;
fprintf(stdout, "Alarm signal!\n");
if ((ptr = getpwnam("root")) == NULL) {
fprintf(stderr, "getpwnam error.\n");
}
alarm(1);
}
int main (int argc, char *argv[])
{
struct passwd *ptr;
signal(SIGALRM, int_handler);
alarm(1);
for (;;) {
if ((ptr = getpwnam("sylixos")) == NULL) {
fprintf(stderr, "getpwnam error.\n");
}
if (strcmp(ptr->pw_name, "sylixos") != 0) {
fprintf(stderr, "ptr->pw_name: %s\n", ptr->pw_name);
}
}
return (0);
}
运行该程序,会发现程序结果具有随机性。一般情况,信号处理函数被调用几次后,程序将可能会发生异常由信号 SIGSEGV 终止结束,也可能 main 函数还能正常运行,此时系统 Shell 却产生异常。
从此实例中可以看出,如果在信号处理函数中调用一个不可重入函数,则结果是不可预测的。