简体   繁体   English

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

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

I was wondering what exactly happens after the execution of a handler on a signal in C, more specifically on the SIGCHLD signal. 我想知道在对C中的信号(尤其是SIGCHLD信号)执行处理程序之后究竟发生了什么。

I'm actually building a shell and I need to do this: 我实际上是在构建外壳,我需要这样做:

  1. User enters a command with "&" as the last argument 用户输入带有“&”作为最后一个参数的命令
  2. A new process is created with fork 用fork创建一个新过程
  3. The parent process returns to the main function and wait for a new input from the user 父进程返回到主函数并等待用户的新输入
  4. The child process executes in the background 子进程在后台执行
  5. The child process ends and SIGCHLD is triggered 子进程结束并触发SIGCHLD
  6. The parent process handles the death of his child 父进程处理孩子的死亡

My code do all of these points, but behaves in a strange way at the very end when the handler function returns. 我的代码完成了所有这些要点,但是在处理程序函数返回时,其行为最终却以奇怪的方式出现。

My implementation 我的实施

Main loop 主循环

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;
}

User's input processing 用户的输入处理

// max 100000 characters
char input[MAX_INPUT];


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

Built-in functions (cd and exit) 内置功能(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 between signal and handler: 信号和处理程序之间的链接:

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

sigaction(SIGCHLD, &sa, NULL);

Handler: 处理程序:

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;
}

The problem 问题

My problem occurs when I call something like: 当我调用类似的东西时,会发生我的问题:

sudo apt-get update &
cd ../

Then when the sudo command terminates (the handler is called and prints "Background job exited with code 0"), even though the command "cd ../" is already executed it gets executed again . 然后,当sudo命令终止时(调用处理程序并打印“以代码0退出的后台作业”),即使命令“ cd ../”已经执行,它也会再次执行。

The ouput looks like: 输出看起来像:

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

Additional information: 附加信息:

cd is a builtin function which only does: chdir(path) with a path given, and the path is simply output with a printf("%s$",path) at the beginning of every loop in the main (after a command has been called). cd是一个内置函数,它仅执行以下操作: chdir(path)具有给定的路径,并且该路径仅在main中每个循环的开头(在命令执行完后printf("%s$",path)printf("%s$",path)进行输出。被称为)。

The question 问题

In order to understand what the real problem is, I believe that I should understand what happens at the end of my child_handler function. 为了了解真正的问题是什么,我相信我应该了解在child_handler函数末尾会发生什么。

If I'm at a certain point in the code with the main process, say waiting on the user input, and the background process dies, child_handler is called and then what? 如果我在主流程的代码中处于特定位置,例如等待用户输入,并且后台流程死了,则调用child_handler,然后呢? Does it change anything for the main process or is it still a the same point in the code, waiting for an input? 它会更改主进程的任何内容还是在代码中等待输入的同一点?

Thanks for you help. 感谢您的帮助。

One definite problem is that you are calling printf in your signal handler, and printf is not async-safe, so if the SIGCHLD happens while in any library function (such as fgets waiting to read a line of input perhaps?), you get undefined behavior. 一个明确的问题是,您正在信号处理程序中调用printf ,而printf也不是异步安全的,因此,如果SIGCHLD发生在任何库函数中(例如fgets正在等待读取一行输入?),您将无法定义行为。 Perhaps corrupting the stdio file buffers and causing it to act as if the input was duplicated. 可能损坏了stdio文件缓冲区,并使它的行为就像输入被重复一样。

You need to very careful how you write the code in your signal handler, making sure it cannot call (directly or indirectly) and non-async-safe library function, and ensuring that all the code is itself async-safe with respect to any side effects in other parts of your program. 您需要非常小心如何在信号处理程序中编写代码,确保它不能(直接或间接)调用非异步安全的库函数,并确保所有代码本身在任何方面都是异步安全的程序其他部分的效果。

As @ChrisDodd said, the problem was that a system call (fgets), was interrupted by the death of the background process and caused undefined behaviour. 就像@ChrisDodd所说的那样,问题在于系统调用(fgets)被后台进程的中断而中断,并导致不确定的行为。

Therefore, I solved my problem by adding the following flag to my handler: 因此,我通过向处理程序中添加以下标志解决了我的问题:

sa.sa_flags = SA_RESTART;

As the documentation says it will: 如文档所述,它将:

Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals. 通过使某些系统调用可跨信号重新启动来提供与BSD信号语义兼容的行为。

Thus no more issue with fgets. 因此,fgets不再存在问题。

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

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