【问题标题】:exit() the program from parent before child process has terminated在子进程终止之前从父进程中退出()程序
【发布时间】:2016-12-28 01:13:30
【问题描述】:

我有一个 C 服务器。该服务器必须处理多个连接用户的输入(通过一个简单的 ncurses GUI)。所以我创造了两个孩子。
我的问题来自用户界面的主菜单,我需要exit 程序(然后从第一个子进程终止第二个子进程-处理连接-)。

我会尝试用一个小例子来解释自己:

int main(){
    pid_t pid;
    int status1, status2;
    if((pid = fork()) < 0){
        perror("main fork failure:");
        exit(1);
    }

    if(pid == 0){
        pid = fork();
        if(pid == 0){
            /*  
                some stuff the second child does while 
                the first child is already running
            */
        }
        /* this is the first child */
        int choice;
        choice = menu();
        switch(choice){
            case 1:
                break;
            case 2:
                /* 
                   HERE I have to exit (from the first child first,
                   and from the program then): how can I kill the
                   second child that is running to prevent
                   zombie processes? 
                */
                // kill() which pid?
                exit(2);
                break;
        }
        wait(&status2);
    }
    wait(&status1);
    return 0;
}

那么,如果我不知道第二个孩子pid和第一个孩子,我怎么kill呢?

【问题讨论】:

  • 孩子不应该杀死他/她的父母。你的问题是关于设计的。你应该使用threadselect(如果你在linux上你可以加0来选择,不可移植)。
  • select?我用它来处理套接字,它与我的问题有什么关系?您是在谈论以与 fork 相关的方式使用 select 吗?
  • 我的意思是您使用fork() 不是它的目的。 fork() 创建一个新的进程,它可以完成您希望它完成的工作。在这里,您希望您的孩子杀死您的父母。这不是fork() 的目的。您应该使用处理 ncurses 的线程。使用变量running,在线程停止时设置为false。当此变量为假时,主程序将停止。或者使用 select 来处理0。因此,您可以使用 ncurses 读取用户输入。
  • 实际上第一个孩子必须杀死自己的孩子(我在我的问题中称之为第二个孩子),而不是父母......仍然不良行为?我不想再次更改我的代码来引入线程。我的意思是,如果我必须这样做,我会这样做,但我宁愿不这样做
  • 你失去了我。谁是ncurses?谁需要杀谁?如果您想杀死父母中的孩子,很容易让他的pid通过叉子返回。你很不清楚。您可以在 fork 之前使用getpid(),这样孩子就知道他父母的 pid,但它非常难看

标签: c fork kill


【解决方案1】:

在您的代码中,您重用了变量 pid,但幸运的是,非零 pid 是您需要发出信号的那个。

因此:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

extern int menu(void);

static void wait_for_pid(int pid)
{
    int status;
    int corpse;
    while ((corpse = wait(&status)) >= 0 && corpse != pid)
        printf("Unexpected child %d exited with status 0x%.4X\n", corpse, status);
    if (corpse == pid)
        printf("Child %d exited with status 0x%.4X\n", corpse, status);
    else
        printf("Child %d died without its death being tracked\n", pid);
}

int main(void)
{
    pid_t pid;
    if ((pid = fork()) < 0)
    {
        perror("main fork failure:");
        exit(1);
    }

    if (pid == 0)
    {
        if ((pid = fork()) < 0)
        {
            perror("child fork failure:");
            exit(1);
        }
        if (pid == 0)
        {
            pause();    /* Do nothing until signalled */
            exit(0);
        }
        /* this is the first child */
        int choice = menu();
        switch (choice)
        {
        case 1:
            /* action 1 */
            break;
        case 2:
            kill(pid, SIGTERM);
            exit(2);
            /*NOTREACHED*/
        }
        wait_for_pid(pid);
        exit(0);
    }
    wait_for_pid(pid);
    return 0;
}

wait_for_pid() 函数中的循环对子进程来说应该是多余的,但在某些情况下父进程可能有它不知道的子进程——不太可能但并非不可能的情况。

