1

如您所见,这是 APUE 中的示例。

#include "apue.h"

static void sig_int(int sig);

int main(int argc, char **argv)
{
    char buf[MAXLINE];
    pid_t pid;
    int status;

    if (signal(SIGINT, sig_int) == SIG_ERR) //sig_int is a simple handler function
        err_sys("signal error");

    printf("%% ");
    while (fgets(buf, MAXLINE, stdin) != NULL) {
    //This is a loop to implement a simple shell
    }
    return 0;
}

这是信号处理程序

void sig_int(int sig)   
/*When I enter Ctrl+C, It'll say a got SIGSTOP, but it would terminate.*/
{
    if (sig == SIGINT)
        printf("got SIGSTOP\n");
}

当我输入 Ctrl+C 时,它会说得到 SIGSTOP,但它现在终止。

4

1 回答 1

6

简短的版本是信号中断当前的系统调用。你正在做fgets()的,现在可能会阻塞read()系统调用。read()调用被中断,它返回 -1 并设置errnoEINTR

这会导致fgets返回 NULL,循环结束,程序结束。

一些背景

linux 上的 glibc 为signal(). 一种是跨信号自动重新启动系统调用,另一种不是。

当一个信号出现并且进程在系统调用中被阻塞时,系统调用被中断(“取消”)。执行在用户空间应用程序中恢复,并出现信号处理程序。中断的系统调用将返回错误,并将 errno 设置为EINTR.

接下来会发生什么取决于系统调用是否跨信号重新启动。

如果系统调用可重新启动,则运行时 (glibc) 只需重试系统调用。对于read()系统调用,这类似于read()实现为:

ssize_t read(int fd, void *buf, size_t len)
{
  ssize_t sz;
  while ((sz = syscall_read(fd, buf, len)) == -1 
          && errno == EINTR);
  return sz;

}

如果系统调用没有自动重新启动,read()其行为类似于:

ssize_t read(int fd, void *buf, size_t len)
{
  ssize_t sz;
  sz = syscall_read(fd, buf, len));
  return sz;
}

在后一种情况下,由您的应用程序检查 read() 是否因为被信号中断而失败。由您决定是否read()由于信号被处理而暂时失败,您可以重试read()呼叫

信号与信号

通过使用sigaction()instead of signal(),您可以控制是否重新启动系统调用。您指定的相关标志sigaction()

SA_RESTART 通过使某些系统调用可跨信号重新启动,提供与 BSD 信号语义兼容的行为。该标志仅在建立信号处理程序时才有意义。有关系统调用重新启动的讨论,请参见 signal(7)。

BSD 与 SVR4 语义

如果你使用signal(),这取决于你想要什么语义。从 的描述中可以看出SA_RESTART,如果是 BSD 信号语义,系统调用会被重启。这是 glibc 中的默认行为。

另一个区别是 BSD 语义在处理信号signal()后留下由安装的信号处理程序。SVR4 语义卸载信号处理程序,如果您想捕获更多信号,您的信号处理程序将不得不重新安装处理程序。

apue.h

然而,“apue.h”_XOPEN_SOURCE 600在包含<signal.h>. 这将导致 signal() 具有 SVR4 语义,系统调用不会重新启动。这将导致您的 fgets() 调用“失败”。

不要使用signal(),使用sigaction()

由于所有这些行为差异,请使用 sigaction() 代替信号。sigaction() 让您可以控制发生的事情,而不是根据(可能)隐藏来更改语义#define,就像使用signal()

于 2013-11-09T13:49:43.660 回答