1. 程式人生 > >linux多執行緒環境下的搶屍行為(system返回-1:No child processes)

linux多執行緒環境下的搶屍行為(system返回-1:No child processes)

#!/usr/bin/env python
#coding:utf8

import os
import time

pid = os.fork()

if pid:
    print 'in parent.sleepin....'
    while True:
        print "{}:{}".format("rece from ",os.waitpid(-1,1))
        time.sleep(1)
    time.sleep(10)
    print 'parent done.'
else:
    print 'in child.sleeping...'
    time.sleep(3)
    #while True:
    print 'child.done.'
[[email protected] ~]# python testwaitpid.py 
in parent.sleepin....
rece from :(0, 0)
in child.sleeping...
rece from :(0, 0)
rece from :(0, 0)
child.done.
rece from :(0, 0)
rece from :(94700, 0)
Traceback (most recent call last):
  File "testwaitpid.py", line 12, in <module>
    print "{}:{}".format("rece from ",os.waitpid(-1,1))
OSError: [Errno 10] No child processes
[
[email protected]
~]# vim testwaitpid.py

工作過程:

父程序,子程序分別執行,父程序sleep 30 s,子程序sleep 10s,父程序沒有處理子程序的程式碼,子程序進入zombie狀態,父程序sleep後,init程序回收父程序資源,父程序退出,子程序仍在,init接管子程序,並回收子程序資源

            使用輪詢解決zombie問題

            python可以使用waitpid()來處理子程序

            waitid()接受兩個引數,第一個引數設定為-1,表示與wait()函式相同;第二個引數如果設定為0表示掛起父程序,直到子程序退出,設定為1表示不掛起父程序

            waitpid()的返回值: 如果子程序尚未結束則返回0,否則返回子程序的PID,沒有子程序了則丟擲異常OSError: [Errno 10] No child processes

os.WNOHANG  WNOHANG就是1

問題背景:

   我們這邊開發了一個動態庫給客戶用,動態庫裡面會呼叫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錯誤。歡迎指正!