乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      從kernel原始碼的角度分析signal的錯(cuò)誤用法和注意事項(xiàng)(zt)

       kiadjb 2011-01-29
      !聲明: 按照Linux的習(xí)慣, 我的這篇文件也遵循GPL 協(xié)議: 你能隨意應(yīng)用并修改本文件,必須發(fā)布你的修改,使其他人能獲得一份Copy,尤其是給我一份Copy! 我的mail :
      bob_zhang2004@163.com
      |
      zhanglinbao@gmail.com
      均可。歡迎論壇轉(zhuǎn)載! 目前有些內(nèi)容已在
      www.
      中進(jìn)行過討論,能前往:
      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論壇的文章:進(jìn)程間通信 信號(hào)(上)
      http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html
        ,和 進(jìn)程間通信 信號(hào)(下)
      http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html
      該作者寫了一個(gè)系列的進(jìn)程間通信的文章, 我只是希望對(duì)該篇作個(gè)補(bǔ)充!
      因?yàn)樗麄兌紱]有從原始碼的角度分析,所以我嘗試了一下把上層應(yīng)用和kernel實(shí)現(xiàn)代碼分析結(jié)合起來,這樣使用者才可能真正的理解signal的用法和原理!
      目前介紹signal理論和用法書不少,缺點(diǎn)是只介紹其用法,非常深?yuàn)W拗口,不容易理解;  而介紹kernel原始碼的書,側(cè)重于代碼分析,不講實(shí)際應(yīng)用!
      我就想到如果把兩者結(jié)合起來,對(duì)上層使用signal函數(shù)的用戶必然能知起所以然了,而且只要順著我的代碼注釋大概粗讀一下源碼就能理解 signal的特性和用法及你碰到的種種疑惑和不解了。
      如果你對(duì)signal的特性和用法有什么疑惑的話, 如果對(duì)kernel也感興趣的話, 就能繼續(xù)讀源碼 , 把這篇文章加以補(bǔ)充和完善!  前提是遵守上面的聲明!

      因?yàn)楣ぷ鞯男枰昧?天的時(shí)間周詳?shù)淖x了一下 linux kernel 2.4.24 版本的signal方面的原始碼,收獲不小, 因?yàn)橐郧鞍l(fā)現(xiàn)看>的時(shí)候 ,不知道是大師的話太深?yuàn)W,還是中文版太爛,有的東西就是理解不了,象吃滿頭囁住了,非常是不爽,總覺得心里不踏實(shí)??纯丛创a才真正明白什么是信號(hào),及他的kernel流程,所以建議大家對(duì)某個(gè)系統(tǒng)調(diào)用,函數(shù)什么的,如果存在疑惑和不理解的,強(qiáng)烈建議讀讀源碼,粗讀也非常不錯(cuò),關(guān)鍵要由參考書領(lǐng)著讀,比如> 就非常不錯(cuò)。
      有的時(shí)候看著一個(gè)系統(tǒng)調(diào)用成堆的手冊(cè)頁,還真不如看看他的實(shí)現(xiàn)來得更快, 當(dāng)然兩下對(duì)照著看就快了。   
      另外提醒大家 > 可不是 《Linux Advanced Programming》?。”M信書不如無書 ......
      在此通過閱讀源碼,弄清晰了5個(gè)問題,每個(gè)問題我都給出了結(jié)論,當(dāng)然這些結(jié)論肯定是正確的,至少《Unix Advanced Programming》是這樣認(rèn)為的, 我只是從kernel的角度是驗(yàn)證他的正確性(簡(jiǎn)單的寫了幾個(gè)測(cè)試程式,以驗(yàn)證kernel的做法),而且也歸納了 一些結(jié)論,比如怎么避免 Zobie進(jìn)程 等。  相信對(duì)大家會(huì)有價(jià)值,也能mail討論!或上相應(yīng)的論壇!
      首先總結(jié)一下:在PC linux(RHT 9.0 + kernel-2.4.24) 鍵盤產(chǎn)生的信號(hào):
      Ctrl + c     SIGINT(2) terminate ,以前我總想當(dāng)然以為是 SIGTERM(15)!
      Ctrl + \ SIGQUIT(3) terminate
      Ctrl + z SIGTSTP(20) 掛起進(jìn)程
      對(duì)于一般應(yīng)用:
      掛起一個(gè)進(jìn)程: kill(pid, SIGSTOP)   或 kill(pid,SIGTSTP) , 或 SIGTTIN , SIGTTOU 信號(hào)
      恢復(fù)一個(gè)進(jìn)程  kill(pid,SIGCONT);  
      殺死所有的符合某個(gè)名字的進(jìn)程 :比如killall  curl ,發(fā)送的是SIGTERM 信號(hào)
      強(qiáng)制殺死某個(gè)進(jìn)程 kill ?9 curl  ,發(fā)送的是SIGKILL 信號(hào), 在kernel中,SIGKILL和SIGSTOP是不能被忽略的
      ....
      剩下的大家都清晰了,這里就不羅嗦了。
      子進(jìn)程結(jié)束時(shí)候發(fā)給父進(jìn)程的信號(hào):   SIGCHLD ,這個(gè)比較特別 , 且看下面3>的論述
      Agenda :
      1>不可靠的信號(hào)
      2>Zombie進(jìn)程(僵尸進(jìn)程)和signal
      3>特別的SIGCHLD 信號(hào)
      4>信號(hào)和進(jìn)程的關(guān)系 ,進(jìn)程的需求
      5>pause() 和 signal
      6>關(guān)于信號(hào)的技巧
      1> 不可靠的信號(hào)(linux繼承Unix的結(jié)果,考慮兼容性) ,  和可靠的信號(hào)(主要就是信號(hào)能排隊(duì)處理,信號(hào)不丟失,linux自己的,但大家似乎用的不多)
      什么是不可靠的信號(hào):簡(jiǎn)單的說,就是當(dāng)你向一個(gè)進(jìn)程發(fā)送 singal( 1~31,注意這里討論是 1~31 )的時(shí)候 , 當(dāng)進(jìn)程還沒有處理該信號(hào)(這時(shí)候叫pending,未決信號(hào))或是正在調(diào)用信號(hào)處理函數(shù)的時(shí)候,  進(jìn)程又收到了一個(gè)同樣的信號(hào) , kernel會(huì)把第二個(gè)信號(hào)丟棄,或叫和一個(gè)信號(hào)合并,這樣的信號(hào)就是 不可靠的信號(hào)  ,具體正方面的比較權(quán)威的解釋請(qǐng)參考
      http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html
      ,這篇文章對(duì)于信號(hào)理論介紹的非常周詳清晰明白, 個(gè)人認(rèn)為比《Unix advanced Programming》要更好!
      系統(tǒng)實(shí)現(xiàn)是這樣的:
      ==>  kernel/signal.c  
      int send_sig_info(int sig, struct siginfo *info, struct task_struct *t)
      {
      .............................................
      /*  
        如果當(dāng)前進(jìn)程的未決信號(hào)集中已包括了這個(gè)信號(hào),就不重新注冊(cè)后來目前的同樣的信號(hào)了,  
        據(jù)個(gè)例子:  給進(jìn)程發(fā)了 SIGTERM 信號(hào) , 不過kernel還沒有來得及處理(進(jìn)程只有在kernel空間即將返回道用戶空間的時(shí)候,
        kernel才會(huì)檢測(cè)pending信號(hào) ,然后才會(huì)調(diào)用do_signal()函數(shù)去處理)
        這個(gè)時(shí)候又發(fā)了一個(gè)SIGTERM,那么第二個(gè)SIGTERM 肯定要被cut掉了。
      */
      if (sig pending.signal, sig))  //SIGRTMIN 是分水嶺 , 小于他的都是不可靠的信號(hào),否則就是實(shí)時(shí)信號(hào)
        goto out;  //跳出了正常執(zhí)行的范圍
      ....................................................
      }
      !正確的: 1~31都是不可靠的信號(hào)! SIGRTMIN ~SIGRTMAX都是可靠的信號(hào)!
      以前大家有個(gè)誤區(qū):
      !誤區(qū)1>
      以為不可靠的信號(hào),是指 給進(jìn)程發(fā)了一個(gè)信號(hào)(之前沒有發(fā)過),那么這個(gè)信號(hào)可能丟失,也就是進(jìn)程收不到
      這樣的理解是錯(cuò)誤的, 根據(jù)上面的定義 , 應(yīng)該是”一個(gè)信號(hào)發(fā)了多遍,后來的信號(hào)丟失了, 而不是第一個(gè)丟了“。
      具體的原因能參照上面的代碼分析,就一目了然,還能看 《unix advanced programming 》,不過我覺得他講的都是老的Unix ,對(duì)Linux只能是參考而已!
      !誤區(qū)2>
      signal() 發(fā)送的是不可靠的信號(hào) ,而 sigaction()發(fā)送的是可靠的信號(hào)


      只要是1-31的信號(hào),他就是不可靠的信號(hào)。 無論在注冊(cè)信號(hào)處理函數(shù)的時(shí)候用的是sigaction() ,還是signal() ,只要你發(fā)送的信號(hào) 是  1-31,那么就是不可靠的信號(hào)。中國有句俗語叫”爛泥扶不上墻“,我看放在這里挺合適!
      signal()和 sigaction()的差別到底在哪里呢?   通過對(duì)比一看便知:
         對(duì)于signal() ,他的kernel實(shí)現(xiàn)函數(shù),也叫系統(tǒng)調(diào)用服務(wù)歷程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:當(dāng)執(zhí)行一次信號(hào)處理程式后, 馬上恢復(fù)為SIG_DFL ,
          //SA_NOMASK : 表示在信號(hào)處理函數(shù)執(zhí)行期間,不屏蔽的當(dāng)前正在處理的那個(gè)信號(hào)
      ret = do_sigaction(sig, &new_sa, &old_sa);   //sys_sigaction 也調(diào)用這個(gè)函數(shù)
      return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
      }
      而sigaction()函數(shù)的kernel實(shí)現(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)的這個(gè)函數(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()這個(gè)結(jié)構(gòu)體的,差別在下面標(biāo)出來了
      struct sigaction {
      __sighandler_t sa_handler;  //2// typedef void (*__sighandler_t)(int);  signal()和sigaction()函數(shù)都需求要戶提供信號(hào)處理函數(shù)
      unsigned long sa_flags; //signal()函數(shù)默認(rèn)就用 SA_ONESHOT | SA_NOMASK;  //sigaction()要由用戶自己指定!
      void (*sa_restorer)(void); //沒用了
      sigset_t sa_mask;    //執(zhí)行信號(hào)處理函數(shù)的時(shí)候要阻塞的信號(hào),signal()使用默認(rèn)的,就屏蔽正處理的信號(hào),其他的不屏蔽,sigaction() 需求用戶自己指定!
      };
      討論時(shí)間: 讀到這里我有個(gè)疑問:sys_signal()函數(shù)明明把 sa_flags = SA_ONESHOT | SA_NOMASK; 而且在kernel執(zhí)行信號(hào)處理函數(shù)之前,他會(huì)檢查SA_ONESHOT標(biāo)志 ,如果有這個(gè)標(biāo)志,  就把sa_handler = SIG_DFL ,如果是這樣的話, 我們需要反復(fù)注冊(cè)某個(gè)信號(hào)的處理函數(shù)才行啊, 不過事實(shí)上,我們并沒有這樣作,而且程式運(yùn)行的非常好!
      Kernel的signal()函數(shù)實(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信息, 確實(shí)執(zhí)行到這里了,
      if (ka->sa.sa_flags & SA_ONESHOT){  //sys_signal()函數(shù)明明設(shè)置了這個(gè)標(biāo)志
        //通過debug ,知道居然沒有到這里,就說明, sa_flags 根本就沒有SA_ONESHOT標(biāo)志了 ,可是sys_signal() 卻又明明設(shè)置了這個(gè)標(biāo)志, 而且我搜索過, 根本沒有地方,取消了 SA_ONESHOT 標(biāo)志
        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(&current->sigmask_lock);
        sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
        sigaddset(&current->blocked,sig);
        recalc_sigpending(current);
        spin_unlock_irq(&current->sigmask_lock);
      }
      }
      既然這樣的話  ,如果我們調(diào)用signal()就應(yīng)該在信號(hào)處理函數(shù)中反復(fù)注冊(cè)自己的信號(hào)處理函數(shù)才對(duì) , 否則無法處理下一個(gè)同樣的信號(hào)了。
      比如 void signal_catch(int signo)
      {
      //信號(hào)處理函數(shù)細(xì)節(jié)
      //最后一行
      signal(signo, signal_catch); //再注冊(cè)一遍, 否則就變成  SIG_DFL 了 。
      }
      對(duì)于這個(gè)問題 《Unix Advanced Programming》 也提到過,說早期的Unix 也存在這個(gè)問題, 是信號(hào)不可靠的一個(gè)原因 (見 P206)
      不過實(shí)際上我們?cè)谟胹ignal()函數(shù)的時(shí)候 , 我們似乎并不必這么作  ,比如一個(gè)簡(jiǎn)單的測(cè)試程式。
      為了測(cè)試, 我寫了一個(gè)最簡(jiǎn)單的例子:
      void sigterm_handler(int signo)
      {
                printf("Have caught sig N.O. %d\n",signo);
                //按照kernel代碼,應(yīng)該還要有signal(signo,sigterm_handler);   才對(duì)呀 ,但事實(shí)上,我們大家都知道沒有必要這樣用 ,為什么呢? 請(qǐng)前往論壇討論:
      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代碼里面寫的, 當(dāng)再發(fā)一個(gè)SIGTERM信號(hào)的時(shí)候 , sa_handler 就編程SIG_DFL 了,那默認(rèn)就是 //terminate ,所以不會(huì)打出來 333333333333333333  了,  
             printf("-------------3333333333333333----------\n");
            
              return 0;
      }
      不過執(zhí)行結(jié)果確實(shí):  
      333333333333333333333333 也打出來了, 這就又說明signal函數(shù) ,不必反復(fù)注冊(cè)信號(hào)處理函數(shù) ,  這不就矛盾嗎?  
      所以目前問題就是
      if (ka->sa.sa_flags & SA_ONESHOT){  
        ka->sa.sa_handler = SIG_DFL;
      是在什么情況下 改動(dòng)了 sigaction->sa_flags (去掉了 SA_ONESHOT 標(biāo)志呢?)我在代碼里面搜索不到??!
      如果感興趣的朋友能前往論壇討論:
      http://www./forum/showflat.php?Cat=&Board=linuxK&Number=607949&page=0&view=collapsed&sb=5&o=7&fpart=&vc=1

      2> 僵尸進(jìn)程:也叫Zombie進(jìn)程:  
      僵尸進(jìn)程定義:進(jìn)程結(jié)束后,該進(jìn)程的父進(jìn)程沒有調(diào)用wait或waitpid()對(duì)子進(jìn)程進(jìn)行回收 , 子進(jìn)程一直是Zombie狀態(tài)。
      關(guān)于kernel怎么殺死Zombie 請(qǐng)看 kernel/exit.c ==>sys_wait4() 函數(shù) , waitpid 就是sys_wait4()實(shí)現(xiàn)的。

      首先看看正確的編程方法:
      當(dāng)一個(gè)進(jìn)程fork()出一個(gè)子進(jìn)程的時(shí)候 ,正確的情況下,父進(jìn)程應(yīng)該回收進(jìn)程的資源:通過下面兩個(gè)辦法中的一個(gè)即可避免Zombie(僵尸進(jìn)程):
       父進(jìn)程顯式的忽略SIGCHLD 信號(hào)
      只要在fork一個(gè)子進(jìn)程之前加上這么 一行:   signal(SIGCHLD, SIG_IGN);  //這樣肯定不會(huì)出現(xiàn)僵尸進(jìn)程,
      為什么呢?  看kernel的代碼吧:
      ==>asm/i386/signal.c  ==>do_signal()
        ka = &current->sig->action[signr-1];//&current->sig : signal_struct
        if (ka->sa.sa_handler == SIG_IGN) {
         if (signr != SIGCHLD)
          continue;  //對(duì)于信號(hào)處理方式是 SIG_IGN , 非SIGCHLD的信號(hào) ,kernel什么也不作! SIGCHLD 比較特別啊!
         /* Check for SIGCHLD: it’s special.  
          類似調(diào)用waitpid()來回收child process的進(jìn)程表項(xiàng)
         */
        //SIG_CHLD 信號(hào)的行為設(shè)置為SIG_IGN  , 由內(nèi)核來處理僵死進(jìn)程。
        //如果你的程式中沒有特別的需求需要處理SIGCHLD , 為了避免僵尸進(jìn)程(Zombie進(jìn)程),你能顯式的忽略他,kernel會(huì)調(diào)用sys_wait4()來處理僵尸進(jìn)程的),他執(zhí)行一個(gè)while() loop , 來處理系統(tǒng)中所有的僵尸進(jìn)程,老黃牛精神??!   
         while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)   // 看看是不是和waitpid的用法相同??!  
          /* nothing */;
         continue;
        }
      如果 SIGCHLD 是默認(rèn)的  SIG_DFL 的話:kernel就不管了,所以肯定會(huì)有僵尸進(jìn)程的!
      ==>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) 進(jìn)程發(fā)信號(hào), 這樣說比較準(zhǔn)確: 發(fā)了也白發(fā),kernel不認(rèn)可
          continue;
         switch (signr) {
         case SIGCONT: case SIGCHLD: case SIGWINCH: case SIGURG:
          continue;  //對(duì)于SIGCHLD 信號(hào),kernel對(duì)他默認(rèn)是忽略的, (請(qǐng)不要和SIG_IGN 混淆了)
            //所以非常明顯, kernel并沒有調(diào)用sys_wait4() 來處理僵尸進(jìn)程 ,你要自己處理了,^_^
        ..............
        }
       父進(jìn)程給SIGCHLD信號(hào)注冊(cè)handler(里面調(diào)用waitpid()回收child Zombie process)
      比如:這樣寫:
      while(waitpid(-1,NULL,WNOHANG) > 0)  {   //自動(dòng)處理所有的僵尸進(jìn)程,當(dāng)然你能不用while,只調(diào)用一次,看需要 : 比如父進(jìn)程是個(gè)http server,就會(huì)fork()出非常多子進(jìn)程 , 所以while()是有必要的。
      //WNOHANG 非常關(guān)鍵,如果沒有僵死進(jìn)程,就馬上返回 ,這樣while()才能結(jié)束啊 , 可是wait()就沒有這個(gè)參數(shù),  所以wait就阻塞了。 所以一般情況下,我們用waitpid還是最佳的了!
      ;//什么也不必作了, 能打印看看到底回收了哪些進(jìn)程pid
      }
      !如果你沒有用上面所有一個(gè)辦法,就會(huì)出現(xiàn)僵尸進(jìn)程。  
      ps ax 命令可能會(huì)顯示:  
      22149 tty8  S  0:00   test_pro
      22150  ?    Z  0:00    [test_pro ]   //這就是僵尸進(jìn)程  Z 就是Zombie的意思 , 你用kill -9 也無法殺掉他 。
      怎么殺掉Zombie進(jìn)程呢?  你能kill他的父進(jìn)程就能殺掉Zombie進(jìn)程。
      kill -SIGTERM 22149 ,  你在ps ax 看看  ,兩個(gè)進(jìn)程都沒有了。

      避免僵尸進(jìn)程的第三種辦法
      個(gè)人不推薦! 因?yàn)樯厦鎯煞N方法已夠用了, 除非你更有其他的需求,比如 使子進(jìn)程無法獲得控制終端,這種情況下, 就必須fork()兩次了 。 否則一般情況下,我們需要父子進(jìn)程同步和通信的, 父親和兒子交流尚且比較方便(用pipe最佳,配合使用select()) , 你讓爺爺和孫子通信不是比較困難嗎?  兩代人的代溝呢。。。。
      當(dāng)       你也能fork()兩次,  父親(比如http server,循環(huán)處理) ->  兒子進(jìn)程(exit) -> 孫子進(jìn)程 (處理每次的任務(wù),正常結(jié)束,就不會(huì)成為Zombie)  

      下面是事例代碼:   
      pid_t pid = 0;
      pid = fork();
      if(pid  0)
      //這里可能是個(gè)Server一類的, 父親進(jìn)程永遠(yuǎn)不會(huì)結(jié)束的,是while() 循環(huán)
      else {
      //目前兒子 process 了,
      if(pid = fork()  0) //兒進(jìn)程也結(jié)束了
        exit(0);//即時(shí)殺死兒子進(jìn)程 ,這樣孫子就成孤兒了,孫子進(jìn)程會(huì)被init(1)領(lǐng)養(yǎng)的。
      else { //到孫子進(jìn)程了。
        /*  some code …………..
        */
        exit(0);
         }
      }
      對(duì)于 原理其實(shí)非常簡(jiǎn)單: 兒子死了, 只有孫子了, 孫子是孤兒了, 那么init(1)進(jìn)程就會(huì)領(lǐng)養(yǎng)這個(gè) 孤兒,  同時(shí)孤兒就認(rèn)為init(1)就是他的父進(jìn)程,由init進(jìn)程負(fù)責(zé)收尸!   
      3> 特別的 SIGCHLD 信號(hào)
      SIGCHLD 特別在哪里呢?? 一般情況下, 子進(jìn)程結(jié)束后 ,都會(huì)給父進(jìn)程發(fā)送 SIGCHLD 信號(hào) ,不過這不是絕對(duì)的 。
      • 當(dāng)一個(gè)父進(jìn)程fork()一個(gè)子進(jìn)程后,  當(dāng)父進(jìn)程沒有為SIGCHLD 注冊(cè)新的處理函數(shù),處理方式為SIG_DFL  ,那么當(dāng)子進(jìn)程結(jié)束的時(shí)候, 就不會(huì)給父進(jìn)程發(fā)送SIGCHLD 信號(hào) 。
         從代碼的角度: 執(zhí)行到send_sig_info(),會(huì)在isgnore_signal() 函數(shù)里面做是否要發(fā)信號(hào)的判斷,結(jié)果 SIGCHLD被忽略了!

      • 就是普通的進(jìn)程,在某個(gè)地方pause(),也不是隨便發(fā)一個(gè)信號(hào)就能喚醒他, 比如 發(fā) SIGCONT 信號(hào)(在kernel中當(dāng)SIGCONT 的處理方式為SIG_DFL的時(shí)候, 他要被ignore的) ,就不能!
      例子:  int main(void)
      {
        pause();
        printf(“I am waken up\n”);
        return 0;
      }
      如果你在外部隨便發(fā)下列信號(hào):SIGCONT , SIGWINCH , SIGCHLD SIGURG ,肯定是要被進(jìn)程忽略的,并不能喚醒該進(jìn)程!  
      如果你發(fā)SIGTERM,SIGQUIT,SIGINT等信號(hào),沒有注冊(cè)handler , 那么默認(rèn)是中止他;如果注冊(cè)了handler , 則能喚醒該進(jìn)程。
      且看下面的代碼分析:
      /*
      * 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.
      */
      //定義了那些信號(hào)要被忽略!

      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;
      //-----------------------------空信號(hào) ignore  -----------------------------
      if (!signals)
        return 0;   //

      handler = (unsigned long) signals->action[sig-1].sa.sa_handler;
      if (handler > 1)   //該信號(hào)有特定的信號(hào)處理函數(shù)不能ignore ,必須wake_up ()
        return 1; //can’t ignore
      // -----父進(jìn)程設(shè)置SIGCHLD 的處理方式為 SIG_IGN : 子進(jìn)程結(jié)束的時(shí)候不會(huì)給父進(jìn)程發(fā)信號(hào),也就無法喚醒了。
      /* "Ignore" handler.. Illogical, but that has an implicit handler for SIGCHLD */
      if (handler == 1)   
        return sig == SIGCHLD;//當(dāng)信號(hào)是 SIGCHLD的時(shí)候,信號(hào)不能被忽略,其他的要被活略
      // --------------------------當(dāng)把信號(hào)設(shè)置為SIG_DFL 時(shí)的情況---------------------
      /* Default handler. Normally lethal, but.. */
      switch (sig) {
      /* Ignored */
      case SIGCONT: case SIGWINCH:
      case SIGCHLD: case SIGURG:
        return 0; //這些信號(hào)忽略干脆就忽略了 ,那你可能奇怪了?那SIGCONT 信號(hào)怎么喚醒 TASK_STOPPED狀態(tài)的進(jìn)程呢?  如果你有這個(gè)疑問 ,請(qǐng)看 5>的討論!
      /* Implicit behaviour */    //can’t ignore
      case SIGTSTP: case SIGTTIN: case SIGTTOU: //這些信號(hào)就時(shí)要暫停進(jìn)程的
        return 1; //這些信號(hào)會(huì)喚醒該進(jìn)程的, 程式會(huì)接著望下跑的,  最后 把進(jìn)程的狀態(tài)置為 TASK_STOPPED 的。
      /* Implicit actions (kill or do special stuff) */
      default:  //對(duì)于象SIGKILL , SIGTERM ,SIGQUIT 這樣的信號(hào)直接就默認(rèn)操作, 一般就是terminate 該進(jìn)程
        return -1;
      }
      ?怎么在應(yīng)用程式驗(yàn)證上述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ā)的信號(hào)有對(duì)應(yīng)的信號(hào)處理函數(shù),或是強(qiáng)行中止的哪些信號(hào))
      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.
      上面的手冊(cè)說得非常清晰了, 對(duì)于pause過的進(jìn)程,  只有發(fā)送類似 SIGKILL , SIGQUIT, SIGTERM 的信號(hào)或是 注冊(cè)了新的處理函數(shù),才能喚醒他!
      另外再看一下:pause()的系統(tǒng)實(shí)現(xiàn):
      asmlinkage int sys_pause(void)
      {
      current->state = TASK_INTERRUPTIBLE;  //設(shè)置成INTERRUPTABLE 狀態(tài),就不在CPU調(diào)度的隊(duì)列里面了
      schedule();//重新調(diào)度,使當(dāng)前進(jìn)程即時(shí)讓出CPU,  kernel從TASK_RUNNING隊(duì)列中選擇合適的進(jìn)程重新運(yùn)行
      return -ERESTARTNOHAND;
      }
      下面是測(cè)試的例子:  
      #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進(jìn)程
                            printf("child_pid = %d  ,  status = %d\n",child_pid,status);
              }
      }
      int main(void)
      {
              pid_t pid = 0;
      /* 感興趣的讀者能試試!你能試著注釋掉下面的兩個(gè)signal()函數(shù), 用這個(gè),試著回答下面的兩個(gè)問題
              struct sigaction sa,old_sa;
              sigaction(SIGCHLD,&sa,&old_sa);
              sa = old_sa;
              sa.sa_flags |= SA_NOCLDSTOP; //當(dāng)子進(jìn)程結(jié)束的時(shí)候,阻止子進(jìn)程向其父進(jìn)程發(fā)SIGCHLD
              sa.sa_handler = SIG_IGN;
              sigaction(SIGCHLD,&sa,NULL);
      */

        signal(SIGCHLD,sig_handler);  //避免僵尸進(jìn)程
        //signal(SIGCHLD,SIG_IGN);   //注釋上面那行,用這行 , 再試著重新回答下面的兩個(gè)問題
        pid = fork();
              if(pid
                      printf("child ................... \n");
                      exit(0);
              }
              else {
                      printf("parent pause() ..............\n");
                      pause();   //父進(jìn)程會(huì)被喚醒嗎???????????????????
                      fflush(stdout);
                      printf("parent process has been waken up \n");
                      return 0;
              }
      }
      大家能思考一下?
      (1)  子進(jìn)程會(huì)成為孤兒進(jìn)程嗎?     
      (2)  父進(jìn)程會(huì)被喚醒嗎?
      如果你看明白了上面的kernel代碼 ,你就非??烀靼琢舜鸢噶耍海ㄉ厦鏇]有被注釋的代碼的運(yùn)行結(jié)果 ,注釋的部分,讀者自己能驗(yàn)證試著讀原始碼解釋程式行為!
      (1)答:不會(huì),正常結(jié)束 ,因?yàn)橛新暶?:signal(SIGCHLD,SIG_IGN);   //避免僵尸進(jìn)程
      (2)答:(至少在linux-2.4.24上會(huì),我在linux-2.4.20-8上試了一下,就不會(huì)喚醒,代碼肯定不同了), 因?yàn)樽舆M(jìn)程結(jié)束后, 會(huì)給parent進(jìn)程發(fā)送一個(gè)SIGCHLD 信號(hào), 此信號(hào)會(huì)喚醒 parent 進(jìn)程! 有關(guān)這方面的討論能訪問論壇頁:
      http://www./forum/showflat.php?Cat=&Board=linuxK&Number=607949&page=0&view=collapsed&sb=5&o=7&fpart=&vc=1

        
      4> 在給一個(gè)進(jìn)程發(fā)送信號(hào)的過程中, 只要目標(biāo)進(jìn)程(遲早要成為當(dāng)前running的進(jìn)程)沒有block該信號(hào), kernel都會(huì)調(diào)用 wake_up_process() 函數(shù)來喚醒他 , 為什么呢?   因?yàn)?只有當(dāng)前活動(dòng)進(jìn)程才會(huì)handle signal ,過程是這樣的:  當(dāng)一個(gè)進(jìn)程被喚醒后, 他肯定處于kernel空間 , 在他即將返回道用戶空間的時(shí)候, 開始檢測(cè) task_struct->sigpending ,如果為1   就說明該進(jìn)程收到了信號(hào)(目前這個(gè)信號(hào)叫pending信號(hào),只要有pending 信號(hào), sigpending 就是等于1 ) ,開始調(diào)用do_signal() 函數(shù)來處理 , 也就是重要的一點(diǎn) , 只有當(dāng)前活動(dòng)進(jìn)程才能處理信號(hào)(類似中斷 , 當(dāng)一個(gè)進(jìn)程收到一個(gè)信號(hào)后, 就active了, 至于該信號(hào)怎么處理,是 "kernel處理信號(hào)的任務(wù)“ 。
      具體說明如下:
      關(guān)于這個(gè)函數(shù)我覺得也也值得注意!
      1> 當(dāng)向一個(gè)進(jìn)程發(fā)送 SIGCONT信號(hào)時(shí)候,
      如果進(jìn)程本身更有一些類似SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等會(huì)使進(jìn)程停止的信號(hào),
      就要把他們刪掉
      2>如果想要停止某個(gè)進(jìn)程的話,
      就一定要?jiǎng)h除SIGCONT信號(hào)(這個(gè)肯定,否則kernel在處理的時(shí)候,
      進(jìn)程優(yōu)先處理SIGCONT信號(hào),然后再處理這4個(gè) ,那就多余了,沒有必要。既然要停止,
      就直接停止,忽略之前的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); //刪除這些未決信號(hào)
        rm_sig_from_queue(SIGTSTP, t);
        rm_sig_from_queue(SIGTTOU, t);
        rm_sig_from_queue(SIGTTIN, t);
        break;
      case SIGSTOP: case SIGTSTP://因?yàn)檫@些信號(hào)排在SIGCONT信號(hào)的后面,如果不刪除隊(duì)列中的SIGCONT信號(hào), 在do_singal()會(huì)先執(zhí)行SIGCONT的操作的,這樣就多次一舉了。Note:我看代碼里面是 kernel在檢測(cè)信號(hào)的時(shí)候, 先處理sigset_t類型值中前面的bit對(duì)應(yīng)的信號(hào)
      case SIGTTIN: case SIGTTOU:
        /* If we’re stopping again, cancel SIGCONT */
        rm_sig_from_queue(SIGCONT, t);
        break;
      }
      }
        
      5> ? 對(duì)于3>提到的特別的SIGCHLD 信號(hào), 我們提到了到底哪些信號(hào)要被忽略。那么對(duì)于善于思考你, 不知道你是否有此疑問:
        (1)對(duì)于一個(gè)普通的進(jìn)程發(fā)SIGCONT 信號(hào)肯定是要被kernel忽略的;
        (2)不過一般的上層熟悉signal用法的R&D都知道 SIGCONT信號(hào)是SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU的后繼信號(hào), 是專門用來把掛起的進(jìn)程恢復(fù)running的,根據(jù)上面的結(jié)論SIGCONT不是也要被忽略的嗎???? 那進(jìn)程又怎么可能恢復(fù)執(zhí)行呢?
        
      答案:要回答這個(gè)問題就要弄清晰kernel在發(fā)一個(gè)signal的流程:  
      1. 判斷信號(hào)是否是bad的,  參考 kernel/signal.c ==>bad_signal()
      2. 處理狀態(tài)是TASK_STOPPED的進(jìn)程, 如果是 , 就調(diào)用wake_up_process() , 參考 kernel/signal.c==>handle_stop_signal() 函數(shù)
      3. 判斷哪些信號(hào)該忽略,參考 kernel/signal.c==>ignore_signal()
      4. 最后 調(diào)用 deliver_signal() 正是發(fā)送信號(hào)(其實(shí)發(fā)送信號(hào),說白了,就是修改task_struct 相應(yīng)的數(shù)據(jù)成員),發(fā)送完信號(hào)成功后,如果進(jìn)程是處于TASK_INTERRUPTABLE 狀態(tài)的(且信號(hào)沒有被阻塞), 就喚醒他。
      到此為止,信號(hào)就算正式發(fā)送完畢了。
      所以,你目前你就知道答案了:
      雖然SIGCONT 信號(hào)要在 3.被忽略,可是2. 卻能被執(zhí)行, 進(jìn)程被變?yōu)門ASK_RUNNING 狀態(tài)了(!TASK_RUNNING狀態(tài)的進(jìn)程有非常多,不過同一個(gè)時(shí)間,占用CPU的就只有一個(gè))
      那為什么要必須把 TASK_STOPPED 狀態(tài)的進(jìn)程 變?yōu)?TASK_RUNNING 呢?  也許你能從 4>中得到答案!
      6> !關(guān)于阻塞信號(hào)注意事項(xiàng)
      1. 阻塞信號(hào)非常有用, 比如你運(yùn)行某個(gè)程式, 比如upgrade 程式, 再比如更新BIOS , 都是不允許忽然斷電和忽然停止的,否則會(huì)發(fā)生災(zāi)難性的后果!至少在代碼某個(gè)部分,是不能停止的,這個(gè)時(shí)候 必須阻塞一些信號(hào)(比如SIGTERM,SIGQUIT,SIGINT等等)。阻塞信號(hào)后, 在適當(dāng)?shù)臅r(shí)機(jī)要對(duì)該信號(hào)解除阻塞!以處理被阻塞信號(hào)!
      利用信號(hào)阻塞能實(shí)現(xiàn)上面的功能,你能在關(guān)鍵的不可停止的代碼上面加 sigprocmask(&block_set)函數(shù)阻塞一個(gè)信號(hào)集,這樣當(dāng)這部分關(guān)鍵代碼執(zhí)行的時(shí)候,阻塞block_set集合里面的信號(hào), 這些信號(hào)只有被解除阻塞的時(shí)候,才能被處理! 當(dāng)關(guān)鍵代碼執(zhí)行完畢后, 你再 調(diào)用suspend(&zero_set) 來允許所有的信號(hào), 為的就是處理剛才被阻塞的信號(hào),比如如果剛才發(fā)了SIGQUIT信號(hào),目前就能處理SIGQUIT信號(hào)了,最后(如果你的程式還沒有中止,要繼續(xù)執(zhí)行其他操作的話)調(diào)用sigprocmask(SIG_SETMASK,&old_set,NULL); 來恢復(fù)原來的信號(hào)掩碼集合,使你的程式繼續(xù)running下去!
      下面是例子程式:例子程式我寫了周詳?shù)牟僮髡f明,讀者能清晰了解sigsuspend()的用法
      程式說明: 在執(zhí)行critical code的過程中,不允許SIGQUIT信號(hào),你能隨意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();//在這里停住僅僅用于測(cè)試, 否則程式馬上掠過這里, 你將沒有機(jī)會(huì)測(cè)試 SIGQUIT信號(hào)了!
      //實(shí)際使用的時(shí)候,你不能用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");//這個(gè)時(shí)候,沒有阻塞SIGQUIT信號(hào)!
      pause();   //這里也僅僅用于測(cè)試 ,你能發(fā)SIGQUIT信號(hào)試試!
      printf("haha , testing over ,this is suspend () usage \n");
      exit(0);
      }
      上面演示了sigsuspend()和sigprocmask的用法, 通常情況下,這兩個(gè)函數(shù)都是配合使用的,其他的例子能參考《Unix Advanced Programming 》P229
      2.?如果我阻塞了一個(gè)信號(hào), 不過當(dāng)我恢復(fù)信號(hào)阻塞掩碼的時(shí)候, 我并不想處理該信號(hào)我該怎么辦呢?比如SIGQUIT信號(hào),如果我不另加處理, 他會(huì)中止程式的,天哪?那怎么行?
      這個(gè)時(shí)候需要用到sigpending()這個(gè)函數(shù)了, 當(dāng)你的關(guān)鍵的code 段,結(jié)束后, 你能利用sigpending()來檢查某個(gè)信號(hào)是否在pending中,然后設(shè)置這個(gè)信號(hào)的處理方式, 比如,如果你想刪除這個(gè)信號(hào), 直接忽略他 。 等恢復(fù)了原來的信號(hào)阻塞掩碼后,再恢復(fù)該信號(hào)的處理方式!
      還是據(jù)個(gè)例子吧:
      #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);  //僅僅是為了測(cè)試,這個(gè)時(shí)候 ,你能發(fā)SIGQUIT信號(hào),實(shí)際應(yīng)用中,這個(gè)肯定不要的!
      printf("key code has been end !\n");
      printf("you can unblock those signals\n");
        //如果你發(fā)了SIGQUIT 信號(hào), 這時(shí)候, 肯定在pending里面了
        
        sigpending(&pending_set);
        if(sigismember(&pending_set,SIGQUIT)) //測(cè)試SIGQUIT是否在pending隊(duì)列中
        { printf("yes , SIGQUIT is pending signal\n");
         signal(SIGQUIT,SIG_IGN);  //暫時(shí)改動(dòng)一下SIGQUIT的行為,稍后再改回去!
        }
      // sigsuspend(&zero_set);  //來處理其他的剛才被阻塞的信號(hào),不過不處理SIGQUIT信號(hào)
        sigprocmask(SIG_SETMASK,&save_set,NULL);
        signal(SIGQUIT,SIG_DFL);  //復(fù)原SIGQUIT的信號(hào)部署方式
        printf("yet pause() , please ctrl+\\ to test SIGQUIT’s handler \n");
        pause(); //僅僅測(cè)試,SIGQUIT 是否還被阻塞
        
        return 0;
        
      }

      3.另外兩個(gè)比較有用的函數(shù):sigwaitinfo(), sigtimedwait(),   專門等待某種信號(hào)的到來,sigtimedwait()能在有限的時(shí)間內(nèi)等待某個(gè)信號(hào)集!而且sigwaitinfo() | sigtimedwait()也是經(jīng)常和sigprocmask配合使用, 當(dāng)然只用sigwaitinfo()系列也能!而且功能也相同!為什么這樣?請(qǐng)man 2 sigwaitinfo,你一定能找到答案!,不過如果配合sigprocmask功能會(huì)更多一些!比如你想要哪些信號(hào)到來(sigwaitinfo()),但又不想要哪些信號(hào)(sigpromask)
      具體的復(fù)雜的可應(yīng)用的例子能參考8>中的父子進(jìn)程通信的例子。
      使用注意事項(xiàng):
      sigwaitinfo(wait_set)等待set信號(hào)集中的信號(hào)的到來, 如果在沒有等到信號(hào)集中的信號(hào),或收到了一個(gè)不在set集合中的信號(hào),該函數(shù)就會(huì)返回-1,在返回-1之前會(huì)處理這個(gè)不速之客(或默認(rèn)處理,或調(diào)用信號(hào)處理函數(shù))。
      這里舉個(gè)簡(jiǎn)單的例子;也加進(jìn)了sigsuspend()函數(shù),來處理增經(jīng)阻塞過的信號(hào)
      請(qǐng)注意block_set和wait_set的差別
      #include
      #include
      #include
      /*
      * 該程式主要是說明并演示 sigwaitinfo()和sigsuspend()和sigprocmask()的用法
      * 用法和說明
        sigwaitinfo僅僅等待set里面的信號(hào), 當(dāng)收到這個(gè)信號(hào)后, 僅僅返回signal值和信號(hào)的信息siginfo_t, 不執(zhí)行信號(hào)處理函數(shù)(如果注冊(cè)了信號(hào)處理函數(shù)的話)
        sigsuspend()相當(dāng)于 先sigprocmask() ,然后再pause() , 不過sigsupend()是個(gè)原子操作
      • 程式功能:
      程式運(yùn)行過程中wait SIGINT信號(hào), 在調(diào)用sigwaitinfo之前阻塞了 SIGINT 和SIGQUIT信號(hào),
      如果sigwaitinfo()阻塞的時(shí)候, 發(fā)SIGQUIT信號(hào),必然要被阻塞, 程式依舊睡眠, 當(dāng)發(fā)送SIGINT信號(hào)的時(shí)候, 也要被阻塞, 不過sigwaitinfo一旦檢測(cè)到pending 信號(hào)中有SIGINT,就即時(shí)返回 。
      程式繼續(xù)執(zhí)行, 不過我們要處理剛才發(fā)送的被阻塞的SIGQUIT信號(hào), 所以調(diào)用suspend(&zero_set)來處理所有被阻塞的信號(hào) , 所以調(diào)用SIGQUIT的信號(hào)處理函數(shù), 然后從suspend()返回, 程式繼續(xù)執(zhí)行!調(diào)用sigprocmask()來恢復(fù)原來的信號(hào)掩碼集。
                 --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信號(hào),隨你的便,如果你不想被SIGQUIT退出的話。
      sigemptyset(&zero_set);
      sigemptyset(&wait_set);
      sigaddset(&wait_set,SIGINT);


      block_set = wait_set;
      sigaddset(&block_set,SIGQUIT);  //阻塞了block_set集合里面的信號(hào), 比wait_set多阻塞了一個(gè)SIGQUIT信號(hào)!,讀者能在這里加上你想阻塞的信號(hào),這些信號(hào)可能會(huì)對(duì)正常的流程不利!

      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);  //確實(shí)收到了想要的信號(hào)時(shí),結(jié)果和上面相同, 收到了 wait_set意外的信號(hào)時(shí), 就返回 -1  ,所以用這個(gè)判斷是否出錯(cuò),比較好!
      /*----this is error ,為什么錯(cuò),please see 《unix advanced programming》P229-230 */
      //sigprocmask(SIG_SETMASK,&old_set,NULL);//restore the signal mask
      //pause();
      /* ----------------------------------------------------------------------------*/
      sigsuspend(&zero_set);//臨時(shí)允許所有的信號(hào),為的處理剛才有可能被阻塞的信號(hào)!

      /* ---------------目前信號(hào)掩碼集合已被恢復(fù)設(shè)置了, 你能再試試 SIGQUIT 信號(hào)了。這個(gè)時(shí)候就能處理 SIGQUIT信號(hào)了。
      * ---------------雖然下面兩行看起來和上面注釋的認(rèn)為的錯(cuò)誤行相同,可是他的用途確實(shí)孑然不同 ,你看出來了嗎?
      */
      sigprocmask(SIG_SETMASK,&old_set,NULL);//restore the signal mask
      pause();

      return 0;
      }
      ?對(duì)于上面的程式讀者不知道發(fā)現(xiàn)一個(gè)bug沒有?  就是如果這個(gè)時(shí)候在等SIGINT的時(shí)候, 發(fā)生了別的信號(hào)比如SIGQUIT,SIGTERM什么的,程式該怎么運(yùn)行呢?  答案: 不妙, 本來時(shí)要等待SIGINT 信號(hào)的,不過沒有等到,由于收到其他的信號(hào)可能會(huì)異常退出, 這怎么能行呢? 所以當(dāng)收到了其他的信號(hào)的時(shí)候, 一定要判斷errno == EINTR
      改進(jìn)如下:
      ……………………………………………………………..
      again:
      sigprocmask(SIG_BLOCK,&block_set,&old_set);
      recv_signo = sigwaitinfo(&wait_set,&info);

      If(recv_signo == -1) {  //那就出錯(cuò)了,不過 -1 ,包含了兩種情況 EINTR 和 EAGAIN
        If(errno == EINTR)  //表示被別的信號(hào)給中斷了
        {
         goto again;  //重新調(diào)用sigwaitinfo() 繼續(xù)等待
        else if(errno == EAGAIN);   //如果你用的時(shí)sigtimedwait()函數(shù),這個(gè)也許有用,表示在規(guī)定的timeout時(shí)間無法獲得資源,  errno== EAGAIN
         ;  //do nothing ,直接退出即可(當(dāng)你用sigtimedwait()的時(shí)候)
      }
      else {  //收到我們想要的信號(hào)
        printf("--debug recv signo = %d\n",info.si_signo);
        printf(“recv signo = %d\n”,recv_signo);  //確實(shí)收到了想要的信號(hào)時(shí),結(jié)果和上面相同, 收到了 wait_set意外的信號(hào)時(shí), 就返回 -1  ,所以用這個(gè)判斷是否出錯(cuò),比較好!
      }
      !我們通過一個(gè)循環(huán)來判斷該sigwaitinfo()系統(tǒng)調(diào)用是否是被信號(hào)中斷的, 如果是就繼續(xù)循環(huán)重新調(diào)用sigwaitinfo()來阻塞并等待期待的信號(hào)的到來!

      7>!不可再入函數(shù)
      比如malloc(), printf(), 這些都是不可再入的函數(shù)!,使用不可再入的函數(shù)使危險(xiǎn)的,不要用他們, 具體哪些是不可再入的,哪些使可再入的, 要查看《unix advanced programming》P209
      這里指出最常見的錯(cuò)誤:
      在信號(hào)處理函數(shù)中:
      • 不能調(diào)用malloc()函數(shù)來動(dòng)態(tài)分配內(nèi)存,因?yàn)橹鞒淌皆诒恍盘?hào)中斷的時(shí)候, 有可能正在調(diào)用malloc()函數(shù),也就是主程式調(diào)用malloc的時(shí)候被中斷了。
      • 不能調(diào)用printf()來打印信息!當(dāng)然偶爾調(diào)試也問題不大, 不過調(diào)試成功后, 要去掉printf()函數(shù)!
      8> 關(guān)于信號(hào)的技巧:
       Q :怎么判斷一個(gè)進(jìn)程是否還活著 ?
      A: 發(fā)一個(gè)空信號(hào)就好了, 什么?什么是空信號(hào)?  就是  0
       Q:怎么暫時(shí)掛起某個(gè)進(jìn)程?
        A: 如果你想暫時(shí)掛起某個(gè)進(jìn)程的運(yùn)行,以后要恢復(fù),請(qǐng)用SIGSTOP信號(hào) , 想重新執(zhí)行該進(jìn)程的時(shí)候,就發(fā)SIGCONT信號(hào)。 記住當(dāng)你對(duì)一個(gè)進(jìn)程發(fā)SIGSTOP信號(hào)的時(shí)候,  子進(jìn)程會(huì)給父進(jìn)程發(fā)SIGCHLD 信號(hào),這樣父進(jìn)程會(huì)被喚醒。具體為什么請(qǐng)看 3> ,kernel 代碼里面已非常清晰了。
       Q:我想父子進(jìn)程間通信而且同步,比如: 父進(jìn)程需要等待子進(jìn)程執(zhí)行的結(jié)果,然后最會(huì)退出, 當(dāng)然不能無限期等待,比如等 5秒鐘 , 用信號(hào)行嗎?  
      A:你以為信號(hào)是萬能的? 不過這個(gè)功能用信號(hào)確實(shí)能實(shí)現(xiàn),而且還非常簡(jiǎn)單。
      具體的我們來分析一下:
      1. 同步:這是信號(hào)的最基本的功能了, 無論你用signal()/kill() 系列還是  sigaction()/sigqueue() 系列肯定能滿足你的需求 。
      2. 通信:如果你用kill(2)函數(shù)發(fā)信號(hào), 通信肯定不可能了, 傳遞不了信息啊,接收進(jìn)程只知道收到收到這個(gè)信號(hào),不過到底發(fā)生了什么事情一概不知!這也是kill(2)的局限了!
         你能用sigqueue()函數(shù) ,里面有項(xiàng)參數(shù)就是用來傳遞數(shù)據(jù)的 ,內(nèi)核里面有個(gè)結(jié)構(gòu)叫 siginfo_t ,就是干這個(gè)用的。具體請(qǐng)看
      http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html
      (進(jìn)程間通信 信號(hào)(下),里面舉了個(gè)例子)
      3. 父進(jìn)程有限時(shí)間等待子進(jìn)程,用sigtimedwait(const sigset_t, siginfo_t *info, const struct timespec timeout) ,不過僅限于等信號(hào),可不是等別的什么!
      舉個(gè)非常有實(shí)用價(jià)值的例子:比如有個(gè)downloader (libcurl),我想在主程式中調(diào)用curl執(zhí)行下載, 然后主程式等待curl的下載結(jié)果,能只等5秒鐘, 如果5秒鐘還是沒有收到信號(hào), 說明curl一直在執(zhí)行!
      主進(jìn)程fork()出一個(gè)子進(jìn)程 ,子進(jìn)程去作真正的事情。
      為了方便理解,用子進(jìn)程運(yùn)行curl下載文件 ,根據(jù)文件的大小和網(wǎng)速,下載時(shí)間會(huì)有非常大不同。
      下面分三種情況:
       file比較小, 子進(jìn)程(curl進(jìn)程)非??旆祷?,下載完畢
       file比較大, 子進(jìn)程(curl進(jìn)程)過20分鐘返回,下載才完畢。父進(jìn)程剛開始timeout時(shí)間就結(jié)束了。
       URL出錯(cuò), 子進(jìn)程馬上返回一個(gè)出錯(cuò)碼!
      父進(jìn)程能根據(jù)這三種情況update某個(gè) database,來記錄這次的下載狀態(tài)!并及時(shí)的反饋給前端的UI
      下面是我寫的程式, 非常高興被你隨意copy!
      思路1: 用signal來實(shí)現(xiàn), 非常簡(jiǎn)單和直觀?。▽?duì)比下面的pipe做法)
      父進(jìn)程需要在有限的時(shí)間內(nèi)等待子進(jìn)程發(fā)送SIGUSR2信號(hào), 而子進(jìn)程結(jié)束時(shí)候,也要報(bào)告自己的exit code ,不過這個(gè)時(shí)候父進(jìn)程已結(jié)束了,子進(jìn)程被init(1)領(lǐng)養(yǎng) ,所以就不用報(bào)告狀態(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); //父進(jìn)程顯式的忽略子進(jìn)程發(fā)來的SIGCHLD信號(hào) ,防止出現(xiàn)Zombie 進(jìn)程,如果忘記了,復(fù)習(xí)一下上面!
      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) { //因?yàn)槿绻^了timeout , 父進(jìn)程就退出了, 該子進(jìn)程被init(1)領(lǐng)養(yǎng), 所以千萬不要向init(1)發(fā)信號(hào)!否則整個(gè)系統(tǒng)都要reboot 了!
         sigqueue(getppid(),SIGUSR2,rc_val);//給父進(jìn)程發(fā)信號(hào)!,getppid()能獲得父進(jìn)程的pid
        }  
        exit(0);
      }
        
      //parent process
      sigprocmask(&wait_set);
      sig_no = sigtimedwait(&wait_set,&sig_info,&tv); //父進(jìn)程會(huì)一直阻塞tv時(shí)間,然后就返回
      if(sig_no == -1) {   //說明超時(shí)
        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)   //信號(hào)傳遞的信息! 我們?cè)谶@里傳遞integer值!
         printf(“下載完畢\n”);
        else
         printf(“下載出錯(cuò),錯(cuò)誤代碼 %d\n”, sig_info.si_int);
      }
      return 0;
      }

      為了比較, 我原來寫過一個(gè)用 無名管道/select 來實(shí)現(xiàn) 上述功能的, 不過你需要了解pipe和select的用法,需要的知識(shí)點(diǎn)比較多。
      你能隨意copy我的代碼,
      思路2:用pipe實(shí)現(xiàn)父子進(jìn)程通信,再配合select()在timeout時(shí)間內(nèi)監(jiān)視管道讀管道)
      父進(jìn)程創(chuàng)建了一個(gè)無名管道, 子進(jìn)程在管道寫端寫入value , 父進(jìn)程通過select()函數(shù)檢測(cè)管道的讀端,如果5秒鐘內(nèi)讀端無反應(yīng),說明超時(shí),否則就能讀value! 這樣就是簡(jiǎn)單的實(shí)現(xiàn)父子進(jìn)程同步,通信,且有限時(shí)間等待的需求!
      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)成為進(jìn)程組的leader process ,(3)沒有終端
          */
         umask(0);
         /* 當(dāng)創(chuàng)建文件的時(shí)候和目錄的時(shí)候 默認(rèn)是 umask(022) ,
          * umask()函數(shù)能改便創(chuàng)建文件時(shí)候的默認(rèn)許可權(quán)位 , 據(jù)個(gè)例子,當(dāng)你用root權(quán)限
          * 創(chuàng)建一個(gè)文件 , > bob.txt   ,你會(huì)發(fā)現(xiàn): ll bob.txt ,  顯示 -rw-r--r-- ,
          * 這就是umask(022)的作用 , 022 對(duì)應(yīng)的二進(jìn)制: 000     010 010 ,表示 對(duì)于
          * 組內(nèi)用戶和其他的用戶 不可有w的權(quán)限。   w位置1 就表示不能w !以此類推!
         close(0);   //關(guān)閉標(biāo)準(zhǔn)輸入
        close(1); //關(guān)閉標(biāo)準(zhǔn)輸出
        close(2); //關(guān)閉標(biāo)準(zhǔn)錯(cuò)誤輸出
        chdir(“/”);
        close(fd[0]); //把管道的讀 一端 關(guān)閉 ,只留寫 一端 即可
      //執(zhí)行你的程式 ,你的code
      。。。
      //在exit(0)之前,通知你的父進(jìn)程你的執(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);
      }  //子進(jìn)程結(jié)束!
      //父進(jìn)程內(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  //假設(shè)父進(jìn)程就等待5s
              fd_max = fd[0]+1;
                     
              //select 不熟悉select()的朋友能到google搜索他的用法,一定要掌控!
              select_rc = select(fd_max,&read_fds,NULL,NULL,&tv);

      if(!select_rc) //wait超時(shí) ,
        //你的處理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)容作進(jìn)一步的處理
        //你的code
      }
      ………………………………..
      附錄:
      參考資料:
      1. 《linux內(nèi)核原始碼情景分析》上冊(cè) ,對(duì)kernel代碼分析的周詳完全,不回避難點(diǎn),非常多kernel代碼分析的書沒有講到的知識(shí),他基本上都講到了,而且周詳透徹,值得一讀!
      2. 《深入理解linux 內(nèi)核》,雖然沒有《情景分析》那么詳盡細(xì)節(jié), 不過總是有畫龍點(diǎn)睛之筆,讓人驚嘆作者的功力之深看問題的銳利!建議先看他,看代碼的時(shí)候,再看情景分析!
      3. 《Unix環(huán)境高級(jí)編程》,是每個(gè)linux編程者必備的手冊(cè)。
      4. 進(jìn)程間通信 信號(hào)(上)
      http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html
      絕對(duì)的好文章
      5. 進(jìn)程間通信 信號(hào)(下)
      http://www-128.ibm.com/developerworks/cn/linux/l-ipc/part2/index2.html
      同一個(gè)人寫的
      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
        均可

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多