標籤:shell 編程 linux unix 核心
shell是一個管理進程和運行進程的程式,下面我們就通過類比一個shell程式這個執行個體來更好地認識認識在Linux/Unix系統中,進程的建立和結束,以及父子進程之間的一些關係。接下來先貼上原始碼的中命令的讀取部分:
numargs=0; while(numargs<MAXARGS){ printf("Arg[%d]?",numargs); if(fgets(argbuf,ARGLEN,stdin)&&*argbuf!='\n') arglist[numargs++]=makestring(argbuf); else{ if(numargs>0){ arglist[numargs]=NULL; execute(arglist); numargs=0; } } }
這一段代碼用於讀取使用者輸入的命令,儲存在arglist這個字元指標的數組中。因為進程間的通訊的參數類型為字串,所以我們選擇指向字串的指標構成的數組作為傳遞的參數,並且注意要將最後一個指標置NULL。當命令讀取完畢完畢之後,即調用execute函數並且將arglist數組傳遞給它,進行關於子進程的一些操作。接下來,我們來看看execute函數具體的實現情況。
execute(char *arglist[]){ int pid,exitstatus; pid=fork();//建立子進程 switch(pid){ case -1: perror("fork failed"); exit(1); case 0: execvp(arglist[0],arglist);//替換子進程 perror("execvp failed"); exit(1); default: while(wait(&exitstatus)!=pid);//父進程等待 printf("child exited with status %d,%d\n",exitstatus>>8,exitstatus&0377); }}
在execute函數中最先調用fork()函數,那fork()函數做了些什麼呢?其實fork()函數建立了和當前進程基本一模一樣的一個子進程。當控制轉到核心中的fork代碼之後,核心先分配新的記憶體塊和核心資料結構,然後將原來的進程複製到新的進程中去。最後向運行進程中添加新的進程並且控制重新返回到進程中。開始可能覺得挺奇怪的,搞個基本差不多的進程幹什嗎?其實看了後面的內容你就會知道,只要調用個execvp函數,子進程就變得完全不一樣啦。好,現在我們就有兩個進程了,並且它們的代碼相同,都運行到
pid=fork();//建立子進程
這一步,那我們怎麼判斷哪個是父進程,哪個是子進程呢?其實在父進程中fork函數的返回值是子進程的進程ID,而在子進程中,fork返回的是0,所以我們通過fork的返回值就能判斷父子進程了。下面進入switch部分,若fork返回-1,說明建立子進程失敗,若是在子進程中,則調用execvp函數(其實execvp不是系統調用,而是一個庫函數,它通過調用execve來調用核心服務)來執行指明的程式。那我們就來看看execvp這個函數幹了些什嗎?
result=execvp(const char*file,const char*argv[])
其中第一個參數指明了要執行的進程,如:“ls”,"ps"等等命令,而第二個參數則為指向要執行的命令及相關參數的字串指標。通過調用execvp我們就能在一個進程中,執行像"ls"這樣另外一個進程了。但是有一個問題需要注意,那就是execvp會清除當前進程,並載入由file指定的進程。也就是說,比如當"ls"執行完之後,execvp下面的那句perror是不會執行的,因為它早就被“ls”的代碼替換掉了。這其實也就是我們為什麼要建立子進程的原因。如果在父進程中調用execvp的話,我們做的這個shell程式就只能調用一條命令了。
那我們就要想了,父進程這個時候在幹嘛呢?其實在fork之後,父子進程是並存執行的,而我們想要的效果是父進程先等等,等子進程結束之後再繼續執行。接下來的wait函數就滿足了我們的願望啦!
pid=wait(&status)
wait函數主要做兩件事,首先wait暫停調用它的進程直到子進程結束,然後wait通過status取得子進程結束時傳給exit的值。wait返回結束進程的PID,如果進程沒有子進程或沒有得到終止狀態值,則返回-1。
這樣通過不斷地建立子進程,用想要執行的程式代替子進程並且讓父進程等待,最後執行完畢,回到父進程,我們也就類比了一個shell程式啦。最後來說說,結束進程的函數exit。exit的話,它會先重新整理所有的流,調用一些函數,執行當前系統定義的其他和exit相關的操作。最後,調用_exit這個核心操作,來進行釋放記憶體,關閉相關檔案這些善後工作。
就這樣,通過幾個函數的調用,我們就幫一個進程走過了它短暫的一生。其實仔細想想也不是那麼複雜嘛,哢哢哢~
參考文獻:《Understanding Unix/Linux Programming ----A Guide to Theory and Practice》
類比shell ( 進程函數:fork(),execvp(),wait() )