【问题标题】:Getline stops working after sigchld is received收到 sigchld 后 Getline 停止工作
【发布时间】:2021-07-31 23:41:58
【问题描述】:

我一直在试验信号,但遇到了一个我无法解释的问题。

我在这个简单的 C 程序中重新创建了我的问题,简而言之,我正在使用 getline() 在循环中读取用户输入。用户可以 fork 进程、kill 子进程或一起退出主进程。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int counter = 0;

void handler(int signum){
    counter++;
}

int main(){
    int bool = 1;
    char *input;
    size_t  size=100;
    input = malloc(sizeof(char)*100);
    memset(input,'\0',size);
    pid_t id;

    struct sigaction sa;

    do{
        printf("counter=%d\n",counter);
        getline(&input,&size,stdin);
        if( strncmp(input,"fork",4) == 0 ){

            id = fork();
            if( id == 0 ){//child
                while(1) sleep(1);
                free(input);
                return 0;
            }else if( id > 0 ){//parent
                sa.sa_handler = handler;
                sigaction(SIGCHLD, &sa, NULL);
            }else{//fork failed
                free(input); return -1;
            }

        }else if( strncmp(input,"kill",4) == 0 ){
            kill(id,9);
        }else if( strncmp(input,"exit",4) == 0 ){ 
            bool = 0;
        }
        
    }while(bool == 1);

    free(input);
    return 0;
}

奇怪的是,如果我 fork 一个子进程然后杀死它,换句话说,输入标准输入:

分叉

杀死

我陷入了一个无限循环,其中以下内容无限期地打印到标准输出(这也表明当孩子被杀死时 SIGCHLD 被缓存)

计数器 1

如果我删除信号处理程序,一切似乎都可以正常工作。我知道 getline() 使用 read() 系统调用并且 SIGCHLD 信号会导致它中断,但除此之外,我几乎可以肯定在 下一次迭代 中 getline() 函数应该可以正常工作。有没有人解释为什么 getline() 停止工作?

(我正在使用 gcc 编译器并在 Ubuntu 20.04 LTS 上执行程序)

【问题讨论】:

  • 关于:char *input;getline(&amp;input,&amp;size,stdin);getline() 的调用将使用堆栈中input 地址处的任何垃圾作为第一个参数。 (这可能会被您的 IDE 通过将所有堆栈设置为 0x00 来掩盖,但是您不能依赖该行为。)强烈建议将 input 的声明替换为:char *input = NULL;
  • 你说得对,谢谢你指出来。我将编辑帖子并添加一个 meset() 调用以避免任何混淆,即使您提到的解决方案是绝对正确的,因为如果输入参数设置为 NULL,getline 将分配所需的任何内存。

标签: c linux signals getline sigaction


【解决方案1】:

在 onlinegdb.com 上,我无法始终重现该问题。有时它似乎按预期工作,有时我收到getline 报告的重复错误。

通过在调用getline之前设置errno = 0,然后检查getlineerrno的返回值,我发现getline反复返回-1。在第一次调用时,它会在后续调用中设置errno = EINTRperror 报告“系统调用中断”),errno 仍然是0(“成功”)。

    /* ... */
    do{
        printf("counter=%d\n",counter);
        errno = 0;
        if(getline(&input,&size,stdin) < 0)
        {
            static int i = 20; // to avoid endless loop
            perror("getline");
            if(--i == 0) return 1;
        }
    /* ... */

显然,在某些/许多情况下,信号会设置输入流stdin 的永久错误条件。

可以通过调用clearrerr来清除永久错误。

很遗憾,我(还)没有找到解释这种行为的文档。

    /* ... */
    do{
        printf("counter=%d\n",counter);
        errno = 0;
        if(getline(&input,&size,stdin) < 0)
        {
            perror("getline");
            if(errno == EINTR)
            {
                //clearerr(stdin); // clearing here would avoid the 2nd error return
            }
            else if(errno == 0)
            {
                clearerr(stdin);
            }
            else
            {
                return 2;
            }
        }
    /* ... */

【讨论】:

  • 首先感谢您的回答。现在我完全可以理解为什么错误会留在标准输入中,并且使用 clearerr() 函数是我迄今为止遇到的最佳解决方案。非常感谢您的帮助。
【解决方案2】:

原因是当read() 系统调用被中断时(当父进程收到SIGCHLDread() 失败并返回EINTR),流被设置为错误状态。这在 POSIX 的getline 中有记录:

如果发生错误,则应设置流的错误指示符,该函数应返回-1并设置errno以指示错误。

如果信号在进入read() 系统调用之前传递给父级,那么它将在系统调用之前处理,因此read() 上没有EINTR。这就是为什么您可能并不总是在getline() 调用中看到无限循环。

但除此之外,我几乎可以肯定,在下一次迭代中,getline() 函数应该可以正常工作。

一旦流设置为错误,下次不会自动清除。所以你必须自己用clearerr清除它。

请注意,这种行为是由于getline 的要求而发生的;不是来自中断的系统调用read()。如果您在循环中直接在文件描述符STDIN_FILENO 上使用read(),它将在下一次迭代中按预期工作,即没有无限循环。

或者,您可以使用SA_RESTART 标志告诉系统调用自动重新启动:

sa.sa_flags = SA_RESTART;

在这种情况下,EINTR 会被透明处理,read() 在处理完信号后会自动重新启动,并且永远不会传递给getline() 函数。


P.S.:你应该初始化sa

struct sigaction sa = {0};

并用sigemptyset清空初始化信号集:

sigemptyset(&sa.sa_mask);

因为您只设置了sa_handler,其余字段未初始化!

【讨论】:

  • 为了回答我的问题,您进行了深入研究,我对此非常满意。我也更好地理解了问题发生的原因。关于 SA_RESTART 解决方案,我想到了它,但在我的程序的后面,我必须中断来自文件的 read() 调用,所以对于我罕见的情况,它无济于事。 Clearerr() 对我来说似乎是更好的选择,但总的来说 SA_RESTART 可能是更好的做法。另外感谢您指出需要初始化sa,我忘记了。
  • struct sigaction 进行零初始化会使sa_mask 字段具有未指定的值。你需要做sigemptyset(&amp;sa.sa_mask)(然后添加任何需要阻塞的信号)。
  • @zwol 确实。更新了答案。
猜你喜欢
  • 2020-02-03
  • 1970-01-01
  • 1970-01-01
  • 2013-10-09
  • 1970-01-01
  • 2022-01-07
  • 1970-01-01
  • 2015-03-15
  • 2013-08-29
相关资源
最近更新 更多