Linux Device Driver 3rd 上

第一章 設(shè)備驅(qū)動(dòng)程序的簡介

處于上層應(yīng)用與底層硬件設(shè)備的軟件層

區(qū)分機(jī)制和策略是Linux最好的思想之一,機(jī)制指的是需要提供什么功能,策略指的是如何使用這個(gè)功能!

通常不同的環(huán)境需要不同的方式來使用硬件,則驅(qū)動(dòng)應(yīng)當(dāng)盡可能地不實(shí)現(xiàn)策略.

驅(qū)動(dòng)程序設(shè)計(jì)需要考慮一下幾個(gè)方面的因素:

? ? ① 提供給用戶盡量多的選項(xiàng)

? ? ② 編寫驅(qū)動(dòng)程序所占用的時(shí)間,驅(qū)動(dòng)程序的操作耗時(shí)需要盡量縮減.

? ? ③ 盡量保持程序簡單

內(nèi)核概覽:

? ? ①? 進(jìn)程管理:

? ? ? ? 負(fù)責(zé)創(chuàng)建和銷毀進(jìn)程,并處理它們和外部世界之間的連接(輸入輸出),調(diào)度器也屬于進(jìn)程管理中的

? ? ②? 內(nèi)存管理:

? ? ? ? 內(nèi)核在有限的可用資源上為每一個(gè)進(jìn)程都創(chuàng)建了一個(gè)虛擬地址空間

? ? ③? 設(shè)備管理:

? ? ? ? 幾乎所有的系統(tǒng)操作都會(huì)映射到具體的硬件設(shè)備上

? ? ④? 文件系統(tǒng):

? ? ? ? 一切皆文件,一個(gè)kernel可能包含多個(gè)文件系統(tǒng),比如 存儲(chǔ)硬盤被抽象成ext3文件系統(tǒng)

? ? ⑤? 網(wǎng)絡(luò):

? ? ? ? 網(wǎng)絡(luò)功能必須用操作系統(tǒng)來管理,因?yàn)榇蟛糠值木W(wǎng)絡(luò)操作和具體的進(jìn)程無關(guān):數(shù)據(jù)包的傳入是異步事件

設(shè)備以及模塊的分類:

? ? 模塊:

? ? ? ? 內(nèi)核模塊可以隨時(shí)載入到系統(tǒng)中,即便系統(tǒng)已經(jīng)處于運(yùn)行狀態(tài),通常使用的方法是insmod加載,rmmod卸載

? ? ? ? 字符模塊,塊模塊,網(wǎng)絡(luò)模塊,這三個(gè)模塊并不是必須單獨(dú)存在的,在設(shè)計(jì)模塊的時(shí)候,常常需要對于同一

? ? ? ? 個(gè)硬件設(shè)備或者功能定義成不同的模塊,這樣可以增強(qiáng)后期的擴(kuò)展性。

? ? 設(shè)備:

? ? ? ? 字符設(shè)備: 那些可以以字節(jié)流的方式進(jìn)行訪問的設(shè)備, 字符設(shè)備與常規(guī)的文件的區(qū)別在于字符設(shè)備只是數(shù)據(jù)管道,另外,字符

? ? ? ? ? ? 設(shè)備也可以被看成數(shù)據(jù)區(qū)

? ? ? ? 塊設(shè)備:塊設(shè)備也是通過文件節(jié)點(diǎn)進(jìn)行操作的,比如 disk硬件就是屬于塊設(shè)備并且掛載了一個(gè)文件系統(tǒng).

? ? ? ? ? ? 和字符設(shè)備類似,塊設(shè)備也是通過/dev 目錄下的文件系統(tǒng)節(jié)點(diǎn)來訪問。另外,Linux可以讓應(yīng)用

? ? ? ? ? ? 程序向字符設(shè)備一樣地讀寫塊設(shè)備,允許一次傳遞任意多字節(jié)的數(shù)據(jù),那兩者的區(qū)別是什么呢?

? ? ? ? ? ? 兩者的區(qū)別僅僅在于內(nèi)核內(nèi)部管理數(shù)據(jù)的方式,也就是內(nèi)核以驅(qū)動(dòng)程序間的軟件接口。

? ? ? ? 網(wǎng)絡(luò)接口:

? ? ? ? ? ? 網(wǎng)絡(luò)接口由內(nèi)核中的網(wǎng)絡(luò)子系統(tǒng)驅(qū)動(dòng),負(fù)責(zé)發(fā)送和接收數(shù)據(jù)包。由于不是面向流的設(shè)備,因此無法將網(wǎng)絡(luò)設(shè)備映射到/dev 下

? ? ? ? ? ? 系統(tǒng)節(jié)點(diǎn),所以內(nèi)核和網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序間的通訊完全不同于內(nèi)核和字符以及塊驅(qū)動(dòng)程序之間的通訊,內(nèi)核擁有一套和數(shù)據(jù)包傳輸

? ? ? ? ? ? 相關(guān)的函數(shù)。

? ? 文件系統(tǒng):

? ? ? ? 文件系統(tǒng)也是內(nèi)核模塊化的表現(xiàn),它決定了如何在塊設(shè)備上組織數(shù)據(jù),以表示目錄和文件形成的樹,但是文件系統(tǒng)因?yàn)闆]有實(shí)際物理設(shè)備所以

? ? ? ? 并不是設(shè)備驅(qū)動(dòng)程序,相反,文件系統(tǒng)是一個(gè)軟件驅(qū)動(dòng)程序,它將低層數(shù)據(jù)結(jié)構(gòu)映射到高層數(shù)據(jù)結(jié)構(gòu),決定文件名以及在目錄項(xiàng)中存儲(chǔ)文件的

? ? ? ? 哪些信息等等。

? ? ? ? 文件系統(tǒng)模塊必須實(shí)現(xiàn)訪問目錄和文件的最底層系統(tǒng)調(diào)用,方法是將文件名和路徑(以及其他一些信息,比如訪問模式等)映射到數(shù)據(jù)塊中的數(shù)據(jù)

? ? ? ? 結(jié)構(gòu)中,這種接口完全獨(dú)立于在磁盤上傳輸?shù)膶?shí)際數(shù)據(jù),而數(shù)據(jù)的傳輸由塊設(shè)備驅(qū)動(dòng)程序負(fù)責(zé)完成。

? ? 驅(qū)動(dòng)程序編寫者應(yīng)當(dāng)盡量避免在代碼中實(shí)現(xiàn)安全策略,安全策略問題最好在系統(tǒng)管理員的控制之下,由內(nèi)核的高層來實(shí)現(xiàn)。

? ? 任何從內(nèi)核中得到的內(nèi)存,都必須在提供給用戶進(jìn)程或者設(shè)備之前清零或者以其他方式初始化,否則就可能發(fā)生信息泄露(如數(shù)據(jù)和密碼的泄露等)。

