1. 程式人生 > >system函式 fork函式

system函式 fork函式

system()函式功能強大,我對linux中的實現比較瞭解,具體分析這個,windows中的類似就不詳解了。

好了,先看linux版system函式的原始碼: 程式碼:

#include #include #include #include

int system(const char * cmdstring) {   pid_t pid;   int status;

  if(cmdstring == NULL){              return (1);   }

  if((pid = fork())<0){

        status = -1;   }   else if(pid == 0){     execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);     -exit(127); //子程序正常執行則不會執行此語句     }   else{         while(waitpid(pid, &status, 0) < 0){           if(errno != EINTER){             status = -1;             break;           }         }     }     return status; } 先分析一下原理,然後再看上面的程式碼大家估計就能看懂了:   

當system接受的命令為NULL時直接返回,否則fork出一個子程序,因為fork在兩個程序:父程序和子程序中都返回,這裡要檢查返回的 pid,fork在子程序中返回0,在父程序中返回子程序的pid,父程序使用waitpid等待子程序結束,子程序則是呼叫execl來啟動一個程式代替自己,execl("/bin/sh", "sh", "-c", cmdstring,(char*)0)是呼叫shell,這個shell的路徑是/bin/sh,後面的字串都是引數,然後子程序就變成了一個 shell程序,這個shell的引數是cmdstring,就是system接受的引數。在windows中的shell是command,想必大家很熟悉shell接受命令之後做的事了。    如果上面的你沒有看懂,那我再解釋下fork的原理:當一個程序A呼叫fork時,系統核心建立一個新的程序B,並將A的記憶體映像複製到B的程序空間中,因為A和B是一樣的,那麼他們怎麼知道自己是父程序還是子程序呢,看fork的返回值就知道,上面也說了fork在子程序中返回0,在父程序中返回子程序的pid。

windows中的情況也類似,就是execl換了個又臭又長的名字,引數名也換的看了讓人發暈的,我在MSDN中找到了原型,給大家看看:

HINSTANCE   ShellExecute(           HWND   hwnd,           LPCTSTR   lpVerb,           LPCTSTR   lpFile,           LPCTSTR   lpParameters,           LPCTSTR   lpDirectory,           INT   nShowCmd   );   

用法如下:    ShellExecute(NULL,   "open",   "c:\\a.reg",   NULL,   NULL,   SW_SHOWNORMAL);   

你也許會奇怪 ShellExecute中有個用來傳遞父程序環境變數的引數 lpDirectory,linux中的execl卻沒有,這是因為execl是編譯器的函式(在一定程度上隱藏具體系統實現),在linux中它會接著產生一個linux系統的呼叫execve, 原型見下:   int execve(const char * file,const char **argv,const char **envp);    看到這裡你就會明白為什麼system()會接受父程序的環境變數,但是用system改變環境變數後,system一返回主函式還是沒變,這就是我在 22樓反覆強調的。原因從system的實現可以看到,它是通過產生新程序實現的,從我的分析中可以看到父程序和子程序間沒有程序通訊,子程序自然改變不了父程序的環境變數。希望小菜們不要拿tc或使用tc庫的其他編譯器中的system的呼叫結果來反駁我,這不是一個概念,DOS早死翹翹了,玩 linux吧。就說到這裡了。

system(執行shell 命令) 相關函式       fork,execve,waitpid,popen 表頭檔案       #include<stdlib.h> 定義函式       int system(const char * string); 函式說明       system()會呼叫fork()產生子程序,由子程序來呼叫/bin/sh-c string來執行引數string字串所代表的命令,此命令執行完後隨即返回原呼叫的程序。在呼叫system()期間SIGCHLD 訊號會被暫時擱置,SIGINT和SIGQUIT 訊號則會被忽略。 返回值       如果system()在呼叫/bin/sh時失敗則返回127,其他失敗原因返回-1。若引數string為空指標(NULL),則返回非零值。如果 system()呼叫成功則最後會返回執行shell命令後的返回值,但是此返回值也有可能為system()呼叫/bin/sh失敗所返回的127,因此最好能再檢查errno 來確認執行成功。 附加說明       在編寫具有SUID/SGID許可權的程式時請勿使用system(),system()會繼承環境變數,通過環境變數可能會造成系統安全的問題。

system()函式的返回值如下: 成功,則返回程序的狀態值; 當sh不能執行時,返回127; 失敗返回-1; http://blog.csdn.net/yankai0219/article/details/6730121

 一、fork入門知識

     一個程序,包括程式碼、資料和分配給程序的資源。fork()函式通過系統呼叫建立一個與原來程序幾乎完全相同的程序,

也就是兩個程序可以做完全相同的事,但如果初始引數或者傳入的變數不同,兩個程序也可以做不同的事。

    一個程序呼叫fork()函式後,系統先給新的程序分配資源,例如儲存資料和程式碼的空間。然後把原來的程序的所有值都

複製到新的新程序中,只有少數值與原來的程序的值不同。相當於克隆了一個自己。

     我們來看一個例子:

  1. /* 
  2.  *  fork_test.c 
  3.  *  version 1 
  4.  *  Created on: 2010-5-29 
  5.  *      Author: wangth 
  6.  */  
  7. #include <unistd.h>  
  8. #include <stdio.h>   
  9. int main ()   
  10. {   
  11.     pid_t fpid; //fpid表示fork函式返回的值  
  12.     int count=0;  
  13.     fpid=fork();   
  14.     if (fpid < 0)   
  15.         printf("error in fork!");   
  16.     else if (fpid == 0) {  
  17.         printf("i am the child process, my process id is %d/n",getpid());   
  18.         printf("我是爹的兒子/n");//對某些人來說中文看著更直白。  
  19.         count++;  
  20.     }  
  21.     else {  
  22.         printf("i am the parent process, my process id is %d/n",getpid());   
  23.         printf("我是孩子他爹/n");  
  24.         count++;  
  25.     }  
  26.     printf("統計結果是: %d/n",count);  
  27.     return 0;  
  28. }  

執行結果是:     i am the child process, my process id is 5574     我是爹的兒子     統計結果是: 1     i am the parent process, my process id is 5573     我是孩子他爹     統計結果是: 1

    在語句fpid=fork()之前,只有一個程序在執行這段程式碼,但在這條語句之後,就變成兩個程序在執行了,這兩個程序的幾乎完全相同,

將要執行的下一條語句都是if(fpid<0)……     為什麼兩個程序的fpid不同呢,這與fork函式的特性有關。

fork呼叫的一個奇妙之處就是它僅僅被呼叫一次,卻能夠返回兩次,它可能有三種不同的返回值:     1)在父程序中,fork返回新建立子程序的程序ID;     2)在子程序中,fork返回0;     3)如果出現錯誤,fork返回一個負值;

    在fork函式執行完畢後,如果建立新程序成功,則出現兩個程序,一個是子程序,一個是父程序。在子程序中,fork函式返回0,在父程序中,

