异步信号安全函数

在学习redis的src/server.c时发现输出日志居然在实现了serverLogRaw()时又实现了下述函数并使用更底层的系统调用write去写日志.通过注释我们发现该函数主要是在信号(那些从Redis角度来看不是致命的信号)处理函数中被调用,对于能够杀死Redis进程的信号调用serverLog()函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Log a fixed message without printf-alike capabilities, in a way that is
 * safe to call from a signal handler.
 *
 * We actually use this only for signals that are not fatal from the point
 * of view of Redis. Signals that are going to kill the server anyway and
 * where we need printf-alike features are served by serverLog(). */
void serverLogFromHandler(int level, const char *msg) {
    int fd;
    int log_to_stdout = server.logfile[0] == '\0';
    char buf[64];

    if ((level&0xff) < server.verbosity || (log_to_stdout && server.daemonize))
        return;
    fd = log_to_stdout ? STDOUT_FILENO :
                         open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644);
    if (fd == -1) return;
    ll2string(buf,sizeof(buf),getpid());
    if (write(fd,buf,strlen(buf)) == -1) goto err;
    if (write(fd,":signal-handler (",17) == -1) goto err;
    ll2string(buf,sizeof(buf),time(NULL));
    if (write(fd,buf,strlen(buf)) == -1) goto err;
    if (write(fd,") ",2) == -1) goto err;
    if (write(fd,msg,strlen(msg)) == -1) goto err;
    if (write(fd,"\n",1) == -1) goto err;
err:
    if (!log_to_stdout) close(fd);
}

通过man signal-safety可以查看异步信号安全的相关信息和哪些函数是异步信号安全的:

  • An async-signal-safe function is one that can be safely called from within a signal handler. Many functions are not async-signal-safe. In particular, non-reentrant functions are generally unsafe to call from a signal handler.

  • The kinds of issues that render a function unsafe can be quickly understood when one considers the implementation of the stdio library, all of whose functions are not async-signal-safe.

  • When performing buffered I/O on a file, the stdio functions must maintain a statically allocated data buffer along with associated counters and indexes (or pointers) that record the amount of data and the current position in the buffer. Suppose that the main program is in the middle of a call to a stdio function such as printf(3) where the buffer and associated variables have been partially updated. If, at that moment, the program is interrupted by a signal handler that also calls printf(3), then the second call to printf(3) will operate on inconsistent data, with unpredictable results.

  • To avoid problems with unsafe functions, there are two possible choices:

    • Ensure that (a) the signal handler calls only async-signal-safe functions, and (b) the signal handler itself is reentrant with respect to global variables in the main program.

    • Block signal delivery in the main program when calling functions that are unsafe or operating on global data that is also accessed by the signal handler.

  • Generally, the second choice is difficult in programs of any complexity, so the first choice is taken.

  • POSIX.1 specifies a set of functions that an implementation must make async-signal-safe. (An implementation may provide safe implementations of additional functions, but this is not required by the standard and other implementations may not provide the same guarantees.) In general, a function is async-signal-safe either because it is reentrant or because it is atomic with respect to signals (i.e., its execution can’t be interrupted by a signal handler).

  • The set of functions required to be async-signal-safe by POSIX.1 is shown in the following table. The functions not otherwise noted were required to be async-signal-safe in POSIX.1-2001; the table details changes in the subsequent standards.

1
2
3
4
5
6
7
Function               Notes
abort(3)               Added in POSIX.1-2003
accept(2)
bind(2)
getgid(2)
......
write(2)

自己的理解

通过搜索我们发现异步信号安全这个名词,异步信号安全也被称为信号安全(man手册中这两个定义也是等价的: signal-safety - async-signal-safe functions),因为信号是异步的.

异步信号安全函数是指那些信号处理函数中可以被安全调用的函数。许多函数不是异步信号安全的函数,通常来说非重入函数在信号处理函数中被调用是不安全的。

我们以stdio库为例,就可以很快的理解异步信号安全函数的概念。stdio库的所有函数都不是异步信号安全的,当我们对文件执行行缓冲的I/O时,stdio函数必须维护一个数据缓冲区及与之相关的计数器和索引(或者指针),这些计数器和索引会记录当前缓冲区中的数据量以及当前数据在缓冲区的位置。假设主程序正在调用stdio库中的printf()函数,于此同时,该程序需要中断执行相应的信号处理函数,不幸的是信号处理函数中也调用了printf()函数,第二次调用将导致数据缓存区的数据不一致且输出结果是不可预测的。

为了避免这种问题我们通常是在信号处理函数中只调用异步信号安全的函数。