linux建立進程的命令:1、fork命令,可以從已存在進程中建立一個新進程,該進程為子進程,而原始進程為父進程;子進程完全複製父進程的資源。 2.vfork指令,所建立的子程序與父行程共享位址空間,也就是說子程序完全運行在父程序的位址空間。 3.clone命令,可以將父進程資源選擇性地複製給子進程,而沒有複製的資料結構則透過指標的複製讓子進程共享。
本教學操作環境:linux7.3系統、Dell G3電腦。
Linux系統種創建程序有fork、vfork、clone這個三個指令可供使用。
fork
fork建立一個行程時,子行程只是完全複製父行程的資源,複製出來的子行程有自己的task_struct結構和pid,但卻複製父進程其它所有的資源。例如,如果父進程打開了五個文件,那麼子進程也有五個打開的文件,而且這些文件的當前讀寫指針也停在相同的地方。所以,這一步所做的就是複製。這樣得到的子進程獨立於父進程, 具有良好的並發性,但是二者之間的通訊需要透過專門的通訊機制,如:pipe,共享內存等機制, 另外透過fork創建子進程,需要將上面描述的每種資源都複製一個副本。這樣看來,fork是一個開銷十分大的系統調用,這些開銷並不是所有的情況下都是必須的,比如某進程fork出一個子進程後,其子進程僅僅是為了調用exec執行另一個可執行文件,那麼在fork過程中對於虛存空間的複製將是一個多餘的過程。但由於現在Linux中是採取了copy-on-write(COW寫時複製)技術,為了降低開銷,fork最初並不會真的產生兩個不同的拷貝,因為在那個時候,大量的數據其實完全是一樣的。寫時複製是在延遲真正的資料拷貝。若後來確實發生了寫入,那意味著parent和child的資料不一致了,於是產生複製動作,每個進程拿到屬於自己的那一份,這樣就可以降低系統呼叫的開銷。所以有了寫時複製後呢,vfork其實現意義就不大了。
fork()呼叫執行一次返回兩個值,對於父進程,fork函數返回子程序的進程號,而對於子程序,fork函數則返回零,這就是一個函數返回兩次的本質。
在fork之後,子程序和父程序都會繼續執行fork呼叫之後的指令。子進程是父進程的副本。它將獲得父進程的資料空間,堆疊和堆疊的副本,這些都是副本,父子進程並不共享這部分的記憶體。也就是說,子行程對父行程中的同名變數進行修改並不會影響其在父行程中的值。但是父子進程又共享一些東西,簡單說來就是程式的正文段。正文段存放著由cpu執行的機器指令,通常是read-only的。
vfork
vfork系統呼叫不同於fork,用vfork創建的子程序與父進程共享地址空間,也就是說子進程完全運行在父進程的在位址空間上,如果這時子程序修改了某個變量,這將影響到父進程。
因此,上面的例子如果改用vfork()的話,那麼兩次印出a,b的值是相同的,所在位址也是相同的。
但這裡有一點要注意的是用vfork()建立的子程序必須顯示呼叫exit()來結束,否則子程序將不能結束,而fork()則不存在這個情況。
Vfork也是在父進程中回傳子進程的進程號,在子進程中回傳0。
用 vfork建立子程序後,父進程會被阻塞直到子程序呼叫exec(exec,將一個新的可執行檔載入到位址空間並執行之。)或exit。 vfork的好處是在子進程被創建後往往只是為了調用exec執行另一個程序,因為它就不會對父進程的地址空間有任何引用,所以對地址空間的複製是多餘的,因此通過vfork共享記憶體可以減少不必要的開銷。
clone
系統呼叫fork()和vfork()是無參數的,而clone()則帶有參數。 fork()是全部複製,vfork()是共享內存,而clone() 是則可以將父進程資源有選擇地複製給子進程,而沒有復制的數據結構則通過指針的複製讓子進程共享,具體要複製哪些資源給子進程,由參數清單中的clone_flags來決定。另外,clone()回傳的是子進程的pid。
下面詳細了解fork指令(進程建立)。
深入fork 函數
#在Linux 中fork 函數是一個非常重要的函數,它從已存在進程中建立一個新進程。新進程為子進程,而原進程為父進程。
fork 函數的回傳值:
- 給父行程回傳子程序的pid
- 給子程序回傳0
#接下來我們舉例使用fork函數()
# 我們編譯,然後執行:
一個行程要執行一個不同的程式。例如子進程從fork返回後,呼叫exec函數。
#fork 的常規用法
- 一個父程序希望複製自己,使父子程序同時執行不同的程式碼段。例如,父進程等待客戶端請求,產生子進程來處理請求。
實際用戶的進程數超過了限制
fork呼叫失敗的原因
- #系統中有太多的程序
##fork() 創建子進程,作業系統做了哪些操作?在重溫了一下fork 函數的使用後,接下來我們來研究一個主題:
進程呼叫fork,當控制權轉移到核心中的fork程式碼後,核心做了以下操作:
- ##分配新得記憶體區塊和內核資料結構給子程序。
- 將父行程部分資料結構內容拷貝至子程序。
- 將子進程新增到系統進程清單中。
- fork返回,開始調度器調度。
父進程執行完fork之前的程式碼(before)後,呼叫fork 建立子進程,父子兩個執行流分別執行。注意:fork 之後,誰先執行完全由調度器決定。
這裡還有一個問題,當fork之後,父子進程程式碼共享是 after 共享,還是所有程式碼都進行共享?為什麼子程序總準確地執行 fork 之後對應的程式碼?
答案: 所有程式碼共享,因為CPU記錄了進程的執行位置。
- 程式碼進行彙編之後,會有很多行程式碼,而且每行程式碼載入到記憶體之後,都有對應的位址。
- 因為行程隨時都可能被中斷(可能並沒有執行完),下次繼續執行時,還必須從之前的位置繼續運行(並不是程式最開始或main函數處),這就要求CPU 必須即時記錄下目前進程執行的位置。
- 所以,CPU內有對應的暫存器數據,用來記錄目前程序的執行位置,此暫存器叫做EIP,也稱為為pc(point code 程式計數器),用來記錄正在執行程式碼的下一行代碼的地址(上下文資料)。
- 當子進程建立時,會修改其EIP。此時子進程便會認為EIP的中保存下的數據,就是要執行的程式碼。
寫時拷貝
#OS 為何採用寫時拷貝技術,對父子程序進行分離
- 寫入時再進行拷貝,是高效率使用記憶體的一種表現。
- 提高了系統的運作效率。
- OS無法在程式碼執行前預知哪些空間會被存取。
程式終止
當進程終止時,作業系統釋放了進程申請的相關內核資料結構和對應的程式碼的數據,其本質就是釋放系統資源,
1、進程退出碼
程式終止的常見方式:區分第一種情況和第二種情況我們可以透過進程的退出碼很清楚的辨別。 關於學習的 C語言中 main 函數的回傳值,其中 main 函數的回傳值就是進程的退出碼。其意義是返回給上一層進程,用來評判該進程執行結果。 ###
- 程式碼跑完,結果正確。
- 程式碼跑完,結果不正確。
- 程式碼沒有跑完,程式崩潰了。
現在我們寫一個簡單的 C 程式。
然後我們可以透過 echo $? 來取得最近一個行程的退出碼。
進程的回傳值有0 和非0兩種情況,其中0 表示程式成功運行並結果正確,而非0表示成功運行但結果有誤,非零值有無數個,不同的非零值就可以表示不同的錯誤,方便我們定義錯誤的原因。
那常見的錯誤訊息有哪些呢?
我們可以使用strerror 將其印出來
# 結果如下:
## 發現linux 下,共有133個錯誤碼當然,程式崩潰的時候,退出碼沒有意義。
2、exit 與 _exit
關於終止一個程序可以使用return 語句,也可以呼叫exit 和_exit 函數exit函數: _exit函數:關於這兩個函數的差異有很多,我們先舉一個小例:我們接下來使用printf 列印一則訊息,然後sleep三秒,再使用exit 退出,並觀察結果 因為我們帶了\n ,加上\n 會刷新緩衝區,螢幕上即出現我們印製的內容。 如果我們不帶\n ,我們再觀察結果: 發現:因為沒有\n ,所以printf 中的內容並沒有在休眠前被印出來,而是在呼叫exit 後將緩衝區的內容刷新出來輸出在螢幕上。 接下來我們使用_exit 函數 運行可執行檔b: 發現什麼內容都沒有被列印出來,使用echo $? 列印最近進程退出碼,發現b 檔案確實被執行了。 這說明,exit是函式庫函數,而_exit 是系統調用,其退出進程時並沒有刷新緩衝區中的內容。 此時我們便能得出一個結論:printf 資料是保存在"緩衝區"中的,exit可以將其刷新,而系統呼叫介面_exit不能將其刷新。所以,緩衝區必定不在作業系統內部,而是由C標準函式庫維護的。 相關推薦:《
Linux影片教學》
以上是linux 建立進程指令是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!