首页 > 解决方案 > 有没有办法在执行期间更改 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; 当信号来自其他进程?!

标签: clinux

解决方案


有几种可能性,从巨大的黑客攻击到正确的(但复杂的)。

最简单的事情是让 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_MSdone如果信号在检查 和之间传递poll(),这是轮询可能阻塞的最大延迟,以毫秒(千分之一秒)为单位;因此大致是接收信号并对其进行操作的最大延迟。

为了使示例更有趣,它还实现了读取整行的超时。上面的示例在到达时打印,但这会打乱用户查看他们正在输入的输入的方式。(不影响输入。)

这绝不是此类函数的完美示例,但我希望它是可读的,注释解释了每个代码块背后的推理。


推荐阅读