【问题标题】:Killing child with SIGTERM用 SIGTERM 杀死孩子
【发布时间】:2019-01-18 20:28:11
【问题描述】:

我有 2 个程序:1) 父亲 2) 孩子。 当 父亲 收到 SIGINT (CTRL-C) 信号时,他的处理程序会向他的孩子发送一个 SIGTERM。问题是它经常(不总是,不知道为什么)在 SIGINT 之后在循环中显示此错误:

Invalid Argument

父亲的目标是创造一个孩子,然后活着准备处理 SIGINT。

父亲

#include "library.h"

static void handler();

int main(int argc, char* argv[]){
    int value, que_id;
    char str_que_id[10], **child_arg;
    pid_t child_pid;
    sigaction int_sa;

    //Create message queue
    do{
        que_id = msgget(IPC_PRIVATE, ALL_PERM | IPC_CREAT);
    }while(que_id == -1);
    snprintf(str_que_id, sizeof(str_que_id), "%d", que_id);

    //Set arguments for child
    child_arg = malloc(sizeof(char*) * 3);
    child[0] = "child";
    child[1] = str_que_id;
    child[2] = NULL;

    //Set handler for SIGINT
    int_sa.sa_handler = &handler;
    int_sa.sa_flags = SA_RESTART;
    sigemptyset(&int_sa.sa_mask);
    sigaddset(&int_sa.sa_mask, SIGALRM);
    sigaction(SIGINT, &int_sa, NULL);

    //Fork new child
    if(value = fork() == 0){
        child_pid = getpid();
        do{
            errno = 0;
            execve("./child", child_arg, NULL);
        }while(errno);
    }

    //Keep alive father
    while(1);

    return 0;
}

static void handler(){
    if(kill(child_pid, SIGTERM) != -1)
        waitpid(child_pid, NULL, WNOHANG);
    while(msgctl(que_id, IPC_RMID, NULL) == -1);
    free(child_arg);
    exit(getpid());
}

孩子的目标(目前仅在我的项目中)只是等待从消息队列传入的新消息。由于不会有任何消息,所以它会一直被屏蔽。

孩子

#include "library.h"

typedef struct _Msgbuf {
    long mtype;
    char[10] message;
} Msgbuf;

int main(int argc, char * argv[]){
    int que_id;

    //Recovery of message queue id
    que_id = atoi(argv[1]);

    //Set handler for SIGTERM
    signal(SIGTERM, handler);

    //Dynamic allocation of message
    received = calloc(1, sizeof(Msgbuf));

    while(1){
        do{
            errno = 0;
            //This will block child because there won't be any message incoming
            msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0);
            if(errno)
                perror(NULL);
        }while(errno && errno != EINTR);
    }
}

static void handler(){
    free(received);
    exit(getpid());
}

我从man pages on msgrcv()知道:

调用进程捕获一个信号。在这种情况下,系统调用失败,errno 设置为 EINTR。 (msgrcv() 在被信号处理程序中断后永远不会自动重新启动,无论在建立信号处理程序时 SA_RESTART 标志的设置如何。)

那么为什么它会循环打印那个错误呢?它应该在处理程序中退出,而不是在处理程序返回并且(因为 free(received) 之后)它没有找到将 errno 设置为 EINVAL 的消息缓冲区。

【问题讨论】:

  • 除非你做一些特别的事情,否则 control-C 会杀死父母和孩子。所以我怀疑你有一个竞争条件:当父母试图杀死孩子时,孩子可能已经死了并且走了,导致 Invalid argument 问题。
  • @SteveSummit 所以问题是父亲的处理程序没有必要杀死孩子。对吗?
  • @G.locurto 好吧,如果父母没有必要杀死孩子,那么在这种情况下,鉴于您描述了parent 作为“创建一个孩子,然后活着准备处理 SIGINT”。
  • @G.locurto 至少在 Unix 和 Linux 下,除非你做一些特别的事情,在这种情况下按下 control-C 会向所有进程发送 SIGINT。 (键盘信号会发送到当前进程组中的所有进程。除非你做一些特别的事情,否则调用fork会得到一个与它的父进程在同一个进程组中的新进程。)
  • @G.locurto 你必须设置一个sig_atomic_t 全局变量,你的应用程序的主循环会检查它。

