2016-03-31 4 views
2

Мы не хотим, чтобы что-либо печаталось после прерывания пользователя через CTRL-C. Мы попытались добавить __fpurge, а также fflush внутри обработчика сигнала sigInt, но он не работает.как очистить stdout после CTRL - C в linux c

Как я могу сразу очистить значения буферизации stdout? Я столкнулся с несколькими подобными темами, но не мог найти рабочее решение.

Немного дополнительной информации: Внутри обработчика сигнала sigInt даже после добавления выхода (0) содержимое буфера печатается, но процессор убит.

добавил выход (0), чтобы сузить этот вопрос, я не хочу, чтобы убить процессор

Я знаю, что выше ожидаемого поведения, не зная, как избежать этого.

+0

Вы пытались закрыть дескриптор файла? – Aconcagua

+0

'exit()' не [async-signal-safe] (http://man7.org/linux/man-pages/man7/signal.7.html). Вместо этого вы должны использовать '_Exit()'. (Кроме того, версия библиотеки GNU C не сбрасывает потоки в '_Exit()', в то время как она работает на 'exit()', так что эта проблема также устраняет проблему. В противном случае вы могли бы перенаправить базовые дескрипторы на '/ dev/null/'-' open() 'и' dup2() 'являются безопасными для асинхронного сигнала, поэтому, если что-то покраснеть, будет сброшено на/dev/null вместо.) –

+1

И скажите, какой компилятор вы используете, там возможно, будут различия ... – Aconcagua

ответ

0

Рассмотрите этот отредактированный пример - отредактирован; этот не выходит из процесса:

#define _POSIX_C_SOURCE 200809L /* For nanosleep() */ 
#include <unistd.h> 
#include <stdlib.h> 
#include <termios.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <signal.h> 
#include <string.h> 
#include <errno.h> 
#include <time.h> 
#include <stdio.h> 

static void exit_handler(int signum) 
{ 
    int fd, result; 

    /* If the standard streams are connected to a tty, 
    * tell the kernel to discard already buffered data. 
    * (That is, in kernel buffers. Not C library buffers.) 
    */ 
    if (isatty(STDIN_FILENO)) 
     tcflush(STDIN_FILENO, TCIOFLUSH); 
    if (isatty(STDOUT_FILENO)) 
     tcflush(STDOUT_FILENO, TCIOFLUSH); 
    if (isatty(STDERR_FILENO)) 
     tcflush(STDERR_FILENO, TCIOFLUSH); 

    /* Redirect standard streams to /dev/null, 
    * so that nothing further is output. 
    * This is a nasty thing to do, and a code analysis program 
    * may complain about this; it is suspicious behaviour. 
    */ 
    do { 
     fd = open("/dev/null", O_RDWR); 
    } while (fd == -1 && errno == EINTR); 
    if (fd != -1) { 
     if (fd != STDIN_FILENO) 
      do { 
       result = dup2(fd, STDIN_FILENO); 
      } while (result == -1 && (errno == EINTR || errno == EBUSY)); 
     if (fd != STDOUT_FILENO) 
      do { 
       result = dup2(fd, STDOUT_FILENO); 
      } while (result == -1 && (errno == EINTR || errno == EBUSY)); 
     if (fd != STDERR_FILENO) 
      do { 
       result = dup2(fd, STDERR_FILENO); 
      } while (result == -1 && (errno == EINTR || errno == EBUSY)); 
     if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO) 
      close(fd); 
    } 
} 

static int install_exit_handler(const int signum) 
{ 
    struct sigaction act; 
    memset(&act, 0, sizeof act); 
    sigemptyset(&act.sa_mask); 
    act.sa_handler = exit_handler; 
    act.sa_flags = 0; 
    if (sigaction(signum, &act, NULL) == -1) 
     return errno; 
    return 0; 
} 

int main(void) 
{ 
    if (install_exit_handler(SIGINT)) { 
     fprintf(stderr, "Cannot install signal handler: %s.\n", strerror(errno)); 
     return EXIT_FAILURE; 
    } 

    while (1) { 
     struct timespec t = { .tv_sec = 0, .tv_nsec = 200000000L }; 

     printf("Output\n"); 
     fflush(stdout); 

     nanosleep(&t, NULL); 
    } 

    /* Never reached. */ 
    return EXIT_SUCCESS; 
} 