? ? Linux內(nèi)核可以編譯為不支持模塊方式,從而可以關(guān)閉任何模塊相關(guān)的安全漏洞。

第二章 構(gòu)造和運(yùn)行模塊

內(nèi)核模塊與應(yīng)用程序的對比:

? ? 大多數(shù)應(yīng)用程序是從頭到尾執(zhí)行單個(gè)任務(wù),而內(nèi)核模塊卻只是預(yù)先注冊自己以便服務(wù)需要的某個(gè)請求,比如初始化函數(shù)的任務(wù)就是為了以后

? ? 調(diào)用模塊函數(shù)預(yù)先作準(zhǔn)備。

? ? 應(yīng)用程序在退出時(shí),可以不管資源的釋放或者其他的清除工作,但內(nèi)核模塊的退出函數(shù)卻必須仔細(xì)撤銷初始化函數(shù)所做的一切,否則,在系統(tǒng)

? ? 重新引導(dǎo)之前某些東西就會(huì)殘留在系統(tǒng)中。

? ? 應(yīng)用程序可以調(diào)用它并未定義的函數(shù),主要原因是鏈接過程能夠解析外部引用從而使用適當(dāng)?shù)暮瘮?shù)庫。然后,內(nèi)核模塊僅僅被鏈接到內(nèi)核,所以

? ? 它僅僅能夠調(diào)用由內(nèi)核導(dǎo)出的那些函數(shù),而不存在任何可鏈接的函數(shù)庫。

? ? 在各環(huán)境下的處理錯(cuò)誤的方式兩者也是不同的,應(yīng)用程序開發(fā)過程中的段錯(cuò)誤是無害的,并且總是可以使用調(diào)試跟蹤到源代碼中的問題所在,

? ? 而一個(gè)內(nèi)核錯(cuò)誤即使不影響整個(gè)系統(tǒng),也至少會(huì)殺死當(dāng)前進(jìn)程。

用戶空間和內(nèi)核空間

? ? 模塊運(yùn)行在內(nèi)核空間,應(yīng)用程序運(yùn)行在用戶空間,Unix使用兩種操作模式或級(jí)別來實(shí)現(xiàn)針對CPU的不同操作,內(nèi)核運(yùn)行在最高級(jí)別(也稱為超級(jí)用戶態(tài)),

? ? 在這個(gè)級(jí)別中可以進(jìn)行所有的操作,而應(yīng)用程序運(yùn)行在最低級(jí)別(即所謂的用戶態(tài)),在這個(gè)級(jí)別中,處理器控制著對硬件的直接訪問以及堆內(nèi)存的非授權(quán)

? ? 訪問。兩種的不同的級(jí)別是針對CPU而言的,所以當(dāng)應(yīng)用程序調(diào)用系統(tǒng)調(diào)用或者被硬件中斷掛起的時(shí)候,CPU的保護(hù)級(jí)別就從最低級(jí)別硬件上切換到最高級(jí)別了。

? ? 另外,一旦從用戶空間切換到內(nèi)核空間,執(zhí)行系統(tǒng)調(diào)用的內(nèi)核代碼運(yùn)行在進(jìn)程上下文中,它代表調(diào)用進(jìn)程執(zhí)行操作,因此能夠訪問進(jìn)程地址空間的所有數(shù)據(jù),

? ? 而處理硬件中斷的內(nèi)核代碼和進(jìn)程是異步的,與任何一個(gè)特定進(jìn)程無關(guān)。

內(nèi)核中的并發(fā):

? ? 為什么內(nèi)核編程需要考慮并發(fā)問題?

? ? 1. Linux系統(tǒng)中通常正在運(yùn)行多個(gè)并發(fā)進(jìn)程,并且可能有多個(gè)進(jìn)程同時(shí)使用我們的驅(qū)動(dòng)程序。

? ? 2. 大多數(shù)設(shè)備能夠中斷處理器,而中斷處理程序異步運(yùn)行,而且可能在驅(qū)動(dòng)程序正試圖處理其他任務(wù)時(shí)被調(diào)用

? ? 3. 部分軟件抽象(內(nèi)核定時(shí)器)也在異步運(yùn)行著

? ? 4. 多處理器系統(tǒng),可能存在多個(gè)CPU運(yùn)行驅(qū)動(dòng)程序

? ? 5. 內(nèi)核已經(jīng)實(shí)現(xiàn)可搶占

當(dāng)前進(jìn)程

? ? struct task_struct *current;

? ? 內(nèi)核代碼可以通過訪問全局項(xiàng)current來獲得當(dāng)前進(jìn)程,而2.6后,current不再是一個(gè)全局變量(為什么呢? 因?yàn)樾枰С侄嗵幚砥飨到y(tǒng)),而是將

? ? 指向task_struct結(jié)構(gòu)的指針隱藏在內(nèi)核棧中。

其他:

? ? 應(yīng)用程序在虛擬內(nèi)存中布局,并具有一塊很大的??臻g(棧是用來保存函數(shù)調(diào)用歷史以及當(dāng)前活動(dòng)函數(shù)中的自動(dòng)變量的),相反內(nèi)核具有非常小的棧

? ? (可能是4096字節(jié)大?。覀冏约旱暮瘮?shù)必須和整個(gè)內(nèi)核空間調(diào)用鏈一同共享這個(gè)棧,則盡量避免聲明大的自動(dòng)變量。

? ? 內(nèi)核代碼不能實(shí)現(xiàn)浮點(diǎn)數(shù)運(yùn)算,為什么? 因?yàn)槿绻蜷_了浮點(diǎn)支持,在某些架構(gòu)上需要在進(jìn)入和退出內(nèi)核空間時(shí)保存和恢復(fù)浮點(diǎn)處理器的狀態(tài),會(huì)帶來一定的

? ? 額外開銷,同時(shí)該開銷無任何價(jià)值。

裝載和卸載模塊:

? ? insmod: 將模塊的代碼和數(shù)據(jù)裝入內(nèi)核,使用sys_init_module給模塊分配內(nèi)核內(nèi)存(vmalloc)以便裝載模塊,然后將模塊正文復(fù)制到內(nèi)存區(qū)域,并

? ? 通過內(nèi)核符號(hào)表解析模塊中的內(nèi)核引用,最后調(diào)用模塊的初始化函數(shù)。

? ? modprobe: 和insmod類似,也是用來將模塊裝載到內(nèi)核中,它與insmod的區(qū)別在于,它除了裝入指定模塊外還同時(shí)裝入指定模塊所依賴的其他模塊。

? ? rmmod: 從內(nèi)核中移出模塊

? ? lsmod: 列出當(dāng)前裝載到內(nèi)核中的所有模塊,還提供了其他一些信息,lsmod是通過讀取/proc/modules虛擬文件來獲得這些信息,有關(guān)當(dāng)前已裝載模塊的信息

? ? ? ? 也可以在sysfs虛擬文件系統(tǒng)的/sys/module下找到

內(nèi)核符號(hào)表:

