




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、題目1 shell 程序設計1.1 實驗目的 Linux操作系統中shell是用戶與系統內核溝通的中介,它為用戶使用操作系統的服務提供了一個命令界面。用戶在shell提示符($或#)下輸入的每一個命令都由shell先解釋,然后傳給內核執行。本實驗要求用C語言編寫一個簡單的shell程序,希望達到以下目的:l 用C語言編寫清晰易讀、設計優良的程序,并附有詳細的文檔。l 熟悉使用Linux下的軟件開發工具,例如gcc、gdb和make。l 在編寫系統應用程序時熟練使用man幫助手冊。l 學習使用POSIX/UNIX系統調用、對進程進行管理和完成進程之間的通信,例如使用信號和管道進行進程間通信。l
2、理解并發程序中的同步問題。l 鍛煉在團隊成員之間的交流與合作能力。1.2 實驗要求1.2.1 ysh解釋程序的重要特征 本實驗要實現一個簡單的命令解釋器,也就是Linux中的shell程序。實驗程序起名為ysh,要求其設計類似于目前流行的shell解釋程序,如bash、csh、tcsh,但不需要具備那么復雜的功能。ysh程序應當具有如下一些重要的特征:l 能夠執行外部程序命令,命令可以帶參數。 。l 能夠執行fg、bg、cd、history、exit等內部命令。l 使用管道和輸入輸出重定向。l 支持前后臺作業,提供作業控制功能,包括打印作業的清單,改變當前運行作業的前臺/后臺狀態,以及控制作業
3、的掛起、中止和繼續運行。 除此之外,在這個實驗中還須做到:l 使用make工具建立工程。l 使用調試器gdb來調試程序。l 提供清晰、詳細的設計文檔和解決方案。1.2.2 ysh解釋程序的具體要求1. Shell程序形式本實驗的ysh程序設計不包括對配置文件和命令行參數的支持。如果實現為像bash那樣支持配置文件,當然很好,但本實驗并不要求。ysh應提供一個命令提示符,如ysh>,表示接收用戶的輸入,每次執行完成后再打印下一個命令提示符ysh>。當用戶沒有輸入時, ysh需要一直處于隨時等待輸入狀態,同時在屏幕上顯示一些必要的信息。2. 外部命令和內部命令在大多數情況下,用戶輸入的
4、命令是執行存儲在文件系統中的可執行程序,我們叫做外部命令或外部程序。ysh應當支持在執行這些程序時可以將輸入輸出重新定向到一個文件,并允許若干個程序使用管道串聯起來。從本實驗的角度來講,我們把由管道連接起來的復合命令以及單獨使用的命令統稱為作業。外部命令的形式是一系列分隔的字符串。第一個字符串是可執行程序的名字,其他的是傳給這個外部程序的參數。如果第一個字符串所聲明的可執行文件并不存在或者不可執行則認為這個命令是錯誤的。解釋器還須支持一些內部命令,這些命令在ysh程序內部實現了特定的動作,下面是一些內部命令,如果用戶提交了一個內部命令,ysh應當按照下面的描述執行相應動作。l exit:結束所
5、有的子進程并退出ysh。l jobs:打印當前正在后臺執行的作業和掛起的作業信息。輸出信息應采用便于用戶理解的格式。jobs自身是一條內部命令,所以不需要顯示在輸出上。l fg <int>:把<int>所標識的作業放到前臺運行。如果這個作業原來已經停止,那么讓它繼續運行。shell應當在打印新的命令提示符之前等待前臺運行的子進程結束。l bg <int>:在后臺執行<int>標識的已掛起的進程。3命令行當用戶在提示符后面輸入命令時,輸入的整行內容叫做“命令行字符串”,ysh應當保存每一條命令行字符串,直到它表示的作業執行結束,其中包括后臺作業和被
6、掛起的作業。ysh應當給每一個命令行字符串賦一個非負整數標識符。這個整數用來標識存儲作業的數據結構,作業的數據結構應包含整個命令行字符串所表示的內容。一旦命令行字符串代表的作業執行結束,ysh就要刪掉表示這個作業的數據結構。標識符可以循環使用。對于包含內部命令的命令行字符串,不需要為它們建立作業的數據結構,因為它們本身的內容全部包含在ysh程序中。4前臺和后臺作業ysh應當能夠執行前臺和后臺作業。shell在前臺作業執行結束之前要一直等待。而在開始執行后臺作業時要立刻打印出提示符ysh>,讓用戶輸入下一條命令。前臺作業的執行總是優先于執行一個后臺作業,ysh不需要在打印下一個提示符前等待
7、后臺作業的完成,無論是否有后臺作業的執行,只要完成一個前臺作業,便立即輸出提示符ysh>。一個后臺作業結束時,ysh應當在作業執行結束后立刻打印出一條提示信息。下面語法中會在命令語法分析程序中介紹相應的語法來支持后臺作業。5特殊鍵又稱組合鍵。通過終端驅動程序,特殊的組合鍵可以產生信號給ysh,程序應當對這些信號做出適當的響應。l Ctrl+Z:產生SIGTSTP信號,這個信號不是掛起ysh,而是讓shell掛起在前臺運行的作業,如果沒有任何前臺作業,則該特殊鍵無效。l Ctrl+C:產生SIGINT信號,這個信號不是中止ysh,而是通過ysh發出信號殺死前臺作業中的進程。如果沒有任何前臺
8、作業,則該特殊鍵無效。6分析用戶輸入1) 分隔符和特殊字符分析用戶輸入的語法分析器應具有下面介紹的功能,它能夠檢查用戶的輸入錯誤。如果用戶輸入的某些地方出錯了,ysh應提供合理的出錯信息。就像商業級別的shell一樣,ysh每次接受用戶輸入的一行命令,在用戶按下回車鍵 (Enter)后開始執行分析動作。空命令不產生任何操作,而只是打印一個新提示符。定義空格符為分隔符,ysh應能處理命令行中間和前后出現的重復空格符。某些字符被稱做“元字符",它們在用戶輸入的上下文中具有特定的含義。這些字符包括“、| 、<、>“。shell假設這些字符不會出現在程序名、參數名和文件名中,它們
9、是ysh的保留字符。下面幾小節會解釋這些元字符的含義。2) 內部命令如果命令行字符串符合前面介紹的內部命令的格式,它就作為一個內部命令被解釋。如果不是,就要考慮可能是外部程序的執行,或者是錯誤的。3) I/O重定向一個程序命令后面可能還跟有元字符“<”或“>”,它們是重定向符號,而在重定向符號后面還跟著一個文件名。在“<”的情況下,程序的輸入被重定向到一個指定的文件中。在“>”的情況下,程序的輸出被重定向到一個指定的文件中。如果輸出文件不存在,需要創建一個輸出文件。如果輸入文件不存在,則認為是出現了錯誤。4) 管道和協同程序在一條命令行中當若干個命令被元字符“|”分隔開
10、時,這個元字符代表管道符號。在這種情況下,ysh為每一個子命令都創建一個進程,并把它們的輸入/輸出用管道連接起來。例如下面這條命令行: progA argA1 argA2<infile | progB argB1>outfile應生成progA和progB兩個進程,progA的輸入來自文件infile,progA的輸出是progB的輸入,并且progB的輸出是文件outfile。這種命令行可以通過進程間通信中的管道來實現。含有一個和多個管道的命令會在如下幾種情況下產生錯誤:l 當其任何一個子程序執行出錯時。l 除了第一個子程序以外的其他子程序的輸入被重定向。l 除了最后一個子程序以
11、外的其他子程序的輸出被重定向。由管道連接的多個進程所組成的作業只有當其所有的子進程都執行完畢后才算結束。5)后臺作業當用戶需要在后臺執行一個作業時,可以在作業命令的后面加上元字符“”。用戶以該種方式輸入的作業命令都必須放在后臺執行,同時并不影響用戶與終端的交互。6)語法下面給出的語法規則描述圖提供了控制用戶輸入的一種更規范的描述形式。如果使用Linux中的現有工具lex和yacc來建立命令行分析器,還需要對這個語法進行修改,以便使它支持LALR(1)(Look Ahead Left Reduction)分析方法。這個語法并不包括特殊鍵,因為它們不會在用戶輸入時顯示出來,而是需要單獨處理。Com
12、mandLine代表用戶的合法輸入,它作為ysh要執行的一條“指令”。這里假設存在一個詞法分析器,它將空格符作為分隔符,并識別元字符作為一個詞法記號等。CommandLine := NULL FgCommandLine FgCommandLine&FgCommandLine := SimpleCommand FirstCommand MidCommand LastCommandSimpleCommand := ProgInvocation InputRedirect OutputRedirectFirstCommand := ProgInvocation InputRedirectMi
13、dCommand := NULL | ProgInvocation MidCommand LastCommand := |ProgInvocation OutputRedirectProgInvocation := ExecFi le Args InputRedirect := NULL, <STRINGOutputRedirect := NULL >STRINGExecFile := STRINGArgs := NULL STRING ArgsSTRING := NULL CHAR STRINGCHAR := 0 |1 | 9| a |b | z | A | B | Z7實驗步驟
14、建議(1)閱讀關于fork、exec、wait和exit系統調用的man幫助手冊。(2)編寫小程序練習使用這些系統調用。(3)閱讀關于函數tcsetpgrp和setpgid的man幫助手冊。(4)練習編寫控制進程組的小程序,要注意信號SIGTTIN和SIGTTOU。(5)設計命令行分析器(包括設計文檔)。(6)實現命令行分析器。(7)使用分析器,寫一個簡單的shell程序,使它能執行簡單的命令。(8)增加對程序在后臺運行的支持,不必擔心后臺作業運行結束時要打印一條信息(這屬于異步通知)。增加jobs命令(這對于調試很有幫助)。(9)增加輸入輸出重定向功能。(10)添加代碼支持在后臺進程結束時打
15、印出一條信息。(11)添加作業控制特征,主要實現對組合鍵Ctrl+Z、Ctrl+C的響應,還有實現fg和bg命令功能。(12)增加對管道的支持。(13)實現上面的所有細節并集成。(14)不斷測試。(15)寫報告。(16)結束。1.3相關基礎知識1.3.1 shell與內核的關系shell是用戶和Linux內核之間的接口程序,如果把Linux內核想象成一個球體的中心, shell就是包圍內核的外殼,如圖51所示。當從shell或其他程序向Linux傳遞命令時,內核會做出相應的反應。shell是一個命令語言解釋器,它擁有自己內建的shell命令集,shell也能被系統中其他應用程序所調用。用戶在提
16、示符ysh>下輸入的命令都是由shell先解釋后傳給Linux核心的 1.3.2 系統調用系統調用是一個“函數調用”,它控制狀態的改變。系統調用與普通函數過程的區別在于系統調用的執行會引起特權級的切換,因為被調用的函數處于操作系統內核中,是內核的一部分。操作系統定義了一個系統調用集合。為了安全起見,調用操作系統內部的函數必須謹慎地控制,這種控制是由硬件通過陷阱向量執行的。只有那些在操作系統啟動時填入陷阱向量的地址,才是正當而且有效的系統調用地址。因此,系統調用就是一種在受約束的行為下進入保護核心的“函數調用”。因為操作系統負責進程控制和調度,ysh就需要調用操作系統內部的函數來控制它的子
17、進程。這些函數叫做系統調用。在Linux中,我們可以區分系統調用和用戶應用層次的庫函數,因為系統調用函數手冊在“幫助”手冊的第二部分,而庫函數在手冊的第三部分。在Linux中可以通過man命令查詢“幫助”手冊。例如,使用命令man fork會給出手冊第二部分關于fork系統調用的描述,而命令man 2 exec會給出exec系統調用族的描述(2表示手冊的第二部分)。還有很多其他的系統調用,都可以通過man命令來查閱,你會發現man是很有用的查閱參考手冊的命令。下面是在實驗中會用到的重要的UNIX系統調用。l pid_t fork(void):創建一個新的進程,它是原來進程的一個副本。在fork
18、成功返回后,父進程和子進程都要繼續執行fork后的指令。這兩個進程通過fork的返回值進行區分,對父進程fork的返回值是子進程的進程號,對子進程的返回值是0。l int execvp(const char*file,char*const argv):加載一個可執行程序到調用進程的地址空間中,然后執行這個程序。如果成功,它就會覆蓋當前運行的進程內容。有若干個類似的exec系統調用。l void exit(int status):退出程序,使調用進程退出,程序結束。它把status作為返回值返回父進程,父進程通過wait系統調用獲得返回值。鏈接器會為每一個程序結尾鏈接一個exit系統調用。l i
19、nt wait(int*stat_loc):如果有退出的子進程,則返回退出的子進程的狀態;如果沒有任何子進程在運行,則返回錯誤。如果當前有子進程正在運行,則函數會一直阻塞直到有一個子進程退出。l pid_t waitpid(pid_t pid,int*stat_loc,int options):類似于函數wait,但允許用戶等待某個進程組的特定進程,并可以設置等待選項,例如 WNOHANG。l int tcsetpgrp(int fildes,pid_t pgid_id):將前臺進程組ID設置為pgid_id,fildes是與控制終端相聯系的文件描述符。終端通常指標準輸入、標準輸出和標準錯誤輸
20、出(文件描述符為0、1、2)。l int setpgid(pid_t pid,pid_t pgid):把pid進程的進程組ID設置為pgid。l int dup2(int fildes,int flides2):把ftildes文件描述符復制到fildes2。如果fildes2已經打開,則先將其關閉,然后進行復制,使filedes和fildes2指向同一文件。l int pipe(int fildes2):創建一個管道,把管道的讀和寫文件描述符放到數組fildes中。1進程創建使用fork系統調用創建新的進程。fork克隆了調用進程,兩者之間只有很少的差別。新進程的進程號pid和父進程號ppi
21、d與原來的進程不同。其他不同之處可以查看man手冊得到。fork的返回值是程序中惟一能夠區別父進程和子進程的地方。fork對父進程返回子進程的進程號,對子進程則返回0。利用這個細小的區別可以使兩個進程執行不同的程序段。wait函數族允許父進程等待子進程執行結束。在ysh創建一個前臺進程時會用到它。須特別注意的是wait函數族會在子進程狀態改變時返回,而不僅僅是在子進程運行結束或者退出時才返回,其中有些狀態的變化可以被忽略。在man手冊中有關于函數waitpid的參數說明,其中有WNOHANG和其他一些有用的參數(WNOHANG指定在沒有子進程退出時,父進程不阻塞等待)。下面的例子介紹創建進程和
22、等待子進程運行結束。int main(int argc,char*argv) int status; int pid; char*prog_arv4; /*建立參數表*/ prog_argv0=”/bin/ls”; prog_argv1=”-1”; prog_argv2=”/”; prog_argv3=NULL;/* *為程序ls創建進程*/ if(pid=fork()<0) perror(”Fork failed”); exit(errno); if(!pid) /*這是子進程,執行程序ls*/ execvp(prog_argv0,prog_argv);if(pid) /* *這是父進
23、程,等待子進程執行結束 */ waitpid(pid,NULL,0);shell程序等待子進程執行結束是很重要的。對一個作業等待子進程發出信號進行處理可以采用阻塞等待的方式,或是采用非阻塞的方式。盡管在進程死亡時它的許多資源都會被釋放,但是進程控制塊和其他的一些信息還沒有釋放,這種狀態被稱為defunct。進程控制塊包含了退出的狀態信息,它可以通過wait函數族獲得。在wait pid函數調用完之后,進程控制塊就被釋放了。如果父進程在子進程之前結束,那么子進程就會成為init進程的孩子,init進程會等待任何子進程的結束,釋放進程控制塊。那些已經終止,但父進程尚未對其進行狀態搜集的進程就成為僵
24、尸進程。2exec系統調用exec函數族允許當前進程執行另外一個程序。典型的應用是一個程序調用fork生成自身的一個副本,然后子進程調用exec執行另外一個程序。exec有許多不同形式,它們最終都是調用內核中的同一個函數,只是它們給用戶提供了更多的調用形式。調用exec的進程不是和原來完全不同,而是調用后進程繼承了原來的父進程標識、組標識和信號掩碼,但不包括信號處理程序。詳細信息可以查看man手冊。除非產生錯誤,否則exec函數從來不返回(從此時開始執行新的程序代碼)。上面的例子介紹了函數execvp被系統調用的使用方法。3. I/0重定向為了實現I/0重定向,需要使用函數dup2:int d
25、up2(int fiides,int flides2);每個進程都有一張它所打開的文件描述符表,每個表項包含了文件描述符標識和指向系統文件表中相對應表項的指針。這個系統文件表是由內核維護,它記錄了系統當前打開的所有文件的信息,其中包括打開這個文件的進程數目、文件狀態標志(讀、寫、非阻塞等)、當前文件指針、指向該文件inode節點表項的指針。還應當認識到許多非文件類的機制也使用文件接口,只是它們的操作被包裝了。例如很多場合終端也被當做文件來操作。默認情況下,進程的文件列表中的前三個入口都指向終端:標準輸入(0),標準輸出(1)和標準錯誤輸出(2)。為實現I/O重定向,我們要打開一個文件,并把它的
26、文件描述符入口復制給標準輸入或者標準輸出(或者標準錯誤輸出)。如果需要在后面恢復原來的入口項,我們可以事先把它保存在文件列表中別的地方。4信號信號是最簡單的進程間通信(IPC)原語。信號允許一個進程在某一事件發生時與另一個進程通信。信號的值表明發生了哪種事件。信號對本實驗來說是很重要的,它們指出了后臺運行的子進程狀態發生的變化,比如子進程的正常終止。當一個進程接收到一個信號時,它會采取某些動作。許多信號都有默認的動作。比如,某些信號默認產生core dumps,或者進程自身掛起。我們也可以聲明讓自己的進程處理某個信號。通過聲明一個信號處理程序可以做到這一點。Linux主要有兩個函數實現對信號的
27、處理,即signal和sigaction。其中signal是庫函數,在可靠信號系統調用的基礎上實現。它只有兩個參數,不支持信號傳遞信息;而sigaction是較新的函數,有三個參數,支持信號傳遞信息,同樣支持非實時信號的安裝。函數sigaction優于signal,主要體現在支持信號帶有參數。1)函數signal格式#include<signal.h>typedef void (*sighandler_t)(int);sighandler_t signal(int signum,sighandler_t handler);參數說明signum指定信號的值。handler指定針對前面
28、信號值的處理,可以忽略該信號(參數設置為SIG-IGN);可以采用系統默認方式處理信號(參數設置為SIG_DFL);也可以自己實現處理方式(參數指定一個函數地址)。如果signal調用成功,返回最后一次為安裝信號signum而調用signal時的handler值;失敗則返回SIG_ERR。2)函數sigaction梧式#include<signal.h>int sigaction(int signum,const struct sigaction*act,struct sigaction *oldact);參數說明sigaction函數用于改變進程接收到特定信號后的行為。signu
29、m為信號的值,可以為除SIGKILL及SIGSTOP外的任何一個特定有效的信號(為這兩個信號定義自己的處理函數,將導致錯誤)。act是指向結構sigaction的一個實例的指針,在結構sigaction的實例中,指定了對特定信號的處理,可以為空,進程會以默認方式對信號處理。這個參數最為重要,其中包含了對指定信號的處理、信號所傳遞的信息、信號處理函數執行過程中應屏蔽哪些函數。oldact是指向的對象用來保存原來對相應信號的處理,可指定oldact為NULL。如果把signum和act都設置為NULL,那么該函數可用于檢查信號的有效性。sigact i on結構定義 struct sigactio
30、n void(*sa_handler)(int); void(*sa_sigaction)(int,siginfo_t*,void*); sigset_t sa_mask; unsigned long sa_flags; void(*sa_restorer)(void);數據結構中的兩個元素sa_hanlder和sa_sigaction是指定信號關聯函數。除了可以是用戶自定義的處理函數外,還可以為SIGDFL(采用默認的處理方式),也可以為SIG_DFL(忽略信號)參數說明sa_handler:由它指定的處理函數只有一個參數,即信號值,所以信號不能傳遞除信號值之外的任何信息sa_sigacti
31、on:由它指定的信號處理函數帶有三個參數,是為實時信號而設的(當然同樣支持非實時信號),這個信號處理函數的第一個參數(int)為信號值,第三個參數(void*)沒有使用(POSIX標準中沒有規范使用該參數的標準),第二個參數(siginfo_t* )是指向siginfo_t結構的指針,結構中包含信號攜帶的數據值,參數所指向的結構如下:siginfo_t int si_signo; /*信號值*/ int si_errno; int si_code; pid_t si_pid;/*發送信號的進程ID/ uid_t si_uid; int si_status;clock_t si_utime
32、60;clock_t si_stime sigval_t si_value int si_int void* si_ptr void* si_addr int si_band int si_fd sa_mask:指定在信號處理程序執行過程中,哪些信號應當屏蔽,如果不指定SA_NODEFER或者SA_NOMASK標志位,默認情況下則屏蔽當前信號,防止信號的嵌套發送。sa_flags :其中包含了許多標志位,包括前面提到的SA_NODEFER及SA_NOMASK標志位。另一個比較重要的標志位是SA_SIGNFO,當設定
33、了該標志位時,表示信號附帶的參數可以被傳遞到信號處理函數中,因此,應該為sigaction結構中的sa_sigaction指定處理函數,而不是為sa_handler指定信號處理函數,否則設置該標志無意義。即使為sa_sigaction指定了信號處理函數,如果不設置SA_SIGNFO,信號處理函數同樣不能得到信號傳遞過來的數據,在信號處理函數中對這些信息的訪問都將導致段錯誤(Segment fault)。sa_restorer :現在已經不是用了,POSIX標準中并未定義它。下面是一個信號處理程序的例子:void ChildHandler (int sig,siginfo_t*sip,
34、 void*notused) int status;printf ( “The process generating the signal is PID:%dn”,sip->si-pid);fflush(stdout); status=0; /*WNOHANG標識如果沒有子進程退出,就不等待*/ if(sip->si_pid=waitpid(sip->si_pid,&status,WNOHANG) /*SIGCHLD并不意味著子進程執行結束*/ i f(WIFEXITED(status)|WTERMSIG(status) printf(“The child
35、is gonen”);/*子進程退出*/else printf (“Uninterestingn”); /*alive*/else printf (“Uninterestingn”); int main() struct sigaction action;action.sa_sigaction=ChildHandler; /*注冊信號處理函數*/sigfillset(&action.sa_mask);action.sa_flags=SA_SIGINFO; /*向處理函數傳遞信息*/sigaction(SIGCHLD,&action,NULL;fork();while(1) pr
36、intf(“PID:dn”,getpid(); sleep(1);5.管道將一個程序或命令的輸出作為另一個程序或命令的輸入,有兩種方法,一種是通過一個臨時文件將兩個命令或程序結合在一起,這種方法由于需要臨時文件而顯得很累贅;另一種是Linux所提供的管道功能,這種方法比前一種方法好。管道是更加復雜的IPC(進程間通信)工具,它允許數據從一個進程向另一個進程單向地流動。管道實際上是利用文件系統中的循環緩沖區。我們以生產者消費者模式來使用它,寫進程相當于生產者,而讀進程相當于消費者。一個進程向管道寫數據,如果緩沖區寫滿了,則阻塞寫進程。另一個進程從管道中讀數據,如果管道為空,則讀進程阻塞。當寫進程
37、關閉了管道或者該進程死掉時,讀進程就會失敗。如果讀進程關閉管道或者該進程死掉時,寫進程就會失敗。下面說明管道如何工作。我們在父進程中使用pipe系統調用創建一個管道,系統調用的參數是包含兩個文件描述符數組:pfd0和pfd1。(1)和文件描述符一樣,使用pfd0作為管道輸入描述符,使用pfd1作為管道輸出描述符。(2)fork產生子進程。(3)現在父進程和子進程都共享了管道文件描述符。每個進程都關閉管道的一端(至于哪一端則取決于誰作為讀進程,誰作為寫進程)。(4)每個進程使用dup2把打開的管道描述符復制給標準輸入或者標準輸出,然后關閉管道描述符(如果后面還要恢復標準輸入或者標準輸出,則要把它
38、們事先保留起來)。(5)現在兩個進程可以使用管道通過標準輸入和標準輸出進行通信了。如果我們在fork和exec之間完成這些動作,那么可以將進程通過管道連接起來。下面是一個管道的例子:int main(int argc,char*argv) int status; int pid2; int pipe_fd2;char*progl_argv4;char*prog2_argv2;/*建立參數表*/progl_argv0=”*/usr/local/bin/ls”;progl_argv1=”1”;progl_argv2=”/”;progl_argv3NULL;prog2_argv0=”/usr/bin
39、/more”;prog2_argv1=NULL;/*創建管道*/if(pipe(pipe_fd)<0) perror(”pipe failed”); exit(errno);/*為ls命令創建進程*/i f(pid0=fork()<0) perror(”Fork failed”); exit(errno);if(!pid0) /*將管道的寫描述符復制給標準輸出,然后關閉*/close(pipe_fd0);dup2(pipe_fd1,1);close(pipe_fd1);/*執行ls命令*/execvp(proglargv0,progl_argv);if(pid0) /*父進程*/
40、/*為命令more創建子進程*/ if(pid1=fork()<0)perror(“Fork failed”);exit(errno);if(!pid1) /*在子進程*/ /*將管道的讀描述符復制給標準輸入*/ close(pipe_fd1); dup2(pipe_fd0,0); close(pipe_fd0); /*執行more命令*/ execvp(prog2一argv0,prog2_argv);/*父進程*/close(pipe_fd0);close(pipe_fd1);waitpid(pid1,&status,0);printf(”Done waiting for mo
41、re.n”);6.進程組、會話和進程組、會話和作業控制當用戶登錄操作系統以后,操作系統為會話(sesson)分配一個終端。會話是指進程運行的環境,即與進程相關的控制終端。shell被分配到前臺的進程組中。進程組是指若干個相關進程的集合它們通常是通過管道連接的。一個終端至多能與一個進程組相關聯。前臺進程組是會話中能夠訪問控制終端的進程組。因為每個會話只有一個控制終端,所以只存在一個前臺進程組。前臺進程組中的進程可以訪問標準輸入和標準輸出。這也意味著組合鍵會使控制終端把產生的信號發送給前臺進程組中的所有進程;在Ctrl+C的操作下信號SIGINT會發送給前臺的每一個進程;在Crtl+Z的操作下信號
42、SIGTSTP會發送給前臺的每一個進程。這些組合鍵不會顯示在終端上。同時還存在著后臺進程組,它們不能訪問會話中控制終端的進程組。因為它們不能訪問控制終端,所以不能進行終端的I/O操作。如果一個后臺進程組試圖與控制終端交互,則產生SIGTTOU或者SIGTTIN信號。默認情況下,這些信號像SIGTSTP一樣會掛起進程。ysh必須處理其子進程中發生的這些變化。進程可以使用函數setpgid,使其加入到某一進程組中。進程組ID是以組長的進程ID命名的。進程組長是創建該組的第一個進程,用它的進程號作為進程組的組號。進程組長死后進程組仍然可以存在。一個進程組可以使用函數tcsetpgrp成為前臺進程組。
43、這個函數調用使指定的進程組成為前臺進程組,這不僅會影響到組本身也會影響到該組的任何一個子進程。如果進程調用函數setsid創建了一個新的會話,那么它就成為了會話組長和進程組組長。對于一個要與終端交互的新會話來說,必須為它分配一個新的終端。由于是在系統shell(csh、sh、bash等)下執行ysh,這會使ysh代替原來的shell,成為前臺進程組中的惟一進程。讓某個進程組成為前臺進程組,不僅保證它能夠與控制終端保持聯系,還保證了前臺進程組中的每一個進程都能從終端接收到控制信號,如SIGTSTP。簡單的方法是,把所有的子進程放在同一個進程組中,在創建它們時屏蔽SIGTSTP信號,這樣只有she
44、ll才能接收到這個信號,然后shell發出與SIGTSTP作用相同的(但沒有屏蔽的)SIGSTOP信號給某個子進程。這種辦法在某些時候會使程序運行失敗,比如在自己的ysh中再次運行ysh,但這種辦法對于本實驗來說是不涉及的。下面是一個進程組的例子:#include<stdio.h>#include<signal.h>#include<stddef.h>#include<sys/wait.h>#include<sys/ioctl.h>#include<sys/termios.h>/*本程序體現了函數tcsetgrp和setp
45、grp的用法,但沒處理SIGTTIN、SIGTTOU信號*/int main() int status; int cpid; int ppid; char buf256; sigset_t blocked;ppid=getpid();if (!(cpid=fork() setpgid(0,0); tcsetpgrp (0, getpid(); execl (“/bin/vi”,”vi”,NULL); exit (1); if(cpid<0) exit(1);setpgid(cpid,cpid); /*設置進程組*/tcsetpgrp(0,cpid); /*設置控制終端為子進程擁有*/wa
46、itpid(cpid,NULL,0);tcsetpgrp(0,ppid)jwhile(1) memset(buf,0,256); fgets(buf,256,stdin); puts(”ECHO:”); puts(buf); puts(”n”);1.4實驗環境本實驗的程序用C語言編寫,使用makefile文件編譯整個程序,生成一個名為ysh可執行程序,在終端輸入“./ysh”即可執行。makefile文件的內容如下:ysh: ysh.ccc ysh.co ysh1.5程序的實現1.5.1數據結構在這個程序中,我們用到的數據結構主要有循環數組和鏈表。1循環數組在history命令中,用數組來存放
47、我們輸入過的歷史命令。假設我們設定一個能夠記錄12條歷史紀錄的數組如圖5.2所示。數組的定義如下: typedef struct ENV_HISTROY int start=0; int end=0; char his_cmd12100; ENV_HISTORY; ENV_HISTORY envhis:可以看到,每個his_cmdi對應圖中一塊圓環(end不一定為12),一共12塊,能存放十二條命令。當用戶輸入一命令時,只須執行如下語句即可將輸入命令存放進相應數組中:envhis.end=envhisend+l;strcpy(his_cmdenvhis.end,input)。但是還需要考慮如圖
48、5.3所示的情況 在這種情況下,end=12,當我們在輸入一條命令時,如果還是用上述兩條命令進行處理end=end+l,則end=13就會出錯。所以應對程序進一步修改:envhis.end=(envhis.end+1)%12;if(envhis.end=envhis.start) envhis.start=(envhis.start+1)12;strcpy(envhis.his_cmdenvhisend,input);經過這樣的處理,就可以達到循環的目的了2鏈表由于我們把作業以鏈表的形式保存起來,所以在處理jobs命令時,實際上就是對鏈表的操作。 首先定義鏈表的節點:typedef struc
49、t NODE(pid_t pid;/*進程號*/char cmd100;/*命令名*/char state10; /*作業狀態*/struct NODE 1ink;/*下一節點指針*/ )NODE;NODE*head,*end;head指針指向鏈表表頭,end指針指向鏈表尾 1.5.2程序結構在shell命令里,我們將ysh中的命令分成4種:普通命令,重定向命令,管道命令和內部命令。這4種命令的分析和執行各有不同,每一種命令的分析執行程序都應包括:初始化環境,打印提示符,獲取用戶輸入的命令,解析命令,尋找命令文件和執行命令幾個步驟,具體程序流程如圖5.4所示。1初始化環境程序一開始,需要對一些
50、環境變量進行初始化。比如將查找路徑放入envpath中,初始化history和jobs的頭尾指針等。這部分工作在程序中由函數init_environ來完成。void init_environ() int fd,n,i; char buf8 0; if(fd=open(”ysh_profile”,O_RDONLY,660)=-1) printf(”init environ variable errorn”); exit(1); while(n=getline(fd,buf) getenviron(n,buf); envhis.start=0; envhis.end=0; head=end=NUL
51、L;可以看到這個函數打開一個名為ysh_profile的文件,它是我們定義的配置文件。然后調用了另外兩個函數getline(fd,buf)和getenviron(n,buf)。getline(fd,buf)的作用是讀取一行的信息到buf中。getenviron(n,buf)的作用是將getline(fd,buf)讀到buf中的信息以冒號分開,分別放于envpath中(見ysh.h中的定義),為后面查找命令做準備。這樣命令查找的準備工作就做好了。接下來函數返回到函數init_environ。接下來的語句將envhis.start和envhis.end置0。這兩條語句的作用是初始化保存histor
52、y命令鏈表的頭尾指針。而語句head=end=NULL是初始化保存jobs命令鏈表的頭尾指針。至此,程序的初始化工作基本完成。接下來就進入一個while循環,就和一般的shell一樣,當一個命令執行完或放到后臺后,shell就可獲取新的用戶輸入命令。2解析命令在這里對讀到input數組中的命令進行分析,以獲取命令和參數。ysh中的命令分成4種:普通命令,重定向命令,管道命令和內部命令。我們對管道和重定向命令單獨處理。 for(i=0,j=0,k=0;i<=input_len;i+) i f(inputi=<| inputi=>| inputi=|) if(inputi=|) pipel(input,input_len); add_history(input); free(input); else redirect(input,input_len); add_history(input); free(input); is_pr=l; break; 該for循環就把帶“>”、“<和|符號的管道和重定向命令單獨處理。同時把is_pr這個管道和重定向命令的標志置l。然后分別調用redirect(input,inpuuen)和pipel(input, input_len)兩個函數來處理這兩類命令。這兩個函數在后面會討論。對于
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 遺跡保護與歷史文化名城保護考核試卷
- 零售業趨勢與未來發展預測考核試卷
- 貴金屬提煉的化學分析方法考核試卷
- 水運市場競爭與發展趨勢考核試卷
- 陶瓷工藝品的耐化學腐蝕性能測試方法與應用研究考核試卷
- 瑞思邁呼吸機產品解析與應用指南
- 妊娠合并高血壓疾病護理
- 衛生法學視角下的職業病防治體系
- 2025年金融數據治理與資產化研究報告:金融行業數據治理與資產化戰略布局與實施效果
- 量子計算在金融風險模擬中的量子計算與金融數據分析應用報告
- 太空安全主題班會課件
- 護理文書課件
- 2025年企業大腦·AI賦能低空經濟白皮書
- 2024北京海淀區六年級(下)期末數學試題及答案
- 2025年中考英語作文預測及滿分范文11篇
- 三級養老護理員職業技能鑒定理論考試題(附答案)
- 2025重慶水務環境控股集團有限公司招聘6人筆試參考題庫附帶答案詳解
- 辦公技能實操考試試題及答案
- 2025年中考道法時政新聞選擇題預測100題
- 小學音樂教師個人成長研修方案及規劃
- 噴涂作業安全專項培訓
評論
0/150
提交評論