在 C 中对 SIGCHLD 信号执行处理函数后会发生什么?

What happens after the execution of a handler function on the SIGCHLD signal in C?

我想知道在 C 中对信号执行处理程序后到底发生了什么,更具体地说是在 SIGCHLD 信号上。

我实际上正在构建一个 shell,我需要这样做:

  1. 用户输入以“&”作为最后一个参数的命令
  2. 使用fork创建了一个新进程
  3. 父进程returns到主函数并等待用户的新输入
  4. 子进程在后台执行
  5. 子进程结束,触发SIGCHLD
  6. 父进程处理其子进程的死亡

我的代码完成了所有这些要点,但是在处理程序函数 returns.

的最后表现得很奇怪

我的实现

主循环

int
main()
{

    // SIGNALS HANDLING :
    sigaction(SIGTERM, NULL, NULL);
    sigaction(SIGQUIT, NULL, NULL);


    // Current location
    char* path = malloc(PATHMAX);
    int argc;

    char** argv;

    // main loop
    while(1) 
    {
        getcwd(path, PATHMAX);
        printf("%s$ ",path);

        argc = 0;

        // Parsing
        argv = malloc(MAX_INPUT); // user's input is limited to 100000 characters
        getParameters(argv, &argc);


        // First we check if the user wants to execute the task in background
        if ( strcmp(argv[argc-1],"&") == 0 ) {

            // builtin functions can't be executed in background:
            int isExit = (strcmp(argv[0],"exit") == 0) ? 1 : 0;
            int isCd = (strcmp(argv[0],"cd") == 0) ? 1 : 0;

            if(isExit || isCd) {
                printf("Built-in functions can't be executed in background. \n");
            } else {
                // Execute the job in background

                // First we delete the last arg
                argv[argc-1] = NULL;
                argc--;

                execBackground(argv,argc);
            }

        } else {

            // Execute built-in functions
            if(!(exeBuiltin(argv, argc))) {
                // Execute jobs if no builtin functions were executed
                exeJob(argv,argc);
            }

        } 


        free(argv);



    }




    return 0;
}

用户输入处理

// max 100000 characters
char input[MAX_INPUT];


// read keyboard inputs
fgets(input,MAX_INPUT,stdin);

内置函数(cd 和退出)

int exeBuiltin(char** argv, int argc) {
    // execute builtin functions if it exists
    char* builtin[2];
    builtin[0] = "exit";
    builtin[1] = "cd";

    // run through the arguments
    for(int i = 0; i < argc; i++) {
        // exit
        if (strcmp(argv[i],builtin[0]) == 0) {
            exitBuiltin(EXIT_SUCCESS);
        } else if (strcmp(argv[i],builtin[1]) == 0) {
            // cd
            if (argc >= 2) {
                cdBuiltin(argv[i+1]);
                return 1;
            }
        }
    }

    return 0;
}

static void cdBuiltin(char* path) {
    if(chdir(path) == -1) {
        printf("%s\n",strerror(errno));
    };
}


static void exitBuiltin(int signal) {
    exit(signal);
}

Link 信号和处理程序之间:

struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = child_handler;

sigaction(SIGCHLD, &sa, NULL);

处理程序:

static void child_handler(int sig)
{
    int status;
    /* kills process 
     pid is a global variable that is set when I call fork() */
    if(waitpid(pid, &status, 0) != -1) {

        if (status == 0)  // Verify child process terminated without error.  
        {
           // success  
           printf("\nBackground job exited with code 0\n");
        }

        if (status == 1)      
        {
            // error
            printf("\nBackground job exited\n");
        }

    }


    return;
}

问题

当我调用类似以下内容时出现问题:

sudo apt-get update &
cd ../

然后当 sudo 命令终止时(调用处理程序并打印 "Background job exited with code 0"),即使命令 "cd ../" 已经执行,它也会再次执行 .

输出看起来像:

$ /home/ubuntu/workspace$ sudo apt-get update &
$ /home/ubuntu/workspace$ cd ../
$ /home/ubuntu$
Background job exited with code 0
$ /home$

附加信息:

cd 是一个内置函数,它只做: chdir(path) 给定的路径,并且在主循环的每个循环开始时(在命令之后)简单地用 printf("%s$",path) 输出路径已被调用)。

问题

为了理解真正的问题是什么,我相信我应该理解 child_handler 函数结束时发生了什么。

如果我在主进程的代码中的某个点,比如等待用户输入,后台进程终止,调用 child_handler 然后怎么办?它是否改变了主进程的任何内容,或者它仍然是代码中的同一点,等待输入?

谢谢你的帮助。

一个明确的问题是您在信号处理程序中调用 printf,并且 printf 不是异步安全的,因此如果在任何库函数(例如 fgets 等待读取一行输入?),你会得到未定义的行为。可能会损坏 stdio 文件缓冲区并导致它的行为就像输入被复制一样。

您需要非常小心如何在信号处理程序中编写代码,确保它不能调用(直接或间接)和非异步安全库函数,并且确保所有代码本身对于程序其他部分的任何副作用都是异步安全的。

正如@ChrisDodd 所说,问题是系统调用 (fgets) 被后台进程的死亡中断并导致未定义的行为。

因此,我通过向我的处理程序添加以下标志解决了我的问题:

sa.sa_flags = SA_RESTART;

正如文档所说:

Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals.

因此 fgets 不再有问题。