诡异的bug: tcsh陷入死循环-阿里云开发者社区

开发者社区> 技术小美> 正文

诡异的bug: tcsh陷入死循环

简介:
+关注继续查看

问题:项目开发中,碰到一个很奇怪的问题:当tcsh启动的子程序退出之后,tcsh本身无法退出,并占用大量CPU资源。

背景:应用程序在fork之后,用tcsh启动另一个子进程,执行特定任务。进程之间使用sockepair(pipe)进行进程间通讯;为简化编程,将子进程的socket fd重定向为stdin和stdout。

具体症状:

Strace tcsh程序:

fstat(250, 0x7fbfffc240)                = -1 EBADF (Bad file descriptor)
fstat(251, 0x7fbfffc240)                = -1 EBADF (Bad file descriptor)
fstat(252, 0x7fbfffc240)                = -1 EBADF (Bad file descriptor)
fstat(253, 0x7fbfffc240)                = -1 EBADF (Bad file descriptor)
fstat(254, 0x7fbfffc240)                = -1 EBADF (Bad file descriptor)
fstat(255, 0x7fbfffc240)                = -1 EBADF (Bad file descriptor)
write(17, "[1] 30274\n", 10)            = -1 EPIPE (Broken pipe)
--- SIGPIPE (Broken pipe) @ 0 (0) ---
lseek(16, 0, SEEK_END)                  = -1 ESPIPE (Illegal seek)
rt_sigprocmask(SIG_BLOCK, [], [], 8)    = 0
rt_sigprocmask(SIG_SETMASK, [], [], 8)  = 0
fstat(0, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(1, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(2, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(3, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(4, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(5, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(6, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(7, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(8, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(9, 0x7fbfffc240)                  = -1 EBADF (Bad file descriptor)
fstat(10, 0x7fbfffc240)                 = -1 EBADF (Bad file descriptor)
fstat(11, 0x7fbfffc240)                 = -1 EBADF (Bad file descriptor)
fstat(12, 0x7fbfffc240)                 = -1 EBADF (Bad file descriptor)
fstat(13, 0x7fbfffc240)                 = -1 EBADF (Bad file descriptor)
fstat(14, 0x7fbfffc240)                 = -1 EBADF (Bad file descriptor)
fstat(20, 0x7fbfffc240)                 = -1 EBADF (Bad file descriptor)
fstat(21, 0x7fbfffc240)                 = -1 EBADF (Bad file descriptor)

tcsh不断打印上述系统调用,可判断tcsh陷入了死循环,无法退出。其中

write(17, "[1] 30274\n", 10)            = -1 EPIPE (Broken pipe)

--- SIGPIPE (Broken pipe) @ 0 (0) ---

lseek(16, 0, SEEK_END)                  = -1 ESPIPE (Illegal seek)

初步这两个系统调用是引发死循环的关键。为什么write到17文件描述符。(如果有经验,估计在这一步就可知道问题的根源。)

Lsof tcsh进程:

COMMAND   PID    USER   FD   TYPE             DEVICE     SIZE      NODE NAME
csh     30246 tachyon  cwd    DIR              253,7     4096 108511249 /h/work/tcpproxy
csh     30246 tachyon  rtd    DIR              253,0    20480         2 /
csh     30246 tachyon  txt    REG              253,0   349696    163890 /bin/tcsh
csh     30246 tachyon  mem    REG              253,3 48524688   1951970 /usr/lib/locale/locale-archive
csh     30246 tachyon  mem    REG              253,3    44473   1757371 /usr/share/locale/en/LC_MESSAGES/tcsh
csh     30246 tachyon  mem    REG              253,3    21546   1425608 /usr/lib64/gconv/gconv-modules.cache
csh     30246 tachyon  mem    REG              253,3    10128   1425550 /usr/lib64/gconv/ISO8859-1.so
csh     30246 tachyon  mem    REG              253,0   105080    196842 /lib64/ld-2.3.4.so
csh     30246 tachyon  mem    REG              253,0  1493409    196843 /lib64/tls/libc-2.3.4.so
csh     30246 tachyon  mem    REG              253,0    15728    196865 /lib64/libtermcap.so.2.0.8
csh     30246 tachyon  mem    REG              253,0    30070    196861 /lib64/libcrypt-2.3.4.so
csh     30246 tachyon   16u  unix 0x0000010293993640           31854667 socket
csh     30246 tachyon   17u  unix 0x0000010293993640           31854667 socket
csh     30246 tachyon   18u   REG              253,7 25770181 108511260 /****.log
csh     30246 tachyon   19u  unix 0x0000010293993640           31854667 socket

tcsh没有0,1,2的文件描述符,只有3个指向同一个unix地址的socket地址,基本断定: 16为stdin,17为stdout,18为stderr,19未知。

问题就变成:tcsh不断尝试往stdout输入特定信息![1] 30274这个信息,应该是tcsh所起子进程的PID。

这样就基本清楚了:tcsh收到子进程的状态信息([1] PID),并将收到的信息输出。由于stdout已被重定向成unix socket,在输出时,发生SIGPIPE (Broken pipe)事件。 SIGPIPE的默认行为是终止程序,显然tcsh没终止,并不断尝试输出。为什么tcsh不终止?难道tcsh内部实现屏蔽了该信号?为了解决这个问题,有两个办法:看tcsh的源码;看是否自己的程序改变了tcsh的信号处理。

最终,从自己的程序入手,发现果然是自己的程序改变了tcsh的信号配置。

A child created via fork(2) inherits a copy of its parent's signal dispositions.  During an execve(2), the dispositions of handled signals are reset to the default; the dispositions of ignored signals are left unchanged.

问题的结论:

1)      自己的程序忽略了SIGPIPE;

2)      fork之后,重定向了stdin和stdout;

3)      tcsh启动子程序,捕获到子进程的状态(后台启动返回PID信息),并尝试输出到stdout(socket),因socket已被关闭,写失败并导致SIGPIPE。因SIGPIPE已被忽略,导致tcsh不断重试,无法退出!

重现程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
 
void Usage(char* program){
    fprintf(stderr, "Usage: %s binary\n", program);
    exit(-1);
}
 
void sighandler(int sig){
    /* fprintf(stderr, "recieve SIGPIPE\n"); */
}
 
int main(int argc, char** argv)
{
    if(argc < 2){
        Usage(argv[0]);
    }
    char* binary = argv[1];
 
    /* signal(SIGPIPE, sighandler); */
    signal(SIGPIPE, SIG_IGN);
 
    /* Get a socketpair we'll use for stdin and stdout. */
    int sp[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) {
        perror("socketpair error");
        return -1;
    }
    pid_t pid = fork();
    if (pid == 0) {
        signal(SIGTERM, SIG_DFL); signal(SIGHUP, SIG_DFL);
        close(sp[0]);
        if (dup2(sp[1], 0) == -1 || dup2(0, 1) == -1) {
            char msg[128];
            snprintf(msg, sizeof msg, "dup2 failed in child process %d", getpid());
        }
 
        sleep(1);
        fprintf(stderr, "start child process\n");
        setsid();
        execvp(binary, &argv[1]);
        char msg[512];
        snprintf(msg, sizeof msg, "exec %s failed", binary);
        exit(-1);
    }else if(pid < 0){
        perror("fork");
        exit(-1);
    }
 
    printf("parent process\n");
    close(sp[1]);
    close(sp[0]);
 
    while(1){
        char* msg = "hello test\n";
        /* write(sp[0], msg, strlen(msg)); */
        sleep(1);
        printf("write successful\n");
    }
 
    return 0;
}

gcc -o pipe pipe_broke.c
./pipe /usr/bin/strace -e trace=write,lseek -o strace.log csh -c "sleep 10 & "

Note: bash has no this problem! csh -> tcsh


本文转自 zhenjing 博客园博客,原文链接:http://www.cnblogs.com/zhenjing/archive/2011/05/26/tcsh_dead_loop.html   ,如需转载请自行联系原作者


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
使用NAT网关轻松为单台云服务器设置多个公网IP
在应用中,有时会遇到用户询问如何使单台云服务器具备多个公网IP的问题。 具体如何操作呢,有了NAT网关这个也不是难题。
26789 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10076 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
11612 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
9158 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13884 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
7365 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
4503 0
+关注
6906
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载