作者: joseph_1118
问题背景:
我们这边开发了一个动态库给客户用,动态库里面会调用system来做insmod/rmmod的操作。拿到客户那边去测试,会随机性的出现system返回-1的问题,也就是system出错了!但是奇怪的是我们在system返回-1后去lsmod发现实际上insmod/rmmod是成功了的。把当时的errno和对应的出错信息打出来发现errno是10(ECHILD),对应的信息是No child processes。
问题定位:
早就听说过system函数不靠谱,一方面是安全方面,另一方面是因为其返回值太多,包含不同的含义。近来也确实体会到了这一点,有一次system出错返回的错误码是一个比较大的值,当时错误原因是因为用户的进程里的环境变量是NULL,导致system里面shell的环境变量是NULL,而当时我system是这样写的:system("rmmod xx.ko");直接导致找不到rmmod!后来弄了个绝对路径才解决问题。
system返回-1,错误码为errno这个问题在google上搜一下,结果一大把,但是基本都是说SIGCHILD的处理方式设置为SIG_IGN的问题。所以,一开始我们也怀疑是这个问题。毕竟我们的so是在客户的环境上用的,我们不清楚客户到底有没有忽略(我们也不是很信任客户的承诺 :))。但是,由于这个是概率性问题,我们猜测可能是客户的某个线程在某个时候会去做这个事情。所以,我这边写了一个jprobe,监控内核的do_sigaction,看看是否有谁去把SIGCHLD的处理方式显式地修改成SIG_IGN。后来重现了问题,但是jprobe没抓到数据。显然,并不是忽略SIGCHLD导致的该问题。
到这个地步就只能再去看内核代码,有没有其他可能导致返回ECHILD。最后发现,如果找不到子进程的尸体,那么也会返回ECHILD。其实从错误提示“No child processes”也早应该想到...
我们可以确定,子进程创建是成功的,而且子进程的活也干了,显然子进程出生过。后来,子进程完成了任务,然后死去了,成为一具僵尸。而父进程这时应该去给子进程收尸,但是,子进程的尸体没了!!!!多么恐怖的一件事情
!子进程的尸体到底被谁抢走了?难道有人捞回去搞阴婚了吗?想想都觉得毛骨悚然~~ 有问题定位思路就是好事,至少有希望了。继续jprobe...这次是在wait_consider_task中,看看谁回去查看这具尸体。感觉就像安排个小人儿在尸体附近的草丛中,看看谁会偷偷摸摸地去打那具尸体的主意。
这次结果没有让我们失望,发现有个进程会很频繁地去干这种龌龊的事情!把这个进程的名字以及其父进程的名字发给客户,询问他们到底是什么关系,为什么要干这事。
也就是说,客户是在线程2中调用我们的so,但是他们的线程1里面在周期性地做waitpid(-1)的事情(他们以为他们的task A只有一个子进程,所以不加选择的用了-1去回收子进程)。然后,在某个时刻,线程2的子进程的尸体被线程1收走了。然后线程2的system就出错了。抢尸就是这么发生的。后来,客户那边把waitpid的第一个参数改成他的子进程的pid就好了。
本文大致描述了这个问题的过程,后面我会详细分析SIGCHLD的作用,以及什么情况下会出现ECHILD错误。欢迎指正!