原文地址: What happens when you start a process on Linux?
這是關於fork和exec在Unix上的工作方式。你可能已經知道這一點,但有些人卻不知道,並且在幾年前當我知道的時候我感到很驚訝!
所以,你想開啟一個程式。在此部落格上,我們討論了很多系統呼叫 –每當你啟動一個程式或開啟檔案時,這就是系統呼叫。所以你可能會認為存在這樣的系統呼叫
start_process(["ls", "-l", "my_cool_directory"])
這是一個合理的考慮,顯然這是它在DOS / Windows中的工作方式。我要說的是這不是 Linux上的工作方式。我去看了看文件,表面上有一個叫posix_spawn的系統呼叫基本上可以做到這一點。這就是我所知道的。無論如何,我們不會談論這個。
fork and exec
Linux上的posix_spawn
幕後操作是透過稱為fork
和exec
(實際上是execve
)的2個系統呼叫來實現的 ,這是人們通常實際使用的。在OS X上,顯然不鼓勵人們使用posix_spawn
和fork / exec!但是我們將討論的是Linux。
Linux中的每個程式都位於“程式樹”中。你可以透過執行pstree
看到那棵樹 。樹的根是PID為1 的init
。每個程式(init除外)都有一個父程式,而任何程式都有很多子程式。
因此,假設我要啟動一個名為ls
的列出目錄過程。我只有一個孩子ls
嗎?沒有!
我要做的是代替你生一個孩子,而這個孩子是我自己的一個克隆人,然後這個孩子的大腦被吞噬並變成ls
。
我們開始像這樣:
my parent
|- me
然後我跑fork()
。我有一個孩子,是我的克隆人。
my parent
|- me
|-- clone of me
然後我整理一下,以便我的孩子執行exec("ls")
。那讓我們
my parent
|- me
|-- ls
當ls退出時,我將再次獨自一人。大部分是這樣的
my parent
|- me
|-- ls (zombie)
此時,ls實際上是一個殭屍程式!這意味著它已經死了,但是它在等我,以防我想檢查它的返回值(使用wait
系統呼叫)。一旦獲得了它的返回值,我真的將再次變得孤單。
my parent
|- me
程式碼中的fork和exec看起來像什麼
如果你要編寫一個shell,這是你必須做的練習之一(這是一個非常有趣且有啟發性的專案!Kamal在Github上舉辦了一個很棒的講習班,介紹瞭如何做的事情:https : //github.com/ kamalmarhubi / shell-workshop)
事實證明,透過一些工作和一些C或Python技能,你可以在短短几個小時內(至少如果你旁邊有一個知道在做什麼的人,如果不知道時間會長一些)用C或Python編寫一個非常簡單的shell(如bash!)。他們正在做,如果沒有的話,要更長的時間:))。我已經做到了,這太棒了。
無論如何,這是fork和exec在程式中的外觀。我寫了偽造的C虛擬碼。請記住, fork can fail!
int pid = fork();
// now i am split in two! augh!
// who am I? I could be either the child or the parent
if (pid == 0) {
// ok I am the child process
// ls will eat my brain and I'll be a totally different process
exec(["ls"])
} else if (pid == -1) {
// omg fork failed this is a disaster
} else {
// ok i am the parent
// continue my business being a cool program
// I could wait for the child to finish if I want
}
好吧,朱莉婭,大腦被吃掉對你意味著什麼
流程有很多屬性!
你有
- 開啟檔案(包括開啟的網路連線)
- 環境變數
- 訊號處理程式(在程式上執行Ctrl + C會發生什麼?)
- 一堆記憶體(你的“地址空間”)
- 暫存器
- 你執行的“可執行檔案”(/ proc / $ pid / exe)
- cgroups和名稱空間(“ linux容器”)
- 當前工作目錄
- 你的程式執行使用者
- 我忘記的一些其他東西
當你執行execve
並有另一個程式吞噬了你的大腦時,實際上幾乎所有內容都保持不變!你擁有相同的環境變數和訊號處理程式以及開啟的檔案等等。
唯一改變的是,所有的記憶體和暫存器以及你正在執行的程式。這是一個很大的問題。
為什麼fork不是超級昂貴(或:寫時複製)
你可能會問“朱莉婭,如果我有一個使用2GB記憶體的程式該怎麼辦!這是否意味著每次啟動子程式時,都會複製所有2GB的記憶體?聽起來很貴!”
事實證明,Linux為fork()呼叫實現了“寫時複製”,因此對於新程式中的所有2GB記憶體,就像“看看舊程式!一樣的!”。然後,如果任何一個程式寫入了任何記憶體,那麼它將開始複製。但是,如果兩個程式的記憶體都相同,則無需複製!
為什麼你可能會關心這一切
好的,茱莉亞,這很酷的瑣事,但為什麼重要呢?有關哪些訊號處理程式或環境變數被繼承的細節,或實際上對我的日常程式設計有什麼影響的細節?
也許會!例如,Kamal的部落格中存在一個令人愉快的錯誤。它討論了Python如何設定SIGPIPE忽略的訊號處理程式。因此,如果你從Python內部執行程式,則預設情況下它將忽略SIGPIPE!這意味著該程式的行為會有所不同,具體取決於你是從Python指令碼還是從Shell啟動它!在這種情況下,它導致了一個奇怪的錯誤!
因此,程式的環境(環境,訊號處理程式等)可能很重要!它是從其父程式繼承其環境的,無論那是什麼!在除錯時,有時這可能是有用的事情。
本作品採用《CC 協議》,轉載必須註明作者和本文連結