Как реализован обработчик SIGINT по умолчанию в определении библиотеки?

Задача: Записать This line must be printed в лог-файл mib_log_test в случае зависания/зависания программы по какой-то непонятной причине.

Для простоты написал программу на 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 по умолчанию приводит к тому, что сообщения журнала не пишутся в приведенном выше случае?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
711
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Обработчик SIGINT по умолчанию аварийно завершает процесс. Это означает Вызывается _exit, который не сбрасывает буферы.

В качестве примечания: вызов exit (который делает очищает буферы) из обработчика сигнала небезопасен (только асинхронно-безопасные функции должны вызываться из обработчиков сигналов). Так что это не реальное решение вашей проблемы.

Вы можете добавить fflush(fp); после fprintf, если вы действительно хотите, чтобы он отображался в файле журнала, даже если процесс завершается аварийно.

Однако промывка может быть довольно дорогостоящей. Если вы хотите избежать очистки каждой отдельной строки журнала, но все же хотите, чтобы файл журнала очищался при получении 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 */
}

Суть в том, что фактическая очистка (которая часто не является асинхронной) не происходит в самом обработчике сигнала.

Как я уже сказал, это всего лишь пример кода, моя реальная программа довольно длинная и содержит десятки fprintf, так где же мне использовать fflush и эффективно ли использовать несколько раз?

Raxesh Oriya 10.05.2019 10:55

@RaxeshOriya : fflush имеет незначительную стоимость, поэтому вы используете его так часто, как это необходимо, но не более того. Как часто это необходимо, это то, что вам нужно будет определить для ваших конкретных потребностей. Однако для долго работающих приложений лучшим подходом к работе с сигналами является установка флага в обработчике сигналов, который затем завершает основной цикл, поэтому перед выходом можно выполнить надлежащую очистку (например, stackoverflow.com/questions/4217037/catch-ctrl-c-in-c). Ключевым моментом здесь является то, что очистка не происходит в обработчике сигнала.

Sander De Dycker 10.05.2019 11:56
Ответ принят как подходящий

Файловые буферы Stdio по умолчанию блочно-буферизованы, и при сбое он не очищает файловый буфер, так что буферизованный вывод теряется.

Одно из решений — вызывать fflush(fp) после каждого fprintf(fp, ...), но это довольно утомительно.

Другим решением является использование setvbuf для перевода файла в режим линейной буферизации сразу после открытия файла, чтобы он очищал буфер для вас при каждом символе новой строки:

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

Это также делает вывод tail -f <logfile> немедленным и построчным, а не отложенным и блоками.

Другие вопросы по теме