Когда процесс получает сигнал SIGINT, он сначала промыть все, что в терминале ядра буфера, а затем перенаправлять стандартные потоки /dev/null (т.е. нигде).

Обратите внимание, что вам нужно будет убить процесс, отправив ему сигнал TERM или KILL (т. Е. killall ./yourprogname в другом терминале).


Когда вы запускаете процесс многословный через удаленное соединение, достаточно много информации может быть в полете в любое время. Как локальная машина, так и удаленная машина, работающая с этим процессом, будут иметь свои буферы сокетов почти полными, поэтому latency может быть намного больше, чем обычно - я видел несколько вторых латентностей в этом случае даже в быстрых (GbE) локальных сетях.

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

Это почему Ctrl + C не делает и не может остановить удаленный выход мгновенно.

В большинстве случаев вы будете использовать SSH-соединение с удаленной машиной. В протоколе также нет функции «очистки», которая может помочь здесь. Многие, включая меня, подумали об этом - по крайней мере, у моих пальцев колбасы случайно вкладка кровать к исполняемому файлу вместо выведенного файла с аналогичным именем и не только получила терминал, полный мусора, но и специальные символы в двоичном формате файлы иногда устанавливают состояние терминала (см., например, xterm control sequences, ANSI escape codes) на что-то неисправимое (т.е., Ctrl + Z затем resetВвод не возвращает терминал в рабочее состояние; если бы это произошло, kill -KILL %- ; fg остановил бы ошибочную команду в Bash и вернул вам ваш терминал), и вам нужно разорвать соединение, которое также прекратит все процессы, запущенные с того же терминала, который удаленно удален в фоновом режиме.

Решение здесь заключается в использовании терминального мультиплексора, например GNU screen, который позволяет подключаться к удаленному компьютеру и отключать его от сети, не прерывая существующее соединение терминала. (Проще говоря, screen ваш терминал аватар на удаленной машине.)

+0