? ? 公共內(nèi)核符號(hào)表中包含了所有的全局內(nèi)核項(xiàng)(即函數(shù)和變量)的地址,當(dāng)模塊被裝入內(nèi)核后,它所導(dǎo)出的任何符號(hào)都會(huì)變成內(nèi)核符號(hào)表的一部分。

? ? 新模塊可以使用已有模塊的導(dǎo)出的符號(hào),這樣就可以在其他模塊上層疊新的模塊。

? ? modprobe 是處理層疊模塊的一個(gè)實(shí)用工具。

? ? EXPORT_SYMBOL(name)以及EXPORT_SYMBOL_GPL(name)均用來將給定的符號(hào)導(dǎo)出到模塊外部,_GPL版本使得要導(dǎo)出的模塊只能被GPL許可證下的模塊使用,

? ? 導(dǎo)出到外部的變量在模塊可執(zhí)行文件的特殊部分(ELF段)中保存,在裝載時(shí),內(nèi)核通過這個(gè)段來尋找模塊導(dǎo)出的變量。

模塊的初始化和關(guān)閉:

? ? 初始化函數(shù)應(yīng)該被聲明為static

? ? __init:該標(biāo)記(非必須)表明該函數(shù)僅在初始化期間使用,在模塊被裝載后,模塊裝載器就會(huì)將初始化函數(shù)扔掉,這樣可將給函數(shù)占用的內(nèi)存釋放出來,

? ? 達(dá)到節(jié)省內(nèi)存的作用? ?

? ? module_init: 這個(gè)宏會(huì)在模塊的目標(biāo)代碼中增加一個(gè)特殊的段,用于說明內(nèi)核初始化函數(shù)所在的位置,所以可見,如果沒有這個(gè)定義,

? ? 初始化函數(shù)永遠(yuǎn)不會(huì)被調(diào)用

初始化過程中的錯(cuò)誤處理:

? ? 內(nèi)核經(jīng)常使用goto來處理錯(cuò)誤,因?yàn)閷ζ涞淖屑?xì)使用可以避免大量復(fù)雜的、高度縮進(jìn)的結(jié)構(gòu)化邏輯。

在用戶空間編寫驅(qū)動(dòng)程序:

? ? 通常用戶空間的驅(qū)動(dòng)程序被實(shí)現(xiàn)為一個(gè)服務(wù)器進(jìn)程,其任務(wù)是替代內(nèi)核作為硬件控制的唯一代理。

? ? 為什么需要在用戶空間編寫驅(qū)動(dòng)程序?

? ? ① 可以和這整個(gè)C庫鏈接。驅(qū)動(dòng)程序不用借助外部程序就可以完成許多非常規(guī)任務(wù)

? ? ② 可以使用通常的調(diào)試器調(diào)試驅(qū)動(dòng)程序代碼

? ? ③ 如果用戶空間程序被掛起,則簡單地殺掉它就行了,驅(qū)動(dòng)程序帶來的問題不會(huì)掛起整個(gè)系統(tǒng),除非所驅(qū)動(dòng)的硬件已經(jīng)發(fā)生嚴(yán)重故障

? ? ④ 和內(nèi)核內(nèi)存不同,用戶內(nèi)存可以換出,如果驅(qū)動(dòng)程序很大但是不經(jīng)常使用,則除了正在使用的情況之外,不會(huì)占太多內(nèi)存。

? ? ⑤ 良好設(shè)計(jì)的驅(qū)動(dòng)程序仍然支持對設(shè)備的并發(fā)訪問

? ? 用戶空間驅(qū)動(dòng)程序缺點(diǎn):

? ? ① 中斷在用戶空間不可用

? ? ② 只有通過mmap映射/dev/mem才能直接訪問設(shè)備內(nèi)存,但只有特權(quán)用戶才可以執(zhí)行這個(gè)操作

? ? ③ 只有在調(diào)用ioperm或iopl后才可以訪問I/O端口,這兩個(gè)系統(tǒng)調(diào)用并不是所有平臺(tái)都支持,而且訪問速度慢,同樣也只有特權(quán)用戶才能調(diào)用這兩個(gè)接口

? ? ④ 響應(yīng)時(shí)間慢

? ? ⑤ 如果驅(qū)動(dòng)程序被換出到磁盤,響應(yīng)時(shí)間會(huì)更長。

? ? ⑥ 用戶空間中不能處理一些非常重要的設(shè)備,包括網(wǎng)絡(luò)接口和塊設(shè)備等


第三章 字符設(shè)備驅(qū)動(dòng)程序

? ? 編寫驅(qū)動(dòng)程序的第一步就是定義驅(qū)動(dòng)程序?yàn)橛脩舫绦蛱峁┑哪芰ΓC(jī)制)

主設(shè)備號(hào)和次設(shè)備號(hào)

? ? 對字符設(shè)備的訪問是通過文件系統(tǒng)內(nèi)的設(shè)備名稱(文件系統(tǒng)樹的節(jié)點(diǎn))進(jìn)行的,它們通常位于/dev目錄。

? ? ls -l : 第一列為c表示是字符設(shè)備,第一列為b表示塊設(shè)備

? ? 通常而言,主設(shè)備號(hào)標(biāo)識(shí)設(shè)備對應(yīng)的驅(qū)動(dòng)程序,次設(shè)備號(hào)由內(nèi)核使用,用于正確確定設(shè)備文件所指的設(shè)備。比如設(shè)備中存在很多l(xiāng)ed,可以理解為所有的led

? ? 都是通過同一個(gè)驅(qū)動(dòng)程序管理,對應(yīng)的主設(shè)備號(hào)是同一個(gè),但是不同的led設(shè)備卻具有不同的次設(shè)備號(hào)。

設(shè)備編號(hào)的內(nèi)部表達(dá):

? ? 內(nèi)核中,dev_t類型用來保存設(shè)備編號(hào)(主設(shè)備號(hào)和次設(shè)備號(hào)), 2.6.0 內(nèi)核版本dev_t是一個(gè)32位的數(shù),其中的12位用來表示主設(shè)備號(hào),其余20位用來表示

? ? 次設(shè)備號(hào)。

內(nèi)核重要的數(shù)據(jù)結(jié)構(gòu):

? ? file_operations:

? ? ? ? file_operations結(jié)構(gòu)是用來建立file與驅(qū)動(dòng)程序操作的連接,每一個(gè)打開的文件(內(nèi)部有一個(gè)file結(jié)構(gòu)表示)和一組函數(shù)關(guān)聯(lián)(通過包含一個(gè)指

? ? ? ? 向file_operations結(jié)構(gòu)的f_op字段)

? ? ? ? file_operations結(jié)構(gòu)介紹:

? ? ? ? ? ? struct module *owner: 指向擁有該結(jié)構(gòu)的模塊的指針,內(nèi)核使用這個(gè)字段以避免在模塊的操作正在被使用時(shí)卸載該模塊。

