歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux資訊 >> 更多Linux >> 系統調用跟我學(4)

系統調用跟我學(4)

日期:2017/2/27 14:19:29   编辑:更多Linux
  進程管理相關的系統調用之三 雷鎮 ([email protected]) 這是本專欄中進程相關的系統調用的最後一篇,用2個實例演示了以往學習的內容。其一是Mini Shell,仿常用的Bash而做,但對其作了大大簡化;其二是一個Daemon程序,可以使讀者一窺服務器編程的端倪。 1.13 Shell 對Linux不是太陌生的讀者都應該對Shell有一定的了解,就是這個程序在我們登陸後自動執行,打印出一個$符號,然後等待我們輸入命令。Linux下最常用的Shell應用程序是Bash,絕大部分Linux發行版默認安裝的都是它。下面我們也來親手編寫一個Shell程序,這個Shell遠遠不如Bash復雜,但也能滿足我們一般的使用,下面,我們就開始。 首先,給這個Shell取一個名字,不妨就叫做Mini Shell。 Linux系統的命令分為內部命令和外部命令兩種,內部命令由Shell程序實現,如cd、echo等,Linux的內部命令數量有限,而且絕大部分都很少用到。而每一個Linux外部命令都是一個單獨的應用程序,我們非常熟悉的ls、cp等絕大多數命令都是外部命令,這些命令都以可執行文件的形式存在,絕大部分放在目錄/bin和/sbin中。這樣一來,我們編程的難度就可以大大下降了,我們只需要實現很有限的內部命令,對於其它的輸入,統統當作應用程序來執行即可。 為了簡單明了起見,Mini Shell只實現了2個內部命令: 1、cd 用於切換目錄,和我們熟悉的命令cd類似,除了沒有那麼多的附加功能。 2、quit 用於退出Mini Shell。 下面是程序清單: 1: /* mshell.c */ 2: #include 1: #include 3: #include 4: #include 5: #include 6: #include 7: 9: void do_cd(char *argv[]); 10: void execute_new(char *argv[]); 11: 12: main() 13: { 14: char *cmd=(void *)malloc(256*sizeof(char)); 15: char *cmd_arg[10]; 16: int cmdlen,i,j,tag; 17: 18: do{ 19: /* 初始化cmd */ 20: for(i=0;i<255;i++) cmd[i]='\0'; 21: 22: printf("-=Mini Shell=-* "); 23: fgets(cmd,256,stdin); 24: 25: cmdlen=strlen(cmd); 26: cmdlen--; 27: cmd[cmdlen]='\0'; 28: 29: /* 把命令行分解為指針數組cmd_arg */ 30: for(i=0;i<10;i++) cmd_arg[i]=NULL; 31: i=0; j=0; tag=0; 32: while(i 64: 65: /* 實現cd的功能 */ 66: void do_cd(char *argv[]) 67: { 68: if(argv[1]!=NULL){ 69: if(chdir(argv[1])<0) 70: switch(errno){ 71: case ENOENT: 72: printf("DirectorY NOT FOUND\n"); 73: break; 74: case ENOTDIR: 75: printf("NOT A DIRECTORY NAME\n"); 76: break; 77: case EACCES: 78: printf("YOU DO NOT HAVE RIGHT TO Access\n"); 79: break; 80: default: 81: printf("SOME ERROR HAPPENED IN CHDIR\n"); 82: } 83: } 84: 85: } 86: 87: /* 執行外部命令或應用程序 */ 88: void execute_new(char *argv[]) 89: { 90: pid_t pid; 91: 92: pid=fork(); 93: if(pid<0){ 94: printf("SOME ERROR HAPPENED IN FORK\n"); 95: exit(2); 96: }else if(pid==0){ 97: if(execvp(argv[0],argv)<0) 98: switch(errno){ 99: case ENOENT: 100: printf("COMMAND OR FILENAME NOT FOUND\n"); 101: break; 102: case EACCES: 103: printf("YOU DO NOT HAVE RIGHT TO ACCESS\n"); 104: break; 105: default: 106: printf("SOME ERROR HAPPENED IN EXEC\n"); 107: } 108: exit(3); 109: }else 110: wait(NULL); 111: } 這個程序稍稍有點長,我們來對它作一下詳細的解釋: 函數main: 14行:定義字符串cmd,用於接收用戶輸入的命令行。 15行:定義指針數組cmd_arg,它的形式和作用都與我們熟悉的char *argv[]一樣。 從以上2個定義可以看出Mini Shell對命令輸入的2個限制:首先,用戶輸入的命令行必須在255個字符之內(除去字符串結束標志'\0');其次,命令行的參數個數不得超過10個(包括命令本身在內)。 18行:進入一個do-while循環,這個循環是本程序的主體部分,基本思想是"等待輸入命令--處理已輸入命令--等待輸入命令"。 22行:打印輸入提示信息。在Mini Shell中,你可以隨意定自己喜歡的命令輸入提示信息,本程序中使用了"-=Mini Shell=-* ",是不是有點像一個CS高手?如果不喜歡,你可以用任意的字符替換它。 23行:接收用戶輸入。 25-27行:fgets接受輸入時,會將輸入字符串時末尾的換行符("\n")一起接受,這是我們不需要的,所以要把它去掉。本程序中簡單的用字符串結束標志'\0'覆蓋了字符串cmd的最後一個字符來實現這個目的。 30行:初始化指針數組cmd_arg。 32-42行:對輸入進行分析,將cmd中參數間的空格用'\0'填充,並把各參數的起始地址分別賦與cmd_arg數組。這樣就把cmd分解成了cmd_arg,但分解後的各命令參數仍然使用著cmd的內存空間,所以在命令執行結束前不宜對cmd另外賦值。 45行:如果還未分析到輸入字符串的末尾(i =10),就認為輸入的命令行超出了10個參數的限制,打印錯誤並重新接收命令。 51-52行:內部命令quit:字符串cmd_arg[0]就是命令本身,如果命令是quit,則退出循環,也就等於退出該程序。 55-58行:內部命令cd:調用函數do_cd()完成cd命令的動作。 61行:對於其它的外部命令和應用程序,調用函數execute_new()執行。 函數do_cd: 68行:僅僅考慮緊跟在命令後面的參數argv[1],而不再考慮其它的參數。如果這個參數存在,就把它作為要轉換的目錄。 69行:調用系統調用chdir切換當前目錄,參見附錄1。 70-82行:對chdir可能出現的錯誤進行處理。 函數execute_new:

