Linux內核設計與實現 - 第3版

Linux內核設計與實現 - 第3版

一、Linux內核簡介

內核態和被其保護起來的內存空間,統稱爲內核空間

應用程序通過系統調用在內核空間運行,則內核被稱爲運行於進程上下文。另一個上下文是中斷上下文,與進程上下文無關。

每個處理器任何時間點上的活動,爲下列之一:

- 運行於用戶空間,執行用戶進程;

- 運行於內核空間,處於進程上下文,代表某個特定的進程執行;

- 運行於內核空間,處於中斷上下文,與任何進程無關,處理某個特定的中斷。


進程上下文:系統調用,內核線程


單內核,微內核

- 單內核,就是把它從整體上作爲一個單獨的大過程來實現,同時也運行於一個單獨的地址空間上。這樣內核之間的通信是微不足道的;

- 微內核,功能被劃分爲多個獨立的過程,每個過程叫做一個服務器。理想情況下,只有強烈請求特權服務的服務器才運行在特權模式,其它運行在用戶空間。所有服務器都保持獨立且運行在各自的地址空間上,需要IPC機制。

Linux是單內核,但其支持模塊化設計,搶佔式內核,支持內核線程,動態裝載內核模塊,內核本身可調度。

- 搶佔式內核是指內核搶佔,即內核執行某個函數時,如果出現了更高優先級的任務,那麼就執行更高優先級的任務。


進程,線程:都一樣,只是有些共享資源。


二、從內核出發

1.git clone git://git.kernel.org/pub/scm/linux/kernel/torvalds/linux-2.6.git; git pull

2. 內核源碼樹

arch           特定體系結構的源碼

block          塊設備IO層

drivers

firmware     使用某些驅動程序而需要的設備固件

fs                VFS和各種文件系統

include        內核頭文件

init               內核引導和初始化

kernel          像調度程序這樣核心子系統

lib                 通用內核函數

mm              內存管理子系統和VM

usr               早期用戶空間代碼(所謂的initramfs)


3. 配置, 編譯,安裝內核

#make config  // #make menuconfig       // #make gconfig

#make

#x86系統,把arch/i386/bzImage拷貝到/boot木下,像vmlinuz-version這樣命名,編輯/etc/grub/grub.conf,然後#make modules_install

編譯時會在內核源代碼的根目錄下創建一個System.map文件,這是一份符號對照表,用以將內核符號和他們的起始地址對應起來。


4.內核中的內存都不分頁。也就是說,你沒用掉一個字節,物理內存就減少一個字節。 -- 不分頁,說明內存不能換出。


三、進程管理

創建進程的時候, 會分配一個內核棧空間,其底部是 struct thread_info,能找到struct task_struct;

線程看起來就是一個普通進程,只是共享某些資源,比如地址空間,也同樣有一個struct task_struct;

內核線程: - #ps -ef

- 內核經常需要在後臺執行一些操作。這種任務可以通過內核線程完成 - 獨立運行在內核空間的標準進程。

- 內核線程沒有獨立的地址空間,從來不切換到用戶空間去;

- 可以被調度,可以被搶佔;


四、進程調度

IO消耗型和CPU消耗型,吞吐量和響應時間之間的平衡。

1. 進程優先級

nice : -20 ~ 19,對別人友好

prio: 0 ~ 99,實時優先級

#ps -eo state,uid,pid,ppid,rtprio,time,comm

如果rtprio列爲-,則說明其不是實時進程。

2. 調度器類

Linux調度器以模塊的方式提供,每個調度器有一個優先級。調度器會按照優先級遍歷每個調度器類。

CFS:公平調查,運行最少的優先調度。SCHED_NORMAL

實時調度:SCHED_FIFO, SCHED_RR


五、系統調用

在Linux中,系統調用是用戶空間訪問內核的唯一手段:除異常和陷入外,他們是內核唯一的合法入口。

提供機制而不是策略

asmlinkage:這個是一個限定詞,是一個編譯器指令,通知編譯器僅從棧中提取該函數的參數。

系統調用,通過軟中斷,通知內核,內核讀取系統調用號和參數,然後執行。

copy_to_user(), copy_from_user()  --- 可能引起阻塞



六、內核數據結構

struct head_list,是鏈表頭指針。

隊列:kfifo


七、中斷和中斷處理

硬件 ----- 電信號 ------> 中斷控制器 --------- 電信號 -------- > 處理器

一般PC機上:IRQ0 - 時鐘中斷, IRQ1 - 鍵盤, 對於PCI總線上的設備,中斷是動態分配的。

中斷和異常

