c - 有没有办法在执行期间更改 sigaction 标志?
问题描述
我在无限循环中有这个子进程,我希望它在从父 pid 接收 SIGUSR1 时停止循环。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int GameOver = 0;
jmp_buf here; // <------- After Joshua's Answer
void trataSIGUSR1(int sig, siginfo_t *info, void *extra);
int main(int argc, char** argv){
int someNumber = 0, score = 0;
char word[15],c;
struct sigaction new_action;
// new_action.sa_flags = SA_SIGINFO; // <------- Before Joshua's Answer
new_action.sa_flags = SA_SIGINFO | SA_RESTART; // <------- After Joshua's Answer
new_action.sa_sigaction = &trataSIGUSR1;
sigfillset(&new_action.sa_mask);
if (sigaction(SIGUSR1, &new_action, NULL) == -1){
perror("Error: cannot handle SIGUSR1"); // não deve acontecer
return EXIT_FAILURE;
}
FILE *f;
f = fopen("randomfile.txt", "r");
if (f == NULL){
printf("Errr Opening File!\n");
return EXIT_FAILURE;
}
// setjmp(here); // <------- After Joshua's Answer
sigsetjmp(here,1); // <-- After wildplasser's Answer
while (!GameOver){
fscanf(f, "%s", word);
printf("\nWord -> %s\n", word);
if(!scanf("%d", &someNumber)){
puts("Invalid Value!");
while ((c = getchar()) != '\n' && c != EOF);
continue;
}
if(someNumber == strlen(word) && !GameOver)
score ++;
if(feof(f)){
printf("\nEnd of file.\n");
break;
}
}
if( GameOver )
puts("\nAcabou o tempo!"); // <-- After wildplasser's Answer
fclose(f);
return score;
}
void trataSIGUSR1(int sig, siginfo_t *info, void *extra){
if (info->si_pid == getppid()){ // only end when parent send SIGUSR1
// puts("\nAcabou o tempo!"); // <-- Before wildplasser's Answer
GameOver = 1;
// longjmp(here,1); // <------- After Joshua's Answer
siglongjmp(here,1); // <---- After wildplasser's Answer
}
}
它工作正常,但是如果我从另一个进程将 SIGUSR1 发送到子 pid,scanf 会被中断......我想中断 scanf 并仅在信号来自父进程时自动停止循环,在其他情况下只是忽略。有什么办法可以将标志更改为 new_action.sa_flags = SA_RESTART; 当信号来自其他进程?!
解决方案
有几种可能性,从巨大的黑客攻击到正确的(但复杂的)。
最简单的事情是让 SIGUSR1 从父级重新打开标准输入到 /dev/null。然后,当失败时,如果为真scanf()
,您可以跳出循环,而不是抱怨和重试。feof(stdin)
不幸的是,freopen()
它不是异步信号安全的,所以这不是符合标准(在这种情况下为 POSIX)的做事方式。
符合标准的处理方式是将您自己的读取输入行实现为动态分配的 string类型的函数,该函数检测信号处理程序何时设置标志。标志也应该是volatile sig_atomic_t
类型,而不是 int;特别是volatile
告诉编译器该值可能会意外更改(由信号处理程序),因此每当引用时,编译器必须重新读取变量值,而不是从先前的访问中记住它。该sig_atomic_t
类型是原子整数类型:进程和信号处理程序只会看到新值或旧值,绝不会混合两者,但可能具有 0 到 127 的小有效范围,包括 0 到 127。
向用户空间处理程序(未安装 SA_RESTART 安装)的信号传递确实会中断阻塞 I/O 操作(如读取或写入;在用于信号传递的线程中 - 您只有一个,因此将始终使用),但它可能会发生在标志检查和 scanf() 之间,所以在这种情况下,它是不可靠的。
此处正确的解决方案是根本不使用stdin
,而是为此使用低级<unistd.h>
I/O。请注意,不能将同一流的stdin
/scanf()
和低级 I/O混合使用。您可以安全地使用、、等。原因是我们的低级访问不会正确更新 C 库内部流结构,如果我们将两者混合(对于同一个流)将与现实不同步。printf()
fprintf(stdout, ...)
fprintf(stderr, ...)
stdin
这是一个示例程序,展示了一种实现(根据知识共享零 v1.0 国际许可- 随心所欲,但不保证):
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
/* Maximum poll() timeout, in milliseconds, so that done flag is checked often enough.
*/
#ifndef DONE_POLL_INTERVAL_MS
#define DONE_POLL_INTERVAL_MS 100
#endif
static volatile sig_atomic_t done = 0;
static void handle_done(int signum, siginfo_t *info, void *context)
{
/* This silences warnings about context not being used. It does nothing. */
(void)context;
if (signum == SIGUSR1 && info->si_pid == getppid()) {
/* SIGUSR1 is only accepted if it comes from the parent process */
done = 1;
} else {
/* All other signals are accepted from all processes (that have the necessary privileges) */
done = 1;
}
}
static int install_done(const int signum)
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&(act.sa_mask));
act.sa_sigaction = handle_done;
act.sa_flags = SA_SIGINFO;
return sigaction(signum, &act, NULL);
}
/* Our own input stream structure type. */
struct input {
int descriptor;
char *data;
size_t size;
size_t head;
size_t tail;
};
/* Associating an input stream with a file descriptor.
Do not mix stdin use and input stream on descriptor STDIN_FILENO!
*/
static int input_use(struct input *const in, const int descriptor)
{
/* Check that the parameters are not obviously invalid. */
if (!in || descriptor == -1) {
errno = EINVAL;
return -1;
}
/* Set the descriptor nonblocking. */
{
int flags = fcntl(descriptor, F_GETFL);
if (flags == -1) {
/* errno set by fcntl(). */
return -1;
}
if (fcntl(descriptor, F_SETFL, flags | O_NONBLOCK) == -1) {
/* errno set by fcntl(). */
return -1;
}
}
/* Initialize the stream structure. */
in->descriptor = descriptor;
in->data = NULL;
in->size = 0;
in->head = 0;
in->tail = 0;
/* Success. */
return 0;
}
/* Read until delimiter from an input stream.
* If 'done' is set at any point, will return 0 with errno==EINTR.
* Returns 0 if an error occurs, with errno set.
* Returns 0 with errno==0 when end of input stream.
*/
static size_t input_getdelim(struct input *const in,
int const delim,
char **const dataptr,
size_t *const sizeptr,
const double timeout)
{
const clockid_t timeout_clk = CLOCK_BOOTTIME;
struct timespec then;
/* Verify none of the pointers are NULL. */
if (!in || !dataptr || !sizeptr) {
errno = EINVAL;
return 0;
}
/* Record current time for timeout measurement. */
clock_gettime(timeout_clk, &then);
char *line_data = *dataptr;
size_t line_size = *sizeptr;
/* If (*sizeptr) is zero, then we ignore dataptr value, like getline() does. */
if (!line_size)
line_data = NULL;
while (1) {
struct timespec now;
struct pollfd fds[1];
ssize_t n;
int ms = DONE_POLL_INTERVAL_MS;
/* Done flag set? */
if (done) {
errno = EINTR;
return 0;
}
/* Is there a complete line in the input buffer? */
if (in->tail > in->head) {
const char *ptr = memchr(in->data + in->head, delim, in->tail - in->head);
if (ptr) {
const size_t len = ptr - (in->data + in->head);
if (len + 2 > line_size) {
/* Since we do not have any meaningful data in line_data,
and it would be overwritten anyway if there was,
instead of reallocating it we just free an allocate it. */
free(line_data); /* Note: free(null) is safe. */
line_size = len + 2;
line_data = malloc(line_size);
if (!line_data) {
/* Oops, we lost the buffer. */
*dataptr = NULL;
*sizeptr = 0;
errno = ENOMEM;
return 0;
}
*dataptr = line_data;
*sizeptr = line_size;
}
/* Copy the line, including the separator, */
memcpy(line_data, in->data + in->head, len + 1);
/* add a terminating nul char, */
line_data[len + 1] = '\0';
/* and update stream buffer state. */
in->head += len + 1;
return len + 1;
}
/* No, we shall read more data. Prepare the buffer. */
if (in->head > 0) {
memmove(in->data, in->data + in->head, in->tail - in->head);
in->tail -= in->head;
in->head = 0;
}
} else {
/* Input buffer is empty. */
in->head = 0;
in->tail = 0;
}
/* Do we need to grow input stream buffer? */
if (in->head >= in->tail) {
/* TODO: Better buffer size growth policy! */
const size_t size = (in->tail + 65535) | 65537;
char *data;
data = realloc(in->data, size);
if (!data) {
errno = ENOMEM;
return 0;
}
in->data = data;
in->size = size;
}
/* Try to read additional data. It is imperative that the descriptor
has been marked nonblocking, as otherwise this will block. */
n = read(in->descriptor, in->data + in->tail, in->size - in->tail);
if (n > 0) {
/* We read more data without blocking. */
in->tail += n;
continue;
} else
if (n == 0) {
/* End of input mark (Ctrl+D at the beginning of line, if a terminal) */
const size_t len = in->tail - in->head;
if (len < 1) {
/* No data buffered, read end of input. */
if (line_size < 1) {
line_size = 1;
line_data = malloc(line_size);
if (!line_data) {
errno = ENOMEM;
return 0;
}
*dataptr = line_data;
*sizeptr = line_size;
}
line_data[0] = '\0';
errno = 0;
return 0;
}
if (len + 1 > line_size) {
/* Since we do not have any meaningful data in line_data,
and it would be overwritten anyway if there was,
instead of reallocating it we just free an allocate it. */
free(line_data); /* Note: free(null) is safe. */
line_size = len + 1;
line_data = malloc(line_size);
if (!line_data) {
/* Oops, we lost the buffer. */
*dataptr = NULL;
*sizeptr = 0;
errno = ENOMEM;
return 0;
}
*dataptr = line_data;
*sizeptr = line_size;
}
memmove(line_data, in->data, len);
line_data[len] = '\0';
in->head = 0;
in->tail = 0;
return 0;
} else
if (n != -1) {
/* This should never occur; it would be a C library bug. */
errno = EIO;
return 0;
} else {
const int err = errno;
if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR)
return 0;
/* EAGAIN, EWOULDBLOCK, and EINTR are not real errors. */
}
/* Nonblocking operation, with timeout == 0.0? */
if (timeout == 0.0) {
errno = ETIMEDOUT;
return 0;
} else
if (timeout > 0.0) {
/* Obtain current time. */
clock_gettime(timeout_clk, &now);
const double elapsed = (double)(now.tv_sec - then.tv_sec)
+ (double)(now.tv_nsec - then.tv_nsec) / 1000000000.0;
/* Timed out? */
if (elapsed >= (double)timeout / 1000.0) {
errno = ETIMEDOUT;
return 0;
}
if (timeout - elapsed < (double)DONE_POLL_INTERVAL_MS / 1000.0) {
ms = (int)(1000 * (timeout - elapsed));
if (ms < 1) {
errno = ETIMEDOUT;
return 0;
}
}
}
/* Negative timeout values means no timeout check,
and ms retains its initialized value. */
/* Another done check; it's cheap. */
if (done) {
errno = 0;
return EINTR;
}
/* Wait for input, but not longer than ms milliseconds. */
fds[0].fd = in->descriptor;
fds[0].events = POLLIN;
fds[0].revents = 0;
poll(fds, 1, ms);
/* We don't actually care about the result at this point. */
}
/* Never reached. */
}
static inline size_t input_getline(struct input *const in,
char **const dataptr,
size_t *const sizeptr,
const double timeout)
{
return input_getdelim(in, '\n', dataptr, sizeptr, timeout);
}
int main(void)
{
struct input in;
char *line = NULL;
size_t size = 0;
size_t len;
if (install_done(SIGINT) == -1 ||
install_done(SIGHUP) == -1 ||
install_done(SIGTERM) == -1 ||
install_done(SIGUSR1) == -1) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
if (input_use(&in, STDIN_FILENO)) {
fprintf(stderr, "BUG in input_use(): %s.\n", strerror(errno));
return EXIT_FAILURE;
}
while (!done) {
/* Wait for input for five seconds. */
len = input_getline(&in, &line, &size, 5000);
if (len > 0) {
/* Remove the newline at end, if any. */
line[strcspn(line, "\n")] = '\0';
printf("Received: \"%s\" (%zu chars)\n", line, len);
fflush(stdout);
continue;
} else
if (errno == 0) {
/* This is the special case: input_getline() returns 0 with
errno == 0 when there is no more input. */
fprintf(stderr, "End of standard input.\n");
return EXIT_SUCCESS;
} else
if (errno == ETIMEDOUT) {
printf("(No input for five seconds.)\n");
fflush(stdout);
} else
if (errno == EINTR) {
/* Break or continue works here, since input_getline() only
returns 0 with errno==EINTR if done==1. */
break;
} else {
fprintf(stderr, "Error reading from standard input: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
}
printf("Signal received; done.\n");
return EXIT_SUCCESS;
}
将其另存为 eg example.c
,使用 eg 编译gcc -Wall -Wextra -O2 example.c -o example
,然后使用 .run 运行./example
。输入输入并输入以提供行,或在行首输入Ctrl+D以结束输入,或Ctrl+C向进程发送 SIGINT 信号。
注意编译时常量DONE_POLL_INTERVAL_MS
。done
如果信号在检查 和之间传递poll()
,这是轮询可能阻塞的最大延迟,以毫秒(千分之一秒)为单位;因此大致是接收信号并对其进行操作的最大延迟。
为了使示例更有趣,它还实现了读取整行的超时。上面的示例在到达时打印,但这会打乱用户查看他们正在输入的输入的方式。(不影响输入。)
这绝不是此类函数的完美示例,但我希望它是可读的,注释解释了每个代码块背后的推理。
推荐阅读
- release - 如果您的应用程序频繁更改,您如何让呼叫中心操作员保持最新状态?
- azure - 能否通过 Azure 防火墙路由点到站点流量?
- python - 高效的条件滚动计算 Pandas
- javascript - 当状态改变时,React 不会重新渲染我的组件
- mysql - SQL。如何找出哪些时间重叠?
- angular - 在 ag-grid 中禁用“ag-column-hover”css 类
- javascript - 如何解决 Firefox 71 版本中的 CSS 警告
- mysql - 如何从 MySQL 中的 INFORMATION_SCHEMA 生成表模式?
- rust - Rust 中的期货向量不会同时执行
- mysql - csv文件上传的存储过程问题