老二中pause()的使用简直就是写了一些代码;它没有用,因此不会是你在那里写的。写评论/* action 1 */ 同样是伪代码;您可以将其替换为有用的代码。我可能有函数来调用第一个孩子和第二个孩子,而不是在main() 中嵌入很多代码。我假设它的编写如图所示是为了创建一个 MCVE (Minimal, Complete, Verifiable Example);感谢您保持代码小。


上面的代码未经测试,因为没有menu() 函数。下面的代码有一个菜单功能——并不是说它非常具有交互性。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

extern int menu(void);

int menu(void)
{
    printf("Dozing...\n");
    sleep(1);
    printf("Menu option 2 chosen\n");
    return 2;
}

static void wait_for_pid(int pid)
{
    int status;
    int corpse;
    int curpid = getpid();
    printf("%d: waiting for children to die\n", curpid);
    while ((corpse = wait(&status)) >= 0 && corpse != pid)
        printf("%d: Unexpected child %d exited with status 0x%.4X\n", curpid, corpse, status);
    if (corpse == pid)
        printf("%d: Child %d exited with status 0x%.4X\n", curpid, corpse, status);
    else
        printf("%d: Child %d died without its death being tracked\n", curpid, pid);
}

int main(void)
{
    pid_t pid;
    if ((pid = fork()) < 0)
    {
        perror("main fork failure:");
        exit(1);
    }

    if (pid == 0)
    {
        if ((pid = fork()) < 0)
        {
            perror("child fork failure:");
            exit(1);
        }
        if (pid == 0)
        {
            printf("Second child (%d) - pausing\n", (int)getpid());
            pause();    /* Do nothing until signalled */
            printf("Second child (%d) - awake despite no signal handling\n", (int)getpid());
            exit(0);
        }
        /* this is the first child */
        printf("First child (%d) - menuing\n", (int)getpid());
        int choice = menu();
        switch (choice)
        {
        case 1:
            /* action 1 */
            break;
        case 2:
            printf("kill(%d, SIGTERM)\n", pid);
            kill(pid, SIGTERM);
            wait_for_pid(pid);
            exit(2);
            /*NOTREACHED*/
        }
        /* Reached on menu choices != 2 */
        /* Probably needs a loop around the menu() - end loop before wait_for_pid()  */
        wait_for_pid(pid);
        exit(0);
    }
    wait_for_pid(pid);
    return 0;
}

运行时,示例输出序列为:

19489: waiting for children to die
First child (19490) - menuing
Dozing...
Second child (19491) - pausing
Menu option 2 chosen
kill(19491, SIGTERM)
19490: waiting for children to die
19490: Child 19491 exited with status 0x000F
19489: Child 19490 exited with status 0x0200

所有这些看起来都符合预期。您可以在状态 0x000F 中看到 SIGTERM 的死亡(SIGTERM 通常为 15,在 macOS Sierra 上为 15,尽管 AFAIK 没有标准要求它为 15)。您可以看到第一个孩子从 0x0200 以状态 2 正常退出。您可以看到父母在孩子们做任何事情之前就开始等待。而且你可以看到调试技术——大量的打印和大部分时间包括 PID。

【讨论】:

  • 是的,我创建了该示例以避免复制我的长代码。谢谢你的时间。我稍后会试试这个,我会告诉你的。
  • 嘿@JonathanLeffler 我刚刚测试了它,它似乎不起作用......基本上它似乎复制了 first child 进程,我不知道。我认为它会同时执行 2 次指令。我这样说是因为当我运行它时,它会重复 printfs(第一个孩子的),当它启动 ncurses 时它基本上会崩溃。
  • 但是 第二个孩子 工作正常,不再有进程僵尸让我的文件保持打开状态。
  • 等等,我不知道是怎么回事,但现在一切正常。我刚刚取消了一个for(;;) 循环的注释——里面有select 和所有处理连接的东西——在第二个孩子 里面。好的,谢谢。
  • 哦,是的,我明白了:一定是因为没有for(;;)第二个孩子之前就终止了。哇塞。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-11
  • 2017-04-21
  • 1970-01-01
  • 2020-12-27
  • 1970-01-01
相关资源
最近更新 更多