- 異常產生時,必須考慮與處理器時鐘同步。實際上,異常也常常稱爲同步中斷。在處理執行到由於編程失誤而導致的錯誤指令,比如除0,或者執行期間出現特殊情況,比如缺頁,必須靠內核來處理的時候,處理器就會產生一個異常。系統調用就是一種特殊的異常。

- 中斷 : 隨時產生

在響應一個終端的時候,內核會執行一個函數,叫做中斷處理程序,或者中斷服務例程ISR。一個設備的中斷處理程序是它驅動程序的一部分。

中斷上下文,也稱原子上下文,不能阻塞,不能睡眠。

註冊中斷處理程序,request_irq() - 可能會睡眠,必須在設備初始化完成後,採取註冊ISR;

共享中斷先,如何區分不同設備,*dev

Linux中的中斷處理程序是無須重入的。當一個給定的中斷處理程序正在執行時,相應的中斷線在所有的處理器上都會被屏蔽掉,以防止在同一個中斷線上接收到另一個新的中斷。但一般來說,其它中斷線都是打開的。

中斷棧,每個CPU一個,大小爲一頁。

#cat /proc/interrupts

in_interrupt() - 返回非0,處於上半部或者下半部

in_irq() - 返回非0,處於上半部


八、下半部和推後執行的工作

1. 軟中斷 - 可以響應中斷,但不能休眠

struct softirq_action, 共計32個,在編譯時確定

一個軟中斷不會搶佔另一個軟中斷。實際上,唯一可以搶佔軟中斷的是中斷處理程序。

不過,其它的軟中斷(甚至是類型相同的軟中斷)可以在其它處理器上同時執行。

觸發:raise_softirq();


2. tasklet - 基於軟中斷 - 不能睡眠,不能使用阻塞函數

兩個相同的tasklet不會同時執行,即使存在多處理也是一樣。

處理函數有時還會自行重複觸發,他們不會立即處理重新觸發的軟中斷。

- 內核線程:ksoftirqd/n,每個處理器一個,當大量軟中斷出現的時候,給內核線程就會處理。


3. 工作隊列 - 進程上下文,允許重新調度,甚至睡眠,可以使用信號量

- events/n 內核線程


選擇:

- 要睡眠,工作隊列

- 否則tasklet

- 必須專注於性能的提高,再考慮軟中斷



九、內核同步介紹

要給數據而不是代碼加鎖。


十、內核同步方法

1.原子操作,保證操作完整性

2.自旋鎖 - spinlock

適合輕量加鎖,不可遞歸;

可以用在中斷處理程序中,但獲取鎖前,先要禁止本地中斷(當前處理上面的中斷請求),否則中斷處理程序可能就會打斷持有鎖的內核代碼,然後去爭用這個鎖。這樣一來,中斷處理程序就會自旋,等待該鎖重新可用。

3. rwlock - 讀寫自旋鎖 - 照顧讀操作

4.信號量 struct semaphore,  up(), down(), down_interruptable()

5.讀寫信號量 - 照顧讀操作

6.互斥體 - mutex - 二值信號量

7. 完成變量 - complete(), wait_for_complete()

8. 順序鎖 - 照顧寫,適用於寫操作很少時,write_pending時不允許讀

9. 順序和屏障


持有自旋鎖時,不允許搶佔 ? 還是擔心中斷處理程序。


十一、定時器和時間管理

系統定時器是一種可編程硬件芯片,他能以固定頻率產生中斷,- 所謂的定時器中斷。

HZ = 1000,即每秒系統定時器中斷的次數。

全局變量jiffies用來記錄自系統啓動以來產生的節拍總數。

jiffies迴繞問題:time_after, time_before

體系結構提供了兩種設備進行計時:

- 系統定時器:提供一種週期性觸發的中斷機制。在x86中,主要採用可編程中斷時鐘PIT

- 實時時鐘(RTC):用來持久存放系統時間的設備,即便系統關閉後,它也可以靠主板上的微型電池提供的電力保持系統的計時。系統啓動時,內核通過讀取RTC來初始化牆上時間,該時間存放在xtime變量中。


定時器: - 動態定時器,內核定時器

- 推後一定時間,執行某個任務。

- 其實現使用軟中斷

- 延遲執行,無論如何不能在持有鎖和禁止中斷的情況下發生

短延遲:udelay(), mdelay(), ndelay() -- BogoMIPS - 在系統啓動信息中可以看到



十二、內存管理

找了兩篇文章

http://www.cnblogs.com/wuchanming/p/4339770.html

http://blog.csdn.net/yusiguyuan/article/details/45155035

struct page

內存管理單元MMU使用頁作爲內存管理的基本單位。struct page描述物理內存本身,而不是內存裏面的內容。