? ? ? ? ? ? unsigned int (*poll) : 該方法是poll、epoll和select這三個(gè)系統(tǒng)調(diào)用的后端實(shí)現(xiàn),這三個(gè)系統(tǒng)調(diào)用可用來查詢某個(gè)或多個(gè)文件描述符

? ? ? ? ? ? 上的讀取或?qū)懭胧欠駮?huì)被阻塞

? ? ? ? ? ? int (*mmap) : 該方法用于請求將設(shè)備內(nèi)存映射到進(jìn)程地址空間,如果設(shè)備沒有實(shí)現(xiàn)這個(gè)方法,那么mmap系統(tǒng)調(diào)用將返回-ENODEV

? ? ? ? ? ? int (*flush) : 對flush操作的調(diào)用發(fā)生在進(jìn)程關(guān)閉設(shè)備文件描述符副本的時(shí)候,它應(yīng)該執(zhí)行設(shè)備上尚未完結(jié)的操作。

? ? file結(jié)構(gòu):

? ? ? ? file結(jié)構(gòu)代表一個(gè)打開的文件(也可以理解為文件描述符)(它并不僅僅限定于設(shè)備驅(qū)動(dòng)程序,系統(tǒng)中每個(gè)打開的文件在內(nèi)核空間都有一個(gè)對應(yīng)的file結(jié)構(gòu)),它

? ? ? ? 由內(nèi)核在open時(shí)創(chuàng)建,并傳遞給在該文件上進(jìn)行的所有函數(shù),直到最后的close函數(shù)。

? ? ? ? file結(jié)構(gòu)的重要成員:

? ? ? ? ? ? loff_t f_pos:

? ? ? ? ? ? ? ? 這個(gè)成員之前一直沒有注意到過,它主要用于表示當(dāng)前的讀/寫位置.

? ? ? ? ? ? struct file_operations *f_op:

? ? ? ? ? ? ? ? 與文件相關(guān)的操作,內(nèi)核在執(zhí)行open操作時(shí)對這個(gè)指針賦值,可以在任何需要的時(shí)候修改文件的關(guān)聯(lián)操作,這有點(diǎn)而類似于面向?qū)ο缶幊碳夹g(shù)中的方法重載。

? ? inode結(jié)構(gòu):

? ? ? ? 內(nèi)核用inode結(jié)構(gòu)在內(nèi)部表示文件,單個(gè)文件對應(yīng)于唯一的inode,inode與file是一對多的關(guān)系,對于該結(jié)構(gòu)可以參考linux文件系統(tǒng)的介紹。

字符設(shè)備的注冊:

? ? 內(nèi)核內(nèi)部使用struct cdev結(jié)構(gòu)來表示字符設(shè)備。

? ? open方法:

? ? ? ? open主要完成以下工作:

? ? ? ? 1. 檢查設(shè)備特定的錯(cuò)誤(諸如設(shè)備未就緒或類似的硬件問題)

? ? ? ? 2. 如果設(shè)備是首次打開,則對其進(jìn)行初始化

? ? ? ? 3. 如有必要,更新f_op指針(這里就是之前所說的可以根據(jù)需要隨時(shí)更新operations指針,從而實(shí)現(xiàn)方法重載)

? ? ? ? 4. 分配并填寫至于filp->private_data里的數(shù)據(jù)結(jié)構(gòu)


? ? contianer_of(pointer, container_type, container_field):

? ? ? ? 已知結(jié)構(gòu)體container_type的成員container_field的地址pointer,求解結(jié)構(gòu)體container_type的結(jié)構(gòu)指針.

? ? release方法:

? ? ? ? 主要完成以下任務(wù):

? ? ? ? 1. 釋放有open分配的、保存在filp->private_data中的所有內(nèi)容

? ? ? ? 2. 在最后一次關(guān)閉操作時(shí)關(guān)閉設(shè)備

? ? read和write方法:

? ? ? ? read: 拷貝數(shù)據(jù)到應(yīng)用程序空間, write: 從應(yīng)用程序空間拷貝數(shù)據(jù)

? ? ? ? 兩個(gè)方法中的buff參數(shù)是用戶空間的指針,內(nèi)核代碼不能直接引用其中的內(nèi)容,主要原因如下:

? ? ? ? ①? 隨著驅(qū)動(dòng)程序所運(yùn)行的架構(gòu)的不同或者內(nèi)核配置的不同,在內(nèi)核模式中運(yùn)行時(shí),用戶空間可能是無效的,該指針地址可能根本無法被映射到內(nèi)核空間

? ? ? ? 或者可能指向某些隨機(jī)數(shù)據(jù)

? ? ? ? ②? 即使該指針在內(nèi)核空間代表相同的東西,但用戶空間的內(nèi)存是分頁的,而在系統(tǒng)調(diào)用被調(diào)用時(shí),涉及到的內(nèi)存可能根本就不在RAM中,

? ? ? ? 對用戶空間內(nèi)存的直接引用將導(dǎo)致頁錯(cuò)誤。

? ? ? ? ③ 這里的指針可能是應(yīng)用空間某個(gè)惡意程序調(diào)用系統(tǒng)調(diào)用傳入的,如果在內(nèi)核中直接訪問則會(huì)帶來系統(tǒng)安全性問題


? ? ? ? read方法返回值:

? ? ? ? ? ? ① 如果返回值等于傳遞給read系統(tǒng)調(diào)用的count參數(shù),則說明所請求的字節(jié)數(shù)傳輸成功完成了

? ? ? ? ? ? ② 如果返回值是正的,但是比count小,則說明只有部分?jǐn)?shù)據(jù)成功傳送,這種情況因設(shè)備的不同可能許多原因,大部分情況下,程序會(huì)重新

? ? ? ? ? ? 讀數(shù)據(jù)。

? ? ? ? ? ? ③ 如果返回值為0,則表示已經(jīng)到達(dá)了文件尾

? ? ? ? ? ? ④ 負(fù)值意味著發(fā)生了錯(cuò)誤

? ? ? ? ? ? ⑤ 如果當(dāng)前還沒有數(shù)據(jù)的話,read系統(tǒng)調(diào)用會(huì)出現(xiàn)阻塞

? ? ? ? write方法返回值:

? ? ? ? ? ? ① 如果返回值等于count,則完成了所請求數(shù)目的字節(jié)傳送

? ? ? ? ? ? ② 如果返回值是正的,但小于count,則只傳輸了部分?jǐn)?shù)據(jù),程序很可能再次試圖寫入余下的數(shù)據(jù)

? ? ? ? ? ? ③ 如果返回值為0,意味著什么也沒寫入,這種結(jié)果不是錯(cuò)誤

? ? ? ? ? ? ④ 負(fù)值意味發(fā)生了錯(cuò)誤

? ? copy_to_user(...)以及copy_from_user(...):

