首先一張熟悉的圖來說明GNU/linux的基本體系結構:
體系的上部分是用戶(或應用程序)空間,這是用戶應用程序執行的地方。
用戶空間之外是內核空間,Linux 內核正是位於這里。
Linux 內核可以進一步划分成 3 層:
- 最上面是系統調用接口,用戶程序通過軟件中斷后,調用系統內核提供的功能,這個在用戶空間和內核提供的服務之間的接口稱為系統調用,它實現了一些基本的功能,例如 read 和 write;
- 系統調用接口之下是內核代碼,可以更精確地定義為獨立於體系結構的內核代碼,這些代碼是 Linux 所支持的所有處理器體系結構所通用的;
- 內核代碼之下是依賴於體系結構的代碼,構成了通常稱為 BSP(Board Support Package)的部分,這些代碼用作給定體系結構的處理器和特定於平台的代碼。
Linux內核主要由進程調度(SCHED)、內存管理(MM)、虛擬文件系統(VFS)、網絡接口(NET)和進程間通信(IPC)5個子系統組成,如圖所示。
子系統之間的依賴關系:
- 進程調度與內存管理之間的關系:這兩個子系統互相依賴。在多道程序環境下,程序要運行必須為之創建進程,而創建進程的第一件事情,就是將程序和數據裝入內存。
- 進程間通信與內存管理的關系:進程間通信子系統要依賴內存管理支持共享內存通信機制,這種機制允許兩個進程除了擁有自己的私有空間,還可以存取共同的內存區域。
- 虛擬文件系統與網絡接口之間的關系:虛擬文件系統利用網絡接口支持網絡文件系統(NFS),也利用內存管理支持RAMDISK設備。
- 內存管理與虛擬文件系統之間的關系:內存管理利用虛擬文件系統支持交換,交換進程(swapd)定期由調度程序調度,這也是內存管理依賴於進程調度的惟一原因。當一個進程存取的內存映射被換出時,內存管理向文件系統發出請求,同時,掛起當前正在運行的進程。
除了這些依賴關系外,內核中的所有子系統還要依賴於一些共同的資源。這些資源包括所有子系統都用到的例程,如分配和釋放內存空間的函數、打印警告或錯誤信息的函數及系統提供的調試例程等。
進程調度
進程調度控制系統中的多個進程對CPU的訪問,使得多個進程能在CPU中“微觀串行,宏觀並行”地執行。進程調度處於系統的中心位置,內核中其他的子系統都依賴它,因為每個子系統都需要掛起或恢復進程。
如上圖所示,Linux的進程在幾個狀態間進行切換:就緒/運行狀態、等待狀態(可以被中斷打斷)、等待狀態(不可以被中斷打斷)、停止狀態和僵死狀態。
- TASK_RUNNING: 正在運行或處於就緒狀態:就緒狀態是指進程申請到了CPU以外的其他所有資源,正所謂:萬事俱備,只欠東風.提醒:一般的操作系統教科書將正在CPU上執行的進程定義為RUNNING狀態、而將可執行但是尚未被調度執行的進程定義為READY狀態,這兩種狀態在Linux下統一為 TASK_RUNNING狀態.
- TASK_INTERRUPTIBLE: 處於等待隊伍中,等待資源有效時喚醒(比如等待鍵盤輸入、socket連接、信號等等),但可以被中斷喚醒.一般情況下,進程列表中的絕大多數進程都處於 TASK_INTERRUPTIBLE狀態.畢竟皇帝只有一個(單個CPU時),后宮佳麗幾千;如果不是絕大多數進程都在睡眠,CPU又怎么響應得過來.
- TASK_UNINTERRUPTIBLE:處於等待隊伍中,等待資源有效時喚醒(比如等待鍵盤輸入、socket連接、信號等等),但不可以被中斷喚醒.
- TASK_ZOMBIE:僵死狀態,進程資源用戶空間被釋放,但內核中的進程PCB並沒有釋放,等待父進程回收.
- TASK_STOPPED:進程被外部程序暫停(如收到SIGSTOP信號,進程會進入到TASK_STOPPED狀態),當再次允許時繼續執行(進程收到SIGCONT信號,進入TASK_RUNNING狀態),因此處於這一狀態的進程可以被喚醒.
在設備驅動編程中,當請求的資源不能得到滿足時,驅動一般會調度其他進程執行,並使本進程進入睡眠狀態,直到它請求的資源被釋放,才會被喚醒而進入就緒態。睡眠分成可被打斷的睡眠和不可被打斷的睡眠,兩者的區別在於可被打斷的睡眠在收到信號的時候會醒。
在設備驅動編程中,當請求的資源不能得到滿足時,驅動一般會調度其他進程執行,其對應進程進入睡眠狀態,直到它請求的資源被釋放,才會被喚醒而進入就緒態。設備驅動中,如果需要幾個並發執行的任務,可以啟動內核線程,啟動內核線程的函數為:
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);
當用戶使用系統提供的庫函數進行進程編程,用戶可以動態地創建進程,進程之間還有等待,互斥等操作,這些操作都是由linux內核來實現的。linux內核通過進程管理子系統實現了進程有關的操作,在linux系統上,所有的計算工作都是通過進程表現的,進程可以是短期的(執行一個命令),也可以是長期的(一種網絡服務)。linux系統是一種動態系統,通過進程管理能夠適應不斷變化的計算需求。
在用戶空間,進程是由進程標示符(PID)表示的。從用戶角度看,一個PID是一個數字值,可以唯一標識一個進程,一個PID值在進程的整個生命周期中不會更改,但是PID可以在進程銷毀后被重新使用。創建進程可以使用幾種方式,可以創建一個新的進程,也可以創建當前進程的子進程。
在linux內核空間,每個進程都有一個獨立的數據結構,用來保存該進程的ID、優先級、地址的空間等信息,這個結構也被稱做進程控制塊(Process Control Block)。所謂的進程管理就是對進程控制塊的管理。
Linux的進程是通過fork()函數系統調用產生的。調用fork()的進程叫做父進程,生成的進程叫做子進程。子進程被創建的時候,除了進程ID外,其它數據結構與父進程完全一致。在fork()系統調用創建內存之后,子進程馬上被加入內核的進程調試隊列,然后使用exec()系統調用,把程序的代碼加入到子進程的地址空間,之后子進程就開始執行自己的代碼。
在一個系統上可以有多個進程,但是一般情況下只有一個CPU,在同一個時刻只能有一個進程在工作,即使有多個CPU,也不可能和進程的數量一樣多。如果讓若干的進程都能在CPU上工作,這就是進程管理子系統的工作。linux內核設計了存放進程隊列的結構,在一個系統上會有若干隊列,分別存放不同狀態的進程。一個進程可以有若干狀態,具體是由操作系統來定義的,但是至少包含運行態、就緒態和等待3種狀態,內核設計了對應的隊列存放對應狀態的進程控制塊。
當一個用戶進程被加載后,會進入就緒態,被加入到就緒態隊列,CPU時間被輪轉到就緒態隊列后,切換到進程的代碼,進程被執行,當進程的時間片到了以后被換出。如果進程發生I/O操作也會被提前被換出,並且存放到等待隊列,當I/O請求返回后,進程又被放入就緒隊列。linux系統對進程隊列的管理設計了若干不同的方法,主要的目的是提高進程調試的穩定性。
內存管理
內存管理的主要作用是控制多個進程安全地共享主內存區域。當CPU提供內存管理單元(MMU)時,Linux內存管理完成為每個進程進行虛擬內存到物理內存的轉換。Linux 2.6引入了對無MMU CPU的支持。
如下圖所示,一般而言,Linux的每個進程享有4GB的內存空間,0~3GB屬於用戶空間,3~4GB屬於內核空間,內核空間對常規內存、I/O設備內存以及高端內存存在不同的處理方式。
地址0G-3G:用戶空間,運行應用程序 (每一個應用程序都認為自己獨占這0~3G 的空間)
地址3G-4G:內核空間
內核空間包括:
1、3G – 3G+896MB 直接映射區
2、vmalloc區
3、永久映射區
4、固定映射區
使用虛擬內存技術的計算機,內存管理的硬件按照分頁方式管理內存。分頁方式是把計算機系統的物理內存按照相同大小等分,每個內存分片稱作內存頁,通常內存頁大小是4KB。Linux內核的內存管理子系統管理虛擬內存與物理內存之間的映射關系,以及系統可用內存空間。
內存管理要管理的不僅是4KB緩沖區。Linux提供了對4KB緩沖區的抽象,例如slab分配器。這種內存管理模式使用4KB緩沖區為基數,然后從中分配結構,並跟蹤內存頁使用情況,比如哪些內存頁是滿的,哪些頁面沒有完全使用,哪些頁面為空。這樣就允許該模式根據系統需要來動態調整內存使用。
在支持多用戶的系統上,由於內存占用的增大,容易出現物理內存被消耗盡的情況。為了解決物理內存被耗盡的問題,內存管理子系統規定頁面可以移出內存並放入磁盤中,這個過程稱為交換。內存管理的源代碼可以在./linux/mm中找到。
虛擬文件系統
如下圖4所示,Linux虛擬文件系統(VFS)隱藏各種了硬件的具體細節,為所有的設備提供了統一的接口。而且,它獨立於各個具體的文件系統,是對各種文件系統的一個抽象,它使用超級塊super block存放文件系統相關信息,使用索引節點inode存放文件的物理信息,使用目錄項dentry存放文件的邏輯信息。
在不同格式的文件分區上,程序都可以正確地讀寫文件,並且結果是一樣的。有時在使用linux系統的時候發現,可以在不同類型的文件分區內直接復制文件,對應用程序來說,並不知道文件系統的類型,甚至不知道文件的類型,這就是虛擬文件系統在背后做的工作。虛擬文件系統屏蔽了不同文件系統間的差異,向用戶提供了統一的接口。
虛擬文件系統,即VFS(Virtual File System)是Linux內核中的一個軟件抽象層。它通過一些數據結構及其方法向實際的文件系統如ext2,vfat等提供接口機制。通過使用同一套文件 I/O 系統調用即可對Linux中的任意文件進行操作而無需考慮其所在的具體文件系統格式;更進一步,文件操作可以在不同文件系統之間進行。在linux系統中,一切都可以被看做是文件。不僅普通的文本文件、目錄可以當做文件進行處理,而且字符設備、塊設備、套接字等都可以被當做文件進行處理。這些文件雖然類型不同,但是卻使用同一種操作方法。這也是UNIX/Linux設計的基本哲學之一。
虛擬文件系統(簡稱VFS)是實現“一切都是文件”特性的關鍵,是Linux內核的一個軟件層,向用戶空間的程序提供文件系統接口;同時提供了內核中的一個抽象功能,允許不同類型的文件系統存在。VFS可以被理解為一種抽象的接口標准,系統中所有的文件系統不僅依靠VFS共存,也依靠VFS協同工作。為了能夠支持不同的文件系統,VFS定義了所有文件系統都支持的、最基本的一個概念上的接口和數據結構,在實現一個具體的文件系統的時候,需要向VFS提供符合VFS標准的接口和數據結構,不同的文件系統可能在實體概念上有差別,但是使用VFS接口時需要和VFS定義的概念保持一致,只有這樣,才能實現對用戶的文件系統無關性。VFS隱藏了具體文件系統的操作細節,所以,在VFS這一層以及內核其他部分看來,所有的文件系統都是相同的。對文件系統訪問的系統調用通過VFS軟件層處理,VFS根據訪問的請求調用不同的文件系統驅動的函數處理用戶的請求。文件系統的代碼在訪問物理設備的時候,需要使用物理設備驅動訪問真正的硬件。
網絡接口
網絡接口提供了對各種網絡標准的存取和各種網絡硬件的支持。如下圖所示,在Linux中網絡接口可分為網絡協議和網絡驅動程序,網絡協議部分負責實現每一種可能的網絡傳輸協議,網絡設備驅動程序負責與硬件設備通信,每一種可能的硬件設備都有相應的設備驅動程序。
(1)網絡協議接口層向網絡層協議提供統一的數據包收發接口,不論上層協議是ARP還是IP,都通過dev_queue_xmit()函數發送數據,並通過netif_rx()函數接收數據。這一層的存在使得上層協議獨立於具體的設備。
(2)網絡設備接口層向協議接口層提供的用於描述具體網絡設備屬性和操作的結構體net_device,該結構體是設備驅動功能層各函數的容器。
(3)設備驅動功能層的各函數是網絡設備接口層net_device數據結構的具體成員,是驅使網絡設備硬件完成相應動作的程序,它通過nto_start_xmit()函數啟動發送操作,並通過網絡設備上的中斷觸發接收操作。
(4)網絡設備與媒介層是完成數據包發送和接收的物理實體,包括網絡適配器和具體的傳輸媒介,網絡適配器被設備驅動功能層中的函數在物理上驅動。
驅動工程師的工作:在設計具體的網絡設備驅動程序時,需要完成的主要工作是編寫設備驅動功能層的相關函數以填充net_device數據結構的內容並將net_device注冊入內核。
寫網絡應用程序,使用socket通過TCP/IP協議與其他機器通信,和前面介紹的內核子系統相似,socket相關的函數也是通過內核的子系統完成的,擔當這部分任務的是內核的網絡子系統,有時也把這部分代碼稱為“網絡堆棧”。Linux內核提供了優秀的網絡處理能力和功能,這與網絡堆棧代碼的設計思想是分不開的,Linux的網絡堆棧部分沿襲了傳統的層次結構,網絡數據從用戶進程到達實際的網絡設備需要四個層次:用戶進程,套接字,網絡協議,網絡設備。
實際上,在每層里面還可以分為好多層次,數據傳輸的路徑是按照層次來的,不能跨越某個層次。linux網絡子系統對網絡層次采用了類似面向對象的設計思路,把需要處理的層次抽象為不同的實體,並且定義了實體之間的關系和數據處理流程:
(1)網絡協議:網絡協議可以理解為一種語言,用於網絡中不同設備之間的通信,是一種通信的規范。
(2)套接字:套接字是內核與用戶程序的接口,一個套接字對應一個數據連接,並且向用戶提供了文件I/O,用戶可以像操作文件一樣在數據連接上收發數據,具體的協議處理由網絡協議部分處理。套接字是用戶使用網絡的接口。
(3)設備接口:設備接口是網絡子系統中軟件和硬件的接口,用戶的數據最終是需要通過網絡硬件設備發送和接收的,網絡設備千差萬別,設備驅動也不盡相同,通過設備接口屏蔽了具體設備驅動的差異。
(4)網絡緩沖區:網絡緩沖區也稱為套接字緩沖區(sk_buff),是網絡子系統中的一個重要結構。網絡傳輸數據存在許多不定因素,除了物理設備對傳輸數據的限制(例如MMU),網絡受到干擾、丟包、重傳等,都會造成數據的不穩定,網絡緩沖區通過對網絡數據的重新整理,使業務處理的數據包是完整的。網絡緩沖區是內存中的一塊緩沖區,是網絡系統與內存管理的接口。
進程通信
進程通信支持提供進程之間的通信,Linux支持進程間的多種通信機制,包含信號量、共享內存、管道等,這些機制可協助多個進程、多資源的互斥訪問、進程間的同步和消息傳遞。