微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

收到 sigchld 后 Getline 停止工作

如何解决收到 sigchld 后 Getline 停止工作

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

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

#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 上执行程序)

解决方法

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

中记录的

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

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

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

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

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

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

SA_RESTART

在这种情况下,sa.sa_flags = SA_RESTART; 被透明处理,EINTR 在处理信号后自动重新启动并且永远不会传送到 read() 函数。


P.S.:您应该使用以下内容初始化 getline()

sa

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

struct sigaction sa = {0};

因为您只设置了 sigemptyset(&sa.sa_mask); ,其余字段未初始化!

,

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

通过在调用errno = 0之前设置getline,然后检查getlineerrno的返回值,我发现getline反复返回{{ 1}}。在第一次调用时,它会在后续调用中设置 -1errno = EINTR 报告“系统调用中断”),perror 保持 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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。