fork返回新建立子程序的程序ID。我們可以通過fork返回的值來判斷當前程序是子程序還是父程序。

    引用一位網友的話來解釋fpid的值為什麼在父子程序中不同。“其實就相當於連結串列,程序形成了連結串列,父程序的fpid(p 意味point)指向子程序的程序id,

因為子程序沒有子程序,所以其fpid為0.

fork出錯可能有兩種原因:     1)當前的程序數已經達到了系統規定的上限,這時errno的值被設定為EAGAIN。     2)系統記憶體不足,這時errno的值被設定為ENOMEM。

    建立新程序成功後,系統中出現兩個基本完全相同的程序,這兩個程序執行沒有固定的先後順序,哪個程序先執行要看系統的程序排程策略。     每個程序都有一個獨特(互不相同)的程序識別符號(process ID),可以通過getpid()函式獲得,還有一個記錄父程序pid的變數,可以通過getppid()函式獲得變數的值。 fork執行完畢後,出現兩個程序,

    有人說兩個程序的內容完全一樣啊,怎麼列印的結果不一樣啊,那是因為判斷條件的原因,上面列舉的只是程序的程式碼和指令,還有變數啊。

    執行完fork後,程序1的變數為count=0,fpid!=0(父程序)。程序2的變數為count=0,fpid=0(子程序),這兩個程序的變數都是獨立的,

存在不同的地址中,不是共用的,這點要注意。可以說,我們就是通過fpid來識別和操作父子程序的。

    還有人可能疑惑為什麼不是從#include處開始複製程式碼的,這是因為fork是把程序當前的情況拷貝一份,執行fork時,程序已經執行完了int count=0;

fork只拷貝下一個要執行的程式碼到新的程序。