? ? ? ? 這兩個(gè)函數(shù)首先會(huì)檢查用戶空間的指針是否有效,之后進(jìn)行內(nèi)核空間和用戶空間之間的數(shù)據(jù)拷貝,否則不會(huì)進(jìn)行拷貝。

? ? ? ? 這里需要注意的是,當(dāng)內(nèi)核空間內(nèi)運(yùn)行的代碼訪問用戶空間時(shí)要多加小心,被尋址的用戶空間的頁面可能當(dāng)前并不在內(nèi)存中(當(dāng)頁面處于交換空間時(shí)),

? ? ? ? 于是虛擬內(nèi)存子系統(tǒng)將該進(jìn)程轉(zhuǎn)入睡眠狀態(tài),直到頁面被傳送到期望的位置。

第四章 調(diào)試技術(shù)

內(nèi)核中的調(diào)試支持

? ? 內(nèi)核配置選項(xiàng):

? ? ? ? CONFIG_DEBUG_KERNEL

? ? ? ? ? ? 該選項(xiàng)僅僅使得其他的調(diào)試選項(xiàng)可用,它本身不會(huì)打開所有的調(diào)試功能

? ? ? ? CONFIG_DEBUG_SLAB

? ? ? ? ? ? 用于打開內(nèi)核內(nèi)存分配函數(shù)中的多個(gè)類型的檢查,打開該檢查后,就可以檢測許多內(nèi)存溢出及忘記初始化的錯(cuò)誤。該選項(xiàng)的主要原理是:

? ? ? ? ? ? ? ? 內(nèi)核會(huì)將其中的每個(gè)字節(jié)設(shè)置為0xa5,而在釋放后將其設(shè)置為0x6b,另外,它還會(huì)在每個(gè)已分配內(nèi)存對象的前面和后面放置一些特殊的防護(hù)值,

? ? ? ? ? ? ? ? 這樣,當(dāng)這些防護(hù)值發(fā)生變化時(shí),內(nèi)核就可以知道有些代碼超出了內(nèi)存的正常訪問范圍。

? ? ? ? CONFIG_DEBUG_PAGEALLOC

? ? ? ? ? ? 在釋放時(shí),全部內(nèi)存頁從內(nèi)核地址空間中移出,該選項(xiàng)將大大降低運(yùn)行速度,但可以快速定位特定的內(nèi)存損壞錯(cuò)誤的所在位置

? ? ? ? CONFIG_DEBUG_SPINLOCK

? ? ? ? ? ? 打開該選項(xiàng),內(nèi)核將捕獲對未初始化自旋鎖的操作,也會(huì)捕獲諸如兩次解開同一鎖的操作

? ? ? ? CONFIG_DEBUG_SPINLOCK_SLEEP

? ? ? ? ? ? 該選項(xiàng)將檢查擁有自旋鎖時(shí)的休眠企圖,實(shí)際上如果調(diào)用可能引起休眠的函數(shù),這個(gè)選項(xiàng)也會(huì)生效,即使該函數(shù)可能不會(huì)導(dǎo)致真正的休眠

? ? ? ? CONFIG_INIT_DEBUG

? ? ? ? ? ? 標(biāo)記為__init(或者_(dá)_initdata)的符號(hào)將會(huì)在系統(tǒng)初始化或者模塊裝載之后被丟棄,該選項(xiàng)可用來檢查初始化完成之后對用于初始化的內(nèi)存空間

? ? ? ? ? ? 的訪問企圖

? ? ? ? CONFIG_DEBUG_STACKOVERFLOW

? ? ? ? CONFIG_DEBUG_STACK_USAGE

? ? ? ? ? ? 這些選項(xiàng)可幫助跟蹤內(nèi)核棧的溢出問題。

? ? ? ? CONFIG_KALLSYMS

? ? ? ? ? ? 該選項(xiàng)用于調(diào)試上下文,沒有此符號(hào),oops清單只能給出十六進(jìn)制的內(nèi)核反向跟蹤信息。

? ? ? ? CONFIG_IKCONFIG

? ? ? ? CONFIG_IKCONFIG_PROC

? ? ? ? ? ? 這些選項(xiàng)會(huì)讓完整的內(nèi)核配置狀態(tài)包含到內(nèi)核中,并可通過/proc訪問。

? ? ? ? CONFIG_ACPI_DEBUG

? ? ? ? ? ? 該選項(xiàng)將打開ACPI(高級(jí)配置和電源接口)中的詳細(xì)調(diào)試信息。

? ? ? ? CONFIG_DEBUG_DRIVER

? ? ? ? ? ? 該選項(xiàng)會(huì)打開驅(qū)動(dòng)程序核心中的調(diào)試信息

? ? ? ? CONFIG_SCSI_CONSTANTS

? ? ? ? ? ? 打開詳細(xì)的SCSI錯(cuò)誤消息

? ? ? ? CONFIG_INPUT_EVBUG

? ? ? ? ? ? 打開對輸入事件的詳細(xì)記錄,該選項(xiàng)會(huì)導(dǎo)致一定的安全問題(會(huì)記錄用戶鍵入的任何東西,包括密碼)

? ? ? ? CONFIG_PROFILING

? ? ? ? ? ? 用于系統(tǒng)性能的調(diào)節(jié)

通過打印調(diào)試:

? ? printk 與printf的差異:

? ? printk與printf最大的不同在于printk缺乏對浮點(diǎn)數(shù)的支持

? ? 通過附加不同日志級(jí)別可讓printk根據(jù)這些級(jí)別所表示的嚴(yán)重程度對消息進(jìn)行分類。

? ? ? ? 例如KENR_INFO 表示日志界別的宏,該宏會(huì)展開為一個(gè)字符串,在編譯時(shí)預(yù)處理器會(huì)將它和消息文本拼接在一起,這也是為什么

? ? ? ? printk中的優(yōu)先級(jí)和格式字串間沒有逗號(hào)。

? ? ? ? 日志級(jí)別:

? ? ? ? ? ? KERN_EMERG: 用于緊急事件消息,一般是系統(tǒng)崩潰之前提示的消息,值為0

? ? ? ? ? ? KERN_ALERT: 用于需要立即采取動(dòng)作的情況,值為1

? ? ? ? ? ? KERN_CRIT: 臨界狀態(tài),通常涉及驗(yàn)證的硬件或軟件操作失敗,值為2

? ? ? ? ? ? KERN_ERR: 用于報(bào)告錯(cuò)誤狀態(tài),例如驅(qū)動(dòng)程序一般使用該宏來報(bào)告來自硬件的問題,值為3

? ? ? ? ? ? KERN_WARNING: 對可能出現(xiàn)問題的情況進(jìn)行警告,但這類情況通常不會(huì)對系統(tǒng)造成嚴(yán)重問題,值為4

? ? ? ? ? ? KERN_NOTICE: 有必要進(jìn)行提示的正常情形,許多與安全相關(guān)的狀況用這個(gè)級(jí)別進(jìn)行匯報(bào),值為5