內核需要知道誰擁有這個頁,1.用戶空間進程,2.動態分配的內核數據,3.靜態內核代碼,4.頁高速緩存(mapping字段)。

struct zone

由於硬件限制,內核不能對所有的頁一視同仁。有些頁位於內存中特定的物理地址上,所以不能將其用於一些特定的任務。由於這些限制,所以內核把頁劃分爲區。

一般包括:DMA,Normal,HighMem。這個是和體系結構相關的。比如x86-32上面,分界線是16M,896M。HighMem中的頁,不能永久地映射到內核地址空間。

在x86-64的體系結構中,可以映射和處理64位的內存空間,所以沒有HighMem。

注意:區的劃分沒有任何物理意義,只不過是內核爲了管理頁而採取的一種邏輯上的分組。


內存分配:

內存的分配,不能跨區。可以按頁分配,也可以按字節。

struct page * alloc_pages(gfp_t  gfp_mask,  unsigned int order) // 返回2的order次冪個連續物理頁,並返回第一個頁的page結構體指針。

void * page_address(struct page * page) // 返回頁的邏輯地址,但對於HighMem中分配的內存,則不能獲取邏輯地址。

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)  // 直接返回邏輯地址。

如果只需要一個頁,可以調用下面的函數:

struct page * alloc_page(gfp_t  gfp_mask)

unsigned long __get_free_page(gfp_t  gfp_mask)

獲得填充爲 0 的頁

unsigned long get_zeroed_page( unsigned int gfp_mask )  // gfp_mask 的類型怎麼不一致 ?

====== 返回邏輯地址的分配函數,不能在HighMem區分配。

釋放內存

void __free_pages( struct page * page, unsigned int order )

void free_pages( unsigned long addr, unsigned int order )  // 釋放函數和分配函數好像不對應 ?

void free_page(unsigned long addr) // 釋放一個頁

====== 在程序開始時就先進行內存分配是有意義的,否則對於內存分配可能陷入困境。


按字節分配

void * kmalloc( size_t size, gfp_t  flags )  // 物理上是連續的

void kfree( const void * ptr ) // 釋放不是kmalloc分配的內存,釋放已經釋放的內存,都會導致嚴重後果。

void * vmalloc( unsigned long size ) // 分配的內存,虛擬地址連續,但物理地址未必連續。

void  vfree( const void * addr )

雖然某些情況下才需要物理上連續的內存,但在內核中,出於性能考慮,都使用kmalloc,而不是vmalloc,因爲vmalloc分配的內存必須一個頁一個頁進行映射。


gfp_mask 標誌:

分爲3類:

- 行爲修飾符:表示內核應當如何分配所需的內存

__GFP_WAIT : 分配器可以睡眠, __GFP_HIGH : 分配器可以訪問緊急事件緩衝池

__GFP_IO:分配器可以啓動磁盤IO,__GFP_FS:分配器可以啓動文件系統IO

__GFP_COLD:分配器應該使用高速緩存中快要淘汰出去的頁,__GFP_NOWARN:分配器將不打印警告

__GFP_REPEAT:分配器在分配失敗時重複進行分配,但是這次分配仍然可能失敗,__GFP_NOFALL:分配器將無限地重複進行分配,分配不能失敗。

__GFP_NORETRY:分配器在分配失敗時絕不會重新分配

__GFP_NO_GROW:由slab層使用,__GFP_COMP:添加混合頁元數據,在hugetlb的代碼內部使用。

 - 區修飾符:表示內存應當從哪個區分配

-- __GFP_DMA:強制內核從ZONE_DMA分配

-- __GFP_HIGHMEM:優先從ZONE_HIGHMEM分配,或者從ZONE_NORMAL分配

-- 如果沒有指定任何標誌,則內核優先從ZONE_NORMAL分配,接下來嘗試ZONE_DMA。

不能給__get_free_pages() 和 kmalloc() 指定 ZONE_HIGHMEM ,因爲他們返回邏輯地址。可能還沒有映射到內核的虛擬地址空間,也就可能還沒有邏輯地址。

- 類型標誌: 前兩者的組合,一般使用類型標誌即可。

ATOMIC, NOWAIT, NOIO, NOFS, KERNEL, USER, HIGHUSER, DMA

一般來說,在進程上下文,且可睡眠,則使用KERNEL,否則使用ATOMIC即可,DMA用在需要DMA內存的設備驅動程序中。




slab層 - 高速緩存

對於頻繁分配的對象,空閒鏈表很好用,但內核無法控制,引入了slab層,通過簡單的接口,實現對象的分配,釋放。比如進程描述符task_struct, inode等。

