本文来自个人博客:https://dunkwan.cn
中断的系统调用
早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno
设置为EINTR
。这样处理是因为一个信号发生了,进程捕捉到它,这意味着已经发生了某种事情,所以是个好机会应当唤醒阻塞的系统调用。
这里系统调用应当与函数进行区分,当捕捉到某个信号时,被中断的是内核中执行的系统调用。
为了支持这种特性,将系统调用分为两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,包括:
- 如果某些类型文件的数据不存在,则读操作可能会使调用者永远阻塞;
- 如果这些数据不能被相同的类型文件立即接受,则写操作可能会使调用者永远阻塞;
- 在这种条件发生之前打开某些类型文件,可能会发生阻塞;
pause
函数和wait
函数;- 某些
ioctl
操作; - 某些进程间通信函数。
下面是几种实现所提供的与信号有关的函数及它们的语义。
可重入函数
在信号处理程序中保证调用安全的函数,这些函数是可重入函数并被称之为异步信号安全的。以下是一些异步信号安全的函数。
测试示例:
#include "../../include/apue.h"
#include <pwd.h>
static void my_alarm(int signo)
{
struct passwd *rootptr;
printf("in signal handler\n");
if((rootptr = getpwnam("root")) == NULL)
err_sys("getpwnam(root) error");
alarm(1);
}
int main(void)
{
struct passwd *ptr;
signal(SIGALRM, my_alarm);
alarm(1);
for(;;){
if((ptr = getpwnam("dunk")) == NULL)
err_sys("getpwnam error");
if(strcmp(ptr->pw_name, "dunk") != 0)
printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);
}
}
在运行该程序时,结果具有随机性。从这个示例中可以看出,如果在信号处理程序中调用一个非可重入函数,则其结果是不可知的。
SIGCLD
语义
SIGCLD
和SIGCHLD
这两个信号很容易被混淆。SIGCLD
是system V
的一个信号名,其语义与名为SIGCHLD
的BSD信号不同。POSIX.1采用BSD的SIGCHLD
信号。
对于SIGCLD
的早期处理方式是:
- 如果进程明确地将该信号的配置设置为
SIG_IGN
,则调用进程的子进程将不产生僵尸进程。 - 如果将
SIGCLD
的配置设置为捕捉,则内核立即检查是否有子进程准备好被等待,如果是这样,则调用SIGCLD
处理程序。
测试示例:
#include "../../include/apue.h"
#include <sys/wait.h>
static void sig_cld(int);
int main(void)
{
pid_t pid;
if(signal(SIGCHLD, sig_cld) == SIG_ERR)
perror("signal error");
if((pid = fork()) < 0){
perror("fork error");
}else if(pid == 0){
sleep(2);
_exit(0);
}
pause();
return 0;
}
static void sig_cld(int signo)
{
pid_t pid;
int status;
printf("SIGCHLD received\n");
if(signal(SIGCHLD, sig_cld) == SIG_ERR)
perror("signal error");
if((pid = wait(&status)) < 0)
perror("wait error");
printf("pid = %d\n", pid);
}
结果如下:
可靠信号术语和语义
- 造成信号的事件:硬件异常(除数为0)、软件条件(alarm定时器超时)、终端产生的信号或调用
kill
函数。- 当一个信号产生时,内核通常在进程表中以某种形式设置一个标志,这种行为称之为向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的(pending)。
- POSIX.1允许系统递送该信号一次或多次,如果信号被递送多次,则称信号进行了排队。
- 每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。
- 信号编号可能会超过一个整型所包含的二进制位数,因此POSIX.1定义了一个新数据类型
sigset_t
,它可容纳一个信号集。