wont tcflush сбрасывает вывод на терминал, мы не хотим отображать в терминал любые старые буферизованные данные. tcflush также не помогает :( –

+0

@manimuthuma: Нет, [tcflush()] (http://man7.org/linux/man-pages/man3/termios.3.html) отбрасывает данные, находящиеся в буферах ядра, но еще не прочитан процессом или обработан терминалом.Это полезно только для удаленных терминалов, так как локальные терминалы имеют тенденцию читать данные немедленно. Вы уже пробовали мою вышеуказанную программу? –

+0

Да, я сделал это и не помогал, termianl все еще печатаю непечатаемые данные –

0

Проблема заключается в том, что библиотека ни Posix, ни Linux заявляет, что fpurge ни __fpurge быть безопасным в функции обработчика сигнала. Как пояснил DevSolar, C язык реши не объявляет много безопасных функций для стандартной библиотеки (по крайней мере _Exit, но Posix явно позволяет close и write Таким образом, вы всегда можете закрыть основной описатель файла, который должен быть 1:.

void handler(int sig) { 
    static char msg[] = "Interrupted"; 
    write(2, msg, sizeof(msg) - 1); // carefully use stderr here 
    close(1); // foo is displayed if this line is commented out 
    _Exit(1); 
} 
int main() { 
    signal(SIGINT, handler); 
    printf("bar"); 
    sleep(15); 
    return 0; 
} 

Когда я типа Ctrl-C во время сна он дает, как и ожидалось:

$ ./foo 
^CInterrupted with 2 
$ 

Системный вызов close должно быть достаточно, потому что, как он закрывает файловый дескриптор Таким образом, даже если есть позже пытается установить смывать стандартный вывод. буфера, они будут писать o n закрытый дескриптор файла как таковой не имеет никакого эффекта. Недостатком является то, что stdout было перенаправлено, программа должна сохранить новое значение базового файлового дескриптора в глобальной переменной.

+0

К сожалению, glibc (используемый в большинстве дистрибутивов Linux, а OP помечен Linux для этого вопроса) не реализует его. У этого есть '__fpurge()' с той же семантикой (объявлено в '' stdio_ext.h ", хотя). –

+0

@NominalAnimal: Хорошо, но даже '__fpurge' должен очистить поток ... если OP не пишет сообщение в stdout сначала ... –

+0

Вы совершенно правы, это должно быть. Мне лично это не нравится, потому что это не безопасно для асинхронного сигнала (что приводит к UB, если оно используется в обработчике сигналов), но, как я писал в своем ответе, это обсуждение; Я определенно не говорю, что ваш ответ ошибочен * как таковой. –

0

Прежде всего, цитата из стандарта C11, курсив мой:

7.14.1.1 signal функция

5 Если сигнал возникает, кроме как результат вызова abort или raise функция, поведение не определено, если [...] обработчик сигнала вызывает любую функцию в стандартной библиотеке, отличную от функции abort, функции _Exit, quick_exit или функция signal с первым аргументом t, равным номеру сигнала, соответствующему сигналу, вызвавшему вызов обработчика.

Это означает, что вызов fflush является неопределенного поведения.

Глядя на функции, которые вы может вызов, abort и _Exit и оставить промывку буферов от реализации и quick_exit вызовов _Exit, так что вы не повезло, насколько далеко, как стандарт касается, так как я мог не найти определения реализации для их поведения для Linux. (Сюрприз. Не.)

Единственные другая функция «терминатор», exit, делает смывать буфера, и вы не можете вызвать его из обработчика в первую очередь.

Таким образом, вам нужно взглянуть на специфичные для Linux функции. Страница руководства до _exit не делает заявление о буферах. Командная страница close предупреждает о закрытии дескрипторов файлов, которые могут быть использованы системными вызовами из других потоков, и заявляет, что «для файловой системы не рекомендуется очищать буферы при закрытии потока», что означает, что она может (т.е. close не гарантирует, что содержимое неписаного буфера фактически отбрасывается).

На данный момент, если бы я тебя, я хотел бы спросить себя «это такая хорошая идея, ведь» ...

+0

Требование это, я не говорю, что мне нужно использовать fpurge ... не смог найти ничего из этого. Я не хочу, чтобы убить процесс, я просто не хочу отображать naything на терминале. –

+0

@manimuthuma: Итак, вы хотите поймать SIGINT и продолжить выполнение программы, просто отключив вывод терминала? Во всяком случае, я бы не стал беспокоиться о том, что находится в буферах. Это максимум на одной строке вывода (поскольку терминал является интерактивным, т. Е. Вывод буферизируется по строке, если вы не настроили его на полностью буферизацию). И Ctrl-C - это * пользовательский вход, т. Е. Точно не синхронизирован. Таким образом, установите флаг 'atomic_t',' freopen() 'stdout для'/dev/null', чтобы отключить * будущий * вывод при следующей возможности (так как вы не можете вызывать 'freopen()' из обработчика сигнала) и просто игнорируйте любые буферизованные данные. – DevSolar

+0

Обратите внимание: поскольку Linux помечен в вопросе, POSIX также является стандартом для рассмотрения. И довольно много можно сказать о [сигналах] (http://man7.org/linux/man-pages/man7/signal.7.html). Кроме того, 'signal()' не рекомендуется использовать, а вместо этого следует использовать '' sigaction() '] (http://man7.org/linux/man-pages/man2/sigaction.2.html). –

0

Если вы kill(getpid(), SIGKILL); с в обработчик сигнала (который является асинхронной-сейф) , вы сразу же будете убиты ОС (так как вы хотели выйти (0)). Дальнейшего выхода больше не следует ожидать.

Только проблема: вы не сможете очистить поперёк, выходите изящно после этого в основной поток. Если вы можете себе это позволить ...

+0

Я не хочу убить процесс –

+0

Хм, поймите, тогда выход (0) был просто для экспериментов ... ОК, тогда у вас нет решения. – Aconcagua

Смежные вопросы