高速緩存由若干個slab組成,每個slab有若干個物理上連續的頁組成,每個slab可以包含若干同類對象。

slab分爲:滿,部分滿,空閒。分配時先找部分滿的slab,沒有的話就找空閒slab,沒有的話就需要分配新的slab。

kmalloc在slab層上實現。

接口: - 注意,可能睡眠的接口,只能在進程上下文中調用。

struct kmem_cache  * kmem_cache_create( const char * name,   // 告訴緩存的名字,比如 “task_struct”

                                                                        size_t   size,            // 每個對象的大小,比如 sizeof(struct task_struct)

                                                                        size_t   align,           // slab內第一個對象偏移,進行頁內對齊,通常0即可

                                                                        unsigned long flags, // 標識,見後面

                                                                       void (*ctor) (void *) ) // 構造函數,NULL即可

int kmem_cache_destroy( struct kmem_cache * cachep )

void * kmem_cache_alloc( struct kmem_cache * cachep, gfp_t flags )

void   kmem_cache_free( struct kmem_cache* cachep, void * objp )


slab高速緩存創建標誌

- SLAB_HWCACHE_ALIGN : 命令slab層把一個slab內的所有對象按照高速緩存行對齊,可以提高性能,代價是增加了一定內存開銷。

- SLAB_POISON :命令slab層使用已知的值(a5a5a5)條從slab,所謂的中毒。

- SLAB_RED_ZONE:命令slab層在已經分配的內存周圍插入“”紅色警戒區“以探測緩衝越界”

- SLAB_PANIC :分配失敗時提醒slab層,在要求分配只能成功時使用。

- SLAB_CACHE_DMA : 



高端內存映射

根據定義,在高端內存中的頁不能永久地映射到內核地址空間上。因此,通過alloc_pages()函數以__GFP_HIGHMEM標誌獲得的頁不可能有邏輯地址。

在x86上,高端內存中的頁被映射到3GB ~ 4GB。

- 永久映射 - 數量是有限的

void * kmap(struct page *page) - 可能睡眠,在高端內存或者低端內存中都能用。

void kunmap(struct page *page)

- 臨時映射 - 原子映射

用於必須創建一個映射而當前的上下文又不能睡眠時

void *kmap_atomic( struct page * page, enum km_type type )

void  kunmap_atomic ( void * kvaddr, enum km_type type )


每個CPU數據


十三、虛擬文件系統

VFS是對各個具體文件系統的抽象,提供統一的接口,具體實現則由具體的文件系統實現,這樣各種文件系統之間就可以交流。

當然VFS是按照unix文件系統風格設計的,對於那些不支持目錄項、inode等概念的文件系統,需要模擬出相應的概念才行,一般都需要在使用時進行某些處理。

1.Unix文件系統

文件系統包含文件、目錄和相關的控制信息。文件系統被安裝在一個特定的安裝點上,所有已經安裝的文件系統都作爲根文件系統樹的枝葉出現在系統中。

Unix文件系統是面向字節流的,不同於面向記錄的文件系統(OpenVMS的File-11)。

文件、目錄:屬於普通文件

目錄項,目錄條目:路徑中的每一個都是目錄項,包含最後的文件。比如:/home/wolfman/butter,/,home,wolfman,butter都是目錄項。

文件相關信息,稱作文件的元數據:inode,不同於文件內容本身。

文件系統的控制信息在超級快中。

2. VFS對象及其數據結構

VFS中有4個主要對象,每個對象結構體中都有操作對象,包含其操作函數。

超級塊對象:super_block,代表一個具體的已經安裝的文件系統

索引節點對象:inode,代表一個具體的文件(文件和目錄)

目錄項對象:dentry,代表一個目錄條目,是路徑的組成部分

文件對象:file,代表由進程打開的文件

另外

file_system_type :每個註冊的文件系統

vfsmount :每個安裝點

3. 超級塊對象 - super_block

超級塊用於存儲特定文件系統的信息,通常對應於存放在磁盤特定扇區中的文件系統超級塊或者文件系統控制塊。對於並非基於磁盤的文件系統(如sysfs),他們會在使用現場創建超級塊並將其保存到內存中。

超級塊操作:super_operations

4.索引節點對象 - inode

索引節點對象包含了內核在操作文件或目錄時需要的全部信息。對於Unix風格的文件系統,這些信息可以從磁盤索引節點直接讀入。

一個索引節點,可以代表普通文件(文件,目錄),也可以代表特殊文件(塊設備,字符設備,管道,套接字)

其操作inode_operations中的mknod用來創建特殊文件。

int mknod( struct inode *dir, struct dentry *dentry, int mode, dev_t rdev )

