!聲明: 按照Linux的習慣, 我的這篇文件也遵循GPL 協(xié)議: 你能隨意應用并修改本文件,必須發(fā)布你的修改,使其他人能獲得一份Copy,尤其是給我一份Copy! 我的mail :
bob_zhang2004@163.com | zhanglinbao@gmail.com 均可。歡迎論壇轉(zhuǎn)載! 目前有些內(nèi)容已在 www. 中進行過討論,能前往: http://www./forum/showflat.php?Cat=&Board=linuxK&Number=607800&page=0&view=&sb=&o=&fpart=&vc=1 和 http://www./forum/showflat.php?Cat=&Board=linuxK&Number=607228&page=1&view=collapsed&sb=5&o=7&fpart = 歡迎大家繼續(xù)討論,以便文件更加完善! 多謝!周末愉快! --bob 讀這份文件之前,建議先瀏覽一下 《Unix Advanced Programming》里面的signal一章和下面這份出自IBM論壇的文章:進程間通信 信號(上) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html ,和 進程間通信 信號(下) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html 該作者寫了一個系列的進程間通信的文章, 我只是希望對該篇作個補充! 因為他們都沒有從原始碼的角度分析,所以我嘗試了一下把上層應用和kernel實現(xiàn)代碼分析結(jié)合起來,這樣使用者才可能真正的理解signal的用法和原理! 目前介紹signal理論和用法書不少,缺點是只介紹其用法,非常深奧拗口,不容易理解; 而介紹kernel原始碼的書,側(cè)重于代碼分析,不講實際應用! 我就想到如果把兩者結(jié)合起來,對上層使用signal函數(shù)的用戶必然能知起所以然了,而且只要順著我的代碼注釋大概粗讀一下源碼就能理解 signal的特性和用法及你碰到的種種疑惑和不解了。 如果你對signal的特性和用法有什么疑惑的話, 如果對kernel也感興趣的話, 就能繼續(xù)讀源碼 , 把這篇文章加以補充和完善! 前提是遵守上面的聲明! 因為工作的需要,用了2天的時間周詳?shù)淖x了一下 linux kernel 2.4.24 版本的signal方面的原始碼,收獲不小, 因為以前發(fā)現(xiàn)看>的時候 ,不知道是大師的話太深奧,還是中文版太爛,有的東西就是理解不了,象吃滿頭囁住了,非常是不爽,總覺得心里不踏實??纯丛创a才真正明白什么是信號,及他的kernel流程,所以建議大家對某個系統(tǒng)調(diào)用,函數(shù)什么的,如果存在疑惑和不理解的,強烈建議讀讀源碼,粗讀也非常不錯,關(guān)鍵要由參考書領著讀,比如> 就非常不錯。 有的時候看著一個系統(tǒng)調(diào)用成堆的手冊頁,還真不如看看他的實現(xiàn)來得更快, 當然兩下對照著看就快了。 另外提醒大家 > 可不是 《Linux Advanced Programming》??!盡信書不如無書 ...... 在此通過閱讀源碼,弄清晰了5個問題,每個問題我都給出了結(jié)論,當然這些結(jié)論肯定是正確的,至少《Unix Advanced Programming》是這樣認為的, 我只是從kernel的角度是驗證他的正確性(簡單的寫了幾個測試程式,以驗證kernel的做法),而且也歸納了 一些結(jié)論,比如怎么避免 Zobie進程 等。 相信對大家會有價值,也能mail討論!或上相應的論壇! 首先總結(jié)一下:在PC linux(RHT 9.0 + kernel-2.4.24) 鍵盤產(chǎn)生的信號: Ctrl + c SIGINT(2) terminate ,以前我總想當然以為是 SIGTERM(15)! Ctrl + \ SIGQUIT(3) terminate Ctrl + z SIGTSTP(20) 掛起進程 對于一般應用: 掛起一個進程: kill(pid, SIGSTOP) 或 kill(pid,SIGTSTP) , 或 SIGTTIN , SIGTTOU 信號 恢復一個進程 kill(pid,SIGCONT); 殺死所有的符合某個名字的進程 :比如killall curl ,發(fā)送的是SIGTERM 信號 強制殺死某個進程 kill ?9 curl ,發(fā)送的是SIGKILL 信號, 在kernel中,SIGKILL和SIGSTOP是不能被忽略的 .... 剩下的大家都清晰了,這里就不羅嗦了。 子進程結(jié)束時候發(fā)給父進程的信號: SIGCHLD ,這個比較特別 , 且看下面3>的論述 Agenda : 1>不可靠的信號 2>Zombie進程(僵尸進程)和signal 3>特別的SIGCHLD 信號 4>信號和進程的關(guān)系 ,進程的需求 5>pause() 和 signal 6>關(guān)于信號的技巧 1> 不可靠的信號(linux繼承Unix的結(jié)果,考慮兼容性) , 和可靠的信號(主要就是信號能排隊處理,信號不丟失,linux自己的,但大家似乎用的不多) 什么是不可靠的信號:簡單的說,就是當你向一個進程發(fā)送 singal( 1~31,注意這里討論是 1~31 )的時候 , 當進程還沒有處理該信號(這時候叫pending,未決信號)或是正在調(diào)用信號處理函數(shù)的時候, 進程又收到了一個同樣的信號 , kernel會把第二個信號丟棄,或叫和一個信號合并,這樣的信號就是 不可靠的信號 ,具體正方面的比較權(quán)威的解釋請參考 http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html ,這篇文章對于信號理論介紹的非常周詳清晰明白, 個人認為比《Unix advanced Programming》要更好! 系統(tǒng)實現(xiàn)是這樣的: ==> kernel/signal.c int send_sig_info(int sig, struct siginfo *info, struct task_struct *t) { ............................................. /* 如果當前進程的未決信號集中已包括了這個信號,就不重新注冊后來目前的同樣的信號了, 據(jù)個例子: 給進程發(fā)了 SIGTERM 信號 , 不過kernel還沒有來得及處理(進程只有在kernel空間即將返回道用戶空間的時候, kernel才會檢測pending信號 ,然后才會調(diào)用do_signal()函數(shù)去處理) 這個時候又發(fā)了一個SIGTERM,那么第二個SIGTERM 肯定要被cut掉了。 */ if (sig pending.signal, sig)) //SIGRTMIN 是分水嶺 , 小于他的都是不可靠的信號,否則就是實時信號 goto out; //跳出了正常執(zhí)行的范圍 .................................................... } !正確的: 1~31都是不可靠的信號! SIGRTMIN ~SIGRTMAX都是可靠的信號! 以前大家有個誤區(qū): !誤區(qū)1> 以為不可靠的信號,是指 給進程發(fā)了一個信號(之前沒有發(fā)過),那么這個信號可能丟失,也就是進程收不到 這樣的理解是錯誤的, 根據(jù)上面的定義 , 應該是”一個信號發(fā)了多遍,后來的信號丟失了, 而不是第一個丟了“。 具體的原因能參照上面的代碼分析,就一目了然,還能看 《unix advanced programming 》,不過我覺得他講的都是老的Unix ,對Linux只能是參考而已! !誤區(qū)2> signal() 發(fā)送的是不可靠的信號 ,而 sigaction()發(fā)送的是可靠的信號 只要是1-31的信號,他就是不可靠的信號。 無論在注冊信號處理函數(shù)的時候用的是sigaction() ,還是signal() ,只要你發(fā)送的信號 是 1-31,那么就是不可靠的信號。中國有句俗語叫”爛泥扶不上墻“,我看放在這里挺合適! signal()和 sigaction()的差別到底在哪里呢? 通過對比一看便知: 對于signal() ,他的kernel實現(xiàn)函數(shù),也叫系統(tǒng)調(diào)用服務歷程sys_signal() ==>kernel/signal.c asmlinkage unsigned long sys_signal(int sig, __sighandler_t handler) { struct k_sigaction new_sa, old_sa; int ret; new_sa.sa.sa_handler = handler; new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK; //SA_ONESHOT:當執(zhí)行一次信號處理程式后, 馬上恢復為SIG_DFL , //SA_NOMASK : 表示在信號處理函數(shù)執(zhí)行期間,不屏蔽的當前正在處理的那個信號 ret = do_sigaction(sig, &new_sa, &old_sa); //sys_sigaction 也調(diào)用這個函數(shù) return ret ? ret : (unsigned long)old_sa.sa.sa_handler; } 而sigaction()函數(shù)的kernel實現(xiàn)是: sys_sigaction() ==>arch/i386/kernel/signal.c asmlinkage int sys_sigaction(int sig, const struct old_sigaction *act,struct old_sigaction *oact) { struct k_sigaction new_ka, old_ka; int ret; if (act) { old_sigset_t mask; if (verify_area(VERIFY_READ, act, sizeof(*act)) || __get_user(new_ka.sa.sa_handler, &act->sa_handler) || __get_user(new_ka.sa.sa_restorer, &act->sa_restorer)) return -EFAULT; __get_user(new_ka.sa.sa_flags, &act->sa_flags); __get_user(mask, &act->sa_mask); siginitset(&new_ka.sa.sa_mask, mask); } ret = do_sigaction(sig, act ? &new_ka : NULL, oact ? &old_ka : NULL);//都調(diào)的這個函數(shù) if (!ret && oact) { if (verify_area(VERIFY_WRITE, oact, sizeof(*oact)) || __put_user(old_ka.sa.sa_handler, &oact->sa_handler) || __put_user(old_ka.sa.sa_restorer, &oact->sa_restorer)) return -EFAULT; __put_user(old_ka.sa.sa_flags, &oact->sa_flags); __put_user(old_ka.sa.sa_mask.sig[0], &oact->sa_mask); } return ret; } signal()和sigaction() 都是用do_signaction()來包裝的, 都是用 struct sigaction()這個結(jié)構(gòu)體的,差別在下面標出來了 struct sigaction { __sighandler_t sa_handler; //2// typedef void (*__sighandler_t)(int); signal()和sigaction()函數(shù)都需求要戶提供信號處理函數(shù) unsigned long sa_flags; //signal()函數(shù)默認就用 SA_ONESHOT | SA_NOMASK; //sigaction()要由用戶自己指定! void (*sa_restorer)(void); //沒用了 sigset_t sa_mask; //執(zhí)行信號處理函數(shù)的時候要阻塞的信號,signal()使用默認的,就屏蔽正處理的信號,其他的不屏蔽,sigaction() 需求用戶自己指定! }; 討論時間: 讀到這里我有個疑問:sys_signal()函數(shù)明明把 sa_flags = SA_ONESHOT | SA_NOMASK; 而且在kernel執(zhí)行信號處理函數(shù)之前,他會檢查SA_ONESHOT標志 ,如果有這個標志, 就把sa_handler = SIG_DFL ,如果是這樣的話, 我們需要反復注冊某個信號的處理函數(shù)才行啊, 不過事實上,我們并沒有這樣作,而且程式運行的非常好! Kernel的signal()函數(shù)實現(xiàn)代碼如下: ==>arch/i386/kernel/signal.c static void handle_signal(unsigned long sig, struct k_sigaction *ka, siginfo_t *info, sigset_t *oldset, struct pt_regs * regs) { ........................................................... /* Set up the stack frame */ if (ka->sa.sa_flags & SA_SIGINFO) setup_rt_frame(sig, ka, info, oldset, regs); else setup_frame(sig, ka, oldset, regs); //here , 我加了debug信息, 確實執(zhí)行到這里了, if (ka->sa.sa_flags & SA_ONESHOT){ //sys_signal()函數(shù)明明設置了這個標志 //通過debug ,知道居然沒有到這里,就說明, sa_flags 根本就沒有SA_ONESHOT標志了 ,可是sys_signal() 卻又明明設置了這個標志, 而且我搜索過, 根本沒有地方,取消了 SA_ONESHOT 標志 printk(" the signal (%d) handler will reset to SIG_DFL\n",sig); ka->sa.sa_handler = SIG_DFL; //這難道還不明確嗎? if (!(ka->sa.sa_flags & SA_NODEFER)) { spin_lock_irq(¤t->sigmask_lock); sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask); sigaddset(¤t->blocked,sig); recalc_sigpending(current); spin_unlock_irq(¤t->sigmask_lock); } } 既然這樣的話 ,如果我們調(diào)用signal()就應該在信號處理函數(shù)中反復注冊自己的信號處理函數(shù)才對 , 否則無法處理下一個同樣的信號了。 比如 void signal_catch(int signo) { //信號處理函數(shù)細節(jié) //最后一行 signal(signo, signal_catch); //再注冊一遍, 否則就變成 SIG_DFL 了 。 } 對于這個問題 《Unix Advanced Programming》 也提到過,說早期的Unix 也存在這個問題, 是信號不可靠的一個原因 (見 P206) 不過實際上我們在用signal()函數(shù)的時候 , 我們似乎并不必這么作 ,比如一個簡單的測試程式。 為了測試, 我寫了一個最簡單的例子: void sigterm_handler(int signo) { printf("Have caught sig N.O. %d\n",signo); //按照kernel代碼,應該還要有signal(signo,sigterm_handler); 才對呀 ,但事實上,我們大家都知道沒有必要這樣用 ,為什么呢? 請前往論壇討論: http://www./forum/showflat.php?Cat=&Board=linuxK&Number=607961&page=0&view=collapsed&sb=5&o=7&fpart=&vc=1&PHPSESSID = } int main(void) { printf("-------------111111111111111-------------\n"); signal(SIGTERM,sigterm_handler); pause(); printf("----------222222222222222----------------\n"); pause();//如果按照kernel代碼里面寫的, 當再發(fā)一個SIGTERM信號的時候 , sa_handler 就編程SIG_DFL 了,那默認就是 //terminate ,所以不會打出來 333333333333333333 了, printf("-------------3333333333333333----------\n"); return 0; } 不過執(zhí)行結(jié)果確實: 333333333333333333333333 也打出來了, 這就又說明signal函數(shù) ,不必反復注冊信號處理函數(shù) , 這不就矛盾嗎? 所以目前問題就是 if (ka->sa.sa_flags & SA_ONESHOT){ ka->sa.sa_handler = SIG_DFL; 是在什么情況下 改動了 sigaction->sa_flags (去掉了 SA_ONESHOT 標志呢?)我在代碼里面搜索不到??! 如果感興趣的朋友能前往論壇討論: http://www./forum/showflat.php?Cat=&Board=linuxK&Number=607949&page=0&view=collapsed&sb=5&o=7&fpart=&vc=1 2> 僵尸進程:也叫Zombie進程: 僵尸進程定義:進程結(jié)束后,該進程的父進程沒有調(diào)用wait或waitpid()對子進程進行回收 , 子進程一直是Zombie狀態(tài)。 關(guān)于kernel怎么殺死Zombie 請看 kernel/exit.c ==>sys_wait4() 函數(shù) , waitpid 就是sys_wait4()實現(xiàn)的。 首先看看正確的編程方法: 當一個進程fork()出一個子進程的時候 ,正確的情況下,父進程應該回收進程的資源:通過下面兩個辦法中的一個即可避免Zombie(僵尸進程): 父進程顯式的忽略SIGCHLD 信號 只要在fork一個子進程之前加上這么 一行: signal(SIGCHLD, SIG_IGN); //這樣肯定不會出現(xiàn)僵尸進程, 為什么呢? 看kernel的代碼吧: ==>asm/i386/signal.c ==>do_signal() ka = ¤t->sig->action[signr-1];//¤t->sig : signal_struct if (ka->sa.sa_handler == SIG_IGN) { if (signr != SIGCHLD) continue; //對于信號處理方式是 SIG_IGN , 非SIGCHLD的信號 ,kernel什么也不作! SIGCHLD 比較特別??! /* Check for SIGCHLD: it’s special. 類似調(diào)用waitpid()來回收child process的進程表項 */ //SIG_CHLD 信號的行為設置為SIG_IGN , 由內(nèi)核來處理僵死進程。 //如果你的程式中沒有特別的需求需要處理SIGCHLD , 為了避免僵尸進程(Zombie進程),你能顯式的忽略他,kernel會調(diào)用sys_wait4()來處理僵尸進程的),他執(zhí)行一個while() loop , 來處理系統(tǒng)中所有的僵尸進程,老黃牛精神??! while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0) // 看看是不是和waitpid的用法相同?。?nbsp; /* nothing */; continue; } 如果 SIGCHLD 是默認的 SIG_DFL 的話:kernel就不管了,所以肯定會有僵尸進程的! ==>asm/i386/signal.c ==>do_signal() if (ka->sa.sa_handler == SIG_DFL) { int exit_code = signr; /* Init gets no signals it doesn’t want. */ if (current->pid == 1) //誰都不能給init(1) 進程發(fā)信號, 這樣說比較準確: 發(fā)了也白發(fā),kernel不認可 continue; switch (signr) { case SIGCONT: case SIGCHLD: case SIGWINCH: case SIGURG: continue; //對于SIGCHLD 信號,kernel對他默認是忽略的, (請不要和SIG_IGN 混淆了) //所以非常明顯, kernel并沒有調(diào)用sys_wait4() 來處理僵尸進程 ,你要自己處理了,^_^ .............. } 父進程給SIGCHLD信號注冊handler(里面調(diào)用waitpid()回收child Zombie process) 比如:這樣寫: while(waitpid(-1,NULL,WNOHANG) > 0) { //自動處理所有的僵尸進程,當然你能不用while,只調(diào)用一次,看需要 : 比如父進程是個http server,就會fork()出非常多子進程 , 所以while()是有必要的。 //WNOHANG 非常關(guān)鍵,如果沒有僵死進程,就馬上返回 ,這樣while()才能結(jié)束啊 , 可是wait()就沒有這個參數(shù), 所以wait就阻塞了。 所以一般情況下,我們用waitpid還是最佳的了! ;//什么也不必作了, 能打印看看到底回收了哪些進程pid } !如果你沒有用上面所有一個辦法,就會出現(xiàn)僵尸進程。 ps ax 命令可能會顯示: 22149 tty8 S 0:00 test_pro 22150 ? Z 0:00 [test_pro ] //這就是僵尸進程 Z 就是Zombie的意思 , 你用kill -9 也無法殺掉他 。 怎么殺掉Zombie進程呢? 你能kill他的父進程就能殺掉Zombie進程。 kill -SIGTERM 22149 , 你在ps ax 看看 ,兩個進程都沒有了。 避免僵尸進程的第三種辦法 個人不推薦! 因為上面兩種方法已夠用了, 除非你更有其他的需求,比如 使子進程無法獲得控制終端,這種情況下, 就必須fork()兩次了 。 否則一般情況下,我們需要父子進程同步和通信的, 父親和兒子交流尚且比較方便(用pipe最佳,配合使用select()) , 你讓爺爺和孫子通信不是比較困難嗎? 兩代人的代溝呢。。。。 當 你也能fork()兩次, 父親(比如http server,循環(huán)處理) -> 兒子進程(exit) -> 孫子進程 (處理每次的任務,正常結(jié)束,就不會成為Zombie) 下面是事例代碼: pid_t pid = 0; pid = fork(); if(pid 0) //這里可能是個Server一類的, 父親進程永遠不會結(jié)束的,是while() 循環(huán) else { //目前兒子 process 了, if(pid = fork() 0) //兒進程也結(jié)束了 exit(0);//即時殺死兒子進程 ,這樣孫子就成孤兒了,孫子進程會被init(1)領養(yǎng)的。 else { //到孫子進程了。 /* some code ………….. */ exit(0); } } 對于 原理其實非常簡單: 兒子死了, 只有孫子了, 孫子是孤兒了, 那么init(1)進程就會領養(yǎng)這個 孤兒, 同時孤兒就認為init(1)就是他的父進程,由init進程負責收尸! 3> 特別的 SIGCHLD 信號 SIGCHLD 特別在哪里呢?? 一般情況下, 子進程結(jié)束后 ,都會給父進程發(fā)送 SIGCHLD 信號 ,不過這不是絕對的 。 • 當一個父進程fork()一個子進程后, 當父進程沒有為SIGCHLD 注冊新的處理函數(shù),處理方式為SIG_DFL ,那么當子進程結(jié)束的時候, 就不會給父進程發(fā)送SIGCHLD 信號 。 從代碼的角度: 執(zhí)行到send_sig_info(),會在isgnore_signal() 函數(shù)里面做是否要發(fā)信號的判斷,結(jié)果 SIGCHLD被忽略了! • 就是普通的進程,在某個地方pause(),也不是隨便發(fā)一個信號就能喚醒他, 比如 發(fā) SIGCONT 信號(在kernel中當SIGCONT 的處理方式為SIG_DFL的時候, 他要被ignore的) ,就不能! 例子: int main(void) { pause(); printf(“I am waken up\n”); return 0; } 如果你在外部隨便發(fā)下列信號:SIGCONT , SIGWINCH , SIGCHLD SIGURG ,肯定是要被進程忽略的,并不能喚醒該進程! 如果你發(fā)SIGTERM,SIGQUIT,SIGINT等信號,沒有注冊handler , 那么默認是中止他;如果注冊了handler , 則能喚醒該進程。 且看下面的代碼分析: /* * Determine whether a signal should be posted or not. * * Signals with SIG_IGN can be ignored, except for the * special case of a SIGCHLD. * * Some signals with SIG_DFL default to a non-action. */ //定義了那些信號要被忽略! static int ignored_signal(int sig, struct task_struct *t) { /* Don’t ignore traced or blocked signals */ if ((t->ptrace & PT_PTRACED) || sigismember(&t->blocked, sig)) return 0; return signal_type(sig, t->sig) == 0; } /* * Signal type: * 0 : wake up. */ // #define SIG_DFL ((__sighandler_t)0) /* default signal handling */ #define SIG_IGN ((__sighandler_t)1) /* ignore signal */ #define SIG_ERR ((__sighandler_t)-1) /* error return from signal */ // static signal_type(int sig, struct signal_struct *signals) { unsigned long handler; //-----------------------------空信號 ignore ----------------------------- if (!signals) return 0; // handler = (unsigned long) signals->action[sig-1].sa.sa_handler; if (handler > 1) //該信號有特定的信號處理函數(shù)不能ignore ,必須wake_up () return 1; //can’t ignore // -----父進程設置SIGCHLD 的處理方式為 SIG_IGN : 子進程結(jié)束的時候不會給父進程發(fā)信號,也就無法喚醒了。 /* "Ignore" handler.. Illogical, but that has an implicit handler for SIGCHLD */ if (handler == 1) return sig == SIGCHLD;//當信號是 SIGCHLD的時候,信號不能被忽略,其他的要被活略 // --------------------------當把信號設置為SIG_DFL 時的情況--------------------- /* Default handler. Normally lethal, but.. */ switch (sig) { /* Ignored */ case SIGCONT: case SIGWINCH: case SIGCHLD: case SIGURG: return 0; //這些信號忽略干脆就忽略了 ,那你可能奇怪了?那SIGCONT 信號怎么喚醒 TASK_STOPPED狀態(tài)的進程呢? 如果你有這個疑問 ,請看 5>的討論! /* Implicit behaviour */ //can’t ignore case SIGTSTP: case SIGTTIN: case SIGTTOU: //這些信號就時要暫停進程的 return 1; //這些信號會喚醒該進程的, 程式會接著望下跑的, 最后 把進程的狀態(tài)置為 TASK_STOPPED 的。 /* Implicit actions (kill or do special stuff) */ default: //對于象SIGKILL , SIGTERM ,SIGQUIT 這樣的信號直接就默認操作, 一般就是terminate 該進程 return -1; } ?怎么在應用程式驗證上述kernel的代碼呢? 既然提到了”喚醒“ ,肯定要用上 pause(2)函數(shù)了, 且看pause(2)的manunal : DESCRIPTION The pause library function causes the invoking process (or thread) to sleep until a signal is received that either terminates it or causes it to call a signal-catching function. (也就是發(fā)的信號有對應的信號處理函數(shù),或是強行中止的哪些信號) RETURN VALUE The pause function only returns when a signal was caught and the sig- nal-catching function returned. In this case pause returns -1, and errno is set to EINTR. 上面的手冊說得非常清晰了, 對于pause過的進程, 只有發(fā)送類似 SIGKILL , SIGQUIT, SIGTERM 的信號或是 注冊了新的處理函數(shù),才能喚醒他! 另外再看一下:pause()的系統(tǒng)實現(xiàn): asmlinkage int sys_pause(void) { current->state = TASK_INTERRUPTIBLE; //設置成INTERRUPTABLE 狀態(tài),就不在CPU調(diào)度的隊列里面了 schedule();//重新調(diào)度,使當前進程即時讓出CPU, kernel從TASK_RUNNING隊列中選擇合適的進程重新運行 return -ERESTARTNOHAND; } 下面是測試的例子: #include #include #include #include #include #include #include void sig_handler(int signo) { printf("signo = %d\n",signo); if(signo == SIGCHLD) { pid_t child_pid = 0; int status = 0; printf("into singal handler\n"); //wait() fault : it will blocked if no defunced process ,so I will use waitpid(.. WNOHANG) ,it will return immidietly while( (child_pid = waitpid(-1,&status,WNOHANG))> 0) //循環(huán)回收所有的Zombie進程 printf("child_pid = %d , status = %d\n",child_pid,status); } } int main(void) { pid_t pid = 0; /* 感興趣的讀者能試試!你能試著注釋掉下面的兩個signal()函數(shù), 用這個,試著回答下面的兩個問題 struct sigaction sa,old_sa; sigaction(SIGCHLD,&sa,&old_sa); sa = old_sa; sa.sa_flags |= SA_NOCLDSTOP; //當子進程結(jié)束的時候,阻止子進程向其父進程發(fā)SIGCHLD sa.sa_handler = SIG_IGN; sigaction(SIGCHLD,&sa,NULL); */ signal(SIGCHLD,sig_handler); //避免僵尸進程 //signal(SIGCHLD,SIG_IGN); //注釋上面那行,用這行 , 再試著重新回答下面的兩個問題 pid = fork(); if(pid printf("child ................... \n"); exit(0); } else { printf("parent pause() ..............\n"); pause(); //父進程會被喚醒嗎??????????????????? fflush(stdout); printf("parent process has been waken up \n"); return 0; } } 大家能思考一下? (1) 子進程會成為孤兒進程嗎? (2) 父進程會被喚醒嗎? 如果你看明白了上面的kernel代碼 ,你就非常快明白了答案了:(上面沒有被注釋的代碼的運行結(jié)果 ,注釋的部分,讀者自己能驗證試著讀原始碼解釋程式行為! (1)答:不會,正常結(jié)束 ,因為有聲明 :signal(SIGCHLD,SIG_IGN); //避免僵尸進程 (2)答:(至少在linux-2.4.24上會,我在linux-2.4.20-8上試了一下,就不會喚醒,代碼肯定不同了), 因為子進程結(jié)束后, 會給parent進程發(fā)送一個SIGCHLD 信號, 此信號會喚醒 parent 進程! 有關(guān)這方面的討論能訪問論壇頁: http://www./forum/showflat.php?Cat=&Board=linuxK&Number=607949&page=0&view=collapsed&sb=5&o=7&fpart=&vc=1 4> 在給一個進程發(fā)送信號的過程中, 只要目標進程(遲早要成為當前running的進程)沒有block該信號, kernel都會調(diào)用 wake_up_process() 函數(shù)來喚醒他 , 為什么呢? 因為 只有當前活動進程才會handle signal ,過程是這樣的: 當一個進程被喚醒后, 他肯定處于kernel空間 , 在他即將返回道用戶空間的時候, 開始檢測 task_struct->sigpending ,如果為1 就說明該進程收到了信號(目前這個信號叫pending信號,只要有pending 信號, sigpending 就是等于1 ) ,開始調(diào)用do_signal() 函數(shù)來處理 , 也就是重要的一點 , 只有當前活動進程才能處理信號(類似中斷 , 當一個進程收到一個信號后, 就active了, 至于該信號怎么處理,是 "kernel處理信號的任務“ 。 具體說明如下: 關(guān)于這個函數(shù)我覺得也也值得注意! 1> 當向一個進程發(fā)送 SIGCONT信號時候, 如果進程本身更有一些類似SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等會使進程停止的信號, 就要把他們刪掉 2>如果想要停止某個進程的話, 就一定要刪除SIGCONT信號(這個肯定,否則kernel在處理的時候, 進程優(yōu)先處理SIGCONT信號,然后再處理這4個 ,那就多余了,沒有必要。既然要停止, 就直接停止,忽略之前的SIGCONT操作) static void handle_stop_signal(int sig, struct task_struct *t) { switch (sig) { case SIGKILL: case SIGCONT: /* Wake up the process if stopped. */ if (t->state == TASK_STOPPED) wake_up_process(t); t->exit_code = 0; rm_sig_from_queue(SIGSTOP, t); //刪除這些未決信號 rm_sig_from_queue(SIGTSTP, t); rm_sig_from_queue(SIGTTOU, t); rm_sig_from_queue(SIGTTIN, t); break; case SIGSTOP: case SIGTSTP://因為這些信號排在SIGCONT信號的后面,如果不刪除隊列中的SIGCONT信號, 在do_singal()會先執(zhí)行SIGCONT的操作的,這樣就多次一舉了。Note:我看代碼里面是 kernel在檢測信號的時候, 先處理sigset_t類型值中前面的bit對應的信號 case SIGTTIN: case SIGTTOU: /* If we’re stopping again, cancel SIGCONT */ rm_sig_from_queue(SIGCONT, t); break; } } 5> ? 對于3>提到的特別的SIGCHLD 信號, 我們提到了到底哪些信號要被忽略。那么對于善于思考你, 不知道你是否有此疑問: (1)對于一個普通的進程發(fā)SIGCONT 信號肯定是要被kernel忽略的; (2)不過一般的上層熟悉signal用法的R&D都知道 SIGCONT信號是SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU的后繼信號, 是專門用來把掛起的進程恢復running的,根據(jù)上面的結(jié)論SIGCONT不是也要被忽略的嗎???? 那進程又怎么可能恢復執(zhí)行呢? 答案:要回答這個問題就要弄清晰kernel在發(fā)一個signal的流程: 1. 判斷信號是否是bad的, 參考 kernel/signal.c ==>bad_signal() 2. 處理狀態(tài)是TASK_STOPPED的進程, 如果是 , 就調(diào)用wake_up_process() , 參考 kernel/signal.c==>handle_stop_signal() 函數(shù) 3. 判斷哪些信號該忽略,參考 kernel/signal.c==>ignore_signal() 4. 最后 調(diào)用 deliver_signal() 正是發(fā)送信號(其實發(fā)送信號,說白了,就是修改task_struct 相應的數(shù)據(jù)成員),發(fā)送完信號成功后,如果進程是處于TASK_INTERRUPTABLE 狀態(tài)的(且信號沒有被阻塞), 就喚醒他。 到此為止,信號就算正式發(fā)送完畢了。 所以,你目前你就知道答案了: 雖然SIGCONT 信號要在 3.被忽略,可是2. 卻能被執(zhí)行, 進程被變?yōu)門ASK_RUNNING 狀態(tài)了(!TASK_RUNNING狀態(tài)的進程有非常多,不過同一個時間,占用CPU的就只有一個) 那為什么要必須把 TASK_STOPPED 狀態(tài)的進程 變?yōu)?TASK_RUNNING 呢? 也許你能從 4>中得到答案! 6> !關(guān)于阻塞信號注意事項 1. 阻塞信號非常有用, 比如你運行某個程式, 比如upgrade 程式, 再比如更新BIOS , 都是不允許忽然斷電和忽然停止的,否則會發(fā)生災難性的后果!至少在代碼某個部分,是不能停止的,這個時候 必須阻塞一些信號(比如SIGTERM,SIGQUIT,SIGINT等等)。阻塞信號后, 在適當?shù)臅r機要對該信號解除阻塞!以處理被阻塞信號! 利用信號阻塞能實現(xiàn)上面的功能,你能在關(guān)鍵的不可停止的代碼上面加 sigprocmask(&block_set)函數(shù)阻塞一個信號集,這樣當這部分關(guān)鍵代碼執(zhí)行的時候,阻塞block_set集合里面的信號, 這些信號只有被解除阻塞的時候,才能被處理! 當關(guān)鍵代碼執(zhí)行完畢后, 你再 調(diào)用suspend(&zero_set) 來允許所有的信號, 為的就是處理剛才被阻塞的信號,比如如果剛才發(fā)了SIGQUIT信號,目前就能處理SIGQUIT信號了,最后(如果你的程式還沒有中止,要繼續(xù)執(zhí)行其他操作的話)調(diào)用sigprocmask(SIG_SETMASK,&old_set,NULL); 來恢復原來的信號掩碼集合,使你的程式繼續(xù)running下去! 下面是例子程式:例子程式我寫了周詳?shù)牟僮髡f明,讀者能清晰了解sigsuspend()的用法 程式說明: 在執(zhí)行critical code的過程中,不允許SIGQUIT信號,你能隨意copy使用! #include #include #include void sig_int(int signo) { printf("int:signo %d\n",signo); } void sig_quit(int signo) { printf("quit:signo %d\n",signo); } int main(void) { sigset_t newmask,oldmask,zeromask; signal(SIGINT,sig_int); //only test signal(SIGQUIT,sig_quit);//only test sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask,SIGQUIT); printf("just sigprocmask , blocked newmask \n"); sigprocmask(SIG_BLOCK,&newmask,&oldmask ); /*critical code begin */ printf("only testing ,please send SIGQUIT signal to test,it will be blocked \n"); pause();//在這里停住僅僅用于測試, 否則程式馬上掠過這里, 你將沒有機會測試 SIGQUIT信號了! //實際使用的時候,你不能用pause() /*critical code over */ //deal these blocked signal printf("allow all signals, and deal all blocked signals \n"); sigsuspend(&zeromask); printf("haha , SIGQUIT has been deal \n"); printf("will restore the signal mask \n"); sigprocmask(SIG_SETMASK,&oldmask,NULL); printf("has restored ,please testing with SIGQUIT \n");//這個時候,沒有阻塞SIGQUIT信號! pause(); //這里也僅僅用于測試 ,你能發(fā)SIGQUIT信號試試! printf("haha , testing over ,this is suspend () usage \n"); exit(0); } 上面演示了sigsuspend()和sigprocmask的用法, 通常情況下,這兩個函數(shù)都是配合使用的,其他的例子能參考《Unix Advanced Programming 》P229 2.?如果我阻塞了一個信號, 不過當我恢復信號阻塞掩碼的時候, 我并不想處理該信號我該怎么辦呢?比如SIGQUIT信號,如果我不另加處理, 他會中止程式的,天哪?那怎么行? 這個時候需要用到sigpending()這個函數(shù)了, 當你的關(guān)鍵的code 段,結(jié)束后, 你能利用sigpending()來檢查某個信號是否在pending中,然后設置這個信號的處理方式, 比如,如果你想刪除這個信號, 直接忽略他 。 等恢復了原來的信號阻塞掩碼后,再恢復該信號的處理方式! 還是據(jù)個例子吧: #include #include #include int main(void) { sigset_t block_set,save_set; sigset_t pending_set; sigemptyset(&pending_set); sigemptyset(&block_set); sigaddset(&block_set,SIGQUIT); sigprocmask(SIG_BLOCK,&block_set,&save_set); /* your key code segment ,can’t be interrupt by SIGQUIT code */ printf("Please send SIGQUIT signal , by Ctrl+ \\ \n"); sleep(6); //僅僅是為了測試,這個時候 ,你能發(fā)SIGQUIT信號,實際應用中,這個肯定不要的! printf("key code has been end !\n"); printf("you can unblock those signals\n"); //如果你發(fā)了SIGQUIT 信號, 這時候, 肯定在pending里面了 sigpending(&pending_set); if(sigismember(&pending_set,SIGQUIT)) //測試SIGQUIT是否在pending隊列中 { printf("yes , SIGQUIT is pending signal\n"); signal(SIGQUIT,SIG_IGN); //暫時改動一下SIGQUIT的行為,稍后再改回去! } // sigsuspend(&zero_set); //來處理其他的剛才被阻塞的信號,不過不處理SIGQUIT信號 sigprocmask(SIG_SETMASK,&save_set,NULL); signal(SIGQUIT,SIG_DFL); //復原SIGQUIT的信號部署方式 printf("yet pause() , please ctrl+\\ to test SIGQUIT’s handler \n"); pause(); //僅僅測試,SIGQUIT 是否還被阻塞 return 0; } 3.另外兩個比較有用的函數(shù):sigwaitinfo(), sigtimedwait(), 專門等待某種信號的到來,sigtimedwait()能在有限的時間內(nèi)等待某個信號集!而且sigwaitinfo() | sigtimedwait()也是經(jīng)常和sigprocmask配合使用, 當然只用sigwaitinfo()系列也能!而且功能也相同!為什么這樣?請man 2 sigwaitinfo,你一定能找到答案!,不過如果配合sigprocmask功能會更多一些!比如你想要哪些信號到來(sigwaitinfo()),但又不想要哪些信號(sigpromask) 具體的復雜的可應用的例子能參考8>中的父子進程通信的例子。 使用注意事項: sigwaitinfo(wait_set)等待set信號集中的信號的到來, 如果在沒有等到信號集中的信號,或收到了一個不在set集合中的信號,該函數(shù)就會返回-1,在返回-1之前會處理這個不速之客(或默認處理,或調(diào)用信號處理函數(shù))。 這里舉個簡單的例子;也加進了sigsuspend()函數(shù),來處理增經(jīng)阻塞過的信號 請注意block_set和wait_set的差別 #include #include #include /* * 該程式主要是說明并演示 sigwaitinfo()和sigsuspend()和sigprocmask()的用法 * 用法和說明 sigwaitinfo僅僅等待set里面的信號, 當收到這個信號后, 僅僅返回signal值和信號的信息siginfo_t, 不執(zhí)行信號處理函數(shù)(如果注冊了信號處理函數(shù)的話) sigsuspend()相當于 先sigprocmask() ,然后再pause() , 不過sigsupend()是個原子操作 • 程式功能: 程式運行過程中wait SIGINT信號, 在調(diào)用sigwaitinfo之前阻塞了 SIGINT 和SIGQUIT信號, 如果sigwaitinfo()阻塞的時候, 發(fā)SIGQUIT信號,必然要被阻塞, 程式依舊睡眠, 當發(fā)送SIGINT信號的時候, 也要被阻塞, 不過sigwaitinfo一旦檢測到pending 信號中有SIGINT,就即時返回 。 程式繼續(xù)執(zhí)行, 不過我們要處理剛才發(fā)送的被阻塞的SIGQUIT信號, 所以調(diào)用suspend(&zero_set)來處理所有被阻塞的信號 , 所以調(diào)用SIGQUIT的信號處理函數(shù), 然后從suspend()返回, 程式繼續(xù)執(zhí)行!調(diào)用sigprocmask()來恢復原來的信號掩碼集。 --bob */ void sig_quit(int signo) { printf("======================SIGQUIT=======================\n"); } int main(void) { sigset_t wait_set,block_set,old_set,zero_set; siginfo_t info; int recv_signo = 0; signal(SIGQUIT,sig_quit); //你能捕捉SIGQUIT信號,隨你的便,如果你不想被SIGQUIT退出的話。 sigemptyset(&zero_set); sigemptyset(&wait_set); sigaddset(&wait_set,SIGINT); block_set = wait_set; sigaddset(&block_set,SIGQUIT); //阻塞了block_set集合里面的信號, 比wait_set多阻塞了一個SIGQUIT信號!,讀者能在這里加上你想阻塞的信號,這些信號可能會對正常的流程不利! sigprocmask(SIG_BLOCK,&block_set,&old_set); recv_signo = sigwaitinfo(&wait_set,&info); printf("--debug recv signo = %d\n",info.si_signo); printf(“recv signo = %d\n”,recv_signo); //確實收到了想要的信號時,結(jié)果和上面相同, 收到了 wait_set意外的信號時, 就返回 -1 ,所以用這個判斷是否出錯,比較好! /*----this is error ,為什么錯,please see 《unix advanced programming》P229-230 */ //sigprocmask(SIG_SETMASK,&old_set,NULL);//restore the signal mask //pause(); /* ----------------------------------------------------------------------------*/ sigsuspend(&zero_set);//臨時允許所有的信號,為的處理剛才有可能被阻塞的信號! /* ---------------目前信號掩碼集合已被恢復設置了, 你能再試試 SIGQUIT 信號了。這個時候就能處理 SIGQUIT信號了。 * ---------------雖然下面兩行看起來和上面注釋的認為的錯誤行相同,可是他的用途確實孑然不同 ,你看出來了嗎? */ sigprocmask(SIG_SETMASK,&old_set,NULL);//restore the signal mask pause(); return 0; } ?對于上面的程式讀者不知道發(fā)現(xiàn)一個bug沒有? 就是如果這個時候在等SIGINT的時候, 發(fā)生了別的信號比如SIGQUIT,SIGTERM什么的,程式該怎么運行呢? 答案: 不妙, 本來時要等待SIGINT 信號的,不過沒有等到,由于收到其他的信號可能會異常退出, 這怎么能行呢? 所以當收到了其他的信號的時候, 一定要判斷errno == EINTR 改進如下: …………………………………………………………….. again: sigprocmask(SIG_BLOCK,&block_set,&old_set); recv_signo = sigwaitinfo(&wait_set,&info); If(recv_signo == -1) { //那就出錯了,不過 -1 ,包含了兩種情況 EINTR 和 EAGAIN If(errno == EINTR) //表示被別的信號給中斷了 { goto again; //重新調(diào)用sigwaitinfo() 繼續(xù)等待 else if(errno == EAGAIN); //如果你用的時sigtimedwait()函數(shù),這個也許有用,表示在規(guī)定的timeout時間無法獲得資源, errno== EAGAIN ; //do nothing ,直接退出即可(當你用sigtimedwait()的時候) } else { //收到我們想要的信號 printf("--debug recv signo = %d\n",info.si_signo); printf(“recv signo = %d\n”,recv_signo); //確實收到了想要的信號時,結(jié)果和上面相同, 收到了 wait_set意外的信號時, 就返回 -1 ,所以用這個判斷是否出錯,比較好! } !我們通過一個循環(huán)來判斷該sigwaitinfo()系統(tǒng)調(diào)用是否是被信號中斷的, 如果是就繼續(xù)循環(huán)重新調(diào)用sigwaitinfo()來阻塞并等待期待的信號的到來! 7>!不可再入函數(shù) 比如malloc(), printf(), 這些都是不可再入的函數(shù)!,使用不可再入的函數(shù)使危險的,不要用他們, 具體哪些是不可再入的,哪些使可再入的, 要查看《unix advanced programming》P209 這里指出最常見的錯誤: 在信號處理函數(shù)中: • 不能調(diào)用malloc()函數(shù)來動態(tài)分配內(nèi)存,因為主程式在被信號中斷的時候, 有可能正在調(diào)用malloc()函數(shù),也就是主程式調(diào)用malloc的時候被中斷了。 • 不能調(diào)用printf()來打印信息!當然偶爾調(diào)試也問題不大, 不過調(diào)試成功后, 要去掉printf()函數(shù)! 8> 關(guān)于信號的技巧: Q :怎么判斷一個進程是否還活著 ? A: 發(fā)一個空信號就好了, 什么?什么是空信號? 就是 0 Q:怎么暫時掛起某個進程? A: 如果你想暫時掛起某個進程的運行,以后要恢復,請用SIGSTOP信號 , 想重新執(zhí)行該進程的時候,就發(fā)SIGCONT信號。 記住當你對一個進程發(fā)SIGSTOP信號的時候, 子進程會給父進程發(fā)SIGCHLD 信號,這樣父進程會被喚醒。具體為什么請看 3> ,kernel 代碼里面已非常清晰了。 Q:我想父子進程間通信而且同步,比如: 父進程需要等待子進程執(zhí)行的結(jié)果,然后最會退出, 當然不能無限期等待,比如等 5秒鐘 , 用信號行嗎? A:你以為信號是萬能的? 不過這個功能用信號確實能實現(xiàn),而且還非常簡單。 具體的我們來分析一下: 1. 同步:這是信號的最基本的功能了, 無論你用signal()/kill() 系列還是 sigaction()/sigqueue() 系列肯定能滿足你的需求 。 2. 通信:如果你用kill(2)函數(shù)發(fā)信號, 通信肯定不可能了, 傳遞不了信息啊,接收進程只知道收到收到這個信號,不過到底發(fā)生了什么事情一概不知!這也是kill(2)的局限了! 你能用sigqueue()函數(shù) ,里面有項參數(shù)就是用來傳遞數(shù)據(jù)的 ,內(nèi)核里面有個結(jié)構(gòu)叫 siginfo_t ,就是干這個用的。具體請看 http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html (進程間通信 信號(下),里面舉了個例子) 3. 父進程有限時間等待子進程,用sigtimedwait(const sigset_t, siginfo_t *info, const struct timespec timeout) ,不過僅限于等信號,可不是等別的什么! 舉個非常有實用價值的例子:比如有個downloader (libcurl),我想在主程式中調(diào)用curl執(zhí)行下載, 然后主程式等待curl的下載結(jié)果,能只等5秒鐘, 如果5秒鐘還是沒有收到信號, 說明curl一直在執(zhí)行! 主進程fork()出一個子進程 ,子進程去作真正的事情。 為了方便理解,用子進程運行curl下載文件 ,根據(jù)文件的大小和網(wǎng)速,下載時間會有非常大不同。 下面分三種情況: file比較小, 子進程(curl進程)非??旆祷兀螺d完畢 file比較大, 子進程(curl進程)過20分鐘返回,下載才完畢。父進程剛開始timeout時間就結(jié)束了。 URL出錯, 子進程馬上返回一個出錯碼! 父進程能根據(jù)這三種情況update某個 database,來記錄這次的下載狀態(tài)!并及時的反饋給前端的UI 下面是我寫的程式, 非常高興被你隨意copy! 思路1: 用signal來實現(xiàn), 非常簡單和直觀!(對比下面的pipe做法) 父進程需要在有限的時間內(nèi)等待子進程發(fā)送SIGUSR2信號, 而子進程結(jié)束時候,也要報告自己的exit code ,不過這個時候父進程已結(jié)束了,子進程被init(1)領養(yǎng) ,所以就不用報告狀態(tài)了,直接被init(1)回收! 下面是代碼: #include #include #include void sig_usr2(int signo,siginfo_t *info,void *myact) { printf("signo = %d\n",signo); } int main(void) { sigset_t wait_set; int sig_no; const struct timespec tv = {5,0}; //timeout siginfo_t sig_info ;//傳遞的信息結(jié)構(gòu) pid_t pid = 0; sigemptyset(&wait_set); sigaddset(&wait_set,SIGUSR2); signal(SIGCHLD,SIG_IGN); //父進程顯式的忽略子進程發(fā)來的SIGCHLD信號 ,防止出現(xiàn)Zombie 進程,如果忘記了,復習一下上面! pid = fork(); if(pid http://www./pub/linux/kernel/v2.4/linux-2.4.24.tar.gz &>/dev/null"); printf("rc = %d\n",rc); rc_val.sival_int = rc/255; //傳遞整型值 if(getppid() > 1) { //因為如果過了timeout , 父進程就退出了, 該子進程被init(1)領養(yǎng), 所以千萬不要向init(1)發(fā)信號!否則整個系統(tǒng)都要reboot 了! sigqueue(getppid(),SIGUSR2,rc_val);//給父進程發(fā)信號!,getppid()能獲得父進程的pid } exit(0); } //parent process sigprocmask(&wait_set); sig_no = sigtimedwait(&wait_set,&sig_info,&tv); //父進程會一直阻塞tv時間,然后就返回 if(sig_no == -1) { //說明超時 printf("child process timeout \n"); printf(“curl 一直沒有返回,這樣下載看起來沒有問題, 正在下載\n”); } else { printf("child process return value = %d\n",sig_info.si_int); if(sig_info.si_int == 0) //信號傳遞的信息! 我們在這里傳遞integer值! printf(“下載完畢\n”); else printf(“下載出錯,錯誤代碼 %d\n”, sig_info.si_int); } return 0; } 為了比較, 我原來寫過一個用 無名管道/select 來實現(xiàn) 上述功能的, 不過你需要了解pipe和select的用法,需要的知識點比較多。 你能隨意copy我的代碼, 思路2:用pipe實現(xiàn)父子進程通信,再配合select()在timeout時間內(nèi)監(jiān)視管道讀管道) 父進程創(chuàng)建了一個無名管道, 子進程在管道寫端寫入value , 父進程通過select()函數(shù)檢測管道的讀端,如果5秒鐘內(nèi)讀端無反應,說明超時,否則就能讀value! 這樣就是簡單的實現(xiàn)父子進程同步,通信,且有限時間等待的需求! pid_t pid = 0; int fd[2]; //pipe operation : unsigned char share_buffer[3]; //share info between parent process and child process fd_set read_fds; int fd_max ; /* for select */ struct timeval tv; int select_rc = 0; if(pipe(fd) setsid(); /* generate a daemon process * setsid()是創(chuàng)建daemon的關(guān)鍵函數(shù),(1)成為session的leader process , *(2)成為進程組的leader process ,(3)沒有終端 */ umask(0); /* 當創(chuàng)建文件的時候和目錄的時候 默認是 umask(022) , * umask()函數(shù)能改便創(chuàng)建文件時候的默認許可權(quán)位 , 據(jù)個例子,當你用root權(quán)限 * 創(chuàng)建一個文件 , > bob.txt ,你會發(fā)現(xiàn): ll bob.txt , 顯示 -rw-r--r-- , * 這就是umask(022)的作用 , 022 對應的二進制: 000 010 010 ,表示 對于 * 組內(nèi)用戶和其他的用戶 不可有w的權(quán)限。 w位置1 就表示不能w !以此類推! close(0); //關(guān)閉標準輸入 close(1); //關(guān)閉標準輸出 close(2); //關(guān)閉標準錯誤輸出 chdir(“/”); close(fd[0]); //把管道的讀 一端 關(guān)閉 ,只留寫 一端 即可 //執(zhí)行你的程式 ,你的code 。。。 //在exit(0)之前,通知你的父進程你的執(zhí)行結(jié)果, rc就是執(zhí)行結(jié)果 buffer[0] = rc; buffer[1] = ’\0’; buffer[2] = ’\0’; write(fd[1],buffer,sizeof(buffer)); close(fd[1]); //end of write to the "write pipe" ,must close it exit(0); } //子進程結(jié)束! //父進程內(nèi) close(fd[1]); //關(guān)閉寫端 ,只要留著讀端即可! FD_ZERO(&read_fds); //clear the read_fds FD_SET(fd[0], &read_fds); tv.tv_sec = 5 //假設父進程就等待5s fd_max = fd[0]+1; //select 不熟悉select()的朋友能到google搜索他的用法,一定要掌控! select_rc = select(fd_max,&read_fds,NULL,NULL,&tv); if(!select_rc) //wait超時 , //你的處理code , 也許這正是你期待的呢! else { read(fd[0],share_buffer,sizeof(share_buffer)); jprintf("read successfully\n"); jprintf("in father :buffer[0] = %d\n",share_buffer[0]); jprintf("in father :buffer[1] = %d\n",share_buffer[1]); jprintf("in father :buffer[2] = %d\n",share_buffer[2]); close(fd[0]); //close read pipe , 讀完畢后記著關(guān)閉他! //你能根據(jù)buffer讀出的內(nèi)容作進一步的處理 //你的code } ……………………………….. 附錄: 參考資料: 1. 《linux內(nèi)核原始碼情景分析》上冊 ,對kernel代碼分析的周詳完全,不回避難點,非常多kernel代碼分析的書沒有講到的知識,他基本上都講到了,而且周詳透徹,值得一讀! 2. 《深入理解linux 內(nèi)核》,雖然沒有《情景分析》那么詳盡細節(jié), 不過總是有畫龍點睛之筆,讓人驚嘆作者的功力之深看問題的銳利!建議先看他,看代碼的時候,再看情景分析! 3. 《Unix環(huán)境高級編程》,是每個linux編程者必備的手冊。 4. 進程間通信 信號(上) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html 絕對的好文章 5. 進程間通信 信號(下) http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html 同一個人寫的 6. signal(),signaction(),sigwaitinfo(),sigtimedwait()等其他函數(shù)的manual 文件完 作者:英文名bob ,職業(yè):Embeded Linux程式員,研發(fā)方向NAS和linux 客人呢了 , birth : 1980/3/28 mail : bob_zhang2004@163.com 或 zhanglinbao@gmail.com 均可 |
|