3.3.1 Linux內核源代碼的目錄結構
Linux內核源代碼包含如下目錄。
arch:包含和硬件體系結構相關的代碼,每種平臺占一個相應的目錄,如i386、arm、arm64、powerpc、mips等。Linux內核目前已經支持30種左右的體系結構。在arch目錄下,存放的是各個平臺以及各個平臺的芯片對Linux內核進程調度、內存管理、中斷等的支持,以及每個具體的SoC和電路板的板級支持代碼。
block:塊設備驅動程序I/O調度。
crypto:常用加密和散列算法(如AES、SHA等),還有一些壓縮和CRC校驗算法。
documentation:內核各部分的通用解釋和注釋。
drivers:設備驅動程序,每個不同的驅動占用一個子目錄,如char、block、net、mtd、i2c等。
fs:所支持的各種文件系統,如EXT、FAT、NTFS、JFFS2等。
include:頭文件,與系統相關的頭文件放置在include/linux子目錄下。
init:內核初始化代碼。的start_kernel()就位于init/main.c文件中。
ipc:進程間通信的代碼。
kernel:內核核心的部分,包括進程調度、定時器等,而和平臺相關的一部分代碼放在arch/*/kernel目錄下。
lib:庫文件代碼。
mm:內存管理代碼,和平臺相關的一部分代碼放在arch/*/mm目錄下。
net:網絡相關代碼,實現各種常見的網絡協議。
scripts:用于配置內核的腳本文件。
security:主要是一個SELinux的模塊。
sound:ALSA、OSS音頻設備的驅動核心代碼和常用設備驅動。
usr:實現用于打包和壓縮的cpio等。
include:內核API級別頭文件。
內核一般要做到drivers與arch的軟件架構分離,驅動中不包含板級信息,讓驅動跨平臺。同時內核的通用部分(如kernel、fs、ipc、net等)則與具體的硬件(arch和drivers)剝離。
3.3.2 Linux內核的組成部分
如圖3.3所示,Linux內核主要由進程調度(SCHED)、內存管理(MM)、虛擬文件系統(VFS)、
網絡接口(NET)和進程間通信(IPC)5個子系統組成。
1.進程調度
進程調度控制系統中的多個進程對CPU的訪問,使得多個進程能在CPU中“微觀串行,宏觀并行”地執行。進程調度處于系統的中心位置,內核中其他的子系統都依賴它,因為每個子系統都需要掛起或恢復進程。如圖3.4所示,Linux的進程在幾個狀態間進行切換。
在設備驅動編程中,當請求的資源不能得到滿足時,驅動一般會調度其他進程執行,并使本進程進入睡眠狀態,直到它請求的資源被釋放,才會被喚醒而進入就緒狀態。睡眠分成可中斷的睡眠和不可中斷的睡眠,兩者的區別在于可中斷的睡眠在收到信號的時候會醒。
完全處于TASK_UNINTERRUPTIBLE狀態的進程甚至都無法被“殺死”,所以Linux 2.6.26之后的內核也存在一種TASK_KILLABLE的狀態,它等于“TASK_WAKEKILL|TASK_UNINTERRUPTIBLE”,可以響應致命信號。
在Linux內核中,使用task_struct結構體(include/linux/sched.h)來描述進程,該結構體中包含描述該進程內存資源、文件系統資源、文件資源、tty資源、信號處理等的指針。Linux的線程采用輕量級進程模型來實現,在用戶空間通過pthread_create()API創建線程的時候,本質上內核只是創建了一個新的task_struct,并將新task_struct的所有資源指針都指向創建它的那個task_struct的資源指針。
絕大多數進程(以及進程中的多個線程)是由用戶空間的應用創建的,當它們存在底層資源和硬件訪問的需求時,會通過系統調用進入內核空間。有時候,在內核編程中,如果需要幾個并發執行的任務,可以啟動內核線程,這些線程沒有用戶空間。啟動內核線程的函數為:pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);
2.內存管理
內存管理的主要作用是控制多個進程安全地共享主內存區域。當CPU提供內存管理單元(MMU)時,Linux內存管理對于每個進程完成從虛擬內存到物理內存的轉換。Linux 2.6引入了對無MMU CPU的支持。
如圖3.5所示,一般而言,32位處理器的Linux的每個進程享有4GB的內存空間,0~3GB屬于用戶空間,3~4GB屬于內核空間,內核空間對常規內存、I/O設備內存以及高端內存有不同的處理方式。內核空間和用戶空間的具體界限是可以調整的,在內核配置選項Kernel Features→Memory split下,可以設置界限為2GB或者3GB。
如圖3.6所示,Linux內核的內存管理總體比較龐大,包含底層的Buddy(伙伴)算法,它用于管理每個頁的占用情況,內核空間的slab分配器以及用戶空間的C庫的二次管理。另外,內核也提供了頁緩存的支持,用內存來緩存磁盤,per backing device info flusher線程用于刷回臟的頁緩存到磁盤。Kswapd(交換進程)則是Linux中用于頁面回收(包括file-backed的頁和匿名頁)的內核線程,它采用近少使用(LRU)算法進行內存回收。
如圖3.7所示,
Linux虛擬文件系統隱藏了各種硬件的具體細節,為所有設備提供了統一的接口。而且,它獨立于各個具體的文件系統,是對各種文件系統的一個抽象。它為上層的應用程序提供了統一的vfs_read()、vfs_write()等接口,并調用具體底層文件系統或者設備驅動中實現的file_operations結構體的成員函數。
4.網絡接口
網絡接口提供了對各種網絡標準的存取和各種網絡硬件的支持。如圖3.8所示,在Linux中網絡接口可分為網絡協議和網絡驅動程序,網絡協議部分負責實現每一種可能的網絡傳輸協議,網絡設備驅動程序負責與硬件設備通信,每一種可能的硬件設備都有相應的設備驅動程序。
Linux內核支持的協議棧種類較多,如Internet、UNIX、CAN、NFC、Bluetooth、WiMAX、IrDA等,上層的應用程序統一使用套接字接口。
5.進程間通信
進程間通信支持進程之間的通信,Linux支持進程間的多種通信機制,包含信號量、共享內存、消息隊列、管道、UNIX域套接字等,這些機制可協助多個進程、多資源的互斥訪問、進程間的同步和消息傳遞。在實際的Linux應用中,人們更多地趨向于使用UNIX域套接字,而不是System V IPC中的消息隊列等機制。Android內核則新增了Binder進程間通信方式。
Linux內核5個組成部分之間的依賴關系如下。
進程調度與內存管理之間的關系:這兩個子系統互相依賴。在多程序環境下,程序要運行,則必須為之創建進程,而創建進程的件事情,就是將程序和數據裝入內存。
進程間通信與內存管理的關系:進程間通信子系統要依賴內存管理支持共享內存通信機制,這種機制允許兩個進程除了擁有自己的私有空間之外,還可以存取共同的內存區域。
虛擬文件系統與網絡接口之間的關系:虛擬文件系統利用網絡接口支持網絡文件系統(NFS),也利用內存管理支持RAMDISK設備。
內存管理與虛擬文件系統之間的關系:內存管理利用虛擬文件系統支持交換,交換進程定期由調度程序調度,這也是內存管理依賴于進程調度的原因。當一個進程存取的內存映射被換出時,內存管理向虛擬文件系統發出請求,同時,掛起當前正在運行的進程。
除了這些依賴關系外,內核中的所有子系統還要依賴于一些共同的資源。這些資源包括所有子系統都用到的API,如分配和釋放內存空間的函數、輸出警告或錯誤消息的函數及系統提供的調試接口等。
3.3.3 Linux內核空間與用戶空間
現代CPU內部往往實現了不同操作模式(級別),不同模式有不同功能,高層程序往往不能訪問低級功能,而必須以某種方式切換到低級模式。
例如,ARM處理器分為7種工作模式。
用戶模式(usr):大多數應用程序運行在用戶模式下,當處理器運行在用戶模式下時,某些被保護的系統資源是不能訪問的。
快速中斷模式(fiq):用于高速數據傳輸或通道處理。
外部中斷模式(irq):用于通用的中斷處理。
管理模式(svc):操作系統使用的保護模式。
數據訪問中止模式(abt):當數據或指令預取中止時進入該模式,可用于虛擬存儲及存儲保護。
系統模式(sys):運行具有特權的操作系統任務。
未定義指令中止模式(und):當未定義的指令執行時進入該模式,可用于支持硬件協處理器的軟件仿真。
ARM Linux的系統調用實現原理是采用swi軟件中斷從用戶(usr)模式陷入管理模式(svc)。
又如,x86處理器包含4個不同的特權級,稱為Ring 0~Ring 3。在Ring0下,可以執行特權級指令,對任何I/O設備都有訪問權等,而Ring3則被限制很多操作。
Linux系統可充分利用CPU的這一硬件特性,但它只使用了兩級。在Linux系統中,內核可進行任何操作,而應用程序則被禁止對硬件的直接訪問和對內存的未授權訪問。例如,若使用x86處理器,則用戶代碼運行在特權級3,而系統內核代碼則運行在特權級0。
內核空間和用戶空間用來區分程序執行的兩種不同狀態,它們使用不同的地址空間。Linux只能通過系統調用和硬件中斷完成從用戶空間到內核空間的控制轉移。