一文看懂Linux內核!Linux內核架構和工作原理詳解

linux內核相關視頻解析:

5個方面分析linux內核架構,讓你對內核不再陌生
90分鐘瞭解Linux內存架構,numa的優勢,slab的實現,vmalloc的原理
手把手帶你實現一個Linux內核文件系統

簡介

作用是將應用層序的請求傳遞給硬件,並充當底層驅動程序,對系統中的各種設備和組件進行尋址。目前支持模塊的動態裝卸(裁剪)。Linux內核就是基於這個策略實現的。Linux進程1.採用層次結構,每個進程都依賴於一個父進程。內核啓動init程序作爲第一個進程。該進程負責進一步的系統初始化操作。init進程是進程樹的根,所有的進程都直接或者間接起源於該進程。virt/ ---- 提供虛擬機技術的支持。

Linux內核預備工作

理解Linux內核最好預備的知識點:

懂C語言
懂一點操作系統的知識
熟悉少量相關算法
懂計算機體系結構


Linux內核的特點:

結合了unix操作系統的一些基礎概念

在這裏插入圖片描述

Linux內核的任務:

1.從技術層面講,內核是硬件與軟件之間的一箇中間層。作用是將應用層序的請求傳遞給硬件,並充當底層驅動程序,對系統中的各種設備和組件進行尋址。

2.從應用程序的層面講,應用程序與硬件沒有聯繫,只與內核有聯繫,內核是應用程序知道的層次中的最底層。在實際工作中內核抽象了相關細節。

3.內核是一個資源管理程序。負責將可用的共享資源(CPU時間、磁盤空間、網絡連接等)分配得到各個系統進程。

4.內核就像一個庫,提供了一組面向系統的命令。系統調用對於應用程序來說,就像調用普通函數一樣。

內核實現策略:

1.微內核。最基本的功能由中央內核(微內核)實現。所有其他的功能都委託給一些獨立進程,這些進程通過明確定義的通信接口與中心內核通信。

2.宏內核。內核的所有代碼,包括子系統(如內存管理、文件管理、設備驅動程序)都打包到一個文件中。內核中的每一個函數都可以訪問到內核中所有其他部分。目前支持模塊的動態裝卸(裁剪)。Linux內核就是基於這個策略實現的。

哪些地方用到了內核機制?

1.進程(在cpu的虛擬內存中分配地址空間,各個進程的地址空間完全獨立;同時執行的進程數最多不超過cpu數目)之間進行通 信,需要使用特定的內核機制。

2.進程間切換(同時執行的進程數最多不超過cpu數目),也需要用到內核機制。

進程切換也需要像FreeRTOS任務切換一樣保存狀態,並將進程置於閒置狀態/恢復狀態。

3.進程的調度。確認哪個進程運行多長的時間。

Linux進程

1.採用層次結構,每個進程都依賴於一個父進程。內核啓動init程序作爲第一個進程。該進程負責進一步的系統初始化操作。init進程是進程樹的根,所有的進程都直接或者間接起源於該進程。

2.通過pstree命令查詢。實際上得系統第一個進程是systemd,而不是init(這也是疑問點)

3.系統中每一個進程都有一個唯一標識符(ID),用戶(或其他進程)可以使用ID來訪問進程。

Linux內核源代碼的目錄結構

Linux內核源代碼包括三個主要部分:

內核核心代碼,包括第3章所描述的各個子系統和子模塊,以及其它的支撐子系統,例如電源管理、Linux初始化等

其它非核心代碼,例如庫文件(因爲Linux內核是一個自包含的內核,即內核不依賴其它的任何軟件,自己就可以編譯通過)、固件集合、KVM(虛擬機技術)等

編譯腳本、配置文件、幫助文檔、版權說明等輔助性文件

使用ls命令看到的內核源代碼的頂層目錄結構,具體描述如下。

include/ ---- 內核頭文件,需要提供給外部模塊(例如用戶空間代碼)使用。

kernel/ ---- Linux內核的核心代碼,包含了3.2小節所描述的進程調度子系統,以及和進程調度相關的模塊。

mm/ ---- 內存管理子系統(3.3小節)。

fs/ ---- VFS子系統(3.4小節)。

net/ ---- 不包括網絡設備驅動的網絡子系統(3.5小節)。

ipc/ ---- IPC(進程間通信)子系統。

arch// ---- 體系結構相關的代碼,例如arm, x86等等。
arch//mach- ---- 具體的machine/board相關的代碼。
arch//include/asm ---- 體系結構相關的頭文件。
arch//boot/dts ---- 設備樹(Device Tree)文件。


