進程管理相關的系統調用之三
雷鎮 (
[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進