简体   繁体   English

有没有办法在执行期间更改 sigaction 标志?

[英]Is there any way to change sigaction flags during execution?

I have this child process in infinite loop and i want it to stop the loop when recive SIGUSR1 from parent pid.我在无限循环中有这个子进程,我希望它在从父 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
    }
}

It works fine but if i send SIGUSR1 to child pid from another process scanf get interupted... I want to interupt the scanf and automaticly stop the loop only when signal come from parent, in other case just ignore.它工作正常,但如果我从另一个进程将 SIGUSR1 发送到子 pid,scanf 会被中断......我想中断 scanf 并仅在信号来自父进程时自动停止循环,在其他情况下只是忽略。 Is there any way to change the flag to new_action.sa_flags = SA_RESTART;有什么办法可以将标志更改为 new_action.sa_flags = SA_RESTART; when signal comes from other process?!当信号来自其他进程?!

There are several possibilities, ranging from a huge hack, to proper (but complicated).有几种可能性,从巨大的黑客攻击到适当的(但复杂的)。

The simplest thing is to have the SIGUSR1 from parent reopen standard input to /dev/null.最简单的事情是让 SIGUSR1 从父级重新打开标准输入到 /dev/null。 Then, when scanf() fails, instead of complaining and retrying, you can break out of the loop if feof(stdin) is true.然后,当scanf()失败时,如果feof(stdin)为真,您可以跳出循环,而不是抱怨和重试。 Unfortunately, freopen() is not async-signal safe , so this is not a standards (POSIX, in this case) compliant way of doing things.不幸的是, freopen()不是异步信号安全的,因此这不是符合标准(在这种情况下为 POSIX)的做事方式。

The standards-compliant way of doing things is to implement your own read input line into a dynamically allocated string -type of function, which detects when the signal handler sets the flag.符合标准的处理方式是将您自己的读取输入行实现为动态分配的字符串类型 function,它检测信号处理程序何时设置标志。 The flag should also be of volatile sig_atomic_t type, not an int;该标志也应该是volatile sig_atomic_t类型,而不是 int; the volatile in particular tells the compiler that the value may be changed unexpectedly (by the signal handler), so whenever referenced, the compiler must re-read the variable value, instead of remembering it from a previous access.特别是volatile告诉编译器该值可能会意外更改(通过信号处理程序),因此无论何时引用,编译器都必须重新读取变量值,而不是从先前的访问中记住它。 The sig_atomic_t type is an atomic integer type: the process and the signal handler will only ever see either the new, or the old value, never a mix of the two, but might have as small valid range as 0 to 127, inclusive. sig_atomic_t类型是原子 integer 类型:进程和信号处理程序只会看到新值或旧值,绝不会混合两者,但可能具有 0 到 127 的有效范围(包括 0 到 127)。

Signal delivery to an userspace handler (installed without SA_RESTART) does interrupt a blocking I/O operation (like read or write; in the thread used for signal delivery – you only have one, so that will always be used), but it might occur between the flag check and the scanf(), so in this case, it is not reliable.向用户空间处理程序(未安装 SA_RESTART 安装)的信号传递确实会中断阻塞 I/O 操作(如读取或写入;在用于信号传递的线程中 - 您只有一个,因此将始终使用),但它可能会发生在标志检查和 scanf() 之间,所以在这种情况下,它是不可靠的。

The proper solution here is to not use stdin at all, and instead use the low-level <unistd.h> I/O for this.此处正确的解决方案是根本不使用stdin ,而是为此使用低级<unistd.h> I/O。 Note that it is imperative to not mix stdin / scanf() and low-level I/O for the same stream .请注意,对于同一个 stream ,必须不要混合stdin / scanf()和低级 I/O。 You can safely use printf() , fprintf(stdout, ...) , fprintf(stderr, ...) , and so on.您可以安全地使用printf()fprintf(stdout, ...)fprintf(stderr, ...)等。 The reason is that the C library internal stdin stream structure will not be updated correctly by our low-level access, and will be out-of-sync with reality if we mix both (for the same stream).原因是 C 库内部stdin stream 结构不会被我们的低级访问正确更新,并且如果我们混合两者(对于同一流)将与现实不同步。

Here is an example program showing one implementation (licensed under Creative Commons Zero v1.0 International – do as you wish with it, no guarantees though):这是一个示例程序,展示了一种实现(根据知识共享零 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;
}

Save it as eg example.c , compile using eg gcc -Wall -Wextra -O2 example.c -o example , and run using ./example .将其保存为例如example.c ,使用例如./example gcc -Wall -Wextra -O2 example.c -o example进行编译。 Type input and enter to supply lines, or Ctrl + D at the beginning of a line to end input, or Ctrl + C to send the process a SIGINT signal.输入输入并输入到供应行,或在一行的开头按 Ctrl + D以结束输入,或按 Ctrl + C向进程发送 SIGINT 信号。

Note the compile-time constant DONE_POLL_INTERVAL_MS .注意编译时常DONE_POLL_INTERVAL_MS If the signal is delivered between a done check and poll() , this is the maximum delay, in milliseconds (1000ths of a second), that the poll may block;如果信号在done检查和poll()之间传递,这是轮询可能阻塞的最大延迟,以毫秒(1000 分之一秒)为单位; and therefore is roughly the maximum delay from receiving the signal and acting upon it.因此大致是接收信号并对其进行操作的最大延迟。

To make the example more interesting, it also implements a timeout on reading a full line also.为了使示例更有趣,它还实现了读取整行的超时。 The above example prints when it is reached, but that messes up how the user sees the input they're typing.上面的示例在到达时打印,但这会打乱用户查看他们正在输入的输入的方式。 (It does not affect the input.) (不影响输入。)

This is by no means a perfect example of such functions, but I hope it is a readable one, with the comments explaining the reasoning behind each code block.这绝不是此类函数的完美示例,但我希望它是可读的,注释解释了每个代码块背后的推理。

Historically we solved this problem by always setting SA_RESTART and calling longjump() to get out of the signal handler when the condition is met.过去,我们通过始终设置 SA_RESTART 并在满足条件时调用longjump()以退出信号处理程序来解决此问题。

The standard makes this undefined but I think this does the right thing when stdin is connected to the keyboard.该标准对此未定义,但我认为当 stdin 连接到键盘时这样做是正确的。 Don't try it with redirected handles.不要尝试使用重定向句柄。 It won't work well.它不会很好地工作。 At least you can check for this condition with isatty(0) .至少您可以使用isatty(0)检查这种情况。

If it doesn't work and you are bent on using signals like this, you'll need to abandon scanf() and friends and get all your input using read() .如果它不起作用并且您一心想要使用这样的信号,您将需要放弃scanf()和朋友,并使用read()获取所有输入。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM