Могу ли я заставить ungetc разблокировать блокирующий вызов fgetc? - PullRequest
3 голосов
/ 12 апреля 2010

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

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

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

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

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

Ответы [ 4 ]

5 голосов
/ 16 апреля 2010

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


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

Чтобы выполнить то, что вы хотите, вы можете установить терминал в необработанный (неканонический) режим, используя <a href="http://www.opengroup.org/onlinepubs/000095399/functions/tcsetattr.html" rel="noreferrer"><b>tcsetattr()</b></a> для управления структурой 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

1 голос
/ 15 апреля 2010

FILE * не являются асинхронными.

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

http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html. (Это может быть отличается на машине с Windows, но все равно ФАЙЛ * там тоже не безопасен.

1 голос
/ 15 апреля 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.

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

1 голос
/ 13 апреля 2010

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

#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
...