init/ ---- Linux系統啓動初始化相關的代碼。
block/ ---- 提供塊設備的層次。
sound/ ---- 音頻相關的驅動及子系統,可以看作“音頻子系統”。
drivers/ ---- 設備驅動(在Linux kernel 3.10中,設備驅動佔了49.4的代碼量)。


lib/ ---- 實現需要在內核中使用的庫函數,例如CRC、FIFO、list、MD5等。
crypto/ ----- 加密、解密相關的庫函數。
security/ ---- 提供安全特性(SELinux)。
virt/ ---- 提供虛擬機技術(KVM等)的支持。
usr/ ---- 用於生成initramfs的代碼。
firmware/ ---- 保存用於驅動第三方設備的固件。




samples/ ---- 一些示例代碼。
tools/ ---- 一些常用工具,如性能剖析、自測試等。

Kconfig, Kbuild, Makefile, scripts/ ---- 用於內核編譯的配置文件、腳本等。

COPYING ---- 版權聲明。
MAINTAINERS ----維護者名單。
CREDITS ---- Linux主要的貢獻者名單。
REPORTING-BUGS ---- Bug上報的指南。


Documentation, README ---- 幫助、說明文檔。

【文章福利】需要C/C++ Linux服務器架構師學習資料加羣812855908(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等)
在這裏插入圖片描述

Linux內核體系結構簡析簡析

在這裏插入圖片描述

圖1 Linux系統層次結構

最上面是用戶(或應用程序)空間。這是用戶應用程序執行的地方。用戶空間之下是內核空間,Linux 內核正是位於這裏。GNU C Library (glibc)也在這裏。它提供了連接內核的系統調用接口,還提供了在用戶空間應用程序和內核之間進行轉換的機制。這點非常重要,因爲內核和用戶空間的應用程序使用的是不同的保護地址空間。每個用戶空間的進程都使用自己的虛擬地址空間,而內核則佔用單獨的地址空間。

Linux 內核可以進一步劃分成 3 層。最上面是系統調用接口,它實現了一些基本的功能,例如 read 和 write。系統調用接口之下是內核代碼,可以更精確地定義爲獨立於體系結構的內核代碼。這些代碼是 Linux 所支持的所有處理器體系結構所通用的。在這些代碼之下是依賴於體系結構的代碼,構成了通常稱爲 BSP(Board Support Package)的部分。這些代碼用作給定體系結構的處理器和特定於平臺的代碼。

Linux 內核實現了很多重要的體系結構屬性。在或高或低的層次上,內核被劃分爲多個子系統。Linux 也可以看作是一個整體,因爲它會將所有這些基本服務都集成到內核中。這與微內核的體系結構不同,後者會提供一些基本的服務,例如通信、I/O、內存和進程管理,更具體的服務都是插入到微內核層中的。每種內核都有自己的優點,不過這裏並不對此進行討論。

隨着時間的流逝,Linux 內核在內存和 CPU 使用方面具有較高的效率,並且非常穩定。但是對於 Linux 來說,最爲有趣的是在這種大小和複雜性的前提下,依然具有良好的可移植性。Linux 編譯後可在大量處理器和具有不同體系結構約束和需求的平臺上運行。一個例子是 Linux 可以在一個具有內存管理單元(MMU)的處理器上運行,也可以在那些不提供 MMU 的處理器上運行。

Linux 內核的 uClinux 移植提供了對非 MMU 的支持。

圖2是Linux內核的體系結構
在這裏插入圖片描述

圖2 Linux內核體系結構

Linux內核的主要組件有:系統調用接口、進程管理、內存管理、虛擬文件系統、網絡堆棧、設備驅動程序、硬件架構的相關代碼。

(1)系統調用接口
SCI 層提供了某些機制執行從用戶空間到內核的函數調用。正如前面討論的一樣,這個接口依賴於體系結構,甚至在相同的處理器家族內也是如此。SCI 實際上是一個非常有用的函數調用多路複用和多路分解服務。在 ./linux/kernel 中您可以找到 SCI 的實現,並在 ./linux/arch 中找到依賴於體系結構的部分。

(2)進程管理
進程管理的重點是進程的執行。在內核中,這些進程稱爲線程,代表了單獨的處理器虛擬化(線程代碼、數據、堆棧和 CPU 寄存器)。在用戶空間,通常使用進程 這個術語,不過 Linux 實現並沒有區分這兩個概念(進程和線程)。內核通過 SCI 提供了一個應用程序編程接口(API)來創建一個新進程(fork、exec 或 Portable Operating System Interface [POSIX] 函數),停止進程(kill、exit),並在它們之間進行通信和同步(signal 或者 POSIX 機制)。