92行:調用系統調用fork產生新的子進程。 93行:如果返回負值,說明fork調用出現錯誤。 96行:如果返回0,說明當前進程是子進程。 97行:調用execvp執行新的應用程序,並檢測調用是否出錯(返回負值)。這裡使用execvp的原因是它可以自動在各默認目錄裡尋找目標應用程序的位置,而不必我們自己編程實現。 98-107行:對execvp可能出現的錯誤進程處理。 108行:如果execvp的執行出現錯誤,子進程在這裡終止。表面上看起來,這個exit是接著97行的錯誤判斷的下一行語句,而非if語句的一部分,似乎無論調用execvp成功與否都會接著執行exit。但事實上,如果execvp調用成功的話,這個進程將會被新的程序代碼填充,因而根本不可能執行到這一行。反之,如果執行到了這一行,說明前面的execvp調用一定出現了錯誤。這樣的效果和exit被包含在if語句中的效果是完全一樣的。 109行:如果fork返回其它值,說明當前進程是父進程。 110行:調用系統調用wait。wait在這裡有兩個作用: 使父進程在此暫停,等待子進程執行完畢。這樣,就可以等子進程的所有信息全部輸出完畢後才打印命令提示符,等待下一條命令的輸入,從而避免了命令提示符和應用程序輸出混雜在一起的現象。 收集子進程退出後留下的僵屍進程。可能有讀者一直對這個問題存有疑問--"我們編程生成的子進程由我們自己設計的父進程負責收集,但我們手動執行的這個父進程由誰收集呢?"現在大家應該明白了,我們從命令行執行的所有進程最後都是由shell收集的。 關於Mini Shell的編譯和運行,這裡就不再敷述了,有興趣的讀者可以自行動手實驗,或者對這個程序進行改進,使之更接近甚至超過我們正使用的Bash。 1.14 daemon進



98-107行:對execvp可能出現的錯誤進程處理。 108行:如果execvp的執行出現錯誤,子進程在這裡終止。表面上看起來,這個exit是接著97行的錯誤判斷的下一行語句,而非if語句的一部分,似乎無論調用execvp成功與否都會接著執行exit。但事實上,如果execvp調用成功的話,這個進程將會被新的程序代碼填充,因而根本不可能執行到這一行。反之,如果執行到了這一行,說明前面的execvp調用一定出現了錯誤。這樣的效果和exit被包含在if語句中的效果是完全一樣的。 109行:如果fork返回其它值,說明當前進程是父進程。 110行:調用系統調用wait。wait在這裡有兩個作用: 使父進程在此暫停,等待子進程執行完畢。這樣,就可以等子進程的所有信息全部輸出完畢後才打印命令提示符,等待下一條命令的輸入,從而避免了命令提示符和應用程序輸出混雜在一起的現象。 收集子進程退出後留下的僵屍進程。可能有讀者一直對這個問題存有疑問--"我們編程生成的子進程由我們自己設計的父進程負責收集,但我們手動執行的這個父進程由誰收集呢?"現在大家應該明白了,我們從命令行執行的所有進程最後都是由shell收集的。 關於Mini Shell的編譯和運行,這裡就不再敷述了,有興趣的讀者可以自行動手實驗,或者對這個程序進行改進,使之更接近甚至超過我們正使用的Bash。 1.14 daemon進



Copyright © Linux教程網 All Rights Reserved