@ heinrich5991: В зависимости от программы может существовать безопасный для сигналов способ выполнения fflush()
. И ответ на ваш последний вопрос (в вашем комментарии к ответу Бретта Хейла) должен быть да, при условии, что все сделано тщательно. Однако это может усложниться.
Для иллюстрации начнем с простой версии программы cat
:
int main(void)
{
int c;
while (1) {
c = getchar();
if (c < 0 || c > UCHAR_MAX) /* Includes EOF */
break;
putchar(c);
}
fflush(stdout);
return 0;
}
Программа корректно завершается, когда getchar()
возвращает EOF
.
Сигналы могут потребоваться, когда эта программа получает входные данные из потока, который может никогда не отправлять EOF
(например, терминал в необработанном режиме). Для таких случаев мы можем попытаться реализовать изящное завершение с помощью сигнала, который он перехватывает, скажем, SIGUSR1
:
void sig_handler(int signo)
{
if (signo == SIGUSR1)
_exit(0);
}
int main(void)
{
/* Code for installing SIGUSR1 signal handler here */
/* Remaining code here as before */
}
Этот обработчик безопасен, так как _exit()
безопасен для асинхронного сигнала. Проблема в том, что сигнал SIGUSR1
завершит программу без сброса выходных буферов, что может привести к потере вывода. Мы можем попытаться исправить это принудительной очисткой, например:
void sig_handler(int signo)
{
if (signo == SIGUSR1) {
fflush(stdout);
_exit(0);
}
}
Но тогда это больше не безопасно для сигналов, так как fflush()
нет. Библиотечная функция putchar()
периодически очищает stdout
. Сигнал может прийти в середине вызова putchar()
, который может быть в середине сброса stdout
. Затем наш обработчик выполняет еще один вызов fflush(stdout)
, в то время как предыдущая очистка stdout
(из putchar
main
) еще не завершилась. В зависимости от реализации стандартной библиотеки это может привести к тому, что буфер stdout
перезапишет свои части (искажение вывода) или дублирует оставшуюся часть буфера stdout
.
Один из возможных выходов — заставить обработчик просто установить глобальную переменную флага, а затем позволить основной программе обрабатывать сброс:
volatile int gotsignal = 0;
void sig_handler(int signo)
{
if (signo == SIGUSR1)
gotsignal = 1;
}
int main()
{
int c;
/* Code for installing signal handler here */
while (1) {
c = getchar();
if (c < 0 || c > UCHAR_MAX)
break;
putchar(c);
if (gotsig == 1)
break;
}
fflush(stdout);
return 0;
}
Это снова становится безопасным для сигналов, но вводит тупиковую ситуацию. Предположим, что программа уже получила все входные данные, которые она когда-либо собиралась получить, и мы отправляем сигнал SIGUSR1
, чтобы изящно завершить ее. Сигнал поступает, когда процесс заблокирован на вызове чтения через getchar()
. Сигнал будет обрабатываться обработчиком, gotsig
будет установлено в 1, но управление вернется к заблокированному вызову getchar()
, поэтому он будет заблокирован навсегда, никогда не завершаясь (изящно). У нас тупик.
Решение:
В той же документации библиотеки GNU C, на которую вы ссылаетесь (подраздел «Обработка сигналов и функции без повторного входа»), говорится:
Если вы вызываете функцию в обработчике, убедитесь, что она является реентерабельной по отношению к сигналам, или убедитесь, что сигнал не может прервать вызов связанной функции.
Мы можем получить некоторое представление из части «или иначе» в приведенном выше предложении о прерывании вызова «связанной функции». Какие функции в нашем основном коде связаны с fflush(stdout)
? Наш основной код вызывает только две функции (стандартная библиотека), getchar()
и putchar()
. Разумно предположить, что getchar() касается только stdin
и поэтому не будет вызывать fflush(stodut)
или какие-либо подчиненные функции. Таким образом, «связанная» функция здесь — putchar()
, поскольку, как упоминалось ранее, она периодически запускает fflush(stdout)
или подобные вещи.
Следуя рекомендации библиотеки GNU C, мы можем заставить обработчик сделать следующее:
Если обработчик вызывается внутри putchar()
вызова в main()
, он не будет fflush(stdout)
; просто вернитесь и позвольте main()
смыть
В противном случае должно быть безопасно fflush(stdout)
из обработчика
Это решает все вышеперечисленные проблемы (сброс/небезопасность/взаимная блокировка).
Чтобы реализовать это, мы используем другой глобальный флаг, unsafe_to_flush
.
Окончательный код ниже должен быть безопасным для сигналов и без взаимоблокировок, но он более сложен, чем предыдущие попытки выше.
volatile int unsafe_to_flush = 0;
volatile int gotsig = 0;
void sig_handler(int signo)
{
if (signo == SIGUSR1) {
gotsig = 1; /* Caller can flush using this flag */
if (unsafe_to_flush) {
/* We got called from putchar()! */
return;
}
else {
/* Safe to call fflush(stdout) */
fflush(stdout);
_exit(0);
}
}
}
int main(void)
{
int c;
/* Code for installing signal handler here */
while (1) {
c = getchar();
if (c < 0 || c > UCHAR_MAX)
break;
/* Critical region */
unsafe_to_flush = 1;
putchar(c); /* non-reentrant fflush related */
unsafe_to_flush = 0;
if (gotsig == 1)
break;
}
fflush(stdout);
return 0;
}
person
GDGP
schedule
09.10.2018
fflush()
. - person Grijesh Chauhan   schedule 30.01.2014