要創建的文件存放在dir目錄中,目錄項是dentry,關聯的設備是rdev

5.目錄項對象 - dentry

VFS把目錄當作文件對待,雖然路徑中每一個部分都可以由inode表示,但VFS經常需要執行目錄相關的操作,比如路徑名查找等。路徑名查找需要解析路徑中的每一個組成部分,不但要確保它有效,而且還需要再進一步尋找路徑中的下一個部分。

爲了方便查找操作,VFS引入了目錄項的概念。

與超級塊對象和索引節點對象不同,目錄項對象沒有對應的磁盤數據結構,使用時根據路徑名現場創建,不會寫到磁盤。

目錄項有3中狀態:

- 被使用:目錄項對應一個有效的inode節點,由d_inode指向,d_count爲正值。

- 未被使用:目錄項對應一個有效的inode節點,但d_count = 0

- 負狀態:目錄項沒有對應的有效的inode節點,d_inode = NULL

目錄項緩存:-----

6. 文件對象 - file

文件對象表示進程已打開的文件,是已打開文件在內存中的表示,該對象不是物理文件。

由於多個進程可以打開和操作同一個文件,因此同一個文件也可能存在多個對應的文件對象。

文件對象僅在進程觀點上代表已打開的文件,它反過來指向目錄項對象(f_path -> f_dentry),而目錄項對象指向索引節點對象,其實目錄項對象才表示已打開的實際文件。

文件操作 - file_operations

ioctl - 當文件是一個被打開的設備節點時,可以通過它進行設置操作。

int mmap( struct file *file, struct vm_area_struct * vma ) - 將給定的文件映射到制定的地址空間上。

unsigned long get_unmapped_area() - 獲取未使用的地址空間來映射給定的文件。

sendfile() , sendpage,從一個文件拷貝內容到另一個文件,在內核中進行,不經過用戶空間。

7. 文件系統 - file_system_type - vfsmount

描述文件系統的功能和行爲。每種文件系統,不管有多少個實例安裝到了系統中,都只有一個file_system_type對象。

當文件系統被安裝時,將有一個vfsmount對象在安裝點被創建,該結構體表示文件系統的實例,代表一個安裝點。

8. 其它數據結構 - file_struct, fs_struct, namespace

系統中的每個進程都有自己的一組打開的文件,像根文件系統,當前工作目錄,安裝點等。

進程描述符中的files指向file_struct,所有與單個進程相關的信息(如打開的文件及文件描述符)都包含在其中。其中的fd_array,指向已打開的文件對象,默認64,如果進程打開的文件過多,則需要動態分配,性能稍有影響。

進程描述符中的fs指向fs_struct,它包含文件系統和進程的相關信息。該結構體也包含了當前進程的當前工作目錄和根目錄。

2.4內核後,單進程命名空間被加入到內核中,它使得每一個進程在系統中都看到唯一的安裝文件系統 - 不僅是唯一的根目錄,而且是唯一的文件系統層次結構。


對於大多數進程來說,他們的進程描述符都指向唯一的files_struct和fs_struct結構體,但對於那些使用克隆標誌CLONE_FILES,CLONE_FS創建的進程,則會共享這兩個結構體,每個結構體都會維護使用計數。

對於namespace,默認情況下,所有的進程都共享同樣的命名空間(也就是,它們都從相同的掛載表中看到同一個文件系統層次結構)。只有在進行clone()操作時使用CLONE_NEWS標誌,纔會給進程一個唯一的命名空間結構體的拷貝。


十四、塊IO層

1.基本概念

系統中能夠隨機(不需要按順序)訪問固定大小數據片(chunks)的硬件設備稱爲塊設備。這些固定大小的數據片稱爲塊。按照字符流方式被有序訪問的設備,稱爲字符設備。他們的區別在於能否隨機訪問。

扇區是塊設備中最小的可尋址單元,常見512字節,這是設備的物理屬性,塊設備無法對比扇區還小的單元進行尋址和操作,但可能一次對多個扇區進行操作。

塊是文件系統的一種抽象,只能基於塊來訪問文件系統。塊不小於扇區,不大於頁。扇區又叫硬件塊,設備塊,而塊又叫文件塊,IO塊。

2.緩衝區和緩衝區頭

當一個塊被調入內存時,他要存儲在一個緩衝區中,每個緩衝區與一個塊對應,它相當於磁盤塊(文件系統塊)在內存中的表示。一個頁可以容納多個塊。

每個緩衝區都有一個對應的緩衝區描述符,存儲緩衝區的控制信息,叫做緩衝區頭,buffer_head。裏面包含緩衝區的狀態,關聯的設備,保存在哪個頁中等。緩衝區頭用於描述磁盤塊和物理內存緩衝區之間的映射關係。