進程管理還包括處理活動進程之間共享 CPU 的需求。內核實現了一種新型的調度算法,不管有多少個線程在競爭 CPU,這種算法都可以在固定時間內進行操作。這種算法就稱爲 O(1) 調度程序,這個名字就表示它調度多個線程所使用的時間和調度一個線程所使用的時間是相同的。O(1) 調度程序也可以支持多處理器(稱爲對稱多處理器或 SMP)。您可以在 ./linux/kernel 中找到進程管理的源代碼,在 ./linux/arch 中可以找到依賴於體系結構的源代碼。

(3)內存管理
內核所管理的另外一個重要資源是內存。爲了提高效率,如果由硬件管理虛擬內存,內存是按照所謂的內存頁 方式進行管理的(對於大部分體系結構來說都是 4KB)。Linux 包括了管理可用內存的方式,以及物理和虛擬映射所使用的硬件機制。不過內存管理要管理的可不止 4KB 緩衝區。Linux 提供了對 4KB 緩衝區的抽象,例如 slab 分配器。這種內存管理模式使用 4KB 緩衝區爲基數,然後從中分配結構,並跟蹤內存頁使用情況,比如哪些內存頁是滿的,哪些頁面沒有完全使用,哪些頁面爲空。這樣就允許該模式根據系統需要來動態調整內存使用。爲了支持多個用戶使用內存,有時會出現可用內存被消耗光的情況。由於這個原因,頁面可以移出內存並放入磁盤中。這個過程稱爲交換,因爲頁面會被從內存交換到硬盤上。內存管理的源代碼可以在 ./linux/mm 中找到。

(4)虛擬文件系統
虛擬文件系統(VFS)是 Linux 內核中非常有用的一個方面,因爲它爲文件系統提供了一個通用的接口抽象。VFS 在 SCI 和內核所支持的文件系統之間提供了一個交換層(請參看圖4)。
在這裏插入圖片描述

圖3 Linux文件系統層次結構

在 VFS 上面,是對諸如 open、close、read 和 write 之類的函數的一個通用 API 抽象。在 VFS 下面是文件系統抽象,它定義了上層函數的實現方式。它們是給定文件系統(超過 50 個)的插件。文件系統的源代碼可以在 ./linux/fs 中找到。文件系統層之下是緩衝區緩存,它爲文件系統層提供了一個通用函數集(與具體文件系統無關)。這個緩存層通過將數據保留一段時間(或者隨即預先讀取數據以便在需要是就可用)優化了對物理設備的訪問。緩衝區緩存之下是設備驅動程序,它實現了特定物理設備的接口。

(5)網絡堆棧
網絡堆棧在設計上遵循模擬協議本身的分層體系結構。回想一下,Internet Protocol (IP) 是傳輸協議(通常稱爲傳輸控制協議或 TCP)下面的核心網絡層協議。TCP 上面是 socket 層,它是通過 SCI 進行調用的。socket 層是網絡子系統的標準 API,它爲各種網絡協議提供了一個用戶接口。從原始幀訪問到 IP 協議數據單元(PDU),再到 TCP 和 User Datagram Protocol (UDP),socket 層提供了一種標準化的方法來管理連接,並在各個終點之間移動數據。內核中網絡源代碼可以在 ./linux/net 中找到。

(6)設備驅動程序
Linux 內核中有大量代碼都在設備驅動程序中,它們能夠運轉特定的硬件設備。Linux 源碼樹提供了一個驅動程序子目錄,這個目錄又進一步劃分爲各種支持設備,例如 Bluetooth、I2C、serial 等。設備驅動程序的代碼可以在 ./linux/drivers 中找到。

(7)依賴體系結構的代碼
儘管 Linux 很大程度上獨立於所運行的體系結構,但是有些元素則必須考慮體系結構才能正常操作並實現更高效率。./linux/arch 子目錄定義了內核源代碼中依賴於體系結構的部分,其中包含了各種特定於體系結構的子目錄(共同組成了 BSP)。對於一個典型的桌面系統來說,使用的是 x86 目錄。每個體系結構子目錄都包含了很多其他子目錄,每個子目錄都關注內核中的一個特定方面,例如引導、內核、內存管理等。這些依賴體系結構的代碼可以在 ./linux/arch 中找到。

如果 Linux 內核的可移植性和效率還不夠好,Linux 還提供了其他一些特性,它們無法劃分到上面的分類中。作爲一個生產操作系統和開源軟件,Linux 是測試新協議及其增強的良好平臺。Linux 支持大量網絡協議,包括典型的 TCP/IP,以及高速網絡的擴展(大於 1 Gigabit Ethernet [GbE] 和 10 GbE)。Linux 也可以支持諸如流控制傳輸協議(SCTP)之類的協議,它提供了很多比 TCP 更高級的特性(是傳輸層協議的接替者)。

