摘要:Linux C為用戶提供了一個強大的編程環境。該文分析并討論了linux OS下微型Shell的基本功能及實現機制,然后運用Linux下的多進程編程技術在Linux C中設計了一個微型Shell,并給出了具體實現辦法。
關鍵詞:Linux C;Linux OS;多進程編程;微型Shell
中圖分類號:TP316文獻標識碼:A文章編號:1009-3044(2010)07-1602-03
Realization of Mini Shell by Multi-Process in Linux C
ZHANG Zhi-feng
(College of Computer Science and Technology, Inner Mongolia University for Nationalities, Tongliao 028043, China)
Abstract: Linux C provides a powerful programming environment for users. The paper analyzes and discusses the mini shell's basic functions and implementation mechanisms under the Linux OS, and then designs a mini-Shell based on multi-process technology in the Linux C, and shows a specific implementation.
Key words: linux C; linux OS; multi-process programming; mini shell
Linux的圖形化環境有很大改進,在X Window系統下,用戶幾乎可以完成所有的操作,只需打開shell提示來完成極少的任務,然而,許多Linux功能在shell提示下要比在圖形化用戶界面GUI下完成得更快[1]。眾所周知,進程是操作系統和并發程序設計的一個非常重要的概念,本文主要應用Linux下的fork( )等系統調用,使用多進程編程技術,設計和實現了一個微型shell(能完成Linux OS標準shell的一部分功能),最后給出了在Linux C下的實現代碼。
1 微型Shell的基本功能及實現機制
1.1 Shell的基本功能
Shell是功能強大的命令解釋器,它不屬于內核部分,而是在核心之外,以用戶態方式運行[2],其最主要的功能是解釋并執行用戶鍵入的命令,實現用戶與Linux核心的接口[3],它是Linux系統的重要組成部分。本文設計的微型shell就是要實現shell的最主要的功能。
1.2 微型Shell的實現機制
系統初啟后,核心為每個終端用戶建立一個進程去執行Shell解釋程序。它的執行過程基本上按如下步驟進行:
1)讀取用戶由鍵盤輸入的命令行,如命令末尾有號(后臺命令符號),則設置后臺執行標識,否則前臺執行。
2)分析命令,以命令名作為文件名,并將其它參數改造為系統調用execvp()內部處理所要求的形式。
3)終端進程調用fork()建立一個子進程,當子進程運行時調用execvp(),從而達到執行用戶提交命令所對應的可執行文件,將它調入內存,執行這個程序(解釋這條命令)。
4)如果命令末尾有號(后臺命令符號),則終端進程不用系統調用wait()(或waitpid())等待,立即發提示符,讓用戶輸入下一個命令,轉1);如果命令末尾沒有號,則終端進程要一直等待。當子進程完成處理后終止,向父進程(終端進程)報告,此時終端進程醒來,在做必要的判別等工作后,終端進程發提示符,讓用戶輸入新的命令,重復上述處理過程。
2 Linux下的多進程編程
欲實現微型shell的設計,要涉及Linux的如下幾個主要的基于進程的系統調用函數:
2.1 fork()函數[4]
fork函數(vfork函數功能與此類似)是用來創建子進程的函數,其函數原型如下所示:
Pid_t fork();
fork函數無參數,當一個進程調用它時,就出現兩個幾乎一模一樣的進程。fork可能有以下三種不同的返回值:
1)在父進程中,fork返回新創建的子進程的進程ID;
2)在子進程中,fork返回0;
3)如果出現錯誤,fork返回一個負值。
2.2exec()函數
調用fork函數后,子進程和父進程幾乎完全一樣,而多數情況下,子進程需要執行與父進程不同的代碼,為此Linux系統提供了exec系統調用,以便用指定的目標程序更換進程的執行映象。Shell進程所創建的子進程正是使用它來執行用戶鍵入的命令。exec函數有6種不同的使用格式,在本文只使用execvp格式,其函數原型如下:
Int execvp(const char *filename,const char *argv[ ]);
其中,參數filename指出可執行目標程序的文件名,它可以通過環境變量PATH來搜索;參數argv是一個字符指針數組,它指出可執行目標程序使用的命令行參數表,按約定第一個字符指針指向與filename相同的字符串,最后一個指針指向一個空字符串,其余的指向該程序執行時所帶的命令行參數。
2.3 wait()函數
進程一旦調用了wait函數(waitpid函數功能與此類似),就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果它找到這樣一個已經變成“僵尸”的子進程,wait就會收集這個子進程的信息,釋放其PCB,并把它徹底銷毀后返回;否則,wait就會一直阻塞下去,直到有一個這樣的子進程出現為止。其函數原型如下:
Pid_t wait(int *status);
參數status是一個指向整數的指針,如果它不為空,子進程的終止狀態字就被存放在該參數所指定的內存位置;如果我們不關心終止狀態字,將該參數置為空即可。
2.4 exit()函數[5]
exit函數是用來終止一個進程的。該函數回收與調用進程相關的各種內核數據結構,把進程狀態置為TASK_ZOMBIE,并把其所有的子進程都托付給init進程,最后調用schedule()函數,選擇一個新的進程來運行。其函數原型如下:
Voidexit(int status);
整型參數satus可以傳遞進程結束時的狀態。一般來說,0表示正常結束;其它數值表示進程非正常結束,出現了錯誤。這里要說明的是,當一個進程已調用exit退出,但其父進程還沒有調用系統調用wait對其進行收集的這段時間里,它會一直保持僵尸狀態。
3 實現微型Shell的具體要求及實現代碼
3.1 具體要求
1)允許用戶輸入一個可執行的程序的名字以及其命令行參數,最多可同時接收5個命令,另外每個命令所帶參數最多不超過10個;
2)可設置命令的前、后臺執行方式,且處理后臺程序時不需要wait;
3)打印提示符;接受和分析命令行(濾去無效的空格、tab符號以及換行符等);
4)能正確執行命令,并且要有出錯處理,最后輸入bye退出。
3.2 Linux C下的實現代碼
#include
#include
#include
#define MAXPARA 10
#define LINE 100
#define CMDLEN 5
char *bye=\"Bye bye!\";
char cmdbuf[CMDLEN][LINE]; /* 用于存放用戶輸入的多條命令 */
int flag[CMDLEN]; /* 用于設置命令后臺運行的標識 */
int main( )
{ int i,n;
While (1)
{printf(\"Hello user\\>\");/* 打印shell提示符 */
for(i=0;i<=4;i++)
{flag[i]=0;cmdbuf[i][0]='\\0';}
if (n= read_analysis_cmd ( )) /* 接收命令 */
execute_cmd ( n ); /*執行命令 */
else
printf(\"read command failed, try again!!!\\");}}
read_analysis_cmd ( )/* 接受和分析命令 */
{char c,*p; int i=0;
p=cmdbuf[0];
while((c=getchar())!='\')
{ if(c==';')
{ *p='\\0';
if(++i==3)
return(i);
p=cmdbuf[i];}
else if(c=='')
flag[i]=1;
else
*p++=c;}
*p='\\0';
return(i); }
execute_cmd ( int n) /*執行命令 */
{int j, stat, pid;
char *argv[MAXPARA], para[LINE];
char c, *parap, **argvp, *p;
for(j=0;j { argvp=argv;parap=para;p=cmdbuf[j]; while((c=*p++)!='\\0') { while(c==' '|| c=='\') c=*p++; if(c=='\\0') {*argsp++='\\0'; break;} *argvp++=parap; while(c!=' 'c!='\'c!='\\0') { *parap++=c; c=*p; if(c)p++;} *parap++='\\0';} *argvp=(char *)0; if(strcmp(argv[0],bye)==0) { printf(\"Bye Bye!\\"); /*輸入bye退出*/ exit(0);} if((pid=fork())==0)/* 終端進程調用fork( )建立一個子進程 */ { if(flag[j])setpgrp();/* 重新把調用進程組標識號改為調用進程的進程ID */ execvp(argv[0],argl); /* 調用execvp()執行用戶提交的命令 */ printf(\"Returned from execve.\\"); exit(4);} else { if (!flag[j]) while(wait(stat)!=pid);}/* 不用等待后臺運行的命令 */}} 參考文獻: [1] 王俊偉,吳俊海.Linux標準教程[M].北京:清華大學出版社,2005. [2] 鳥哥.鳥哥的Linux私房菜[M].北京:科學出版社,2001. [3] 陳忠文.Linux操作系統實訓教程[M].北京:中國電力出版社,2006. [4] 陳莉君,康華.Linux操作系統原理及應用[M].北京:清華大學出版社,2006. [5] 徐誠,高瑩婷.Linux環境C程序設計[M].北京:清華大學出版社,2010.