對內核來說,更傾向於操作頁面結構。若使用緩衝區頭進行IO操作,需要將對頁的操作分解到若干個對緩衝區頭的操作。

3.bio結構體 - block IO

內核中塊IO操作的基本容器由bio結構體表示。該結構體代表了正在現場的(活動的)以片段(segment)鏈表形式組織的IO操作。一個片段就是一小塊連續的內存緩衝區。像這樣的向量IO就是所謂的聚散IO。

struct bio結構體包含bio_vec鏈表,每個對應一個頁面的IO操作,包括頁面指針,緩衝區起始位置,長度。

相對於buffer_head,struct bio可以表示多個離散的IO操作,且是一個簡單的結構體。

4.請求隊列

塊設備將它們掛起的塊IO請求保存在請求隊列中。其中的請求由request表示。由於磁盤尋址比較慢,內核會對請求進行合併和排序,這可以極大提高系統的整體性能。

IO調度程序 - 電梯調度:

- Linus電梯

- 最終期限 - deadline:每個請求都有一個最終期限,讀500ms,寫5s

- 預測IO調度程序 - as:利用程序的局部性,等待一會兒,看看能否合併;

- 完全公正的排隊調度程序:cfq:每個進程一個隊列,不同隊列按照時間片輪轉;

- 空操作的IO調度程序 - noop:除了合併,不做其它操作。適用於完全隨機的設備。



十五、進程地址空間

內核除了管理本身的內存外,還必須管理用戶空間中進程的內存,這個內存稱爲進程地址空間,也就是系統中每個用戶空間進程所看到的內存。

進程地址空間由進程可尋址的虛擬內存組成,而且更爲重要的特點是:內核允許進程使用這種虛擬內存中的地址。

1.地址空間

在32位系統上,儘管一個進程可以尋址4GB的虛擬內存,但這並不代表它就有權訪問所有的虛擬地址。在地址空間中,我們更關心的是一些虛擬內存的地址區間,這些可被訪問的合法地址空間稱爲內存區域(memory areas)。通過內核,進程可以給自己的地址空間動態地添加或者減少內存區域。內存區域不能相互重疊。

進程只能訪問有效內存區域內的內存地址。每個內存區域也有相關的權限。如果一個進程訪問了不在有效範圍中的內存區域,或者以不正確的方式訪問了有效地址,那麼內核就會終止該進程,並返回“段錯誤”信息。

內存區域可以包含各種內存對象:代碼段、數據段、未初始化全局變量段bss,用於進程用戶空間棧的零頁內存映射、任何內存映射文件、任何共享內存段,任何匿名的內存映射(比如由malloc分配的內存)

2.內存描述符 - mm_struct,由task_struct->mm指向

內核使用內存描述符結構體表示進程的地址空間.其中包含 - struct vm_area_struct  *mmap - 字段,是內存區域鏈表。兩個線程可能共享地址空間(clone時設置CLONE_VM)。

內核線程沒有進程地址空間,也沒有相關的內存描述符。所以內核線程對應的進程描述符中的mm爲NULL。事實上,這也正是內核線程的真實含義 - 他們沒有用戶上下文。

3.虛擬內存區域

內存區域由vm_area_struct結構體描述,Linux內核中也經常成爲虛擬內存區域(virtual memory areas - VMAs),它描述了指定地址空間內連續區間上的一個獨立內存範圍。內核將每個內存區域作爲一個單獨的內存對象管理,每個區域都擁有一致的屬性,比如訪問權限等,另外,相應的操作也一樣。

VMA標誌,讀寫執行權限,VM_SHARED表明該VMA可以在多個進程間共享,則成爲共享映射,否則稱爲私有映射。

查看實際的內存區域:cat /proc/pid/maps或者#pmap pid

4.VMA操作

find_vma():找到一個內存地址屬於哪個VMA

5、創建地址區間,即VMA

mmap() -> do_mmap()創建一個新的VMA,如果新的VMA和一個已有的VMA相鄰,且具有相同的訪問權限的話,兩個區間將合併成一個。do_mmap()函數將一個地址區間加入到進程的地址空間中 - 無論是擴展已存在的內存區域還是創建一個新的內存區域。

unsigned long do_mmap( struct file * file,  unsigned long addr,  unsigned long len , unsigned long prot,

                                                   unsigned long flag,  unsigned long offset )

如果file = NULL,且offset = 0,那麼就代表這次映射沒有和文件相關,稱作匿名映射(annoymous mapping)。如果指定文件名和偏移量,那麼該映射稱爲文件映射(file-backed mapping)