Linux 還是一個動態內核,支持動態添加或刪除軟件組件。被稱爲動態可加載內核模塊,它們可以在引導時根據需要(當前特定設備需要這個模塊)或在任何時候由用戶插入。

Linux 最新的一個增強是可以用作其他操作系統的操作系統(稱爲系統管理程序)。最近,對內核進行了修改,稱爲基於內核的虛擬機(KVM)。這個修改爲用戶空間啓用了一個新的接口,它可以允許其他操作系統在啓用了 KVM 的內核之上運行。除了運行 Linux 的其他實例之外, Microsoft Windows也可以進行虛擬化。惟一的限制是底層處理器必須支持新的虛擬化指令。

Linux體系結構和內核結構區別

1.當被問到Linux體系結構(就是Linux系統是怎麼構成的)時,我們可以參照下圖這麼回答:從大的方面講,Linux體系結構可以分爲兩塊:

(1)用戶空間:用戶空間中又包含了,用戶的應用程序,C庫

(2)內核空間:內核空間包括,系統調用,內核,以及與平臺架構相關的代碼

在這裏插入圖片描述

2.Linux體系結構要分成用戶空間和內核空間的原因:

1)現代CPU通常都實現了不同的工作模式,

以ARM爲例:ARM實現了7種工作模式,不同模式下CPU可以執行的指令或者訪問的寄存器不同:

(1)用戶模式 usr

(2)系統模式 sys

(3)管理模式 svc

(4)快速中斷 fiq

(5)外部中斷 irq

(6)數據訪問終止 abt

(7)未定義指令異常

以(2)X86爲例:X86實現了4個不同級別的權限,Ring0—Ring3 ;Ring0下可以執行特權指令,可以訪問IO設備;Ring3則有很多的限制

2)所以,Linux從CPU的角度出發,爲了保護內核的安全,把系統分成了2部分;

3.用戶空間和內核空間是程序執行的兩種不同狀態,我們可以通過“系統調用”和“硬件中斷“來完成用戶空間到內核空間的轉移

4.Linux的內核結構(注意區分LInux體系結構和Linux內核結構)
在這裏插入圖片描述

Linux驅動的platform機制

Linux的這種platform driver機制和傳統的device_driver機制相比,一個十分明顯的優勢在於platform機制將本身的資源註冊進內核,由內核統一管理,在驅動程序中使用這些資源時通過platform_device提供的標準接口進行申請並使用。這樣提高了驅動和資源管理的獨立性,並且擁有較好的可移植性和安全性。下面是SPI驅動層次示意圖,Linux中的SPI總線可理解爲SPI控制器引出的總線:
在這裏插入圖片描述

和傳統的驅動一樣,platform機制也分爲三個步驟:

1、總線註冊階段:
內核啓動初始化時的main.c文件中的kernel_init()→do_basic_setup()→driver_init()→platform_bus_init()→bus_register(&platform_bus_type),註冊了一條platform總線(虛擬總線,platform_bus)。

2、添加設備階段:
設備註冊的時候Platform_device_register()→platform_device_add()→(pdev→dev.bus = &platform_bus_type)→device_add(),就這樣把設備給掛到虛擬的總線上。

3、驅動註冊階段:
Platform_driver_register()→driver_register()→bus_add_driver()→driver_attach()→bus_for_each_dev(), 對在每個掛在虛擬的platform bus的設備作__driver_attach()→driver_probe_device(),判斷drv→bus→match()是否執行成功,此時通過指針執行platform_match→strncmp(pdev→name , drv→name , BUS_ID_SIZE),如果相符就調用really_probe(實際就是執行相應設備的platform_driver→probe(platform_device)。)開始真正的探測,如果probe成功,則綁定設備到該驅動。

從上面可以看出,platform機制最後還是調用了bus_register() , device_add() , driver_register()這三個關鍵的函數。

下面看幾個結構體:

struct platform_device           
(/include/linux/Platform_device.h)
{
   
           
const char    * name;        
int        id;        
struct device    dev;        
u32        num_resources;        
struct resource    * resource;
};

Platform_device結構體描述了一個platform結構的設備,在其中包含了一般設備的結構體struct device dev;設備的資源結構體struct resource * resource;還有設備的名字const char * name。(注意,這個名字一定要和後面platform_driver.driver àname相同,原因會在後面說明。)

