Прервать чтение вызова по сигналу - PullRequest
0 голосов
/ 03 апреля 2020

Я должен прервать вызов чтения, если нажата ctrl- c, используя сигнал. Я написал этот (упрощенный) пример кода:

#include <unistd.h>
#include <sys/wait.h>

int should_stop = 0;

void sighandler(int signal)
{
    write(1, "\nctrl-c has been pressed\n", 25);
    should_stop = 1;
}

void read_function()
{
    char c;

    while (!should_stop)
        read(0, &c, 1);
    //Do some stuff and return someting
}

int main()
{
    signal(SIGINT, &sighandler);
    read_function();
    write(1, "read_function is over\n", 22);
    return (0);
}

Поскольку чтение - это блокирующий вызов (насколько я понял), глобальная переменная should_stop не будет оцениваться после вызова read. Поэтому я не знаю, как я мог прервать вызов чтения, нажав ctrl- c.

Другое ограничение заключается в том, что мне разрешено использовать только эти функции:

- write
- read
- fork
- wait
- signal
- kill
- exit

Поэтому я не могу использовать select для установки значения тайм-аута. Так как мне также нужно возвращаемое значение read_function, я не могу использовать fork и просто выйти из процесса с другим обработчиком сигнала.

Есть ли другой способ прервать вызов read?

Ответы [ 2 ]

2 голосов
/ 03 апреля 2020

Это то, что происходит в настоящее время: когда вы отправляете сигнал прерывания с клавиатуры, включается обработчик сигнала, записывает сообщение \nctrl-c has been pressed\n на вашу консоль и устанавливает переменную should_stop. Затем управление возвращается обратно в оператор read(0, &buf, 1). Поскольку stdin буферизуется, read не закончится, пока не встретит символ новой строки. Если вы нажмете Enter впоследствии - read читает один бит и возвращается. После этого снова проверяется условие should_stop, и поскольку оно теперь содержит значение 1 - l oop закончено.

Теперь мы хотим изменить это поведение, чтобы ваша программа корректно закрывалась вниз после SIGINT.

с man 7 signal:

If  a  blocked  call  to one of the following interfaces is interrupted by a
signal handler, then the call is automatically restarted  after  the  signal
handler  returns  if  the SA_RESTART flag was used; otherwise the call fails
with the error EINTR:

с man 2 signal:

certain  blocking  system calls are automatically
restarted if interrupted by a signal handler (see signal(7)).  The  BSD  se‐
mantics are equivalent to calling sigaction(2) with the following flags:

   sa.sa_flags = SA_RESTART;

Итак, вот как мы используем sigaction(2) для нашего случая :

int main()
{
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_handler = sighandler;
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);
    read_function();
    write(1, "read_function is over\n", 22);
    return (0);
}

Таким образом, при прерывании обработчиком сигнала read(2) возвращается с ошибкой EINTR и не перезапускается.

signal(2) обычно уступает sigaction(2), когда Что касается переносимости кода, вы можете прочитать об этом здесь

1 голос
/ 03 апреля 2020

Должно работать, если вы объявите переменную should_stop равной volatile. Это будет указывать компилятору перечитывать его из памяти при каждом доступе:

...
volatile int should_stop = 0;
...

Просто в зависимости от вашей системы, вызов read может быть перезапущен после сигнала, и вам придется нажать клавишу return после Ctrl- C, чтобы завершить программу. По умолчанию мой FreeBSD 11 bos ведет себя так.

Если вы хотите, чтобы вызов read не был перезапущен, вы должны явно запросить это поведение с помощью siginterrupt:

...
signal(SIGINT, &sighandler);
siginterrupt(SIGINT, 1);
...

Таким образом, программа остановится сразу после нажатия Ctrl- C

...