Могу ли я заставить ungetc разблокировать блокирующий вызов fgetc?

Я хотел бы вернуть символ «A» обратно в стандартный ввод с помощью ungetc при получении SIGUSR1. Представьте, что у меня есть для этого веская причина.

При вызове foo() чтение блокировки на стандартном вводе не прерывается вызовом ungetc при получении сигнала. Хотя я не ожидал, что это будет работать как есть, мне интересно, есть ли способ добиться этого - есть ли у кого-нибудь предложения?

void handler (int sig)
{
  ungetc ('A', stdin);
}

void foo ()
{
  signal (SIGUSR1, handler);

  while ((key = fgetc (stdin)) != EOF)
  {
    ...
  }
}

person Paul Beckingham    schedule 12.04.2010    source источник


Ответы (4)


Вместо того, чтобы пытаться заставить ungetc() разблокировать блокирующий вызов fgetc() с помощью сигнала, возможно, вы могли бы попробовать не иметь блока fgetc() с самого начала и дождаться активности на стандартном вводе с помощью select().


По умолчанию линейная дисциплина для терминального устройства может работать в каноническом режиме. В этом режиме драйвер терминала не представляет буфер пользовательскому пространству до тех пор, пока не будет видна новая строка (нажата клавиша Enter).

Чтобы выполнить то, что вы хотите, вы можете установить терминал в необработанный (неканонический) режим, используя tcsetattr() для управления структурой termios. Это должно привести к тому, что блокирующий вызов fgetc() немедленно вернет символ, вставленный с помощью ungetc().


void handler(int sig) {
   /* I know I shouldn't do this in a signal handler,
    * but this is modeled after the OP's code.
    */
   ungetc('A', stdin);
}

void wait_for_stdin() {
   fd_set fdset;
   FD_ZERO(&fdset);
   FD_SET(fileno(stdin),&fdset);
   select(1, &fdset, NULL, NULL, NULL);
}

void foo () {
   int key;
   struct termios terminal_settings;

   signal(SIGUSR1, handler);

   /* set the terminal to raw mode */
   tcgetattr(fileno(stdin), &terminal_settings);
   terminal_settings.c_lflag &= ~(ECHO|ICANON);
   terminal_settings.c_cc[VTIME] = 0;
   terminal_settings.c_cc[VMIN] = 0;
   tcsetattr(fileno(stdin), TCSANOW, &terminal_settings);

   for (;;) {
      wait_for_stdin();
      key = fgetc(stdin);
      /* terminate loop on Ctrl-D */
      if (key == 0x04) {
         break;
      }      
      if (key != EOF) {
         printf("%c\n", key);
      }
   }
}

ПРИМЕЧАНИЕ. В этом коде для простоты опущена проверка ошибок.

Сброс флагов ECHO и ICANON соответственно отключает отображение символов по мере их ввода и приводит к тому, что запросы на чтение выполняются непосредственно из входной очереди. . Установка нулевых значений VTIME и VMIN в массиве c_cc приводит к тому, что запрос на чтение (fgetc()) возвращается немедленно, а не блокируется; эффективно опрашивая стандартный ввод. Это приводит к тому, что key устанавливается в EOF, поэтому необходим другой метод завершения цикла. Ненужный опрос стандартного ввода уменьшается за счет ожидания активности на стандартном вводе с использованием select().


Выполнение программы, отправка сигнала SIGUSR1 и ввод t e s t приводит к следующему результату1:

A
t
e
s
t

1) протестировано на Linux

person jschmier    schedule 15.04.2010
comment
Разве это не должно быть terminal_settings.c_cc[VMIN] = 1;? вернуться, когда доступен хотя бы 1 символ? - person jww; 16.11.2019

Не совсем понятно, какая у вас цель, но это ли то, что вы ищете?

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

int handle = 0;

void handler (int sig) {
  handle = 1;
}

void foo () {
  int key;

  signal (SIGUSR1, handler);

  while ((key = fgetc (stdin)) != EOF) {
    printf("%c\n",key);
    if (handle) {
      handle = 0;
      ungetc('A',stdin);
    }
  }
}

int main(void) {
  printf("PID: %d\n",getpid());
  foo();
}

Он производит этот вывод:

PID: 8902
test (typed on stdin)
t
A
e
s
t
person Jamie    schedule 13.04.2010
comment
Не совсем - я ищу способ поставить эту букву «А» перед буквой «т». - person Paul Beckingham; 15.04.2010

ФАЙЛ* не являются безопасными для асинхронного режима.

Вы не можете работать с ФАЙЛОМ* в обработчике сигнала, пока кто-то еще использует тот же ФАЙЛ*. функции, которые вы можете использовать в обработчике сигналов, указаны здесь:

http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04. HTML . (Это может быть по-другому на машине с Windows, но все равно любой ФАЙЛ * там тоже небезопасен.

person nos    schedule 15.04.2010

По сути, это то же самое, что и ответ @Jamie, немного измененный, чтобы поддержать ваше желание обработать A перед t, но слишком сложно вводить код в поле для комментариев, поэтому я разместил этот ответ отдельно.

int insert_an_A = 0;
void handler(int sig) { insert_an_A = 1; }

void process_char(char c) { ... }

int main(int argc, char **argv) {
    int c;
    /* skip signal setup */
    while ((c = fgetc(stdin)) != EOF) {
        if (insert_an_A) {
            process_char('A');
            insert_an_A = 0;
        }
        process_char(c);
    }
}

Если вы хотите обработать обработчик, полученный во время вызова fgetc, который возвращает EOF, вы также должны проверить insert_an_A после выхода из цикла while.

Также обратите внимание, что в общем случае для обработчиков сигналов рекомендуется устанавливать глобальную переменную и возвращать ее из обработчика. В другом месте вашей программы ищите изменение этой переменной и реагируйте соответствующим образом.

person Dale Hagglund    schedule 15.04.2010