【问题标题】:Child Process in Background Issues for Handmade Shell手工壳的背景问题中的子进程
【发布时间】:2013-02-26 04:57:45
【问题描述】:

我在实现自己的手工外壳时遇到了一些麻烦。我已经能够派生一个进程并使用waitpid在前台运行它,但是当我尝试在后台运行简单的进程(例如'sleep 5 &')时,该进程似乎永远运行。 checkListJobs 将确定进程是否已完成运行,但它永远不会停止。任何帮助将不胜感激。我认为错误出在我的“foo”函数中。

void insertJob(int pid) {
    printf("beginning job %d.\n", pid);
    struct job *node = malloc(sizeof(struct job));
    node->pid = pid;
    node->next = NULL;

    if(root == NULL) {
        root = node;
    } else {
        node->next = root;
        root = node;
    }
}    

void checkListJobs(int z) {
    curr = root;
    while(curr!=NULL) {
        if(kill(curr->pid,0) != 0)   {
            if(prev==NULL) {
                prev = curr;
                root = curr;
            } else {
                prev->next = curr->next;
            }
        } else {
            if(!z) printf("%d is still running.\n", curr->pid);
        }
        prev = curr;
        curr = curr->next;
    }
}   


//code for child forking
void foo(char *cmd, char *argv[], int args) {
    int bgFlag;

    if(!strcmp(argv[args], "&")){
        argv[args] = '\0';
        bgFlag = 1;
    }

    int pid = fork();
    int status = 0;

    if(pid==0){
        if(bgFlag) {
            fclose(stdin); // close child's stdin
            fopen("/dev/null", "r"); // open a new stdin that is always empty
        }
        execvp(cmd, argv);
        // this should never be reached, unless there is an error
            fprintf (stderr, "unknown command: %s\n", cmd);
            exit(0);
    } else {
        if(!bgFlag) {
            waitpid(pid, &status, 0);
        } else {
            insertJob(pid);
        }
        if (status != 0) {
            fprintf  (stderr, "error: %s exited with status code %d\n", cmd,     status);
        } else {
            // cmd exec'd successfully
        }
    }

    // this is the parent still, since the child always terminates from exec or exit

    // continue being a shell...
}

【问题讨论】:

  • 您是否尝试过使用调试器(即 GDB gnu.org/software/gdb 或使用 VS)来调试程序?
  • @JerryCoffin 已删除。对不起!
  • @RyanSchulze:没问题。

标签: c shell exec fork


【解决方案1】:

您需要为 SIGCHLD 安装一个信号处理程序,因为它会在子进程完成时告诉您的程序。收到 SIGCHLD 后,您应该调用 wait()(或 PID 值为 -1 的 waitpid(),因为您不知道哪个孩子完成了,只是 a 个孩子已经完成)。

编写处理程序最安全的方法是:

volatile sig_atomic_t sigchld;
int handle_child(int sig)
{
  if (sig == SIGCHLD)
    sigchld = 1;
}

在你的主循环中,检查sigchld是否为1。如果是,一个子进程结束,然后你可以调用waidpid()(使用-1的PID,因为你不会知道哪个孩子结束)在一个循环中(见下文),因为多个孩子可能同时结束。此外,如果任何系统调用返回错误并且 errnoEINTR 则它被信号中断,所以要么返回到主循环的顶部,要么检查 sigchld 并相应地处理(不要忘记尽快将sigchld 重置为 0)。

for(;;)
{
  int status;
  pid_t child;

  child = waitpid(-1,&status,WNOHANG);
  if (child == -1) 
  {
    if (errno == ECHILD) break; /* no more children */
    /* error, handle how you wish */
  }
  /* handle the return status of the child */
}
sigchld = 0;

可以在信号处理程序中调用waitpid()(POSIX 说这样做是安全的),但您真的不应该在信号处理程序中进行任何其他操作因为它可能会导致非常微妙的错误(例如,在调用 malloc() 期间会引发 SIGCHLD --- 导致调用 malloc() 的信号处理程序中的任何代码都会导致非常讨厌的问题 em>。这就是我建议在信号处理程序中设置标志的原因——在信号处理程序中做的越少越好。

【讨论】:

  • 感谢您的帮助,但我实际上以不同的方式解决了这个问题。我为链表中的每个子进程节点添加了一个指向状态位置的指针,并将waitpid(curr->pid,curr->status,WNOHANG); 作为我在 checkListJobs 内的 while 循环中的第一行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-10
  • 1970-01-01
  • 2017-01-31
  • 2010-10-26
  • 1970-01-01
  • 2018-11-03
相关资源
最近更新 更多