6、刪除地址區間 -- munmap() - do_munmap()

7、頁表

雖然應用程序操作的對象是映射到物理內存之上的虛擬內存,但是處理器直接操作的卻是物理內存。所以當應用程序訪問一個虛擬地址時,首先必須將虛擬地址轉換成物理地址,然後處理器才能解析地址訪問請求。

Linux通過3級頁表完成轉換 - PGD,PMD - PTE每個進程都有自己的頁表,線程會共享頁表,內存描述符的pgd指向的就是進程的頁表。

搜索內存中的物理地址的速度很悠閒,因此爲了加快搜索,多數體系結構都實現了一個翻譯後緩衝器(translate lookaside buffer, TLB),TLB作爲一個將虛擬地址映射到物理地址的硬件緩存。



十六、頁高速緩存和頁回寫

頁高速緩存(cache)是Linxu內核實現的磁盤緩存。它主要用來減少磁盤的IO操作。具體講,是通過把磁盤中的數據緩存到物理內存中,把對磁盤的訪問變爲對物理內存的訪問。

- 訪問磁盤和訪問內存時好幾個數量級的差別,ms - ns

- 程序的局部性原理,一旦被訪問,可能再次被訪問

1.緩存手段

頁高速緩存是由內存中的物理頁面組成的,其內容對應磁盤上面的物理塊。頁高速緩存的大小能夠動態調整 - 它可以通過佔用空閒內存以擴張大小,也可以自我收縮以緩解內存的使用壓力。我們稱正被緩存的存儲設備爲後備存儲。

寫緩存策略:

-- 不緩存

-- 寫透緩存(write-through cache),寫操作將自動更新內存緩存,同時也更新磁盤文件

-- 回寫:寫操作直接寫到緩存中,後端存儲不會立即更新,而是將也告訴緩存標記爲“髒”,由回寫進程週期寫到磁盤。

緩存回收策略:

-- 最近最少使用 - LRU

-- 雙鏈策略

2. Linux頁高速緩存

頁高速緩存,緩存的是內存頁面。緩存中的頁來自對正規文件、塊設備文件和內存映射文件的讀寫。如此一來,頁高速緩存就包含了最近被訪問過的文件的數據塊。在執行一個IO操作前,內核會檢查數據是否已經在頁高速緩存中了,如果在,就不需要再從磁盤讀取了。

在頁高速緩存中的頁,可能包含了多個不連續的物理磁盤塊(扇區/設備塊,還是文件塊 ?似乎是文件塊)。

Linux頁高速緩存使用了一個新對象管理緩存項和頁IO操作 - address_space結構體,它是 vm_area_struct的物理地址對等體。當一個文件被10個VMA結構體標識(比如5個進程,每個進程調用mmap()映射2次),那麼這個文件只能有一個address_space數據結構,也就是文件可以有多個虛擬地址,但是只能在物理內存有一份。

address_space的host字段是其owner,如果和文件對應,那就是inode節點,否則爲NULL。

3.緩衝區高速緩存

獨立的磁盤塊通過IO緩衝也要被存入頁高速緩存。一個緩衝區是一個物理磁盤塊在內存裏面的表示。緩衝的作用就是映射內存中的頁面到磁盤塊,這樣一來頁高速緩存在塊IO操作時也減少了磁盤訪問,因爲它緩存磁盤塊和減少IO操作。這個緩存通常稱爲緩衝區高速緩存。

緩衝區和頁高速緩存現在是統一的。緩衝區請參考ch14,它是用頁映射塊的,所以正好在頁高速緩存中。


十七、設備與模塊

1. 設備類型:在所有unix系統中爲了統一普通設備的操作所採用的分類

三種類型:塊設備、字符設備、網絡設備

塊設備通過稱爲“塊設備節點”的特殊文件來訪問,並且通常被掛在爲文件系統。 - 應用程序通過與文件系統交互 ?

字符設備是通過稱爲字符設備節點的特殊文件訪問的。與塊設備不同,應用程序通過直接訪問字符設備節點與字符設備交互。

網絡設備打破了所有東西都是文件的設計原則,它不是通過設備節點來訪問,而是通過套接字API這樣的特殊接口來訪問的。

Linux也提供了不少其它設備類型。

並不是所有設備驅動都表示物理設備。有些設備驅動是虛擬的,僅提供訪問內核功能而已 - 僞設備(pseudo device),最常見的如內核隨機數發生器/dev/random,空設備/dev/null等。

2. 模塊

Kconfig,Makefile,obj-m,fishing-objs

如果不在內核代碼樹中維護,那麼需要指定內核源代碼的目錄