? ? ? ? ? ? KERN_INFO:提示性信息,值為6

? ? ? ? ? ? KERN_DEBUG: 用于調(diào)試信息,值為7

? ? ? ? ? ? 一般情況下,未指定優(yōu)先級(jí)的printk語句采用的是KERN_WARNING,這個(gè)默認(rèn)值可以在系統(tǒng)中進(jìn)行人為修改,也可以通過對

? ? ? ? ? ? 文本文件/proc/sys/kernel/printk的訪問來讀取和修改控制臺(tái)日志級(jí)別,這個(gè)文件包含了4個(gè)整數(shù)值,分別是: 當(dāng)前的日志

? ? ? ? ? ? 級(jí)別、未明確指定日志級(jí)別時(shí)的默認(rèn)消息級(jí)別、最小允許的日志級(jí)別以及引導(dǎo)時(shí)的默認(rèn)日志級(jí)別。

? ? ? ? ? ? 向該文件寫入單個(gè)數(shù)值,將會(huì)把當(dāng)前日志級(jí)別修改至該值,例如,可以簡單地輸入下面的命令使所有的內(nèi)核消息顯示到控制臺(tái)上:

? ? ? ? ? ? echo 8 > /proc/sys/kernel/printk

? ? 重定向控制臺(tái)消息:

? ? ? ? 內(nèi)核可以將消息發(fā)送到一個(gè)指定的虛擬控制臺(tái)

? ? 消息如何被記錄

? ? ? ? printk函數(shù)將消息寫到一個(gè)長度為__LOG_BUF_LEN字節(jié)的循環(huán)緩沖區(qū)中(可以在配置內(nèi)核時(shí)指定__LOG_BUG_LEN為4 KB~1MB之間的值),然后

? ? ? ? 該函數(shù)會(huì)喚醒任何正在等待消息的進(jìn)程,即那些睡眠在syslog系統(tǒng)調(diào)用上的進(jìn)程或者正在讀取/proc/kmsg的進(jìn)程。

? ? ? ? 對/proc/kmsg進(jìn)行讀操作時(shí),日志緩沖區(qū)中被讀取的數(shù)據(jù)就不再保留,而syslog系統(tǒng)調(diào)用卻能通過選項(xiàng)返回日志數(shù)據(jù)并保留這些數(shù)據(jù),

? ? ? ? 以便其他進(jìn)程也能使用。

? ? 開啟及關(guān)閉消息:

? ? ? ? printk調(diào)試技巧:

? ? ? ? ? ? ① 通過在宏名字中刪減或增加一個(gè)字母來啟用或者禁用每一條打印語句

? ? ? ? ? ? ② 在編譯前修改CFLAGS變量,則可以一次禁用所有消息

? ? ? ? ? ? ③ 同樣的打印語句可以在內(nèi)核代碼中也可以在用戶級(jí)代碼使用

? ? ? ? ? ? 這里記錄下內(nèi)核中定義CFLAGS的代碼:

? ? ? ? ? ? ? ? DEBFLAGS = -O -g -DSCULL_DEBUG

? ? ? ? ? ? ? ? CFLAGS += $(DEBFLAGS)? ? //這樣就可以在內(nèi)核代碼中判斷SCULL_DEBUG宏定義了

? ? 速度限制

? ? ? ? 為了避免過多的打印信息,可以使用以下方法來解決:

? ? ? ? ? ? int printk_ratelimit(void): 在打印一條可能被重復(fù)的信息之前,調(diào)用這個(gè)方法,如果返回值為非零值,則表示之前沒有打印過,否則就表示

? ? ? ? ? ? 之前已經(jīng)打印成功,我們應(yīng)當(dāng)跳過該printk,該方法通過跟蹤發(fā)送到控制臺(tái)消息數(shù)量工作,如果輸出的速度超過一個(gè)閾值,則該方法將返回零,

? ? ? ? ? ? 從而避免發(fā)送重復(fù)消息。

? ? ? ? ? ? /proc/sys/kernel/printk_ratelimit: 在重新打開消息之前應(yīng)該等待的秒數(shù)

? ? ? ? ? ? /proc/sys/kernel/printk_ratelimit_burst: 在進(jìn)行速度限制之前可以接受的消息數(shù)量

通過查詢調(diào)試

? ? printk的缺點(diǎn):

? ? ? ? 由于syslgd會(huì)一直保持對其輸出文件的同步刷新,即使可以降低console_loglevel以避免裝載控制臺(tái)設(shè)備,但大量使用prink仍然會(huì)顯著降低系統(tǒng)

? ? ? ? 性能

? ? 使用/proc文件系統(tǒng)

? ? ? ? /proc文件系統(tǒng)是一種特殊的、由軟件創(chuàng)建的文件系統(tǒng),內(nèi)核使用它向外界導(dǎo)出信息。

? ? ? ? /proc下面的每一個(gè)文件都綁定于一個(gè)內(nèi)核函數(shù),用戶讀取其中的文件時(shí),該函數(shù)動(dòng)態(tài)地生成文件的內(nèi)容。

? ? ? ? 注意: 對于之后的內(nèi)核版本,內(nèi)核開發(fā)者盡量避免使用/proc導(dǎo)出信息,而選擇通過sysfs來向外界導(dǎo)出信息!

? ? 在/proc中實(shí)現(xiàn)文件

? ? ? ? 為創(chuàng)建一個(gè)只讀的/proc 文件,驅(qū)動(dòng)程序必須實(shí)現(xiàn)一個(gè)read_proc函數(shù),用于在讀取文件時(shí)生成數(shù)據(jù)。

? ? ? ? 當(dāng)某個(gè)進(jìn)程讀取這個(gè)文件時(shí)(使用read系統(tǒng)調(diào)用),讀取請求會(huì)通過這個(gè)函數(shù)發(fā)送到驅(qū)動(dòng)程序模塊,此時(shí)內(nèi)核會(huì)分配一個(gè)

? ? ? ? 內(nèi)存頁(即PAGE_SIZE字節(jié)的內(nèi)存塊),驅(qū)動(dòng)程序可以將數(shù)據(jù)通過這個(gè)內(nèi)存頁返回到用戶空間

? ? 創(chuàng)建自己的/proc文件

? ? ? ? create_proc_read_entry將read_proc函數(shù)指針關(guān)聯(lián)到/proc 節(jié)點(diǎn),remove_proc_entry便是相應(yīng)的撤銷create_proc_read_entry所做的工作的函數(shù)。

? ? ? ? 為什么現(xiàn)在內(nèi)核不鼓勵(lì)使用/proc文件,原因主要有以下幾點(diǎn):

? ? ? ? ① 刪除操作可能在文件正在被使用時(shí)發(fā)生

? ? ? ? ② 內(nèi)核不會(huì)檢查某個(gè)名稱是否已經(jīng)被注冊,所以如果不小心,將可能導(dǎo)致兩個(gè)或多個(gè)入口項(xiàng)具有相同的名字。

