2017-3-29 實驗目的 1.熟悉fork,execve,exit等系統呼叫的使用 2.通過編寫程式理解linux程序生命週期 實驗內容 [基本要求] 編寫linux環境下c程式,使用fork,exec,exit系統呼叫 [具體要求] ·程式呼叫fork建立子程序 ·父子程序至少有乙個呼叫exec族系統呼叫執行其他程式 ·呼叫exit終止程序 ·**有注釋,提交實驗報告 [進一步要求] ·用wait系統呼叫代替示例**中的sleep ·在呼叫exec時使用自己編寫的c程式或者shell程式 ·將實驗1與實驗2結合 ·使用exec其他幾個函式或者在實驗報告中給出它們的定義與區別 ·實驗報告中寫出fork與vfork的區別,exit與_exit的區別,fork與clone這兩個系統呼叫的異同 ·談談你對fork返回兩次的理解 ·另外了解五個常用系統呼叫,在實驗報告中寫明用法和對其功能的理解
3.實驗報告 (1)用wait()呼叫代替sleep() 實際上wait()是要求父程序等到子程序結束之後再執行,而sleep()是讓這個父程序等待一段時間。
(2)呼叫exec時使用自己編寫的c或者shell程式 以系統呼叫函式execve()為例,使用execve_str 存入對應的引數,呼叫的程式是實驗一中寫的s**件。 execve("/mytest/test.sh",execve_str,null)
(3)exec函式族的各個函式的定義與區別 在 linux 中使用exec函式族主要有兩種情況:
當程序認為自己不能再為系統和使用者做出任何貢獻時,就可以呼叫 exec 函式族中的任意乙個函式讓自己重生。
如果乙個程序想去執行另乙個程式,那麼它就可以呼叫fork()函式新建乙個程序,然後呼叫exec函式族中的任意乙個函式,指定新的程式來覆蓋子程序的**段、資料段、堆和棧。從而讓子程序去執行乙個新的程式,而不是執行父程序的副本。這樣看起來就像通過執行應用程式而產生了乙個新程序。 標頭檔案: #include 六個定義如下: int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char *const envp); int execv(const char *path, char *const ar**); int execvp(const char *file, char *const ar**); 上面 5 個函式屬於庫函式,這些函式都最終呼叫了下面的 execve 函式,這6個函式中,只有execve 函式屬於linux的系統呼叫 int execve(const char *path, char *const ar**, char *const envp); 這些函式在呼叫成功時不會返回,只有在呼叫出錯時才返回 -1 實際上六個函式的功能是差不多的, 只是因為c語言沒有函式的過載,所以接受不同的引數就要用不同的名字區分它們。
六個函式的命名是有規律的: exec[l or v][p][e] exec函式裡的引數可以分成3個部分,執行檔案部分,命令引數部分,環境變數部分. 環境變數部分是1個陣列,最後必須以null結尾 命名規則: e後續:引數必須帶環境變數部分,環境變零部分引數會成為執行exec函式期間的環境變數, 比較少用 l 後續:命令引數部分必須以"," 相隔, 最後1個命令引數必須是null v 後續:命令引數部分必須是1個以null結尾的字串指標陣列的頭部指標.例如char * pstr就是1個字串的指標, char * pstr 就是陣列了, 分別指向各個字串. p後續:執行檔案部分可以不帶路徑, exec函式會在$path中找
(4)fork與vfork的區別 呼叫fork()之後,子程序和父程序都會繼續執行fork呼叫之後的指令。子程序是父程序的副本。它將獲得父程序的資料空間,堆和棧的副本,這些都是副本,父子程序並不共享這部分的記憶體。 而用vfork建立的子程序與父程序共享位址空間,也就是說子程序完全執行在父程序的位址空間上,如果這時子程序修改了某個變數,這將影響到父程序。vfork的好處是在子程序被建立後往往僅僅是為了呼叫exec執行另乙個程式,因為它就不會對父程序的位址空間有任何引用,所以對位址空間的複製是多餘的 ,因此通過vfork共享記憶體可以減少不必要的開銷。 要注意的是用vfork()建立的子程序必須顯示呼叫exit()來結束,否則子程序將不能結束,而fork()則不存在這個情況。
(5)exit與_exit的區別 exit和_exit都是linux下的退出函式,exit作用是:直接使程序停止執行,清除其使用的記憶體空間,並銷毀其在核心中的各種資料結構;而exit是_exit函式的進一步封裝,執行了其他的清理工作,然後才呼叫_exit函式,在與輸入輸出或fork等函式一起使用時候會表現出一些差異 exit函式能保證資料的完整性,在退出之前會做些清理工作,然後再呼叫_exit再退出的;而_exit是直接退出程式,但最終兩者都會關閉程序開啟的檔案描述符,釋放記憶體。
(6)fork與clone這兩個系統呼叫的異同 系統呼叫fork()是無引數的,而clone()則帶有引數。fork()是全部複製, clone()是則可以將父程序資源有選擇地複製給子程序,而沒有複製的資料結構則通過指標的複製讓子程序共享,具體要複製哪些資源給子程序,由引數列表中的clone_flags來決定。另外,clone()返回的是子程序的pid
(5)對fork返回兩次的理解 fork是建立子程序,該程序是對父程序的完全複製,二者最大的區別只在於pid,如果要對子程序進行利用就需要分辨父子關係,fork兩次返回值就幫我們做到了這一點,返回0 就是對應的子程序,而返回pid的就是對應的父程序。
(6)了解五個常用系統呼叫 程序控制:getpid() linux函式庫的原型: #include/* 提供型別pid_t的定義 / #include/ 提供函式的定義 */ pid_t getpid(void); 返回值:目前程序的程序識別碼 getpid的作用很簡單,就是返回當前程序的程序id,許多程式利用取到的此值來建立臨時檔案, 以避免臨時檔案相同帶來的問題。 檔案讀寫:read() write() read(由已開啟的檔案讀取資料) #includessize_t read(int fd,void * buf ,size_t count); 函式說明:read()會把引數fd所指的檔案傳送count個位元組到buf指標所指的記憶體中。若引數count為0,則read()不會有作用並返回0。返回值為實際讀取到的位元組數,如果返回0,表示已到達檔案尾或是無可讀取的資料,此外檔案讀寫位置會隨讀取到的位元組移動。 p.s. 如果順利,read()會返回實際讀到的位元組數,最好能將返回值與引數count 作比較,若返回的位元組數比要求讀取的位元組數少,則有可能讀到了檔案尾、從管道(pipe)或終端機讀取,或者是read()被訊號中斷了讀取動作。當有錯誤發生時則返回-1,錯誤**存入errno中,而檔案讀寫位置則無法預期. write(將資料寫入已開啟的檔案內)
#includessize_t write (int fd,const void * buf,size_t count);
函式說明 write()會把引數buf所指的記憶體寫入count個位元組到引數fd所指的檔案內。當然,檔案讀寫位置也會隨之移動。 返回值 如果順利write()會返回實際寫入的位元組數。當有錯誤發生時則返回-1,錯誤**存入errno中。
檔案系統操作:access() 用於檢查呼叫程序是否可以對指定的檔案執行某種操作。 access函式用法:#include int access(const char *pathname, int mode);
linux access函式引數: pathname: 需要測試的檔案路徑名。 mode: 需要測試的操作模式,可能值是乙個或多個r_ok(可讀?), w_ok(可寫?), x_ok(可執行?) 或 f_ok(檔案存在?)組合體。 函式返回說明:成功執行時,返回0。失敗返回-1
記憶體管理:brk() 功能就是分配/**記憶體,一般用於**記憶體#include int brk(void* position)
引數position就是新位置,無論原來的位置在**。返回:成功返回0,失敗返回 -1。 brk系統呼叫,可以讓程序的堆指標增長一定的大小,邏輯上消耗掉一塊本程序的虛擬位址區間,malloc向os獲取的記憶體大小比較小時,將直接通過brk呼叫獲取虛擬位址,結果是將本程序的brk指標推高
5.錯誤 1)想呼叫execve的時候傳進當前子程序的pid結果失敗 解決方案:這其實是對於execve的傳參,以及shell傳參不夠清楚的問題 查詢對execve( )定義如下: int execve( char *pathname, char *ar**[ ], char *envp[ ]) 第乙個引數是命令所在路徑,第二個引數是命令集合,第三個是傳遞給執行檔案的環境變數集。一般對於envp預設引數是null,那麼如果要將實驗一與實驗二結合,就需要呼叫自己寫的.sh**,將程序的pid傳進檔案中。 困難1:一開始我是想直接將定義的pid傳入,結果發現pid_t是乙個int型別的定義,然後這樣定義的pid只有1、0、-1三種情況,並不是真正的子程序的pid。 困難2:如果用getpid()得到子程序的pid,那麼返回的值是乙個int類,就需要將其轉換成乙個字串。【在這裡發現直接呼叫c程式裡面的itoa()失敗,但是標頭檔案沒有問題,可能是編譯環境問題?最終我用sprintf()自己寫了乙個itoa() 】 困難3:直接在execve裡面傳入轉換後的字串是會報錯的,傳入指標也是。主要是因為在定義的時候已經規定了必須傳入乙個指標陣列(第二個引數),所以傳入其他的情況會失敗。 困難4:.sh報錯(在第二點談),傳入的引數沒有顯示,導致sh這個程序無法執行。 查詢對shell傳遞引數如下: 指令碼內獲取引數的格式為:$n n代表乙個數字,1 為執行指令碼的第乙個引數,2 為執行指令碼的第二個引數... 以$ ./test.sh 1 2 這個語句為例,$0表示執行檔名(./test.sh),$1 = 1 $2=2 由此,我將sh 內容做了處理:首先開頭改為#!/bin/bash,然後直接令pid=$1; 最後當我從c程式裡面呼叫execve() 的時候如果同時將子程序pid 傳入,該s**件將會根據該pid做出相應的處理。
2)呼叫自己的.sh的時候報錯/無法退出:全部都說 :不是有效識別符號read: 之類的內容,估計是windows環境下sublime沒有轉換格式的問題,當我換成notepad++開啟s**件並將環境調成unix就沒有問題了。
3)想同時呼叫六個exec 系列函式但是失敗:因為只建立了乙個子程序,以及,函式在執行期間會被相互覆蓋。(同時發生的緣故)
作業系統 程序控制
附錄 1.程序的建立 include includemain 2.程序的互斥 1 同步 include includemain else else 2 互斥 include includemain else else 3.程序的軟中斷通訊 include include includevoid wa...
作業系統 程序控制
知識總結 目錄 程序控制 1.程序建立 使用者登入,作業排程,列印等會導致新程序的建立 原語 原語 作業系統或 計算機網路用語範疇。是由若干條指令組成的,用於完成一定功能的乙個過程。是由若干個 機器指令 構成的完成某種特定功能的一段程式,具有不可分割性 即原語的執行必須是連續的,在執行過程中不允許被...
作業系統 程序控制
程序控制的主要功能是對系統中的所有程序實施有效的管理,它具有建立新程序 撤銷已有程序 實現程序狀態轉換等功能。簡化理解 程序控制就是要實現程序狀態的轉換。用原語實現程序控制。原語的特點是執行期間不允許中斷,只能一氣呵成。這種不可中斷的操作即原子操作。原語採用 關中斷 指令和 開中斷指令 實現。關中斷...