該結構體中最重要的就是resource結構,這也是之所以引入platform機制的原因。

struct resource                            
( /include/linux/ioport.h)
{
   
           
resource_size_t start;        
resource_size_t end;        
const char *name;        
unsigned long flags;        
struct resource *parent, *sibling, *child;
};

其中 flags位表示該資源的類型,start和end分別表示該資源的起始地址和結束地址(/include/linux/Platform_device.h):

struct platform_driver              
{
   
           
int (*probe)(struct platform_device *);        
int (*remove)(struct platform_device *);        
void (*shutdown)(struct platform_device *);        
int (*suspend)(struct platform_device *, pm_message_t state);        
int (*suspend_late)(struct platform_device *, pm_message_t state);        
int (*resume_early)(struct platform_device *);        
int (*resume)(struct platform_device *);        
struct device_driver driver;
};

Platform_driver結構體描述了一個platform結構的驅動。其中除了一些函數指針外,還有一個一般驅動的device_driver結構。
名字要一致的原因:

上面說的驅動在註冊的時候會調用函數bus_for_each_dev(), 對在每個掛在虛擬的platform bus的設備作__driver_attach()→driver_probe_device(),在此函數中會對dev和drv做初步的匹配,調用的是drv->bus->match所指向的函數。platform_driver_register函數中drv->driver.bus = &platform_bus_type,所以drv->bus->match就爲platform_bus_type→match,爲platform_match函數,該函數如下:

static int platform_match(struct device * dev, struct device_driver * drv)   
{
   
          
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}

是比較dev和drv的name,相同則會進入really_probe()函數,從而進入自己寫的probe函數做進一步的匹配。所以dev→name和driver→drv→name在初始化時一定要填一樣的。

不同類型的驅動,其match函數是不一樣的,這個platform的驅動,比較的是dev和drv的名字,還記得usb類驅動裏的match嗎?它比較的是Product ID和Vendor ID。

個人總結Platform機制的好處:

1、提供platform_bus_type類型的總線,把那些不是總線型的soc設備都添加到這條虛擬總線上。使得,總線——設備——驅動的模式可以得到普及。

2、提供platform_device和platform_driver類型的數據結構,將傳統的device和driver數據結構嵌入其中,並且加入resource成員,以便於和Open Firmware這種動態傳遞設備資源的新型bootloader和kernel 接軌。

Linux內核體系結構

因爲Linux內核是單片的,所以它比其他類型的內核佔用空間最大,複雜度也最高。這是一個設計特性,在Linux早期引起了相當多的爭論,並且仍然帶有一些與單內核固有的相同的設計缺陷。

在這裏插入圖片描述

爲了解決這些缺陷,Linux內核開發人員所做的一件事就是使內核模塊可以在運行時加載和卸載,這意味着您可以動態地添加或刪除內核的特性。這不僅可以向內核添加硬件功能,還可以包括運行服務器進程的模塊,比如低級別虛擬化,但也可以替換整個內核,而不需要在某些情況下重啓計算機。
想象一下,如果您可以升級到Windows服務包,而不需要重新啓動……

內核模塊

如果Windows已經安裝了所有可用的驅動程序,而您只需要打開所需的驅動程序怎麼辦?這本質上就是內核模塊爲Linux所做的。內核模塊,也稱爲可加載內核模塊(LKM),對於保持內核在不消耗所有可用內存的情況下與所有硬件一起工作是必不可少的。
在這裏插入圖片描述

模塊通常向基本內核添加設備、文件系統和系統調用等功能。lkm的文件擴展名是.ko,通常存儲在/lib/modules目錄中。由於模塊的特性,您可以通過在啓動時使用menuconfig命令將模塊設置爲load或not load,或者通過編輯/boot/config文件,或者使用modprobe命令動態地加載和卸載模塊,輕鬆定製內核。

第三方和封閉源碼模塊在一些發行版中是可用的,比如Ubuntu,默認情況下可能無法安裝,因爲這些模塊的源代碼是不可用的。該軟件的開發人員(即nVidia、ATI等)不提供源代碼,而是構建自己的模塊並編譯所需的.ko文件以便分發。雖然這些模塊像beer一樣是免費的,但它們不像speech那樣是免費的,因此不包括在一些發行版中,因爲維護人員認爲它通過提供非免費軟件“污染”了內核。

內核並不神奇,但對於任何正常運行的計算機來說,它都是必不可少的。Linux內核不同於OS X和Windows,因爲它包含內核級別的驅動程序,並使許多東西“開箱即用”。希望您能對軟件和硬件如何協同工作以及啓動計算機所需的文件有更多的瞭解。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章