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

时间:2016-12-13 23:04:54

标签: c linux process signals system-calls

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

我实际构建了一个shell,我需要这样做:

  1. 用户输入命令"&"作为最后一个论点
  2. 使用fork
  3. 创建一个新进程
  4. 父进程返回主函数并等待来自用户的新输入
  5. 子进程在后台执行
  6. 子进程结束并触发SIGCHLD
  7. 父进程处理其子女的死亡
  8. 我的代码完成了所有这些要点,但在处理函数返回时最终会以奇怪的方式运行。

    我的实施

    主循环

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

    信号和处理程序之间的链接:

    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命令终止时(处理程序被调用并打印&#34;后台作业退出,代码为0&#34;),即使命令&#34; 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然后呢?它是否会改变主进程的任何内容,或者它仍然是代码中的相同点,等待输入?

    谢谢你的帮助。

2 个答案:

答案 0 :(得分:1)

一个明确的问题是你在信号处理程序中调用printf,并且printf不是异步安全的,所以如果SIGCHLD在任何库函数中发生(例如{{1})等待读取一行输入?),你得到未定义的行为。也许破坏stdio文件缓冲区并使其像输入一样重复。

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

答案 1 :(得分:0)

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

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

sa.sa_flags = SA_RESTART;

正如文件所述:

  

通过使某些系统调用可以跨信号重新启动,提供与BSD信号语义兼容的行为。

因此fgets不再存在问题。