? ? seq_file接口

? ? ? ? /proc下大文件的實(shí)現(xiàn)有些笨拙,為了讓內(nèi)核開發(fā)工作更加容易,現(xiàn)增加了seq_file接口,這一接口為大的內(nèi)核虛擬文件提供了一組簡單的函數(shù)。

? ? ? ? 如果用戶的/proc文件包含大量的輸出行,則建議使用seq_file接口來實(shí)現(xiàn)該文件

通過監(jiān)視調(diào)試

? ? strace命令是一個(gè)功能非常強(qiáng)大的工具,它可以顯示由用戶空間程序所發(fā)出的所有系統(tǒng)調(diào)用。

? ? strace有許多命令行選項(xiàng):

? ? ? ? -t 顯示調(diào)用的發(fā)生的時(shí)間

? ? ? ? -T 顯示調(diào)用所花費(fèi)的時(shí)間

? ? ? ? -e 限定被跟蹤的調(diào)用類型

? ? ? ? -o 將輸出重定向到一個(gè)文件中,默認(rèn)情況下,strace將跟蹤信息打印到stderr上

? ? strace 使用 strace ls /dev

調(diào)試系統(tǒng)故障

? ? 如果故障發(fā)生在驅(qū)動(dòng)程序中,通常會(huì)導(dǎo)致進(jìn)程被終止,大多數(shù)情況下的驅(qū)動(dòng)故障都可以恢復(fù),而唯一不可恢復(fù)的損失是,此時(shí)為進(jìn)程上下文分配的一些內(nèi)存可能會(huì)丟失

oops消息

? ? 大部分的錯(cuò)誤都是因?yàn)閷ULL指針取值或因?yàn)槭褂昧似渌徽_的指針值,這些錯(cuò)誤通常會(huì)導(dǎo)致一個(gè)oops消息

? ? 由處理器使用的地址幾乎都是虛擬地址,這些地址(除了內(nèi)存管理子系統(tǒng)本身所使用的物理內(nèi)存之外)通過一個(gè)復(fù)雜的被稱為"頁表"的結(jié)構(gòu)

? ? 被映射為物理地址。當(dāng)引用一個(gè)非法指針時(shí),分頁機(jī)制無法將改地址映射到物理地址,此時(shí)處理器就會(huì)向操作系統(tǒng)發(fā)出一個(gè)"page fault"的信號(hào),

? ? 如果地址非法,內(nèi)核就無法"page in"缺失頁面,這時(shí)如果處理器恰好處于超級(jí)用戶模式,系統(tǒng)就會(huì)產(chǎn)生一個(gè)oops。

系統(tǒng)掛起

? ? 如果代碼進(jìn)入一個(gè)死循環(huán),內(nèi)核就會(huì)停止調(diào)度,系統(tǒng)不會(huì)再相應(yīng)任何動(dòng)作。

? ? 通過在一些關(guān)鍵點(diǎn)插入schedule調(diào)用可以防止死循環(huán),schedule函數(shù)會(huì)調(diào)用調(diào)度器,并因此允許其他進(jìn)程偷取當(dāng)前進(jìn)程的CPU時(shí)間。但是如何進(jìn)行插入呢?

? ? SysRq魔法鍵,具體說明這里不記錄。

調(diào)試器和相關(guān)工具

? ? 可以使用調(diào)試器來一步步地跟蹤代碼,查看變量和計(jì)算寄存器的值。

? ? 使用gdb,kgb: 這里不進(jìn)行記錄,后續(xù)使用的時(shí)候再補(bǔ)充

? ? 用戶模式的Linux虛擬機(jī)(User-Mode Linux,UML)

? ? ? ? 作為一個(gè)獨(dú)立的、可移植的Linux內(nèi)核而被創(chuàng)建,包含在子目錄arch/um中,可以理解為將一個(gè)內(nèi)核副本當(dāng)做用戶模式下的進(jìn)程。對于內(nèi)核開發(fā)

? ? ? ? 人員來說UML可以很容易地利用gdb或者其他調(diào)試器來進(jìn)行調(diào)試,所以UML理論上可以加快內(nèi)核的開發(fā)過程.但是其缺點(diǎn)也很明顯,無法進(jìn)行硬件相關(guān)

? ? ? ? 的操作.

動(dòng)態(tài)探測

? ? 可在系統(tǒng)的幾乎任何一個(gè)地方放置一個(gè)探針,既可以是用戶空間也可以是內(nèi)核空間,這個(gè)探針有一些特殊代碼組成,當(dāng)控制到達(dá)給定點(diǎn)時(shí),這些代碼開始

? ? 執(zhí)行,這種代碼能向用戶空間匯報(bào)數(shù)據(jù)、修改寄存器,或者完成許多其他工作.

? ? 該方法有點(diǎn)兒類似與prink功能,過多的處理會(huì)拖慢整個(gè)系統(tǒng),所以慎用.

第五章 并發(fā)和競態(tài)

并發(fā)及其管理? ?

? ? 并發(fā)來源:

? ? ① 多進(jìn)程調(diào)用

? ? ② 多處理器系統(tǒng)

? ? ③ 內(nèi)核代碼是可搶占的

? ? ④ 設(shè)備中斷

? ? ⑤ 延遲代碼執(zhí)行機(jī)制(workqueue/tasklet/timer)

信號(hào)量和互斥體

? ? 休眠:

? ? ? ? 當(dāng)一個(gè)Linux進(jìn)程到達(dá)某個(gè)時(shí)間點(diǎn),此時(shí)它不能進(jìn)行任何處理時(shí),它將進(jìn)入休眠(或阻塞)狀態(tài),這將把處理器讓給其他執(zhí)行線程直到

? ? ? ? 將來它能夠繼續(xù)完成自己的處理為止。

? ? 信號(hào)量:

? ? ? ? 一個(gè)信號(hào)量本質(zhì)上是一個(gè)整數(shù)值,它是一對函數(shù)聯(lián)合使用,這一對函數(shù)通常稱為P和V,希望進(jìn)入臨界區(qū)的進(jìn)程將在相關(guān)信號(hào)量上調(diào)用P,

