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()函式後,系統先給新的程序分配資源,例如儲存資料和程式碼的空間。然後把原來的程序的所有值都
複製到新的新程序中,只有少數值與原來的程序的值不同。相當於克隆了一個自己。
我們來看一個例子:
- /*
- * fork_test.c
- * version 1
- * Created on: 2010-5-29
- * Author: wangth
- */
- #include <unistd.h>
- #include <stdio.h>
- int main ()
- {
- pid_t fpid; //fpid表示fork函式返回的值
- int count=0;
- fpid=fork();
- if (fpid < 0)
- printf("error in fork!");
- else if (fpid == 0) {
- printf("i am the child process, my process id is %d/n",getpid());
- printf("我是爹的兒子/n");//對某些人來說中文看著更直白。
- count++;
- }
- else {
- printf("i am the parent process, my process id is %d/n",getpid());
- printf("我是孩子他爹/n");
- count++;
- }
- printf("統計結果是: %d/n",count);
- return 0;
- }
執行結果是: 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進階知識
先看一份程式碼:
- /*
- * fork_test.c
- * version 2
- * Created on: 2010-5-29
- * Author: wangth
- */
- #include <unistd.h>
- #include <stdio.h>
- int main(void)
- {
- int i=0;
- printf("i son/pa ppid pid fpid/n");
- //ppid指當前程序的父程序pid
- //pid指當前程序的pid,
- //fpid指fork返回給當前程序的值
- for(i=0;i<2;i++){
- pid_t fpid=fork();
- if(fpid==0)
- printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid);
- else
- printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
- }
- return 0;
- }
執行結果是: 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),程式碼內容為:
- for(i=0;i<2;i++){
- pid_t fpid=fork();//執行完畢,i=0,fpid=3225
- if(fpid==0)
- printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid);
- else
- printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
- }
- return 0;
p3225(子程序)的變數為i=0,fpid=0(fork函式在子程序中返回0),程式碼內容為:
- for(i=0;i<2;i++){
- pid_t fpid=fork();//執行完畢,i=0,fpid=0
- if(fpid==0)
- printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid);
- else
- printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
- }
- 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()函式。 我們再來看一份程式碼:
- /*
- * fork_test.c
- * version 3
- * Created on: 2010-5-29
- * Author: wangth
- */
- #include <unistd.h>
- #include <stdio.h>
- int main(void)
- {
- int i=0;
- for(i=0;i<3;i++){
- pid_t fpid=fork();
- if(fpid==0)
- printf("son/n");
- else
- printf("father/n");
- }
- return 0;
- }
它的執行結果是: 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("+");來統計建立了幾個程序,這是不妥當的。具體原因我來分析。 老規矩,大家看一下下面的程式碼:
- /*
- * fork_test.c
- * version 4
- * Created on: 2010-5-29
- * Author: wangth
- */
- #include <unistd.h>
- #include <stdio.h>
- int main() {
- pid_t fpid;//fpid表示fork函式返回的值
- //printf("fork!");
- printf("fork!/n");
- fpid = fork();
- if (fpid < 0)
- printf("error in fork!");
- else if (fpid == 0)
- printf("I am the child process, my process id is %d/n", getpid());
- else
- printf("I am the parent process, my process id is %d/n", getpid());
- return 0;
- }
執行結果如下: 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函式。
- #include <stdio.h>
- #include <unistd.h>
- int main(int argc, char* argv[])
- {
- fork();
- fork() && fork() || fork();
- fork();
- return 0;
- }
問題是不算main這個程序自身,程式到底建立了多少個程序。 為了解答這個問題,我們先做一下弊,先用程式驗證一下,到此有多少個程序。
- #include <stdio.h>
- int main(int argc, char* argv[])
- {
- fork();
- fork() && fork() || fork();
- fork();
- printf("+/n");
- }
答案是總共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函式講一下作業系統程序的建立、死亡和排程等。因為時間和精力限制,我先寫到這裡,下次找個時間我爭取把剩下的內容補齊。