第二章 程式
譯者:飛龍
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 CMD
2687 pts/1 00:00:00 bash
2801 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 CMD
1 ? 00:00:17 init
2 ? 00:00:00 kthreadd
3 ? 00:00:02 ksoftirqd/0
4 ? 00:00:00 kworker/0:0
8 ? 00:00:00 migration/0
9 ? 00:00:00 rcu_bh
10 ? 00:00:16 rcu_sched
47 ? 00:00:00 cpuset
48 ? 00:00:00 khelper
49 ? 00:00:00 kdevtmpfs
50 ? 00:00:00 netns
51 ? 00:00:00 bdi-default
52 ? 00:00:00 kintegrityd
53 ? 00:00:00 kblockd
54 ? 00:00:00 ata_sff
55 ? 00:00:00 khubd
56 ? 00:00:00 md
57 ? 00:00:00 devfreq_wq
init
是作業系統啟動時首先建立的程式。它又會建立許多其它程式,之後會閒置,直到它建立的程式執行完畢。
kthreadd
是作業系統用於建立新的“執行緒”的程式。之後我們將會談論更多關於執行緒的東西,但是你暫時你可以認為執行緒是一種程式。