make modules_install # 將編譯後的模塊安裝到 /lib/modules/version/kernel/ 目錄下,比如 /lib/modules/version/kernel/drivers/char/fishing.ko

模塊依賴性:depmod or depmod -A,模塊依賴信息保存在 /lib/modules/version/modules.dep  中

載入模塊:insmod, rmmod, modprobe

3. 設備模型

統一設備模型(device model)最初是爲了以正確的順序關閉設備的電源。

struct kobject {

  const char *name;

  struct list_head entry;

  struct kobject *parent;   // 指向其parent

  struct kset      *kset;      // 其所屬子系統

  struct kobj_type *ktype;  // 一類kobject所具有的普遍屬性。

  struct sysfs_dirent  *sd;  // 在sysfs中表示這個kobject,從sysfs內部看,是一個inode

  ......

}


struct kobj_type {

  void (*release) (struct kobject *);  // 引用計數減至0時調用

  const struct sysfs_ops   * sysfs_ops; // show, store,讀寫函數

  struct attribute   ** default_attrs;  // 每個屬性,在sysfs都對應一個文件

}


struct kset {

  struct list_head  list;  // 連接集合中所有的kobject

  spinlock_t    list_lock;

  struct kobject   kobj;  // 該集合的基類 ***&&&&

  struct kset_uevent_ops  * uevent_ops; // 用於處理集合中kobject對象的熱插拔操作;

}

kset是一個容器,把kobject集合到一個集合中,而ktype描述相關類型kobject所共有的特性。他們之間的重要區別在於:具有相同ktype的kobject可以被分組到不同的kset。就是說Linux內核中,只有少數一些的ktype,卻有多個kset(每個kset代表一個子系統 ?)。


管理操作kobject

kmaloc  /  memset / kobject_init /  - > kobject_create


4. sysfs

sysfs文件系統是一個處於內存中的虛擬文件系統,他爲我們提供了kobject對象層次結構的視圖。sysfs的訣竅是把kobject對象與目錄項(directory entries)緊急聯繫起來,這點是通過kobject對象中的dentry字段實現的。kobject對象被映射到目錄項。

kobject_add  // kobject_create_and_add // kobject_del

爲kobject創建新屬性

sysfs_create_file  /  sysfs_create_link  /  sysfs_remove_file   /  sysfs_remove_link


內核事件層

內核事件層實現了內核到用戶的消息通知系統,藉助kobject和sysfs實現。內核事件層把事件模擬爲信號 - 從明確的kobject對象發出,所以每個事件源都是一個sysfs路徑。

每個事件都被賦予了一個動作或者字符串表示信號,比如“被修改過”,“未被掛載”等。每個事件都有一個可選的負載(payload),內核事件層使用屬性作爲負載。

kobject_uevent()觸發內核事件,經netlink傳送給用戶空間的daemon,事件不使用字符串,而使用枚舉值,避免打字錯誤。如KOBJ_MOUNT, KOBJ_UNMOUNT, KOBJ_ADD, KOBJ_REMOVE, KOBJ_CHANGE等。


十八、調試

引用空指針會導致產生一個oops,但垃圾數據可能導致系統崩潰。

printk() - 除了arch相關的啓動階段,幾乎任何地方都能使用,可以指定等級。 0 -> 7 降低。被保存到一個環形隊列中,一般爲16kb,可以在編譯時通過 CONFIG_LOG_BUF_SHIFT 調整其大小。

printk()  -> /proc/kmsg  -> 用戶空間守護進程 klogd -> 用戶空間守護進程 syslogd  ->  /var/log/messages ,可以通過 /etc/syslog.conf 重定向。


OOPS 會打印寄存器信息和call track

ksymoops - 需要編譯內核時產生的 System.Map 才能解碼 OOPS

kallsyms - 通過 CONFIG_KALLSYMS ,編譯進內核,這樣內核中就會保存那些地址的函數名稱( CONFIG_KALLSYMS_ALL  包括函數和其它符號)。


神奇的系統請求鍵 - Magic SysRq key

該功能可以通過定義 CONFIG_MAGIC_SYSRQ 配置選項來啓用。這時,無論內核處於什麼狀態,都可以通過特殊的組合鍵跟內核進行通信。

除了配置選項,還要通過一個 sysctl 標記該特性的開關:echo 1 > /proc/sys/kernel/sysrq

#sysrq -h  // help

#sysrq -s // 將髒緩衝區跟硬盤交換分區同步

#sysrq -u // 卸載所有的文件系統

#sysrq -b  // 重啓設備

詳細見文檔:Documentations/sysrq.txt。實際的實現,在 /drivers/char/sysrq.c 中


十九、移植

二十、社區

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