После большой помощи и терпения от @Shawn мне удалось найти ответ, который я нашел удовлетворительным. Вот содержимое файла с именем pipe_watcher.c
:
#include <stdio.h> //printf etc.
#include <errno.h> //errno
#include <string.h> //perror
#include <signal.h> //SIGALRM, sigaction, sigset
#include <time.h> //timer_create, timer_settime
#include <fcntl.h> //open, O_RDONLY
#include <unistd.h> //close
/* This code demonstrates how you can monitor a named pipe with timeouts on the
* read() system call.
*
* Compile with:
*
* gcc -o pipe_watcher pipe_watcher.c -lrt
*
* And run with:
*
* ./pipe_watcher PIPE_FILENAME
*/
//Just needed a dummy handler
void sigalrm_handler(int s) {
return;
}
int main(int argc, char **argv) {
//Check input argument count
if (argc != 2) {
puts("Usage:\n");
puts("\t./pipe_watcher PIPE_FILENAME");
return -1;
}
//Create a timer object
timer_t clk;
int rc = timer_create(CLOCK_REALTIME, NULL, &clk);
if (rc < 0) {
perror("Could not create CLOCK_REALTIME timer");
return -1;
}
//Create some time values for use with timer_settime
struct itimerspec half_second = {
.it_interval = {.tv_sec = 0, .tv_nsec = 0},
.it_value = {.tv_sec = 0, .tv_nsec = 500000000}
};
struct itimerspec stop_timer = {
.it_interval = {.tv_sec = 0, .tv_nsec = 0},
.it_value = {.tv_sec = 0, .tv_nsec = 0}
};
//Set up SIGALRM handler
struct sigaction sigalrm_act = {
.sa_handler = sigalrm_handler,
.sa_flags = 0
};
sigemptyset(&sigalrm_act.sa_mask);
rc = sigaction(SIGALRM, &sigalrm_act, NULL);
if (rc < 0) {
perror("Could not register signal handler");
timer_delete(clk);
return -1;
}
//We deliberately omit O_NONBLOCK, since we want blocking behaviour on
//read(), and we're willing to tolerate dealing with the blocking open()
int fd = open(argv[1], O_RDONLY);
if (fd < 0) {
char msg[80];
sprintf(msg, "Could not open [%s]", argv[1]);
perror(msg);
timer_delete(clk);
return -1;
}
puts("File opened");
while (1) {
//Buffer to read() into
char buf[80];
int len;
//Set up a timer to interrupt the read() call after 0.5 seconds
timer_settime(clk, 0, &half_second, NULL);
//Issue read() system call
len = read(fd, buf, 80);
//Check for errors. The else-if checks for EOF
if (len < 0) {
if (errno == EINTR) {
//This means we got interrupted by the timer; we can keep going
fprintf(stderr, "Timeout, trying again\n");
continue;
} else {
//Something really bad happened. Time to quit.
perror("read() failed");
//No point waiting for the timer anymore
timer_settime(clk, 0, &stop_timer, NULL);
break;
}
} else if (len == 0) {
puts("Reached end of file");
break;
}
//No error or EOF; stop the timer and print the results
timer_settime(clk, 0, &stop_timer, NULL);
write(STDOUT_FILENO, buf, len);
}
//Cleanup after ourselves
timer_delete(clk);
close(fd);
return 0;
}
Техника заключается в установке таймера перед (блокированием) read()
вызова. Затем мы можем просто проверить возвращаемое значение read()
, чтобы увидеть, было ли оно прервано из-за тайм-аута, произошла ли общая ошибка, достигнут ли EOF или успешно прочитаны данные.
Есть только одна загвоздка: вы не можете открыть файл в неблокирующем режиме; это заставляет open()
блокироваться, пока другой процесс не откроет канал для записи. Тем не менее, в моем приложении это на самом деле желательная функция. Вы также можете настроить SIGALRM для принудительного тайм-аута на open()
, или, возможно, сделать это в другом потоке.
На самом деле, этот метод должен работать с любым другим системным вызовом, поэтому я мог бы собрать немного вспомогательная библиотека для упрощения использования этого шаблона.
EDIT
Еще одна вещь: очень важно не использовать флаг SA_RESTART
при регистрации обработчика сигнала , В противном случае, даже если системный вызов прерван сигналом, Linux попытается повторить его после обработки сигнала.