二、fork進階知識

    先看一份程式碼:

  1. /* 
  2.  *  fork_test.c 
  3.  *  version 2 
  4.  *  Created on: 2010-5-29 
  5.  *      Author: wangth 
  6.  */  
  7. #include <unistd.h>  
  8. #include <stdio.h>  
  9. int main(void)  
  10. {  
  11.    int i=0;  
  12.    printf("i son/pa ppid pid  fpid/n");  
  13.    //ppid指當前程序的父程序pid  
  14.    //pid指當前程序的pid,  
  15.    //fpid指fork返回給當前程序的值  
  16.    for(i=0;i<2;i++){  
  17.        pid_t fpid=fork();  
  18.        if(fpid==0)  
  19.            printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
  20.        else  
  21.            printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
  22.    }  
  23.    return 0;  
  24. }  

    執行結果是:     i son/pa ppid pid  fpid     0 parent 2043 3224 3225     0 child  3224 3225    0     1 parent 2043 3224 3226     1 parent 3224 3225 3227     1 child     1 3227    0     1 child     1 3226    0 

    這份程式碼比較有意思,我們來認真分析一下:     第一步:在父程序中,指令執行到for迴圈中,i=0,接著執行fork,fork執行完後,系統中出現兩個程序,分別是p3224和p3225

(後面我都用pxxxx表示程序id為xxxx的程序)。可以看到父程序p3224的父程序是p2043,子程序p3225的父程序正好是p3224。我們用一個連結串列來表示這個關係:     p2043->p3224->p3225      第一次fork後,p3224(父程序)的變數為i=0,fpid=3225(fork函式在父程序中返向子程序id),程式碼內容為:

  1. for(i=0;i<2;i++){  
  2.     pid_t fpid=fork();//執行完畢,i=0,fpid=3225  
  3.     if(fpid==0)  
  4.        printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
  5.     else  
  6.        printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
  7. }  
  8. return 0;  

    p3225(子程序)的變數為i=0,fpid=0(fork函式在子程序中返回0),程式碼內容為:

  1. for(i=0;i<2;i++){  
  2.     pid_t fpid=fork();//執行完畢,i=0,fpid=0  
  3.     if(fpid==0)  
  4.        printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
  5.     else  
  6.        printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);  
  7. }  
  8. return 0;  

    所以打印出結果:     0 parent 2043 3224 3225     0 child  3224 3225    0     第二步:假設父程序p3224先執行,當進入下一個迴圈時,i=1,接著執行fork,系統中又新增一個程序p3226,對於此時的父程序,

p2043->p3224(當前程序)->p3226(被建立的子程序)。     對於子程序p3225,執行完第一次迴圈後,i=1,接著執行fork,系統中新增一個程序p3227,對於此程序,p3224->p3225(當前程序)->p3227(被建立的子程序)。

從輸出可以看到p3225原來是p3224的子程序,現在變成p3227的父程序。父子是相對的,這個大家應該容易理解。只要當前程序執行了fork,該程序就變成了父程序了,就打印出了parent。   所以打印出結果是:     1 parent 2043 3224 3226     1 parent 3224 3225 3227      第三步:第二步建立了兩個程序p3226,p3227,這兩個程序執行完printf函式後就結束了,因為這兩個程序無法進入第三次迴圈,無法fork,該執行return 0;了,其他程序也是如此。     以下是p3226,p3227打印出的結果:     1 child     1 3227    0     1 child     1 3226    0      細心的讀者可能注意到p3226,p3227的父程序難道不該是p3224和p3225嗎,怎麼會是1呢?這裡得講到程序的建立和死亡的過程,

在p3224和p3225執行完第二個迴圈後,main函式就該退出了,也即程序該死亡了,因為它已經做完所有事情了。p3224和p3225死亡後,

p3226,p3227就沒有父程序了,這在作業系統是不被允許的,所以p3226,p3227的父程序就被置為p1了,p1是永遠不會死亡的,至於為什麼,

這裡先不介紹,留到“三、fork高階知識”講。

    總結一下,這個程式執行的流程如下:

     這個程式最終產生了3個子程序,執行過6次printf()函式。     我們再來看一份程式碼:

  1. /* 
  2.  *  fork_test.c 
  3.  *  version 3 
  4.  *  Created on: 2010-5-29 
  5.  *      Author: wangth 
  6.  */  
  7. #include <unistd.h>  
  8. #include <stdio.h>  
  9. int main(void)  
  10. {  
  11.    int i=0;  
  12.    for(i=0;i<3;i++){  
  13.        pid_t fpid=fork();  
  14.        if(fpid==0)  
  15.            printf("son/n");  
  16.        else  
  17.            printf("father/n");  
  18.    }  
  19.    return 0;  
  20. }  

     它的執行結果是:     father     son     father     father     father     father     son     son     father     son     son     son     father     son      這裡就不做詳細解釋了,只做一個大概的分析。     for        i=0         1           2               father     father     father                                         son                             son       father                                         son                son       father     father                                         son                             son       father                                         son     其中每一行分別代表一個程序的執行列印結果。     總結一下規律,對於這種N次迴圈的情況,執行printf函式的次數為2*(1+2+4+……+2N-1)次,建立的子程序數為1+2+4+……+2N-1個。

