第二章 進程
作者:Allen B. Downey
原文:Chapter 2 Processes
譯者:飛龍
協議:CC BY-NC-SA 4.0
2.1 抽象和虛擬化
在我們談論進程之前,我打算先定義幾個東西:
-
抽象(Abstraction):抽象是復雜事物的簡單表示。例如,如果你開車的話,應該知道車輪向左轉的時候車也會向左行駛,反之亦然。當然,方向盤由一系列機械和傳動系統所連接,用于使輪子轉向,并且輪子和路面的相互作用方式也很復雜。但是作為一個司機,你通常不需要考慮這些細節。你可以僅僅建立方向盤的心智模型,這種心智模型就是一個抽象。
軟件工程的很大一部分就是設計類似這樣的抽象,允許用戶和其它程序員使用強大而復雜的系統,而不必知道其實現的細節。
-
虛擬化(Virtualization):一類非常重要的抽象就是虛擬化,它是創建可取的幻像的過程。例如,許多公共圖書館都參與了館際合作,允許它們互相借閱圖書。當我需要一本書時,有時它在我的本地圖書館的架子上,但更多情況下它會被運到其它的館藏中。無論是哪一種,我都會收到它可借閱的提醒。我并不需要知道它來自哪里,我也不需要知道我的圖書館擁有哪一本書。一般來說,這個系統創建了一個幻象,好像我的圖書館擁有全世界的每一本書。
在物理上,我的圖書館的館藏可能很小,但是虛擬上我能獲得的館藏包含了館際合作的每一本書。另外一個例子,大多數電腦都只連接到一個網絡中,而這個網絡又鏈接到其它網絡,等等。我們所談論的“互聯網”,是一系列網絡和協議的合集,它將數據包從一個網絡傳送到另一個網絡。從用戶和程序員的角度來看,整個系統的行為就像是互聯網的每臺計算機都互相連接。物理連接的數量十分少,但是虛擬連接的數量十分龐大。
“虛擬”這個詞通常用于虛擬機的語境中,它是一種軟件,可以創建運行特定系統的專用計算機的幻象。實際上,虛擬機可能和其它虛擬機一起運行在不同的操作系統上。
在虛擬化的語境中,我們通常把真實發生的事情叫做“物理的”,而把虛擬上發生的事情叫做“邏輯的”或者“抽象的”。
2.2 隔離
工程最重要的原則之一就是隔離(Isolation):當你設計一個帶有多個組件的系統時,將它彼此隔離是個很好的方法,這樣某個組件中的改變就不會對其它組件造成不良影響。
操作系統最重要的目標之一,就是將每個進程和其它進程隔離,使程序員不必考慮每個可能的交互情況。提供這種隔離的軟件對象叫做進程(Process)。
進程是表示運行中程序的軟件對象。我按照面向對象編程把它稱之為“軟件對象”。工程一個對象包含數據,并且提供用于操作數據的方法。進程正是包含以下數據的對象:
程序文本,通常是機器語言的指令序列。
程序相關的數據,包括靜態數據(編譯時分配)和動態數據,后者包括運行時的棧和堆。
任何等待中的IO狀態。例如,如果進程正在等待從磁盤中讀取的數據,或者從網絡到達的數據包,這些操作的狀態也是進程的一部分。
程序的硬件狀態,這包括儲存在寄存器中的數據,狀態信息,以及程序計數器,它表示當前執行了哪個指令。
通常一個進程運行一個程序,但是對于進程來說,加載并運行新的程序也是可能的。
也可以在多于一個進程中運行相同的程序,這非常常見。這種情況下,各個進程共享程序文本,但是擁有不同的數據和硬件狀態。
大多數操作系統提供了隔離進程的基本功能:
多任務:大多數操作系統有能力在幾乎任何時候中斷一個進程,保存它的硬件狀態,并且在以后恢復它。通常,程序員不需要考慮這些中斷。程序的行為就像在一個專用的處理器上持續運行,除了兩條指令之間的時間是不可預測的。
虛擬內存:大多數操作系統會創建幻象,每個進程看似擁有獨立內存片并且孤立于其他進程。同樣,程序員通常也不需要考慮虛擬內存如何工作,他們可以當做每個程序都擁有專用的內存片來處理。
設備抽象:運行于同一臺計算機的進程共享磁盤、網絡接口、顯卡和其它硬件。如果進程直接和這些硬件交互而不加協調,就一定會產生混亂。例如,一個進程預期的網絡數據可能會被另一個進程讀取。或者多個進程可能嘗試在磁盤的相同位置儲存數據。操作系統負責通過提供合適的抽象來維持秩序。
作為程序員,你不需要知道太多關于這些功能如何實現的事情。但是如果你很好奇,你可以在這個屏蔽層的后面發現一大堆有趣的事情。而且,如果你知道其中所發生的事情,你會成為更好的程序員。
2.3 Unix 進程
當我寫這本書的時候,我最關注的進程就是我的文本編輯器,Emacs。偶爾我也會切換到終端窗口,它是一個運行Unix shell并提供命令行接口的窗口。
當我移動鼠標時,窗口的管理器會被喚醒,看到鼠標在終端窗口上方,并且喚醒終端。終端又喚醒shell。如果我在shell中鍵入make
,它就會創建一個新的進程來運行Make。Make會創建另一個進程來運行LaTeX,之后另一個進程會顯示結果。
如果我需要查詢一些東西,我會切換到另一個桌面,這會再次喚醒窗口管理器。如果我點擊Web瀏覽器的圖標,窗口管理器會創建進行來運行Web瀏覽器。許多瀏覽器,類似Chrome,會為每個窗口和每個選項卡創建新的進程。
并且這些只是我所了解的進程,同時還有許多其它進程“在后臺”運行。它們中許多都在執行操作系統相關的工作。
Unix命令ps
能打印出運行中進程的信息。如果你在終端里運行它,可能會看到這些:
PID TTY TIME CMD2687 pts/1 00:00:00 bash2801 pts/1 00:01:24 emacs
24762 pts/1 00:00:00 ps
第一列是唯一的進程ID。第二列是創建進程的終端,“TTY”代表“電傳打字機”(Teletypewriter),它是原始的機械終端。
第三行是用于該進程的處理器時間總計,依次為時、分、秒。最后一行是所運行進程的名稱。這個例子中,bash
是shell的名稱,用于解釋我鍵入到終端中的命令。Emacs是我的文本編輯器,而ps
是生成這份輸出的程序。
通常,ps
只會列出有關當前終端的進程。如果你使用-e
選項,你會得到所有進程(也包括屬于其他用戶的進程,我認為這是個安全缺陷)。
在我的系統上有233個進程,下面是它們的一部分:
PID TTY TIME CMD1 ? 00:00:17 init2 ? 00:00:00 kthreadd3 ? 00:00:02 ksoftirqd/04 ? 00:00:00 kworker/0:08 ? 00:00:00 migration/09 ? 00:00:00 rcu_bh10 ? 00:00:16 rcu_sched47 ? 00:00:00 cpuset48 ? 00:00:00 khelper49 ? 00:00:00 kdevtmpfs50 ? 00:00:00 netns51 ? 00:00:00 bdi-default52 ? 00:00:00 kintegrityd53 ? 00:00:00 kblockd54 ? 00:00:00 ata_sff55 ? 00:00:00 khubd56 ? 00:00:00 md57 ? 00:00:00 devfreq_wq
init
是操作系統啟動時首先創建的進程。它又會創建許多其它進程,之后會閑置,直到它創建的進程運行完畢。
kthreadd
是操作系統用于創建新的“線程”的進程。之后我們將會談論更多關于線程的東西,但是你暫時你可以認為線程是一種進程。