标签: c unix signals posix handler


【解决方案1】:

(几乎)总是errno 当且仅当函数调用失败时才带有合理的值。

msgrcv() 就是这种情况。

来自msgrcv()'s documentation

返回值

成功完成后,msgrcv() 应返回一个值,该值等于实际放入缓冲区 mtext 的字节数。否则,将不会收到任何消息,msgrcv() 应返回-1,并设置errno 指示错误。

因此,如果msgrcv() 返回-1,则仅使用errno,否则errno 的值未定义并且它很可能包含垃圾或不包含垃圾...

下面的代码没有意义……

        msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0);
        if(errno)
            perror(NULL);
      } while(errno && errno != EINTR);

...应该看起来像:

        if (-1 == msgrcv(que_id, received, sizeof(Msgbuf) - sizeof(long), getpid(), 0))
        {
          /* Only here errno had a well defined value. */
          perror("msgrcv() failed"); /* perror() translates errno into a human readable text prefixed by its argument and logs it to the stderr. */
        }
        else
        {
          errno = 0;
        }
      } while (errno && errno != EINTR);

顺便说一句

   do{
        errno = 0;
        execve("./child", child_arg, NULL);
    }while(errno);

仅作为exec*() 系列函数的成员使用仅在出错时返回。因此,当while 的条件被测试时,execve() had 失败,尽管 errno had 已设置。这里初始的errnr = 0; 设置也是没用的。

【讨论】:

  • 非常感谢。我可以在 pvt 中问你一些问题吗?
  • @G.locurto:为什么不公开提出你的问题,这就是 SO 的目的。
【解决方案2】:

您的程序存在许多问题。它通过在信号处理程序中调用exitfreemsgctl 来调用未定义的行为。 开放组基本规范Signal Actions 部分中的表格列出了可以从信号处理程序中安全调用的函数。在大多数情况下,您只需在处理程序中切换一个“正在运行”标志并让您的主循环运行直到它被告知退出。类似于以下简单示例:

#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


/* this will be set when the signal is received */
static sig_atomic_t running = 1;


void
sig_handler(int signo, siginfo_t *si, void *context)
{
    running = 0;
}


int
main(int argc, char *argv[])
{
    int rc;
    struct sigaction sa;

    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = &sig_handler;
    rc = sigaction(SIGINT, &sa, NULL);
    if (rc < 0) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    printf("Waiting for SIGINT\n");
    while (running) {
        printf("... sleeping for 10 seconds\n");
        sleep(10);
    }
    printf("Signal received\n");

    return 0;
}

我也在repl.it 上组织了一个更复杂的会话。

另一个问题是您假设errno 在函数调用中保持零值。这很可能是这种情况,但您应该假设errno 的唯一一件事是,当库函数返回失败代码时,它将被分配一个值——例如,read 返回-1 并将errno 设置为指示错误的东西。调用 C 运行时库函数的常规方法是检查返回值并在适当时咨询errno

int bytes_read;
unsigned char buf[128];

bytes_read = read(some_fd, &buf[0], sizeof(buf));
if (bytes_read < 0) {
    printf("read failed: %s (%d)\n", strerror(errno), errno);
}

您的应用程序可能正在循环,因为父级行为不端并且没有等待子级或类似的事情(请参阅上文关于未定义的行为)。如果消息队列在子进程退出之前被删除,那么msgrcv 调用将失败并将errno 设置为EINVAL。你应该检查msgrcv 是否失败你检查errno。孩子也应该在遇到msgrcv 失败且errno 等于EINVAL 时终止循环,因为这是一个终止条件——匿名消息队列在它不存在后永远无法重新创建。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-01
    • 2012-01-03
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 2013-08-05
    • 2012-02-25
    • 1970-01-01
    相关资源
    最近更新 更多