(感謝gao_jiawei網友指出的錯誤,原本我的結論是“執行printf函式的次數為2*(1+2+4+……+2N)次,建立的子程序數為1+2+4+……+2N ”,這是錯的)     網上有人說N次迴圈產生2*(1+2+4+……+2N)個程序,這個說法是不對的,希望大家需要注意。

    數學推理見http://202.117.3.13/wordpress/?p=81(該博文的最後)。     同時,大家如果想測一下一個程式中到底建立了幾個子程序,最好的方法就是呼叫printf函式列印該程序的pid,也即呼叫printf("%d/n",getpid());或者通過printf("+/n");

來判斷產生了幾個程序。有人想通過呼叫printf("+");來統計建立了幾個程序,這是不妥當的。具體原因我來分析。     老規矩,大家看一下下面的程式碼:

  1. /* 
  2.  *  fork_test.c 
  3.  *  version 4 
  4.  *  Created on: 2010-5-29 
  5.  *      Author: wangth 
  6.  */  
  7. #include <unistd.h>  
  8. #include <stdio.h>  
  9. int main() {  
  10.     pid_t fpid;//fpid表示fork函式返回的值  
  11.     //printf("fork!");  
  12.     printf("fork!/n");  
  13.     fpid = fork();  
  14.     if (fpid < 0)  
  15.         printf("error in fork!");  
  16.     else if (fpid == 0)  
  17.         printf("I am the child process, my process id is %d/n", getpid());  
  18.     else  
  19.         printf("I am the parent process, my process id is %d/n", getpid());  
  20.     return 0;  
  21. }  

    執行結果如下:     fork!     I am the parent process, my process id is 3361     I am the child process, my process id is 3362      如果把語句printf("fork!/n");註釋掉,執行printf("fork!");     則新的程式的執行結果是:     fork!I am the parent process, my process id is 3298     fork!I am the child process, my process id is 3299 

    程式的唯一的區別就在於一個/n回車符號,為什麼結果會相差這麼大呢?     這就跟printf的緩衝機制有關了,printf某些內容時,作業系統僅僅是把該內容放到了stdout的緩衝佇列裡了,並沒有實際的寫到螢幕上。

但是,只要看到有/n 則會立即重新整理stdout,因此就馬上能夠列印了。     運行了printf("fork!")後,“fork!”僅僅被放到了緩衝裡,程式執行到fork時緩衝裡面的“fork!”  被子程序複製過去了。因此在子程序度stdout

緩衝裡面就也有了fork! 。所以,你最終看到的會是fork!  被printf了2次!!!!     而執行printf("fork! /n")後,“fork!”被立即列印到了螢幕上,之後fork到的子程序裡的stdout緩衝裡不會有fork! 內容。因此你看到的結果會是fork! 被printf了1次!!!!     所以說printf("+");不能正確地反應程序的數量。     大家看了這麼多可能有點疲倦吧,不過我還得貼最後一份程式碼來進一步分析fork函式。

  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. int main(int argc, char* argv[])  
  4. {  
  5.    fork();  
  6.    fork() && fork() || fork();  
  7.    fork();  
  8.    return 0;  
  9. }  

    問題是不算main這個程序自身,程式到底建立了多少個程序。     為了解答這個問題,我們先做一下弊,先用程式驗證一下,到此有多少個程序。

  1. #include <stdio.h>  
  2. int main(int argc, char* argv[])  
  3. {  
  4.    fork();  
  5.    fork() && fork() || fork();  
  6.    fork();  
  7.    printf("+/n");  
  8. }  

    答案是總共20個程序,除去main程序,還有19個程序。     我們再來仔細分析一下,為什麼是還有19個程序。     第一個fork和最後一個fork肯定是會執行的。     主要在中間3個fork上,可以畫一個圖進行描述。     這裡就需要注意&&和||運算子。     A&&B,如果A=0,就沒有必要繼續執行&&B了;A非0,就需要繼續執行&&B。     A||B,如果A非0,就沒有必要繼續執行||B了,A=0,就需要繼續執行||B。     fork()對於父程序和子程序的返回值是不同的,按照上面的A&&B和A||B的分支進行畫圖,可以得出5個分支。

    

     加上前面的fork和最後的fork,總共4*5=20個程序,除去main主程序,就是19個程序了。

三、fork高階知識

        這一塊我主要就fork函式講一下作業系統程序的建立、死亡和排程等。因為時間和精力限制,我先寫到這裡,下次找個時間我爭取把剩下的內容補齊。