<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    system函數的總結

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    最近在看APUE第10章中關于system函數的POSIX.1的實現。關于POSIX.1要求system函數忽略SIGINT和SIGQUIT,并且阻塞信號SIGCHLD的論述,理解得不是很透徹,本文就通過實際的實例來一探究竟吧。

    RFID設備管理軟件

     

    一、為什么要阻塞SIGCHLD信號

    #include <stdlib.h>

    int system(const char *command);

    函數工作大致流程:system()函數先fork一個子進程,在這個子進程中調用/bin/sh -c來執行command指定的命令。/bin/sh在系統中一般是個軟鏈接,指向dash或者bash等常用的shell,-c選項是告訴shell從字符串command中讀取要執行的命令(shell將擴展command中的任何特殊字符)。父進程則調用waitpid()函數來為變成僵尸的子進程收尸,獲得其結束狀態,然后將這個結束狀態返回給system()函數的調用者。

     

    知道了以上基本知識點,也就好理解為什么偏偏是SIGCHLD信號了,而不是其他的信號:因為fork的子進程結束后,內核會向其父進程發送SIGHLD信號,即system()函數的調用者。

    那么為什么在調用system()函數,運行command指定的命令時要阻塞SIGCHLD這個信號呢? 接下來我們就通過兩個不同的system版本對比運行的結果,從而找到阻塞SIGCHLD信號的真正原因。

     

    先來具體看看這兩個不同的system函數實現版本:

    system_without_signal.c:

     

    [cpp] view plain copy    print?在CODE上查看代碼片派生到我的代碼片
    1. #include <stdio.h>  
    2. #include <errno.h>  
    3. #include <unistd.h>  
    4. #include <stdlib.h>  
    5. #include <string.h>  
    6. #include "apue.h"  
    7.   
    8. /* version without signal handling */  
    9. int system_without_signal(const char *cmd_string)  
    10. {  
    11.     pid_t pid;  
    12.     int status = -1;  
    13.   
    14.     if (cmd_string == NULL)  
    15.         return (1);     /* always a command processor with UNIX */  
    16.   
    17.     if ((pid = fork()) < 0) {  
    18.         status = -1;    /* probably out of processes */  
    19.     } else if (pid == 0) {  /* child */  
    20.         execl("/bin/sh", "sh", "-c", cmd_string, (char *)0);  
    21.         _exit(127); /* execl error */  
    22.     } else {                /* parent */  
    23. //      sleep(1);  
    24.         pid_t wait_pid;  
    25.         while ((wait_pid = waitpid(pid, &status, 0)) < 0) {  
    26.             printf("[in system_without_signal]: errno = %d(%s)\n",  
    27.                                         errno, strerror(errno));  
    28.             if (errno != EINTR) {  
    29.                 status = -1;    /* error other than EINTR form waitpid() */  
    30.                 break;  
    31.             }  
    32.         }  
    33.         printf("[in system_without_signal]: pid = %ld, wait_pid = %ld\n",  
    34.                                         (long)pid, (long)wait_pid);  
    35.         pr_exit("[in system_without_signal]", status);  
    36.     }  
    37.   
    38.     return (status);  
    39. }  

     

    system_with_signal.c

     

    [cpp] view plain copy    print?在CODE上查看代碼片派生到我的代碼片
    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <errno.h>  
    4. #include <string.h>  
    5. #include <unistd.h>  
    6. #include <signal.h>  
    7. #include <sys/types.h>  
    8. #include <sys/wait.h>  
    9.   
    10. /* with appropriate signal handling */  
    11. int system_with_signal(const char *cmd_string)  
    12. {  
    13.     pid_t       pid;  
    14.     int         status;  
    15.     struct      sigaction ignore, saveintr, savequit;  
    16.     sigset_t    chld_mask, save_mask;  
    17.       
    18.     if (cmd_string == NULL)  
    19.         return (1);     /* always a command processor with UNIX */  
    20.   
    21.     /* ignore signal SIGINT and SIGQUIT */  
    22.     ignore.sa_handler = SIG_IGN;  
    23.     ignore.sa_flags = 0;  
    24.     sigemptyset(&ignore.sa_mask);  
    25.     if (sigaction(SIGINT, &ignore, &saveintr) < 0)   
    26.         return (-1);  
    27.     if (sigaction(SIGQUIT, &ignore, &savequit) < 0)  
    28.         return (-1);  
    29.   
    30.     /* block SIGCHLD and save current signal mask */  
    31.     sigemptyset(&chld_mask);  
    32.     sigaddset(&chld_mask, SIGCHLD);  
    33.     if (sigprocmask(SIG_BLOCK, &chld_mask, &save_mask) < 0)  
    34.         return (-1);  
    35.   
    36.     if ((pid = fork()) < 0) {  
    37.         status = -1;    /* probably out of processes */  
    38.     } else if (pid == 0) {      /* child */  
    39.         /* restore previous signal actions & reset signal mask */  
    40.         sigaction(SIGINT, &saveintr, NULL);  
    41.         sigaction(SIGQUIT, &savequit, NULL);  
    42.         sigprocmask(SIG_SETMASK, &save_mask, (sigset_t *)NULL);  
    43.   
    44.         execl("/bin/sh", "sh", "-c", cmd_string, (char *)0);  
    45.         _exit(127);  
    46.     } else {                    /* parent */  
    47.         int wait_pid;  
    48.     //  sleep(10);  /* */  
    49.         while ((wait_pid = waitpid(pid, &status, 0)) < 0) {  
    50.             printf("[in system_with_signal]: errno = %d(%s)\n",   
    51.                                         errno, strerror(errno));  
    52.             if (errno != EINTR) {  
    53.                 status = -1;    /* error other than EINTR from waitpid() */  
    54.                 break;  
    55.             }  
    56.         }  
    57.         printf("[in system_with_signal]: pid = %ld, wait_pid = %ld\n",   
    58.                                         (long)pid, (long)wait_pid);  
    59.         pr_exit("[in system_with_signal]", status);  
    60.     }  
    61.   
    62.     /* in parent: restore previous signal action & reset signal mask */  
    63.     if (sigaction(SIGINT, &saveintr, NULL) < 0)   
    64.         return (-1);  
    65.     if (sigaction(SIGQUIT, &savequit, NULL) < 0)  
    66.         return (-1);  
    67.     if (sigprocmask(SIG_SETMASK, &save_mask, (sigset_t *)NULL) < 0)  /* */  
    68.         return (-1);  
    69.   
    70.     return (status);  
    71. }  

     

    好,接下來具體看一個例子:

     

    [cpp] view plain copy    print?在CODE上查看代碼片派生到我的代碼片
    1. #include <stdio.h>  
    2. #include <errno.h>  
    3. #include <stdlib.h>  
    4. #include <signal.h>  
    5. #include "apue.h"  
    6.   
    7. #define SETSIG(sa, sig, fun, flags) \  
    8. do {                                \  
    9.     sa.sa_handler = fun;            \  
    10.     sa.sa_flags = flags;            \  
    11.     sigemptyset(&sa.sa_mask);       \  
    12.     sigaction(sig, &sa, NULL);      \  
    13. while (0)  
    14.   
    15. extern int system_without_signal(const char *cmd_string);  
    16.   
    17. static void sig_chld(int signo)  
    18. {  
    19.     printf("\nenter SIGCHLD handler\n");  
    20.       
    21.     pid_t pid;  
    22.     int exit_status = -1;  
    23.     int errno_saved = errno;  
    24.     pid = wait(&exit_status);  
    25.     if (pid != -1) {  
    26.         printf("[in sig_chld] reaped %ld child,", (long)pid);  
    27.         pr_exit("wait: ", exit_status);  
    28.         printf("\n");  
    29.     } else {  
    30.         printf("[in sig_chld] wait error: errno = %d(%s)\n\n",   
    31.                                         errno, strerror(errno));  
    32.     }  
    33.   
    34.     errno = errno_saved;  
    35.     printf("leave SIGCHLD handler\n");  
    36. }  
    37.   
    38. int main(int argc, const char *argv[])  
    39. {  
    40.     pid_t pid;  
    41.     struct sigaction sigchld_act;  
    42.   
    43.     SETSIG(sigchld_act, SIGCHLD, sig_chld, 0);  
    44.   
    45.     int status;  
    46.     if ((status = system_without_signal("/bin/ls -l; exit 44")) < 0) {  
    47.         err_sys("system() error(status = %d): ", status);  
    48.     }  
    49.     pr_exit("system() return:", status);  
    50.   
    51.     exit(EXIT_SUCCESS);  
    52. }  

     

    在這個例子中,我們調用的是system_without_signal,即不處理信號的system實現版本,并且調用者還設置了SIGCHLD的信號處理函數。好,基于這些條件,接下來我們考慮兩種情形:

    情形1:在子進程正在運行指定程序時,或者說在子進程結束之前,父進程中的waitpid阻塞在那里。

    這種情形下,一旦子進程結束,內核會向應用程序遞送SIGCHLD信號,運行信號處理函數,在信號處理函數中調用wait系列函數,那么現在問題來了:究竟是信號處理函數中的wait系列函數還是system_without_signal中的waitpid為子進程收尸呢? 答案是未知的。因為信號本身是異步的,我們掌控不了(在我的系統中,waitpid還總能正確的獲取子進程退出狀態,而在信號處理函數中的wait卻返回-1,errno設置為ECHLD,表明沒有可收尸的子進程,見下圖。但是,在你的系統中,結果也許就是相反的噢)。所以,在這種情形下,我們得出的結論是:盡管system函數完成了其任務(正確執行了我們指定的程序),但卻有可能返回-1。很顯然,這不是我們希望發生的。

    RFID設備管理軟件

     

    情形2:在一個繁忙的系統中,很可能在調用waitpid之前子進程就已經結束了,此時內核會向父進程遞送SIGCHLD信號。

    在這種情形下,問題就更明顯了。在調用waitpid之前就已經調用了SIGCHLD信號的信號處理函數,信號處理函數中的wait函數為子進程收了尸,那么接下來的waitpid不就獲取不了子進程的退出狀態了嗎? 事實也的確如此!我們可以在waitpid之前調用加個sleep來模擬系統負荷重的情形,會發現waitpid會出錯,返回-1,errno設置為ECHLD,表明沒有可收尸的子進程,最終system函數返回-1。所以,在這種情形下,我們得出的結論是:盡管system函數完成了其任務(正確執行了我們指定的程序),但卻一直返回-1。很顯然,這也不是我們希望發生的。

     

    如果將上面例子中的system_without_signal替換成system_with_signal,那么system函數在調用fork之前就已經阻塞了SIGCHLD信號的話,那么就不會出現上述兩種情況了。因為阻塞了SIGCHLD信號,那么不管system函數創建的子進程什么時候結束,即不管SIGCHLD信號什么時候來,在沒有解除阻塞之前,是不會處理該信號的,即SIGCHLD信號是未決的。所以,無論如何,waitpid都會正確獲取子進程的退出狀態。只有在最后調用sigprocmask時,系統才會解除對SIGCHLD的阻塞。解除阻塞后,這才調用信號處理函數,不過這次信號處理函數中的wait會出錯,返回-1,errno設置為ECHLD,表明沒有可收尸的子進程。那么system函數就能正確的返回子進程的退出狀態了。

    RFID設備管理軟件

     

    看到這里,你可能會說,問題都是SIGCHLD信號處理函數中的wait惹的禍,如果去掉SIGCHLD信號處理函數中的wait函數,不就不會帶來上述的兩個問題了嗎? 我的答案是:的確可以避免上述兩個問題,即system函數可以正確的獲取子進程的退出狀態。但是這樣做還是會有問題的:我們先不管在SIGCHLD信號處理函數中不調用wait系列函數這種不正統的做法,我們在這里考慮這樣一種情形:如果信號處理函數需要運行一分鐘的時間才返回(實際編程中,信號處理函數要盡量短噢,這里只是一種極端的假設),那么system函數豈不是也要阻塞一分鐘才能返回?因為如果不阻塞SIGCHLD信號并且主進程注冊了SIGCHLD信號處理函數(未調用wait系列函數),那么就需要等主進程的信號處理函數返回后waitpid才能接受到子進程的退出狀態,也就是信號處理函數需要運行多長時間,那么system也就需要這么多時間才能返回。一個函數的運行受到外界不確定因素的影響,這種情形還是應該避免的。所以在調用system函數的時候阻塞SIGCHLD,這樣在執行期間信號被阻塞就不會調用信號處理函數了,system中的waitpid就能"及時"地獲取到子進程的狀態。-- 但是仔細想想,其實system函數還是避免不了這種情形的,因為在最后調用sigprocmask解除阻塞時(一般在sigprocmask返回之前,就至少遞送一個阻塞的信號),還是會調用信號處理函數,system依然會阻塞,唯一的不同是,這種情況下waitpid是在調用信號處理函數之前就獲取了子進程的退出狀態,避免了多線程的諸多影響。所以,在平時的編程實踐當中,信號處理函數要盡量的短,這樣才不會對其他函數造成不必要的未知影響。

     

    好,稍微總結一下:

    system函數之所以阻塞SIGCHLD,是為了保證system函數能夠正確獲取子進程的退出狀態,并返回給system的調用者。

    由此我們也可以引申出以下結論:

    如果以后要寫一個函數,函數中fork了一個子進程,并且定義的函數要得到子進程的一些信息,例如子進程的ID、子進程的終止狀態等,而該函數的調用者所注冊的SIGCHLD信號處理函數會影響這個函數獲取這些信息,因此為了避免該函數在獲取這些信息之前,由于子進程的終止觸發SIGCHLD信號而先調用信號處理函數,在fork之前應該將SIGCHLD信號阻塞,在函數正確獲取相關信息后,才對SIGCHLD信號解除阻塞。

     

    二、為什么忽略SIGINT和SIGQUIT

    關于這點,APUE的解釋已經很明白了:因為由system執行的命令可能是交互式命令(例如ed程序),以及因為system的調用者在指定的命令執行期間放棄了對程序的控制(waitpid阻塞在那里),等待該執行程序的結束,所以system的調用者就不應該接收SIGINT和SIGQUIT信號,而只由子進程接收,這也是在子進程中一開始恢復SIGINT和SIGQUIT信號的原因。其實說白了,還是因為希望獲取子進程的退出狀態不受到外界干擾。

     

    三、system函數的返回值 

    很多人不推薦使用system函數,是因為它的返回值很多人沒有弄清楚。

    (1)當參數command是NULL的時候

    在參數為NULL的情況下,system函數的返回值很簡單明了,只有0和1。返回1,表明系統的命令處理程序,即/bin/sh是可用的。相反,如果命令處理程序不可用,則返回0。我們可以通過一個簡單的實驗來驗證下這個結論:

    [cpp] view plain copy    print?在CODE上查看代碼片派生到我的代碼片
    1. <span style="font-family:Microsoft YaHei;">#include <stdio.h>  
    2. #include <stdlib.h>  
    3.   
    4. int main(int argc, const char *argv[])  
    5. {  
    6.     int ret = system(NULL);  
    7.     printf("ret = %d\n", ret);  
    8.   
    9.     return 0;  
    10. }  
    11. </span>  

     在我的系統上通過ls -l /bin/sh可以看出/bin/sh是個軟鏈接,指向/bin/dash這個SHELL,我們可以通過unlink命令先取消這個軟鏈接,會發現程序返回0,如果再次建立這個軟鏈接,則system返回1.

    RFID設備管理軟件

     

    (2)當參數command不是NULL的時候

    當參數不為NULL的時候,情況有些小復雜,根據APUE這里可以分為以下三種情況:

        (2.1)如果fork等系統調用失敗,或者waitpid函數發生除EINTR外的錯誤時,system返回-1

        這種情況下,我們沒有辦法了,只能檢測errno的值來判斷是哪個系統調用出錯以及出錯的原因!

         那么為什么要排除waitpid發生EIINTR呢? 對于這個問題,我們可以假設system函數的調用者設置了SIGUSR1信號的處理函數,那么當waitpid阻塞在那里時,向程序發送SIGUSR1信號,則waitpid會返回-1,errno被設置為EINTR。所以應該排除EINTR錯誤值,否則就獲取不到/bin/sh的退出狀態了。

        (2.2)一切致使execl失敗的情況下,system返回127

        致使execl失敗的原因應該只有兩個:/bin/sh不存在,再者就是指定的shell命令是非法的。   

    [cpp] view plain copy    print?在CODE上查看代碼片派生到我的代碼片
    1. <span style="font-family:Microsoft YaHei;">#include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include "apue.h"  
    4.   
    5. int main(int argc, const char *argv[])  
    6. {  
    7.     int ret = system("no_such_command");  
    8.     pr_exit("", ret);  
    9.   
    10.     return 0;  
    11. }  
    12. </span>  

    測試結果:

    RFID設備管理軟件

    第一次返回127是因為非法的指令,第二次卻是/bin/sh不存在導致的。
    那么現在的問題是:如果指定的指令執行成功,且指令的返回值正好也是127,那么如何分辨是什么原因呢(例如上述程序中的是system("exit 127"))? 貌似沒有辦法哦,所以我們在程序中盡量避免使用127作為返回值。

        (2.3)除此之外,system返回/bin/sh的終止狀態

              到這里,要強調的一點是:system返回的是/bin/sh的結束狀態,而不是我們指定的指令的返回狀態,盡管大部分時間它們是一樣的。因為/bin/sh也有可能異常終止,例如人為的通過kill向其發送SIGKILL,那么/bin/sh退出狀態就是9,而這跟指定的指令沒有任何關系。

         盡管有時參數command代表的指令執行過程中出了錯,但這不會影響/bin/sh的正常退出,看下面實例:

    RFID設備管理軟件

    其中的tsys請自行參考APUE。很明顯,xxx目錄是不存在的,ls執行過程中發生了錯誤,返回值為2,shell接收到的就是512(為什么是512,具體下篇文章),shell將該值轉換成2后,最后由waitpid接收到該終止狀態,即512,pr_exit打印的結果是2,正是ls返回的終止狀態。

     

    好了,通過之前的陳述我們知道system函數的返回值即shell的終止狀態,這個終止狀態是通過waitpid獲得的,那么怎么解釋這個返回值也就很明朗了 -- 使用檢查waitpid返回值的那些宏就可以了,這也正式pr_exit實現的方式(參考APUE第8章)。

     

    以上說的都是指令正常終止,那么如果是異常終止了?system函數返回值可以正確反映這種狀態嗎?我們通過實驗來驗證,先看信號SIGINT:

    RFID設備管理軟件

    再來看下信號SIGQUIT:

    RFID設備管理軟件

    可見通過system函數的返回值是不可能知道程序是異常終止的,上面的返回值之所以分別是130和131,是/bin/sh特殊處理的:當正在執行的指令是被信號終止的話,那么終止狀態是128加上這個信號的編碼。

    這里提醒一下讀者,如果你照著APUE的實驗操作,即直接在終端鍵入Ctrl+C和Ctrl+\的話,你的結果可能與作者的是不一樣的。我的結果就與作者的不一樣:

    RFID設備管理軟件

    你的系統上的結果也許和我的也不一樣的,原因是不同的shell對信號的處理方式是不一樣的,APUE作者使用的shell對SIGINT和SIGQUI的處理應該都是忽略,從我上面的結果可以看出,dash忽略信號SIGQUIT。未完待續!

    參考鏈接:

    http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=2078496

    http://blog.chinaunix.net/uid-24774106-id-3048281.html?page=3

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全