【问题标题】:What happens after the execution of a handler function on the SIGCHLD signal in C?在 C 中对 SIGCHLD 信号执行处理函数后会发生什么?
【发布时间】:2017-04-29 03:54:32
【问题描述】:

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

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

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

我的代码完成了所有这些工作,但在处理函数返回时,其行为在最后表现得很奇怪。

我的实现

主循环

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 和 exit)

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 命令终止时(调用处理程序并打印“后台作业退出,代码 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) 并给出路径,并且在 main 中每个循环的开头(在调用命令之后)简单地输出带有 printf("%s$",path) 的路径。

问题

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

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

感谢您的帮助。

【问题讨论】:

  • 不确定 hat 是否能解决您的问题(因为我看不到代码),但大多数阻塞系统调用可以在被信号中断时由系统重新启动(取决于 SA_RESTART 标志)。
  • 你的 shell 将如何处理在后台运行 2 个或更多命令的 shell 脚本(例如 long-running-command file1.txt &amp; long-running-command file2.txt &amp; long-running-command file3.txt)?那么管道(ls | grep -v 'xy.*t' | wc)呢?后台会运行多个进程,因此单个全局变量将严重不足。但是,这与您当前的问题无关。
  • 您将需要显示更多代码。问题不在您展示的代码中,因此我们需要查看更多代码。请查看如何创建 MCVE (minimal reproducible example),然后创建一个足以重现您的问题而无需调用 sudoapt-get 的命令(sleep 是模拟较慢程序的好工具)。跨度>
  • @Jonathan Leffler 实际上,我不必担心多个后台进程,因为在我的练习表(大学练习)中指定外壳将被限制为一个前台和一个后台进程。
  • @tofro 那么,前台进程会从 main 的开头重新开始代码吗?

标签: c linux process signals system-calls


【解决方案1】:

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

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

【讨论】:

  • 好的,谢谢,但实际上我试图删除所有 printf 并没有任何改变,它仍然“召回” cd 函数,我最终得到这个:/home/ubuntu$/home$
【解决方案2】:

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

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

sa.sa_flags = SA_RESTART;

正如文档所说,它将:

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

因此 fgets 不再有问题。

【讨论】:

  • 这意味着您的实际错误是您没有检查 fgets 的返回值是否有错误——如果信号中断它,它将返回 NULL 并设置错误指示器(您需要检查ferror并使用clearerr重置)
  • 我确实检查了错误并且我有一个但我不知道如何处理它。但是,是的,错误是“中断系统调用”或类似的东西,所以它应该在我的脑海中弹出一些东西。
猜你喜欢
  • 2019-08-04
  • 1970-01-01
  • 1970-01-01
  • 2016-09-17
  • 2010-11-28
  • 2020-09-20
  • 2023-04-01
  • 1970-01-01
  • 2011-07-21
相关资源
最近更新 更多