在Linux上啟動程式時會發生什麼?

Brewin發表於2020-09-25

原文地址: 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幕後操作是透過稱為forkexec(實際上是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 協議》,轉載必須註明作者和本文連結

相關文章