默认的 SIGINT 处理程序是如何在它的库定义中实现的?

How is default SIGINT handler implemented in it's library definition?

Objective:由于某些奇怪的原因,将 This line must be printed 写入日志文件 mib_log_test 以防程序 hangs/stuck。

为简单起见,写了一个C程序如下:

#include <stdio.h>
#include <stdlib.h>

#define FILE_NAME "./mib_log_test"
FILE *fp = NULL;

int main()
{
    fp = fopen(FILE_NAME, "w+");
    if (fp == NULL) {
        fprintf(stderr, "Unable to open %s file", FILE_NAME);
        exit(EXIT_FAILURE);
    }
    fprintf(fp, "This line must be printed\n");
    while(1);
    return 0;
}

编译和运行以上程序,它永远不会因为无限循环而自行终止。所以我必须按 ctrl + c 来终止它。使用 ctrl + c 我没有看到 This line must be printed 被写入我的日志文件 (mib_log_test)

并且如果我如下所示覆盖默认的 SIGINT 处理程序,This line must be printed 会写入我的日志文件 (mib_log_test)。

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

#define FILE_NAME "./mib_log_test"
FILE *fp = NULL;

void sigint_handler(int sig_num)
{
    exit(EXIT_FAILURE);
}

int main()
{
    fp = fopen(FILE_NAME, "w+");
    if (fp == NULL) {
        fprintf(stderr, "Unable to open %s file", FILE_NAME);
        exit(EXIT_FAILURE);
    }
    signal(SIGINT, sigint_handler);
    fprintf(fp, "This line must be printed\n");
    while(1);
    return 0;
}

问题:什么默认的 SIGINT 处理程序会导致在上述情况下不写入日志消息?

调用 default SIGINT handler terminates the process abnormally. This means _exit,它不会刷新缓冲区。

作为旁注,调用 exit (which does flush buffers) from a signal handler is unsafe (only async-safe functions should be called from signal handlers)。所以,这不是您问题的真正解决方案。

如果您确实希望它出现在日志文件中,即使进程异常终止,您也可以在 fprintf 之后添加一个 fflush(fp);

然而,冲洗可能相当昂贵。如果您想避免刷新每一行日志,但仍希望在收到 SIGINT 时刷新日志文件,一种方法是:

#include <signal.h>

static volatile sig_atomic_t keepRunning = 1;

void sigHandler(int sig) {
  keepRunning = 0;
}

int main(void) {
  signal(SIGINT, sigHandler);

  while (keepRunning) {
    /* normal operation, including logging */
  }

  /* cleanup */

  return 0; /* this will close (and thus flush) the log file */
}

关键是实际的清理(通常不是 async-safe)不会发生在信号处理程序本身。

Stdio 文件缓冲区默认为 block-buffered,当它崩溃时它不会刷新文件缓冲区,因此缓冲输出会丢失。

一个解决方案是在每个 fprintf(fp, ...) 之后调用 fflush(fp),但这相当乏味。

另一个解决方案是在打开文件后立即使用 setvbuf 将文件设置为 line-buffered 模式,以便它在每个 new-line 符号上为您刷新缓冲区:

fp = fopen(FILE_NAME, "w+");
setvbuf(fp, NULL, _IOLBF, BUFSIZ);

这也使得 tail -f <logfile> 立即并逐行输出,而不是延迟并成块输出。