? ? ? ? 如果信號(hào)量的值大于零,則該值會(huì)減小一,而進(jìn)程可以繼續(xù),相反,如果信號(hào)量的值為零(或者更?。?,進(jìn)程必須等待直到其他人釋放

? ? ? ? 該信號(hào)量,對信號(hào)量的解鎖通過調(diào)用V完成,該函數(shù)增加信號(hào)量的值,并在必要時(shí)喚醒等待的進(jìn)程。

? ? 當(dāng)信號(hào)量用于互斥時(shí)(既避免多個(gè)進(jìn)程同時(shí)在一個(gè)臨界區(qū)中運(yùn)行),信號(hào)量的值應(yīng)初始化為1。這種信號(hào)量在任何給定時(shí)刻只能由單個(gè)進(jìn)程或線程

? ? 擁有,在這種使用模式下,一個(gè)信號(hào)量有時(shí)也稱為一個(gè)互斥體(mutex),Linux內(nèi)核中幾乎所有的信號(hào)量運(yùn)用于互斥。

? ? Linux信號(hào)量的實(shí)現(xiàn)

? ? ? ? void sema_init(struct semaphore *sem, int val): 內(nèi)核創(chuàng)建信號(hào)量

? ? 讀取者/寫入者信號(hào)量

? ? ? ? 允許多個(gè)并發(fā)的讀取者是可行的,只要它們之中沒有哪個(gè)要做修改,這樣做可以大大提高性能。

? ? ? ? Linux內(nèi)核提供了一個(gè)特殊的信號(hào)量類型: rwsem(reader/writer sempaphore, 讀取者/寫入者信號(hào)量)

? ? ? ? rwsem 初始化:void init_rwsem(struct rw_semaphore *sem)

? ? ? ? 一個(gè)rwsem 可允許一個(gè)寫入者或者無限多個(gè)讀取者擁有該信號(hào)量,寫入者具有更高的優(yōu)先級(jí),當(dāng)某個(gè)給定寫入者試圖進(jìn)如臨界區(qū)時(shí),在所有寫入者完成其

? ? ? ? 工作之前,不會(huì)允許讀取者獲得訪問。如果有大量的寫入者競爭該信號(hào)量,則這種實(shí)現(xiàn)會(huì)導(dǎo)致讀取者”餓死“,即可能會(huì)長期拒絕讀取者的訪問,為此,

? ? ? ? 最好在很少需要寫訪問者只會(huì)短期擁有信號(hào)量的時(shí)候使用rwsem.

completion

? ? completion是一種輕量級(jí)的機(jī)制,它允許一個(gè)線程告訴另一個(gè)線程某個(gè)工作已經(jīng)完成。

? ? 一個(gè)completion通常是一個(gè)單次設(shè)備(只會(huì)被使用一次然后被丟棄),但是如果沒有使用complete_all,則可以重復(fù)使用,否則就必須重新初始化才能重復(fù)

? ? 使用該completion。

自旋鎖

? ? 自旋鎖可以在不能休眠的代碼中使用,比如中斷處理例程。

? ? 一個(gè)自旋鎖是一個(gè)互斥設(shè)備,它只能有兩個(gè)值: 鎖定和解鎖,通常實(shí)現(xiàn)為某個(gè)整數(shù)值中的單個(gè)位。如果鎖可用,則鎖定位被設(shè)置,而代碼繼續(xù)進(jìn)入臨界區(qū);

? ? 相反,如果鎖被其他部分獲得,則代碼進(jìn)入忙循環(huán)并重復(fù)檢查這個(gè)鎖,直到該鎖可用為止,這個(gè)循環(huán)就是自旋鎖的自旋部分。其中,"測試并設(shè)置單個(gè)位"的操作

? ? 必須是原子的,這樣即使有多個(gè)線程在給定時(shí)間自旋,也只有一個(gè)線程可獲得該鎖。

? ? 自旋鎖和原子上下文

? ? ? ? 如果驅(qū)動(dòng)程序A獲得了一個(gè)自旋鎖,然后在臨界區(qū)開始了它的工作,如果意外地在臨界區(qū)內(nèi)發(fā)生了休眠或者內(nèi)核強(qiáng)占,讓出了cpu,這樣就會(huì)造成其他

? ? ? ? 試圖擁有該自旋鎖的驅(qū)動(dòng)程序會(huì)一直等待直到A釋放掉自旋鎖,最壞的情況,可能會(huì)引起整個(gè)系統(tǒng)進(jìn)入死鎖狀態(tài),所以對于自旋鎖有以下規(guī)則:

? ? ? ? 自旋鎖的核心規(guī)則:

? ? ? ? ? ? ① 任何擁有自旋鎖的代碼都必須是原子的,不能進(jìn)行休眠,事實(shí)上,它不能因?yàn)槿魏卧蚍艞壧幚砥?,除了服?wù)中斷以外(某些情況下此時(shí)也

? ? ? ? ? ? 不能放棄處理器)

? ? ? ? ? ? ② 自旋鎖必須在可能的最短時(shí)間擁有,即擁有鎖的時(shí)間越短越好。原因如下:

? ? ? ? ? ? ? ? 擁有自旋鎖的時(shí)間越長,其他處理器不得不自旋以等待釋放該自旋鎖的時(shí)間就越長,而它不得不永遠(yuǎn)自旋的可能性就越大,從而阻止對當(dāng)前

? ? ? ? ? ? ? ? 處理器的調(diào)度。? ?


? ? 讀取者/寫入者自旋鎖

? ? ? ? 與信號(hào)量類似,該鎖允許任意數(shù)量的讀取者同時(shí)進(jìn)入臨界區(qū),但寫入者必須互斥訪問。

? ? 鎖陷阱

? ? ? ? 不明確的規(guī)則

? ? ? ? 鎖的順序規(guī)則

? ? ? ? ? ? 在必須獲取多個(gè)鎖時(shí),應(yīng)該始終以相同的順序獲得。

? ? ? ? 細(xì)粒度鎖和粗粒度鎖的對比

? ? ? ? ? ? 早期存在一個(gè)大的內(nèi)核鎖,該鎖讓整個(gè)內(nèi)核進(jìn)入一個(gè)大的臨界區(qū),而只有一個(gè)CPU可以在任意給定時(shí)間執(zhí)行內(nèi)核代碼。但是這種大鎖機(jī)制不具有

? ? ? ? ? ? 良好的伸縮性,導(dǎo)致性能下降。

? ? ? ? ? ? 現(xiàn)在內(nèi)核中存在更加細(xì)粒度的鎖,但該機(jī)制又會(huì)導(dǎo)致系統(tǒng)復(fù)雜性提高。

? ? 除了鎖之外的辦法

? ? ? ? 免鎖算法

? ? ? ? ? ? 經(jīng)常用于免鎖的生產(chǎn)者/消費(fèi)者任務(wù)的數(shù)據(jù)結(jié)構(gòu)之一是循環(huán)緩沖區(qū)。在這個(gè)算法中,一個(gè)生產(chǎn)者將數(shù)據(jù)放入數(shù)據(jù)的結(jié)尾,而消費(fèi)者從數(shù)組的另一端

? ? ? ? ? ? 移走數(shù)據(jù)。

? ? ? ? 原子變量

? ? ? ? ? ? atomic_t : 原子的整數(shù)類型,所以的CPU對于該類型數(shù)據(jù)的處理都是原子的(可能會(huì)被編譯成單個(gè)機(jī)器指令)

? ? ? ? 位操作

? ? ? ? ? ? 原子位操作非??欤灰讓佑布试S,這種操作就可以使用單個(gè)機(jī)器指令來執(zhí)行,并且不需要禁止中斷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容