作為現代操作系統的代表之一,Linux操作系統非常復雜,內部有多得令人眼花繚亂的各種組件在同步運行和相互通信。對于初學者來說,我認為理解操作系統工作原理最好的方法是利用抽象的思維去理解,也就是說,你可以暫時忽略大部分細節。就像坐車一樣,通常你不會去在意車內固定發動機的裝配螺栓,也不會關心你走的路是誰修筑的。如果你是一名乘客,可能只會關心如何打開或關閉車門、如何系好安全帶以及車要把你帶到哪兒去。如果你是一名司機,就需要了解更多的細節,比如如何控制油門、剎車和換擋,以及如何處理意外情況。如果你是一名維修工程師或汽車設計師,則需要更深入地了解汽車構造及其工作原理了。
我們試一下運用“抽象思維”來理解開車這件事情,首先我們可以將“一輛汽車在路上行駛”抽象為三個部分:汽車、道路和駕駛操作。這樣一來,開車這件事情就簡單多了,我們幾乎只需要知道駕駛操作即可。如果道路顛簸,也不會去埋怨汽車本身和自己的駕駛技術,反而我們會問這條路為什么這么爛,而我們是不是一定要走這條路。同樣,在軟件開發過程中,開發人員通常不用太關心他們需要使用的組件的內部結構,他們只關心能使用哪些組件,以及這些組件該怎么用。跟汽車零部件一樣,每一個組件都可能包含著復雜的技術細節,但我們可以暫時忽略這些細節,而專注于這些組件在系統中發揮的功能。實際上,抽象思維形成的這種“分層思想”無論在計算機技術還是其他社會生產活動中都是適用的。
Linux操作系統的層次
下面我們來看一下,通過抽象可以將系統分解為哪些組件,以及這些組件在用戶和硬件系統之間所處的位置。
簡單來說,Linux操作系統可以大體分為三層,如下圖所示,最底層是硬件系統,包括CPU、內存、硬盤、網卡等;硬件系統之上是內核,這是操作系統的核心,內核負責管理硬件系統,同時為應用程序提供操作接口;用戶進程在這里表示計算機中運行的所有程序,它們運行于用戶空間,由內核統一管理。
內核和用戶進程之間最大的區別在于:內核運行于內核模式(kernel mode,也稱內核態),用戶進程運行于用戶模式(user mode,也稱用戶態)。在內核模式中運行的代碼可以不受限制地訪問中央處理器和內存,也就是說內核可以為所欲為,那這就非常危險了,因為內核進程可以輕而易舉地使整個系統崩潰。所以為了提高系統穩定性,限制進程對中央處理器和內存的訪問權限,提出了用戶模式的概念。
一般我們將只有內核可以訪問的空間稱為內核空間,而將用戶進程能夠訪問的空間稱為用戶空間。通過這種限制,即使某個用戶進程運行時崩潰了,也不會對整個系統造成嚴重的影響。
內核模式和用戶模式
實際上,內核模式和用戶模式是需要處理器支持的。內核程序和用戶程序的本質區別在于:除了可以執行大部分通用指令,內核程序還可以執行特權指令。說到計算機指令,就不得不提到RISC(Reduced Instruction Set Computer,精簡指令集)和CISC(Complex Instruction Set Computer,復雜指令集),我們知道Intel的x86架構芯片采用的是CISC,而ARM架構芯片則采用RISC。也就是說,內核模式和用戶模式之間的切換以及模式的實現依托于CPU指令集架構。
Intel的x86處理器通過Ring級別來進行訪問控制,共分為4個級別,即Ring0~Ring3。Ring0層擁有的權限最高,Ring3層擁有的權限最低。按照Intel原來的設想,應用程序工作在Ring3層,只能訪問Ring3層的空間;操作系統工作在Ring0層,可以訪問所有層的空間;而其他驅動程序工作與Ring1和Ring2層,每一層只能訪問本層和權限更低層的數據。這種設計可以有效保障操作系統的穩定性和安全性。但是現代操作系統,包括Windows和Linux都沒有采用4層權限,只使用了Ring0和Ring3層,對應于內核空間和用戶空間。因此,驅動一旦加載,就運行在Ring0層,擁有與操作系統內核一樣的權限。
和x86架構不同,ARM沒有Ring0~Ring3,也不存在Root模式和非Root模式。眾所周知,ARM有7種工作模式,即usr(用戶模式,User)、fiq(快速中斷模式,FIQ)、irq(外部中斷模式,IRQ)、svc(管理模式,Supervisor)、abt(數據訪問中止模式,Abort)、und(未定義指令中止模式,Undef)和sys(系統模式,System)。除了用戶模式以外的其他6種處理器模式都稱為特權模式(Privileged Modes)。在特權模式下,程序可以訪問所有的系統資源,也可以任意地進行處理器模式切換。
除此之外,還有在ARM v6中引入的Security Extensions帶來的Monitor模式,以及在ARM v7中引入的Virtualization Extensions帶來的Hyp模式。對于ARM v8架構則更為復雜一些,它定義了兩種執行狀態(Execution State),分別是AArch32狀態和 AArch64狀態。同時定義了4個異常等級(Exception Level)來進行權限控制,分別是EL0~EL3。對于AArch32,ARMv8定義了9種PE模式(也就是上面提到的9種工作模式)來確定執行權限,而不使用EL;而對于AArch64,則不支持PE模式。(更多關于處理器架構的信息,請查閱相關手冊)。
內存的作用
除了CPU,內存可以說是是硬件系統中最為重要的部分。內存中存儲的是0或1這樣的比特數據,內核和進程也都是運行在內存里面的,它們在內存中就是一系列的比特數據集合,所有外圍設備的數據輸入和輸出都通過內存完成。而CPU就像一個操作員一樣處理內存中的數據,它從內存讀取指令和數據,然后將運算結果寫回內存。Linux內核幾乎所有的操作都和內存有關,例如:將內存劃分為很多區塊,并且一直維護著這些區塊的狀態信息;每一個進程擁有自己的內存區塊,并且由內核保證每個進程只使用它自己的內存區塊。
Linux內核
Linux內核采用的是整體式結構(Monolithic),整個內核是一個單獨的、非常大的程序,這樣雖然能夠使系統的各個部分直接溝通,提高系統相應速度,但與嵌入式系統存儲容量小、資源有限的特點不相符合。因此,在嵌入式系統中經常采用的是另一種稱為微內核(Microkernel)的體系結構,即內核本身只提供一些最基本的操作系統功能,如任務調度、內存管理、中斷處理等,而類似于文件系統和網絡協議等附加功能則運行在用戶空間中,并且可以根據實際需要進行取舍。這樣可以大大減小內核的體積,便于維護和移植。
對于Linux這樣一個宏內核操作系統來說,一個完整的Linux內核主要由五個子系統組成:進程調度,內存管理,虛擬文件系統,網絡接口,進程間通信。
·進程調度(SCHED)控制進程對CPU的訪問。當需要選擇下一個進程運行時,由調度程序選擇最值得運行的進程??蛇\行進程實際上是僅等待CPU資源的進程,如果某個進程在等待其它資源,則該進程是不可運行進程。Linux使用了比較簡單的基于優先級的進程調度算法選擇新的進程。
·內存管理(MM)允許多個進程安全的共享主內存區域。Linux 的內存管理支持虛擬內存,即在計算機中運行的程序,其代碼,數據,堆棧的總量可以超過實際內存的大小,操作系統只是把當前使用的程序塊保留在內存中,其余的程序塊則保留在磁盤中。必要時,操作系統負責在磁盤和內存間交換程序塊。內存管理從邏輯上分為硬件無關部分和硬件有關部分。硬件無關部分提供了進程的映射和邏輯內存的對換;硬件相關的部分為內存管理硬件提供了虛擬接口。
·虛擬文件系統(Virtual File System,VFS)隱藏了各種硬件的具體細節,為所有的設備提供了統一的接口,VFS提供了多達數十種不同的文件系統。虛擬文件系統可以分為邏輯文件系統和設備驅動程序。邏輯文件系統指Linux所支持的文件系統,如ext2,fat等,設備驅動程序指為每一種硬件控制器所編寫的設備驅動程序模塊。
·網絡接口(NET)提供了對各種網絡標準的存取和各種網絡硬件的支持。網絡接口可分為網絡協議和網絡驅動程序。網絡協議部分負責實現每一種可能的網絡傳輸協議。網絡設備驅動程序負責與硬件設備通訊,每一種可能的硬件設備都有相應的設備驅動程序。
·進程間通訊(IPC)支持進程間各種通信機制。進程間通信主要用于控制不同進程之間在用戶空間的同步、數據共享和交換。由于不用的用戶進程擁有不同的進程空間,因此進程間的通信要借助于內核的中轉來實現。一般情況下,當一個進程等待硬件操作完成時,會被掛起;當硬件操作完成,進程被恢復執行,而協調這個過程的就是進程間的通信機制。
Linux內核子系統的結構如下圖所示,處于中心位置的進程調度,所有其它的子系統都依賴它,因為每個子系統都需要掛起或恢復進程。一般情況下,當一個進程等待硬件操作完成時,它被掛起;當操作真正完成時,進程被恢復執行。例如,當一個進程通過網絡發送一條消息時,網絡接口需要掛起發送進程,直到硬件成功成功地完成消息的發送,當消息被成功的發送出去以后,網絡接口給進程返回一個代碼,表示操作的成功或失敗。其他子系統以相似的理由依賴于進程調度。
各個子系統之間的依賴關系如下:進程調度與內存管理之間的關系:這兩個子系統互相依賴。在多道程序環境下,程序要運行必須為之創建進程,而創建進程的第一件事情,就是將程序和數據裝入內存。
進程間通信與內存管理的關系:進程間通信子系統要依賴內存管理支持共享內存通信機制,這種機制允許兩個進程除了擁有自己的私有空間,還可以存取共同的內存區域。
虛擬文件系統與網絡接口之間的關系:虛擬文件系統利用網絡接口支持網絡文件系統(NFS),也利用內存管理支持RAMDISK設備。
內存管理與虛擬文件系統之間的關系:內存管理利用虛擬文件系統支持交換,交換進程(swapd)定期由調度程序調度,這也是內存管理依賴于進程調度的唯一原因。當一個進程存取的內存映射被換出時,內存管理向文件系統發出請求,同時,掛起當前正在運行的進程。
除了這些依賴關系外,內核中的所有子系統還要依賴于一些共同的資源。這些資源包括所有子系統都用到的過程。例如:分配和釋放內存空間的過程,打印警告或錯誤信息的過程,還有系統的調試例程等等。
內核管理的四個方面
Linux內核負責管理以下四個方面:
·進程:內核決定哪個進程可以使用CPU。
·內存:內核管理所有的內存,為進程分配內存,管理進程間的共享內存以及空閑內存。
·設備驅動程序:作為硬件系統(如磁盤)和進程之間的接口,內核負責操控硬件設備。
·系統調用和支持:進程通常使用系統調用和內核進行通信。
進程管理
進程管理設計進程的啟動、暫停、恢復和終止。啟動和終止比較直觀,但是要解釋清楚進程在執行過程中如何使用CPU則相對復雜一些。
在現代操作系統中,很多進程貌似都是同時運行的。例如,你可以同時在桌面打開Web瀏覽器和電子表格應用程序。然而,雖然它們表面上看是同時運行,但實際上這些應用程序背后的進程并不完全是同時運行的。
我們設想一下,在只有一個CPU的計算機系統中,可能會有很多進程可以使用CPU,但是在任何一個特定的時間段內只能有一個進程可以使用CPU。所以實際上是多個進程輪流使用CPU,每個進程使用一段時間后就暫停,然后讓另一個進程使用,依次輪流,時間單位是毫秒級。一個進程讓出CPU使用權給另一個進程稱為上下文切換(context switch)。
進程在其時間段內有足夠的時間完成主要的計算工作(實際上,進程通常在單個時間段內就能完成它的工作)。由于時間段非常短,短到我們根本察覺不到,所以在我們看來,系統是在同時運行多個進程(我們稱之為多任務執行)。
內核負責上下文切換。我們來看看下面的場景,以便理解它的工作原理。
(1)CPU為每個進程計時,到時即停止進程,并切換至內核模式,由內核接管CPU控制權。(2)內核記錄下當前CPU和內存的狀態信息,這些信息在恢復被停止的進程時需要用到。(3)內核執行上一個時間段內的任務(如從輸入輸出設備獲得數據,磁盤讀寫操作等)。(4)內核準備執行下一個進程,從準備就緒的進程中選擇一個執行。(5)內核為新進程準備CPU和內存。(6)內核將新進程執行的時間段通知CPU。(7)內核將CPU切換至用戶模式,將CPU控制權交給新進程。
上下文切換回答了一個十分重要的問題,即內核是在什么時候運行的。答案就是:內核是在上下文切換時的時間段間隙中運行的。
在多CPU系統中,情況要稍微復雜一些。如果新進程將在另一個CPU上運行,內核就不需要讓出當前CPU的使用權。不過為了將所有CPU的使用效率最大化,內核會使用一些其他的方式來獲取CPU控制權。
內存管理
內核在上下文切換過程中管理內存,這是一項十分復雜的工作,因為內核要保證以下所有條件:
(1)內核需要自己的專有內存空間,其他的用戶進程無法訪問;(2)每個用戶進程有自己的專有內存空間;(3)一個進程不能訪問另一個進程的專有內存空間;(4)用戶進程之間可以共享內存;(5)用戶進程的某些內存空間可以是只讀的;(6)通過使用磁盤交換,系統可以使用比實際內存容量更多的內存空間。
新型的CPU提供MMU(Memory Management Unit,內存管理單元),MMU使用了一種叫作虛擬內存的內存訪問機制,即進程不是直接訪問內存的實際物理地址,而是通過內核使得進程看起來可以使用整個系統的內存。當進程訪問內存的時候,MMU截獲訪問請求,然后通過內存映射表(或稱為內存頁面表,page table)將要訪問的內存地址轉換為實際的物理地址。內核需要初始化、維護和更新這個地址映射表。例如,在上下文切換時,內核將內存映射表從被移出進程轉給被移入進程使用。
設備驅動程序和設備管理
對于設備來說,內核的角色比較簡單。通常設備只能在內核模式中被訪問(例如用戶進程請求內核關閉系統電源),因為設備訪問不當有可能會讓系統崩潰。另一個原因是不同設備之間沒有一個統一的編程接口,即使同類設備也是如此(比如兩個不同的網卡)。所以設備驅動程序傳統意義上來說是內核的一部分,它們盡可能為用戶進程提供統一的接口,以簡化開發人員的工作。
系統調用和系統支持
內核還對用戶進程提供其他功能。例如,系統調用(system call或syscall)為進程執行一些它們不擅長或無法完成的工作。打開、讀取和寫文件這些操作都涉及系統調用。
fork()和exec()這兩個系統調用對于我們了解進程如何啟動很重要。
(1)fork():當進程調用fork()時,內核創建一個和該進程幾乎一模一樣的副本。(2)exec():當進程調用exec(program)時,內核啟動program來替換當前的進程。
除了init以外,Linux中的所有用戶進程都是通過fork()來啟動的。除了創建現有進程的副本以外,大多數情況下你還可以使用exec()來啟動新的進程。一個簡單的例子是你在命令行運行ls命令來顯示目錄內容。當你在終端窗口中輸入ls時,終端窗口中的shell調用fork()創建一個shell的副本,然后該副本調用exec(ls)來運行ls。
除了傳統的系統調用,內核還為用戶進程提供其他很多功能,最為常見的是虛擬設備。虛擬設備對于用戶進程而言是物理設備,但其實它們都是通過軟件實現的。因此從技術角度來說,它們并不需要存在于內核中,但是實際上它們很多都存在于內核中。例如:內核的隨機數生成器(/dev/random)這樣的虛擬設備,如果由用戶進程來實現,難度要大很多。
用戶空間
前面提到過,內核分配給用戶進程的內存我們稱之為用戶空間。因為一個進程簡單來說就是內存中的一個狀態。(用戶空間也可以指所有用戶進程占用的所有內存)
Linux中大部分的操作都發生在用戶空間中。雖然從內核的角度來說所有進程都是一樣的,但是實際上它們執行的是不同的任務。相對于系統組件,用戶進程位于一個基礎服務層中。底層的基礎服務層中提供了上層應用程序所需的工具服務(也稱為中間件),比如郵件、打印和數據庫服務。頂層組件則可專注于完成用戶交互和復雜的功能。當然,組件之間也是可以相互調用的。
雖然這里提到底層、頂層、中間層等概念,但實際上它們在用戶空間里并沒有明顯的界限。其實很多用戶空間的組件也比較難分類,比如Web服務器和數據庫,你可以把它們歸為上層組件,也可以歸為中間層組件。
另外,從技術上來說,用戶進程還是需要通過使用系統調用打開設備的方式來訪問虛擬設備,所以進程總是避免不了要和系統調用打交道。
-
Linux
+關注
關注
87文章
11296瀏覽量
209348
原文標題:如何學習Linux?一位老司機總結每個Linux開發者都應該知道的一些知識
文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論