Linux Device Driver 3rd 中

第六章 高級字符驅(qū)動程序操作

ioctl方法:

? ? 為了保證ioctl命令的唯一性,對于命令的定義使用了4個位字段,其含義如下:

? ? ① type

? ? ? ? 幻數(shù),選擇一個的號碼(參考ioctl-number.txt),并在整個驅(qū)動程序中使用這個號碼,這個字段有8位寬(_IOC_TYPEBITS)

? ? ② number

? ? ? ? 序數(shù)(順序編號),它也是8位寬(_IOC_NRBITS)

? ? ③ direction

? ? ? ? 如果相關(guān)命令涉及到數(shù)據(jù)的傳輸,則該位字段定義數(shù)據(jù)傳輸方向。可以使用的值包括_IOC_NONE(沒有數(shù)據(jù)傳輸)、_IOC_READ、_IOC_WRITE

? ? ? ? 以及_IOC_READ|_IOC_WRITE(雙向傳輸數(shù)據(jù))。數(shù)據(jù)傳輸是從應(yīng)用程序的角度看的,也就是說,IOC_READ意味著從設(shè)備中讀取數(shù)據(jù)。

? ? ④ size

? ? ? ? 所涉及的用戶數(shù)據(jù)大小,這個字段的寬度與體系結(jié)構(gòu)有關(guān),通常是13位或者14位,具體可通過宏_IOC_SIZEBITS找到針對特定體系結(jié)構(gòu)的具體數(shù)值。

? ? ? ? 該字段并不強制使用。

? ? 預(yù)定義命令

? ? ? ? 盡管ioctl系統(tǒng)調(diào)用絕大部分用于操作設(shè)備,但還有一些命令式可以由內(nèi)核識別的。什么意思呢? 有一些ioctl命令是內(nèi)核專有的,在下發(fā)到具體驅(qū)動程序前

? ? ? ? 已經(jīng)被內(nèi)核攔截并處理了。

? ? ? ? 預(yù)定義命令

? ? ? ? ① 可用于任何文件(普通、設(shè)備、FIFO和套接字)的命令,該類幻數(shù)是“T”

? ? ? ? ② 只用于普通文件的命令

? ? ? ? ③ 特定于文件系統(tǒng)類型的命令

? ? 使用ioctl參數(shù)

? ? ? ? access_ok用于驗證地址(而不傳輸數(shù)據(jù))

? ? ? ? access_ok有兩點需要注意:

? ? ? ? ? ? ① 該方法并沒有完成驗證內(nèi)存的全部工作,而只是檢查了所引用的內(nèi)存是否位于進程有對應(yīng)訪問權(quán)限的區(qū)域內(nèi),特別是要確保訪問地址

? ? ? ? ? ? 沒有指向內(nèi)核空間的內(nèi)存區(qū)。

? ? ? ? ? ? ② 大多數(shù)驅(qū)動程序都不需要真正顯示地調(diào)用access_ok,換句話說,內(nèi)存管理程序會去處理

權(quán)能與受限操作

? ? 權(quán)能(capability):Linux內(nèi)核提供的一個針對權(quán)限管理的更為靈活的系統(tǒng)。它將特權(quán)操作劃分為獨立的組,這樣,某個特定的用戶或程序就可以被授權(quán)執(zhí)行

? ? 某一指定的特權(quán)操作,同時有沒有執(zhí)行其他不相關(guān)操作的能力。

? ? 全部權(quán)能操作見 <linux/capability.h>,對于驅(qū)動程序開發(fā)者來講有意義的權(quán)能如下所示:

? ? CAP_DAC_OVERRIDE: 越過文件或目錄的訪問限制(數(shù)據(jù)訪問控制或DAC)的能力

? ? CAP_NET_ADMIN: 執(zhí)行網(wǎng)絡(luò)管理任務(wù)的能力,包括那些能影響網(wǎng)絡(luò)接口的任務(wù)

? ? CAP_SYS_MODULE: 載入或卸除內(nèi)核模塊的能力

? ? CAP_SYS_RAWIO: 執(zhí)行“裸”I/O操做的能力,例如,訪問設(shè)備端口或直接與USB設(shè)備通信

? ? CAP_SYS_ADMIN: 截獲的能力,它提供了訪問許多系統(tǒng)管理操作的途徑

? ? CAP_SYS_TTY_CONFIG: 執(zhí)行tty配置任務(wù)的能力

? ? 在執(zhí)行一項特權(quán)操作之前,設(shè)備驅(qū)動程序應(yīng)該檢查調(diào)用進程是否有合適的權(quán)能,使用如下函數(shù)進行檢查:

? ? int capable(int capability)

非ioctl的設(shè)備控制

? ? 有時通過向設(shè)備寫入控制序列可以更好地控制設(shè)備,稱為轉(zhuǎn)義序列,換句話說就是通過發(fā)送約定俗成的一系列字符串到驅(qū)動程序來達到控制

? ? 驅(qū)動設(shè)備的目的。

? ? 該技術(shù)的缺點是,它給設(shè)備增加了策略限制。這種技術(shù)在之前的掃描頭的驅(qū)動(honeywell的掃描頭)上表現(xiàn)得淋漓盡致。

阻塞型I/O

? ? 休眠的簡單介紹

? ? ? ? 當一個進程被置入休眠時,他會被標記為一種特殊狀態(tài)并從調(diào)度器的運行隊列中移走,直到某些情況下修改了這個狀態(tài),進程才會在任意CPU

? ? ? ? 上調(diào)度,也即運行該進程,休眠的進程會被擱置在一邊,等待將來的某個事件發(fā)生

? ? ? ? 休眠的規(guī)則:

? ? ? ? ? ? ① 永遠不要在原子上下文(在執(zhí)行多個步驟時,不能有任何的并發(fā)訪問)進入休眠,對于休眠來講,驅(qū)動程序不能在擁有自旋鎖、seqlock或者

? ? ? ? ? ? RCU鎖時休眠,如果已經(jīng)禁止了中斷也不能休眠。

? ? ? ? ? ? ② 當線程被喚醒時必須檢查以確保之前休眠等待的條件真正為真

? ? ? ? ? ? ③ 進程休眠必須有被喚醒的地方,否則不能進行休眠

? ? ? ? Tips: 休眠進程存儲在一個等待隊列上,該等待隊列就是一個進程鏈表,其中包含等待某個特定事件的所有進程。

? ? ? ? 簡單休眠

? ? ? ? ? ? 當一個休眠進程被喚醒時,它必須再次檢查它所等待的條件的確為真!

? ? ? ? ? ? Linux內(nèi)核中最簡單的休眠方式是稱為wait_event的宏,在實現(xiàn)休眠的同時,它也檢查進程等待的條件。

? ? ? ? ? ? wait_evet(queue, condition): 將當前進程放入等待隊列頭,condition是任意一個布爾表達式,在休眠前后都要對該表達式求值。

? ? ? ? ? ? wakt_up(wait_queue_head_t *queue): 喚醒等待在給定queue上的所有滿足condition條件的進程,

? ? ? ? 高級休眠

? ? ? ? ? ? 進程如何休眠

? ? ? ? ? ? ? ? 位于linux/wait.h文件中的wait_queue_head_t數(shù)據(jù)結(jié)構(gòu)主要由一個自旋鎖和一個鏈表組成,鏈表中保存的是一個等待隊列入口,

? ? ? ? ? ? ? ? 該入口聲明為wait_queue_t類型,這個結(jié)構(gòu)中包含了休眠進程的信息及其期望被喚醒的相關(guān)細節(jié)信息

? ? ? ? ? ? ? ? 將進程置于休眠的幾個步驟:

? ? ? ? ? ? ? ? ? ? ① 分配并初始化一個wait_queue_t結(jié)構(gòu),然后將其加入到對應(yīng)的等待隊列。

? ? ? ? ? ? ? ? ? ? ② 設(shè)置進程的狀態(tài),將其標記為休眠。進程狀態(tài)主要有: TASK_RUNNING(表示進程可運行),TASK_INTERRUPTIBLE和

? ? ? ? ? ? ? ? ? ? TASK_UNINTERRUPUTIBLE(進程處于休眠狀態(tài)前者是可中斷后者是不可中斷)

? ? ? ? ? ? ? ? ? ? ③ 再次檢查休眠等待的條件,如果依然等待就調(diào)用schedule調(diào)用調(diào)度器讓出CPU,否則繼續(xù)該進程的操作

? ? ? ? ? ? 獨占等待

? ? ? ? ? ? ? ? 由于wait_up會喚醒所有處于等待隊列中的進程,但是一般只有一個進程會真正被喚醒,其他的進程會再次進入休眠狀態(tài)

? ? ? ? ? ? ? ? 但是這些被假喚醒的進程都會去嘗試獲得處理器,為資源競爭,所以會嚴重影響系統(tǒng)性能。為了解決這個問題,內(nèi)核增加了

? ? ? ? ? ? ? ? "獨占等待"選項:

? ? ? ? ? ? ? ? ? ? 一個獨占等待與通常的休眠有如下兩個重要的不同:

? ? ? ? ? ? ? ? ? ? ① 等待隊列入口設(shè)置了WQ_FLAG_EXCLUSIEV標志時,則會被添加到等待隊列的尾部。而沒有這個標志的入口會被添加到頭部

? ? ? ? ? ? ? ? ? ? ② 在某個等待隊列上調(diào)用wake_up時,它會在喚醒第一個具有WQ_FLAG_EXCLUSIEVE標志的進程之后停止喚醒其他進程

? ? ? ? ? ? 喚醒的相關(guān)細節(jié)

? ? ? ? ? ? ? ? 當一個進程被喚醒時,實際的結(jié)果由等待隊列入口中的一個函數(shù)控制,默認的喚醒函數(shù)將進程設(shè)置為可運行狀態(tài),并且如果該進程具有

? ? ? ? ? ? ? ? 更高的優(yōu)先級,則會執(zhí)行一次上下文切換以便切換到該進程。

? ? ? ? ? ? poll和select

? ? ? ? ? ? ? ? poll、select和epoll的功能本質(zhì)上是一樣的:都允許進程決定是否可以對一個或多個打開的文件做非阻塞的讀取或?qū)懭耄@些調(diào)用

? ? ? ? ? ? ? ? 也會阻塞進程,直到給定的文件描述符集合中的任何一個可讀取或?qū)懭搿?/p>

? ? ? ? ? ? ? ? 三個系統(tǒng)調(diào)用均通過驅(qū)動程序的poll方法提供,該方法原型如下:

? ? ? ? ? ? ? ? unsigned int (*poll)(struct file *filp, poll_table *wait)

? ? ? ? ? ? ? ? 該方法的處理分為以下兩步:

? ? ? ? ? ? ? ? ? ? ① 在一個或多個可指示poll狀態(tài)變化的等待隊列上調(diào)用poll_wait,如果當前沒有文件描述符可用來執(zhí)行I/O,則內(nèi)核將使進程在傳遞

? ? ? ? ? ? ? ? ? ? 到該系統(tǒng)調(diào)用的所有文件描述符對應(yīng)的等待隊列上等待。

? ? ? ? ? ? ? ? ? ? ② 返回一個用來描述操作是否可以立即無阻塞執(zhí)行的位掩碼


? ? ? ? ? ? ? ? poll幾個標志位說明:

? ? ? ? ? ? ? ? ? ? POLLIN:

? ? ? ? ? ? ? ? ? ? ? ? 如果設(shè)備可以無阻塞地讀取,就設(shè)置該位

? ? ? ? ? ? ? ? ? ? POLLRDNORM:

? ? ? ? ? ? ? ? ? ? ? ? 如果通常的數(shù)據(jù)已經(jīng)就緒,可以讀取,就設(shè)置該位,一個可讀設(shè)備返回(POLLIN|POLLRDNORM)

? ? ? ? ? ? ? ? ? ? POLLRDBAND:

? ? ? ? ? ? ? ? ? ? ? ? 這一位指示可以從設(shè)備讀取out-of-band(頻帶之外)的數(shù)據(jù)。

? ? ? ? ? ? ? ? ? ? POLLPRI:

? ? ? ? ? ? ? ? ? ? ? ? 可以無阻塞地讀取高優(yōu)先級(即out-of-band)的數(shù)據(jù)。設(shè)置該位會導(dǎo)致select報告文件發(fā)生一個異常,這是由于select把

? ? ? ? ? ? ? ? ? ? ? ? "out-of-band"的數(shù)據(jù)作為異常對待

? ? ? ? ? ? ? ? ? ? POLLHUP:

? ? ? ? ? ? ? ? ? ? ? ? 當讀取設(shè)備的進程到達文件尾時,驅(qū)動程序必須設(shè)置POLLHUP(掛起)位。依照select的功能描述,調(diào)用select的進程會被

? ? ? ? ? ? ? ? ? ? ? ? 告知設(shè)備是可讀的。

? ? ? ? ? ? ? ? ? ? POLLERR:

? ? ? ? ? ? ? ? ? ? ? ? 設(shè)備發(fā)生了錯誤,如果調(diào)用poll,就會報告設(shè)備即可讀也可以寫,因為讀寫都會無阻塞地返回一個錯誤碼

? ? ? ? ? ? ? ? ? ? POLLOUT:

? ? ? ? ? ? ? ? ? ? ? ? 如果設(shè)備可以無阻塞地寫入,就在返回值中設(shè)置該位

? ? ? ? ? ? ? ? ? ? POLLWRNORM:

? ? ? ? ? ? ? ? ? ? ? ? 該位和POLLOUT的意義一樣,有時其實就是同一個數(shù)字,一個可寫的設(shè)備將返回(POLLOUT|POLLWRNORM)

? ? ? ? ? ? ? ? ? ? POLLWRBAND:

? ? ? ? ? ? ? ? ? ? ? ? 與PLLRDBAND類似,這一位表示具有非零優(yōu)先級的數(shù)據(jù)可以被寫入設(shè)備。

? ? 與read 和write的交互:

? ? ? ? 從設(shè)備讀取數(shù)據(jù)

? ? ? ? ? ? ① 如果輸入緩沖區(qū)有數(shù)據(jù),那么即使就緒的數(shù)據(jù)比程序所請求的少,并且驅(qū)動程序保證剩下的數(shù)據(jù)馬上就能到達,read調(diào)用仍然立即返回。

? ? ? ? ? ? ② 如果輸入緩沖區(qū)中沒有數(shù)據(jù),那么默認情況下read必須阻塞等待,直到至少有一個字節(jié)到達。另一方面,如果設(shè)置了O_NONBLOCK標志,read

? ? ? ? ? ? 應(yīng)立即返回,這個時候應(yīng)當配合poll使用。

? ? ? ? ? ? ③ 如果已經(jīng)到達文件尾,read應(yīng)該立即返回0


? ? ? ? 向設(shè)備寫數(shù)據(jù)

? ? ? ? ? ? ① 如果輸出緩沖區(qū)中有空間,則write應(yīng)該無延遲地立即返回。

? ? ? ? ? ? ② 如果輸出緩沖區(qū)已滿,那么默認情況下write被阻塞直到有空間釋放,如果設(shè)置了O_NONBLOCK標志,write應(yīng)立即返回,同時應(yīng)當配合poll

? ? ? ? ? ? 使用等待可寫狀態(tài)

? ? ? ? ? ? ③ 永遠不要讓write調(diào)用在返回前等待數(shù)據(jù)的傳輸結(jié)束,即使O_NONBLOCK標志被清除。

? ? ? ? 底層的數(shù)據(jù)結(jié)構(gòu)

? ? ? ? ? ? 當用戶程序調(diào)用了poll、select或epoll函數(shù)時,內(nèi)核會調(diào)用由該系統(tǒng)調(diào)用引用的全部文件的poll方法,并向它們傳遞同一個poll_table.

? ? ? ? ? ? poll_table結(jié)構(gòu)是構(gòu)成實際數(shù)據(jù)結(jié)構(gòu)的一個簡單封裝。對poll和select系統(tǒng)調(diào)用,后面這個結(jié)構(gòu)是包含poll_table_entry結(jié)構(gòu)的內(nèi)存頁鏈表。

? ? ? ? ? ? 每個poll_table_entry結(jié)構(gòu)包括一個指向被打開設(shè)備的struct file類型的指針、一個wait_queue_head_t指針以及一個關(guān)聯(lián)的等待隊列入口。

? ? ? ? ? ? 對poll_wait的調(diào)用有時也會將進程添加到這個給定的等待隊列。整個結(jié)構(gòu)必須由內(nèi)核維護,因而在poll或select返回前,進程可從所有

? ? ? ? ? ? 這些隊列中移除。

? ? ? ? ? ? 如果輪詢(poll)時沒有一個驅(qū)動程序指明可以進行非阻塞I/O,這個poll調(diào)用就進入休眠,直到休眠在其上的某個(或多個)等待隊列

? ? ? ? ? ? 喚醒它為止。

? ? ? ? ? ? epoll系統(tǒng)調(diào)用用來優(yōu)化poll和select同時監(jiān)聽多數(shù)量的文件而造成的效率低下的問題

? ? ? ? 異步通知

? ? ? ? ? ? 使用異步通知,應(yīng)用程序可以在數(shù)據(jù)可用時收到一個信號,而不需要不停地使用輪詢(poll)來關(guān)注數(shù)據(jù)。

? ? ? ? ? ? 為了啟用文件的異步通知機制,用戶程序必須執(zhí)行兩個步驟:

? ? ? ? ? ? ? ? ① 指定一個進程作為文件owner,當進程使用fcntl系統(tǒng)調(diào)用執(zhí)行F_SETOWN命令時,owner進程的進程ID號就被保存在filp->f_owner中。

? ? ? ? ? ? ? ? ② 通過fcntl的F_SETFL命令在設(shè)備中設(shè)置FASYNC標志

? ? ? ? ? ? 執(zhí)行完上面兩個步驟之后,輸入文件就可以在新數(shù)據(jù)到達時請求發(fā)送一個SIGIO信號,該信號被發(fā)送到存放在filp->f_owner中的進程

? ? ? ? 從驅(qū)動程序的角度考慮

? ? ? ? ? ? 從內(nèi)核角度來看,異步通知的詳細操作過程

? ? ? ? ? ? ? ? ① F_SETOWN被調(diào)用時對filp->f_owner賦值,此外什么也不做

? ? ? ? ? ? ? ? ② 在執(zhí)行F_SETFL啟用FASYNC時,調(diào)用驅(qū)動程序的fasync方法。只要filp->f_flags中的FASYNC標志發(fā)生了變化,就會調(diào)用該方法,

? ? ? ? ? ? ? ? 以便把這個變化通知驅(qū)動程序,使其能正確響應(yīng)。文件打開時,F(xiàn)ASYNC標志被默認為是清除的。

? ? ? ? ? ? ? ? ③ 當數(shù)據(jù)到達時,所有注冊為異步通知的進程都會被發(fā)送一個SIGIO信號。

? ? ? ? 定位設(shè)備

? ? ? ? ? ? llseek實現(xiàn)

? ? ? ? ? ? ? ? llseek方法實現(xiàn)了lseek和llseek系統(tǒng)調(diào)用。

? ? ? ? ? ? ? ? 如果設(shè)備操作未定義llseek方法,內(nèi)核默認通過修改filp->f_pos而執(zhí)行定位,filp->f_pos是文件的當前讀取/寫入位置。

? ? ? ? ? ? ? ? 為了是lseek系統(tǒng)調(diào)用能正確工作,read和write方法必須通過更新它們收到的偏移量參數(shù)來配合。

? ? ? ? ? ? 如果設(shè)備有一個明確定義的數(shù)據(jù)區(qū),實現(xiàn)lseek是有意義的,然而大多數(shù)設(shè)備只提供了數(shù)據(jù)流(串口和鍵盤),而不是數(shù)據(jù)區(qū),定位

? ? ? ? ? ? 這些設(shè)備是沒有意義的。

? ? ? ? 設(shè)備文件的訪問控制

? ? ? ? ? ? 獨享設(shè)備

? ? ? ? ? ? ? ? 一次只允許一個進程打開設(shè)備(獨享),最好避免使用這種技術(shù),因為它制約了用戶的靈活性。

? ? ? ? ? ? 替代EBUSY的阻塞型open

? ? ? ? ? ? ? ? 當設(shè)備不能訪問是返回一個錯誤,通常這是最合理的方式,但有些情況下可能需要讓進程等待設(shè)備。

? ? ? ? ? ? 在打開時復(fù)制設(shè)備

? ? ? ? ? ? ? ? 在進程打開設(shè)備時創(chuàng)建設(shè)備的不同私有副本,這也是實現(xiàn)訪問控制的一個方法

? ? ? ? ? ? ? ? 這種方式只有在沒有綁定到某個硬件對象時才能實現(xiàn)。

? ? ? ? ? ? ? ? 比如/dev/tty內(nèi)部就使用了這類技術(shù),以提供給它的進程一個不同于/dev入口點所表現(xiàn)出的"情景",如果復(fù)制的設(shè)備是由

? ? ? ? ? ? ? ? 軟件驅(qū)動程序創(chuàng)建的,我們稱它們?yōu)?虛擬設(shè)備"

第七章 時間、延遲及延緩操作

度量時間差

? ? 內(nèi)核通過定時器中斷來跟蹤時間流.

? ? 時鐘中斷由系統(tǒng)定時硬件以周期性的間隔產(chǎn)生,這個間隔由內(nèi)核根據(jù)HZ的值設(shè)定,HZ是一個與體系結(jié)構(gòu)有關(guān)的常數(shù)。

? ? 每次當時鐘中斷發(fā)生時,內(nèi)核內(nèi)部計數(shù)器的值就增加一,這個計數(shù)器的值在系統(tǒng)引導(dǎo)時被初始化為0,表示的是自上次操作系統(tǒng)引導(dǎo)以來的

? ? 時鐘滴答數(shù)。

使用jiffies計數(shù)器

? ? jiffies變量被聲明為volatile,這樣避免編譯器對訪問該變量的語句的優(yōu)化。

? ? 用戶空間的時間表述方法(struct timeval和struct timespec)可以通過以下方法和內(nèi)核計數(shù)器進行轉(zhuǎn)換。

? ? ? ? unsigned long timespec_to_jiffies(struct timespec *value)

? ? ? ? void jiffies_to_timespec(unsigned long jiffies, struct timespec *value)

? ? 在64位計算機架構(gòu)上jiffies_64與jiffies其實是同一個,但是在32位上,對jiffies_64的訪問不是原子的,需要通過特殊輔助函數(shù)進行訪問:

? ? ? ? u64 get_jiffies_64(void)

? ? 通過/proc/interrupts 獲得的計數(shù)值除以/proc/uptime文件報告的系統(tǒng)運行時間,可以獲得內(nèi)核確切的HZ值

處理器特定的寄存器

? ? 絕大多數(shù)現(xiàn)代處理器都包含一個隨時鐘周期不斷遞增的計數(shù)寄存器,這個時鐘計數(shù)器是完成高分辨率計數(shù)任務(wù)的唯一可靠途徑。

? ? TSC(timestamp counter,時間戳計數(shù)器)便是其中一種計數(shù)器寄存器,它是一個64位寄存器,記錄CPU時鐘周期數(shù),從內(nèi)核空間和

? ? 用戶空間都可以讀取它

獲取當前的時間

? ? 內(nèi)核一般通過jiffies值來獲取當前的時間,該數(shù)值表示的是子最近一次系統(tǒng)啟動到當前的時間間隔,它和設(shè)備驅(qū)動程序無關(guān),因為它的生命

? ? 期只限于系統(tǒng)的運行期(uptime)

? ? 墻鐘時間: 日常生活使用的時間,用年月日來表達。

? ? do_gettitmeofday函數(shù):該函數(shù)用秒或微秒來填充一個指向struct timeval的指針變量,該內(nèi)核函數(shù)在許多體系結(jié)構(gòu)上有“接近微秒級的分辨率”,

? ? 它依賴于定時硬件

延遲執(zhí)行

? ? 長延時

? ? ? ? 忙等待

? ? ? ? ? ? 如果想把執(zhí)行延遲若干個時鐘滴答或者對延遲的精度要求不高,最簡單的實現(xiàn)方法就是一個監(jiān)視jiffies計數(shù)器的循環(huán)。

? ? ? ? ? ? 代碼如下:

? ? ? ? ? ? ? ? while(time_before(jiffies, j1))

? ? ? ? ? ? ? ? ? ? cpu_relax();

? ? ? ? ? ? 該方式的缺點是會嚴重降低系統(tǒng)性能。如果內(nèi)核并非搶占型的,那么這個循環(huán)將在延遲期間整個鎖住處理器,而調(diào)度器從來不會搶占

? ? ? ? ? ? 運行在內(nèi)核空間的進程,這樣在并未到達指定時間時,計算機處于忙碌狀態(tài)。如果內(nèi)核時搶占型,則除非代碼擁有一個鎖,否則處理

? ? ? ? ? ? 器的時間還可以用作他用,但是依然還是會造成CPU資源浪費。最為嚴重的情況是在進入循環(huán)之前正好禁止了中斷,jiffies值就不會

? ? ? ? ? ? 得到更新,那將永遠處于循環(huán)中,造成計算機一直死掉。

? ? ? ? 讓出處理器

? ? ? ? ? ? 在不需要CPU時主動釋放CPU,通過schedule函數(shù)實現(xiàn)

? ? ? ? ? ? 代碼如下:

? ? ? ? ? ? ? ? while(time_before(jiffies, j1))

? ? ? ? ? ? ? ? ? ? schedule();

? ? ? ? ? ? 該方式的實現(xiàn)所產(chǎn)生的延時可能要比所請求的時間長幾個時鐘滴答,因為無法保證讓出CPU后何時才會獲取CPU資源。

? ? ? ? 超時

? ? ? ? ? ? 現(xiàn)在存在兩種構(gòu)造基于jiffies超時的途徑:

? ? ? ? ? ? ① 如果驅(qū)動程序使用等待隊列來等待其他一些事件,而我們同時希望在特定時間段中運行,則可以使用wait_event_timeout或者

? ? ? ? ? ? wait_event_interruptible_timeout函數(shù),該兩個函數(shù)會在給定的等待隊列上休眠,并會在超時到期時返回,該方式可能會被其他

? ? ? ? ? ? 進程調(diào)用wake_up喚醒。

? ? ? ? ? ? ② 為了適應(yīng)不等待特定事件而延遲,內(nèi)核提供了schedule_timeout函數(shù):

? ? ? ? ? ? ? ? set_current_state(TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE)

? ? ? ? ? ? ? ? schedule_timeout(delay)? ?

? ? ? ? 短延時

? ? ? ? ? ? ndelay/udelay/mdelay這幾個內(nèi)核函數(shù)可很好地完成短延遲任務(wù),該三個方法均是忙等待函數(shù),因而在延遲過程中無法運行其他任務(wù)。

? ? ? ? ? ? udelay(以及可能的ndelay)的實現(xiàn)使用了軟件循環(huán),它根據(jù)引導(dǎo)期間計算出的處理器速度以及l(fā)oops_pre_jiffy整數(shù)變量

? ? ? ? ? ? 確定循環(huán)的次數(shù)。

? ? ? ? ? ? msleep/msleep_interruptible/ssleep實現(xiàn)毫秒級的延遲,這系列方式不涉及忙等待,都是將驅(qū)動程序放入某個等待隊列中等待。

? ? ? ? 內(nèi)核定時器

? ? ? ? ? ? 如果需要在將來的某個時間點調(diào)度執(zhí)行某個動作,同時在該時間點到達之前不會阻塞當前進程,則可以使用內(nèi)核定時器,內(nèi)核定時器

? ? ? ? ? ? 可用來在未來的某個特定時間點(基于時鐘滴答)調(diào)度執(zhí)行某個函數(shù)。

? ? ? ? ? ? 內(nèi)核定時器常常作為“軟件中斷”的結(jié)果而運行

? ? ? ? ? ? 當定時器運行時,調(diào)度該定時器的進程可能正在休眠或在其他處理器上執(zhí)行,或者干脆已經(jīng)退出,所以定時器函數(shù)通常運行在進程上下文之外,

? ? ? ? ? ? 對于運行進程上下文之外,則必須遵守如下規(guī)則:

? ? ? ? ? ? ? ? ① 不允許訪問用戶空間,因為沒有進程上下文,無法將任何特定進程與用戶空間關(guān)聯(lián)。

? ? ? ? ? ? ? ? ② current指針在原子模式下是沒有任何意義

? ? ? ? ? ? ? ? ③ 不能執(zhí)行休眠或調(diào)度,原子代碼不可以調(diào)用schedule或者wait_event,也不能調(diào)用任何可能引起休眠的函數(shù),例如調(diào)用

? ? ? ? ? ? ? ? kmalloc(..., GFP_KERNEL)以及信號量由于可能引起休眠,也不能用。

? ? ? ? ? ? 內(nèi)核代碼可以通過調(diào)用函數(shù)in_interrupt()來判斷自己是否運行與中斷上下文。

? ? ? ? ? ? 重要特性:

? ? ? ? ? ? ? ? ① 任務(wù)可以將自己注冊以在稍后的時間重新運行,因為每個timer_list結(jié)構(gòu)都會在運行之前從活動定時器鏈表中移走,

? ? ? ? ? ? ? ? 這樣就可以立即鏈入其他的鏈表。

? ? ? ? ? ? ? ? ② 在多處理器系統(tǒng)中,定時器函數(shù)會由注冊它的同一個CPU執(zhí)行。

? ? ? ? ? ? ? ? ③ 任何通過定時器函數(shù)訪問的數(shù)據(jù)結(jié)構(gòu)都應(yīng)該針對并發(fā)訪問進行保護。

? ? ? ? ? ? 內(nèi)核定時器的實現(xiàn)

? ? ? ? ? ? ? ? ① 定時器的管理必須盡可能做到輕量級

? ? ? ? ? ? ? ? ② 其設(shè)計必須在活動定時器大量增加時具有很好的伸縮性

? ? ? ? ? ? ? ? ③ 大部分定時器會在最多幾秒或者幾分鐘內(nèi)到期,而很少存在長期延遲的定時器

? ? ? ? ? ? ? ? ④ 定時器應(yīng)該在注冊它的同一CPU上運行

? ? ? ? tasklet

? ? ? ? ? ? 與內(nèi)核定時器類似,始終在中斷期間運行(定時器類似于軟中斷),始終會在調(diào)度它們的同一CPU上運行,而且都接收

? ? ? ? ? ? 一個unsigned long參數(shù)。

? ? ? ? ? ? 與內(nèi)核定時器不同的是,無法要求tasklet在某個給定時間執(zhí)行。

? ? ? ? ? ? 軟件中斷是打開硬件中斷的同時執(zhí)行某些異步任務(wù)的一種內(nèi)核機制。

? ? ? ? ? ? tasklet特性:

? ? ? ? ? ? ? ? ① 一個tasklet可在稍后被禁止或者重新啟用;只有啟用的次數(shù)和禁止的次數(shù)相同時,tasklet才會被執(zhí)行

? ? ? ? ? ? ? ? ② 和定時器類似,tasklet可以注冊自己本身

? ? ? ? ? ? ? ? ③ tasklet可被調(diào)度以在通常的優(yōu)先級或者高優(yōu)先級執(zhí)行,高優(yōu)先級的tasklet總會首先執(zhí)行

? ? ? ? ? ? ? ? ④ 如果系統(tǒng)負荷不重,則tasklet會立即得到執(zhí)行,但始終不會晚于下一個定時器滴答

? ? ? ? ? ? ? ? ⑤ 一個tasklet可以和其他tasklet并發(fā),但對于自身來講是嚴格串行處理的,也就是說,同一tasklet永遠不會在多個處理器

? ? ? ? ? ? ? ? 上同時運行。

? ? ? ? 工作隊列

? ? ? ? ? ? 工作隊列類似于tasklet,它們都允許內(nèi)核代碼請求某個函數(shù)在將來的時間被調(diào)用。

? ? ? ? ? ? 與tasklet的區(qū)別如下:

? ? ? ? ? ? 1. tasklet在軟件中斷上下文中運行,所以所有的tasklet代碼都必須是原子的,相反,工作隊列函數(shù)在一個特殊內(nèi)核進程的上下文中

? ? ? ? ? ? 運行,因此它們具有更好的靈活性,工作隊列函數(shù)可以休眠

? ? ? ? ? ? 2. tasklet始終運行在被初始化提交的統(tǒng)一處理器上,但這只是工作隊列的默認方式

? ? ? ? ? ? 3. 內(nèi)核代碼可以請求工作隊列函數(shù)的執(zhí)行延遲給定的時間間隔

? ? ? ? 共享隊列

? ? ? ? ? ? 內(nèi)核提供了共享的默認工作隊列

第八章 分配內(nèi)存

kmalloc函數(shù)

? ? #include <linux/slab.h>

? ? void *kmalloc(size_t size, int flags);

? ? 該函數(shù)運行得很快,而且不對所獲取的內(nèi)存空間清零,也就是說,分配給它的區(qū)域仍然保持著原有的數(shù)據(jù)(所以我們要將內(nèi)存顯式地清空,有其是可能導(dǎo)出給

? ? 用戶空間或者寫入設(shè)備的內(nèi)存,否則,就可能將私有信息泄露出去),另外,它分配的區(qū)域在物理內(nèi)存中也是連續(xù)的.

? ? flags參數(shù)

? ? ? ? GFP_KERNEL: 內(nèi)核內(nèi)存的通常分配方法,可能引起休眠

? ? ? ? ? ? 該參數(shù)是最常用的,它表示內(nèi)存分配(最終總是調(diào)用get_free_pages來實現(xiàn)實際的分配)是代表運行在內(nèi)核空間的進程執(zhí)行的。

? ? ? ? ? ? 該參數(shù)允許kmalloc在空閑內(nèi)存較少時把當前進程轉(zhuǎn)入休眠以等待一個頁面,因此使用GFP_KERNEL分配內(nèi)存的函數(shù)必須是可重入的,

? ? ? ? ? ? 在當前進程休眠時,內(nèi)核會采取適當?shù)男袆?,或者是把緩沖區(qū)的內(nèi)容刷寫到硬盤上,或者是從一個用戶進程換出內(nèi)存,以獲取一個

? ? ? ? ? ? 內(nèi)存頁面。

? ? ? ? GFP_ATOMIC:用于在中斷處理例程或其他運行進程上下文之外的代碼中分配內(nèi)存,不會休眠。

? ? ? ? ? ? 內(nèi)核通常會為原子性的分配預(yù)留一些空閑頁面。使用該參數(shù)時,kmalloc甚至可以用掉最后一個空閑頁面,如果連最后一頁都沒有了,便

? ? ? ? ? ? 會分配失敗。

? ? 內(nèi)存區(qū)段

? ? ? ? Linux內(nèi)核把內(nèi)存分為三個區(qū)段: 可用于DMA的內(nèi)存、常規(guī)內(nèi)存(normal)以及高端內(nèi)存(highmem)。

? ? ? ? 通常的內(nèi)存分配都發(fā)生的在常規(guī)內(nèi)存區(qū),但通過設(shè)置上面介紹過的標志也可以請求在其他區(qū)段中分配。

? ? ? ? DMA的內(nèi)存:

? ? ? ? ? ? 指存在與特別地址范圍內(nèi)的內(nèi)存,外設(shè)可以利用這些內(nèi)存執(zhí)行DMA訪問,在x86平臺上,DMA區(qū)段是RAM的前16MB。

? ? ? ? 高端內(nèi)存:

? ? ? ? ? ? 該部分內(nèi)存是32位平臺為了訪問(相對)大量的內(nèi)存而存在的一種機制。

? ? ? ? 當一個新頁面為滿足kmalloc的要求被分配時,內(nèi)核會創(chuàng)建一個內(nèi)存區(qū)段的列表以供搜索,如果指定為__GFP_DMA標志,則只有DMA區(qū)段會被搜索,如果

? ? ? ? 低地址段上沒有可用內(nèi)存,分配就會失敗。如果沒有指定特定的標志,則常規(guī)區(qū)段和DMA區(qū)段都會被搜索;如果設(shè)置了__GFP_HIGHMEM標志,則

? ? ? ? 所有三個區(qū)段都會被搜索以獲取一個空閑頁(但是kmalloc不能分配高端內(nèi)存)

? ? size 參數(shù)

? ? ? ? Linux處理內(nèi)存分配的方法是,創(chuàng)建一系列的內(nèi)存對象池,每個池中的內(nèi)存塊大小是固定一致的。處理分配請求時,就直接在包含有

? ? ? ? 足夠大的內(nèi)存塊的池中傳遞一個整塊給請求者。? ?

后備高速緩存

? ? Linux內(nèi)核的高速緩存管理被稱為"slab分配器",內(nèi)核可以統(tǒng)計高速緩存的使用情況,高速緩存的使用統(tǒng)計情況可以從/proc/slabinfo獲得

內(nèi)存池(mempool)

? ? 內(nèi)存池其實就是某種形式的后備高速緩存,它試圖始終保存空閑的內(nèi)存,以便在緊急狀態(tài)下使用

? ? 盡量避免在驅(qū)動程序代碼中使用mempool,為什么? 因為mempool會分配一些內(nèi)存塊,而這些內(nèi)存塊有可能一直處于空閑狀態(tài)且不會真正得到使用,

? ? 這樣會浪費大量內(nèi)存。

get_free_page和相關(guān)函數(shù)

? ? 如果模塊需要分配大塊的內(nèi)存,使用面向頁的分配技術(shù)會更好些。

? ? 分配頁面可使用下面的函數(shù):

? ? ① get_zeroed_page(unsigned int flags): 返回指向新頁面的指針并將頁面清零

? ? ② __get_free_page(unsigned int flags): 類似于get_zeroed_page,但不清零頁面

? ? ③ __get_free_pages(unsigned int flags, unsigned int order): 分配若干(物理連續(xù)的)頁面,并返回指向給內(nèi)存

? ? 區(qū)域的第一個字節(jié)的指針,但不清零頁面

? ? /proc/buddyinfo 存儲了系統(tǒng)中每個內(nèi)存區(qū)段上每個階數(shù)下可獲得的數(shù)據(jù)塊數(shù)目

? ? 與kmalloc相比,按頁分配不會浪費內(nèi)存空間,而用kmalloc函數(shù)則會因分配粒度的原因而浪費一定數(shù)量的內(nèi)存。

? ? __get_free_page函數(shù)的最大優(yōu)點是這些分配的頁面完全屬于我們自己,而且在理論上可以通過適當?shù)卣{(diào)整頁表將它們合并成

? ? 一個線性區(qū)域,例如可以允許用戶進程對這些單一但互不相關(guān)的頁面分配得到的內(nèi)存區(qū)域進行mmap

alloc_pages接口

? ? struct page是內(nèi)核用來描述單個內(nèi)存頁的數(shù)據(jù)結(jié)構(gòu),內(nèi)核中有許多地方需要使用page結(jié)構(gòu),尤其在需要使用高端

? ? 內(nèi)存(高端內(nèi)存在內(nèi)核空間沒有對應(yīng)不變的地址)的地方。

? ? 這里插一句介紹下NUMA計算機的概念:

? ? ? ? NUMA計算機是多處理器系統(tǒng),其中的內(nèi)存對特定處理器組(節(jié)點)來講是"本地的"。訪問本地內(nèi)存要比訪問非本地內(nèi)存快,

? ? ? ? 在這類系統(tǒng)中,在正確節(jié)點上的內(nèi)存分配非常重要。

vmalloc及其輔助函數(shù)

? ? 該函數(shù)用于分配虛擬地址空間的連續(xù)區(qū)域(這段區(qū)域在物理上可能不是連續(xù)的),但內(nèi)核區(qū)認為它們在地址上是連續(xù)的。

? ? vmalloc在發(fā)生錯誤時返回0(NULL地址),成功時返回一個指針,該指針指向一個線性的、大小最少為size的線性內(nèi)存區(qū)域

? ? 內(nèi)存編程中不鼓勵使用vmalloc,因為通過該方法獲得的內(nèi)存使用起來效率不高,而且在某些體系架構(gòu)上,用于vmalloc的地址空間總量相對較小。? ?


? ? 注意:由kmalloc和__get_free_pages返回的內(nèi)存地址也是虛擬地址,其實際值仍然要由MMU(內(nèi)存管理單元,通常是CPU的組成部分)處理

? ? 才能轉(zhuǎn)為物理內(nèi)存地址,兩者使用的虛擬地址范圍與物理內(nèi)存是一一對應(yīng)的,可能會有基于常量PAGE_OFFSET的一個偏移,它們不需要為該地址

? ? 修改頁表,但另一方面,vmalloc和ioremap使用的地址范圍完全是虛擬的,每次分配都要通過對頁表的適當設(shè)置來建立(虛擬)內(nèi)存區(qū)域。

? ? 用vmalloc分配得到的地址是不能在CPU之外使用的,因為它只在處理器的內(nèi)存管理單元上才有意義。

? ? 使用vmalloc函數(shù)的正確場合是在分配一大塊連續(xù)的、只在軟件中存在的、用于緩沖的內(nèi)存區(qū)域的時候。

? ? vmalloc函數(shù)的缺點:

? ? ? ? 不能在原子上下文中使用,因為它內(nèi)部實現(xiàn)調(diào)用了kmalloc(GFP_KERNEL)來獲取頁表的存儲空間,因而可能休眠。

per-CPU變量

? ? 當建立一個per-CPU變量時,系統(tǒng)中的每個處理器都會擁有該變量的特有副本。由于每個處理器在其自己的副本上工作,所以對per-CPU變量的訪問

? ? (幾乎)不需要鎖定,另外,per-CPU變量還可以保存在對應(yīng)處理器的高速緩存中,這樣就可以在頻繁更新時獲得更好的性能。

? ? per-CPU變量可使用的地址空間是受限制的,所以如果要創(chuàng)建per-CPU變量,則應(yīng)該保持這些變量較小。

獲取大的緩沖區(qū)

? ? 在引導(dǎo)時獲得專用緩沖區(qū)

? ? ? ? 如果的確需要連續(xù)的大塊內(nèi)存用作緩沖區(qū),則最好在系統(tǒng)引導(dǎo)期間通過請求內(nèi)存來分配。

? ? ? ? 在引導(dǎo)時就進行分配是獲得大量連續(xù)內(nèi)存頁面的唯一方法,它繞過了__get_free_pages函數(shù)在緩沖區(qū)大小上的最大尺寸和固定粒度的雙重限制。

? ? ? ? 由于它通過保留私有內(nèi)存池而跳過了內(nèi)核的內(nèi)存管理策略,所以這種技術(shù)不推薦使用。

第九章 與硬件通訊(這一章需要多次理解)

? ? 裸I/O: 寫到設(shè)備的數(shù)據(jù)位出現(xiàn)在輸出引腳上,而輸入引腳的電壓值可以由處理器直接獲取。

I/O端口和I/O內(nèi)存

? ? 每種外設(shè)都通過讀寫寄存器進行控制,大部分外設(shè)都有幾個寄存器,不管是在內(nèi)存地址空間還是在I/O地址空間,這些寄存器的訪問地址都是連續(xù)的。

? ? 在硬件層面,內(nèi)存區(qū)域和I/O區(qū)域沒有概念上的區(qū)別: 它們都是通過向地址總線和控制總線發(fā)送電平信號(比如讀和寫信號)進行訪問,再通過數(shù)據(jù)

? ? 總線讀寫數(shù)據(jù)。

I/O寄存器和常規(guī)內(nèi)存

? ? I/O寄存器和RAM的最主要的區(qū)別就是I/O操作具有邊際效應(yīng),而內(nèi)存操作則沒有: 內(nèi)存寫操作的唯一結(jié)果就是在指定位置存儲一個數(shù)值;內(nèi)存

? ? 讀操作則僅僅返回指定位置的最后一次寫入的數(shù)值。

? ? 編譯器能夠?qū)?shù)值緩存在CPU寄存器中而不寫入內(nèi)存,即使存儲數(shù)據(jù),讀寫操作也都能在高速緩存中進行而不用訪問物理RAM。

? ? 處理器無法預(yù)料某些其他進程(在另一個處理器上運行,或在某個I/O控制器中發(fā)生的操作)是否會依賴于內(nèi)存訪問的順序。所以驅(qū)動程序必須

? ? 確保不使用高速緩存,并且在訪問寄存器時不發(fā)生讀或?qū)懼噶畹闹匦屡判?。關(guān)于這一點,我自己的理解是這樣的:

? ? ? ? 針對硬件的控制也是通過一系列存儲在內(nèi)存中的命令通過I/O寄存器來傳入硬件設(shè)備中,如果使用了高速緩存,而緩存中進行了內(nèi)存的一系列優(yōu)化,

? ? ? ? 所以會導(dǎo)致命令可能的錯亂,從而導(dǎo)致發(fā)送至硬件設(shè)備的命令出現(xiàn)錯誤!

? ? 為了解決這個問題,只要把底層硬件配置成(可以是自動的或是由Linux初始化代碼完成)在訪問I/O區(qū)域(不管是內(nèi)存還是端口)時禁止硬件

? ? 緩存即可。

? ? 由編譯器優(yōu)化和硬件重新排序引起的問題的解決辦法是:

? ? ? ? 對硬件(或其他處理器)必須以特定順序執(zhí)行的操作之間設(shè)置內(nèi)存屏障(memory barrier),但是很顯然內(nèi)存屏障會影響系統(tǒng)性能。

使用I/O端口

? ? I/O端口是驅(qū)動程序與許多設(shè)備的之間的通信方式。

? ? I/O端口分配

? ? ? ? 內(nèi)核提供了一個注冊用的接口,它允許驅(qū)動程序聲明自己需要操作的端口。核心函數(shù)如下:

? ? ? ? struct resource *request_region(unsigned long first, unsinged long n, const char *name)

? ? ? ? 所有的端口分配可從/proc/ioports中得到。

? ? ? ? 釋放端口的占用,核心函數(shù)如下:

? ? ? ? void release_region(unsigned long start, unsiged long n)

? ? 操作I/O端口

? ? ? ? 在用戶空間訪問I/O端口

? ? ? ? 串操作

? ? ? ? ? ? 有些處理器上實現(xiàn)了一次傳輸一個數(shù)據(jù)序列的特殊指令,序列中的數(shù)據(jù)單元可以是字節(jié)、字或者雙字。

? ? ? ? ? ? 在使用串I/O操作函數(shù)時,它們直接將字節(jié)流從端口中讀取或?qū)懭?。但當端口和主機系統(tǒng)具有不同的字節(jié)序時,將導(dǎo)致

? ? ? ? ? ? 不可預(yù)期的結(jié)果。

? ? ? ? 暫停式I/O

? ? ? ? ? ? 當處理器時鐘相比外設(shè)時鐘快時就會出現(xiàn)問題,并且在設(shè)備板卡特別慢時表現(xiàn)出來。如何解決呢? 主要是在每條

? ? ? ? ? ? I/O指令之后,如果還有其他類似指令,則插入一個小的延遲。

? ? ? ? I/O端口示例

? ? ? ? ? ? 數(shù)字I/O端口最常見的形式是一個字節(jié)寬度的I/O區(qū)域,它或者映射到內(nèi)存,或者映射到端口。當數(shù)值寫入到輸出區(qū)域時,

? ? ? ? ? ? 輸出引腳上的電平信號隨著寫入的各位而發(fā)生相應(yīng)變化。從輸入?yún)^(qū)域讀取到的數(shù)據(jù)則是輸入引腳各位當前的邏輯電平值。

? ? ? ? ? ? 大多數(shù)情況下,I/O引腳是由兩個I/O區(qū)域控制的:一個區(qū)域中可以選擇用于輸入和輸出的引腳,另一個區(qū)域中可以讀寫實際的

? ? ? ? ? ? 邏輯電平。

? ? ? ? 并口簡介

? ? ? ? ? ? 并口的最小配置(不涉及ECP和EPP模式)由3個8位端口組成。

? ? ? ? 使用I/O內(nèi)存

? ? ? ? ? ? 和設(shè)備通訊的另一種主要機制是通過使用映射到內(nèi)存的寄存器或設(shè)備內(nèi)存。

? ? ? ? ? ? I/O內(nèi)存僅僅是類似RAM的一個區(qū)域,在那里處理器可以通過總線訪問設(shè)備。

第十章 中斷處理

? ? 中斷僅僅是一個信號,當硬件需要獲得處理器對它的關(guān)注時,就可以發(fā)送這個信號。

? ? 中斷信號線是非常珍貴且有限的資源,內(nèi)核維護了一個中斷信號線的注冊表,該注冊表類似于I/O端口的注冊表。模塊在使用中斷前要先請求一個

? ? 中斷通道(或者中斷請求IRQ),然后再使用后釋放該通道。

? ? 由于中斷信號線資源有限,通常并不是在設(shè)備初始化的時候而是在設(shè)備打開的時候注冊分配中斷信號線。

/proc 接口

? ? 產(chǎn)生的中斷報告顯示在文件/proc/interrupts中。之前開發(fā)掃描頭驅(qū)動的時候為了查看isp中斷,cat過這個文件? ? ? ? ? ?

? ? Linux內(nèi)核通常會在第一個CPU上處理中斷,以便最大化緩存的本地性。

? ? /proc樹結(jié)構(gòu)中還包含另一個與中斷相關(guān)的文件,即/proc/stat. /proc/stat記錄了一些系統(tǒng)活動的底層統(tǒng)計信息,包括從系統(tǒng)啟動開始

? ? 接收到的中斷數(shù)量。

自動檢測IRQ號

? ? 內(nèi)核幫助下的探測

? ? ? ? Linux內(nèi)核提供了一個底層設(shè)施來探測中斷號,它只能在非共享中斷的模式下工作,但是大多數(shù)硬件有能力工作在共享中斷的模式下,并

? ? ? ? 可提供更好的找到配置中斷號的方法,函數(shù)如下:

? ? ? ? unsigned long probe_irq_on(void)

? ? ? ? ? ? 這個函數(shù)返回一個未分配中斷的位掩碼,驅(qū)動程序必須保存返回的位掩碼,并且將它傳遞給后面的probe_irq_off函數(shù),調(diào)用該函數(shù)

? ? ? ? ? ? 之后,驅(qū)動程序要安排設(shè)備產(chǎn)生至少一次中斷。

? ? ? ? int probe_irq_off(undigned long)

? ? ? ? ? ? 在請求設(shè)備產(chǎn)生中斷之后,驅(qū)動程序調(diào)用這個函數(shù),并將前面probe_irq_on返回的位掩碼作為參數(shù)傳遞給它,probe_irq_off返回"probe_irq_on"

? ? ? ? ? ? 之后發(fā)生的中斷編號。如果沒有中斷發(fā)生,就返回0,如果產(chǎn)生了多次中斷,probe_irq_off會返回一個負值。

快速和慢速處理例程

? ? x86平臺上中斷處理的內(nèi)幕(中斷處理流程)

? ? ① 在所有情況下,一旦有中斷產(chǎn)生,處于entry.S文件中的中斷處理代碼(最底層的中斷處理代碼可見entry.S文件,該文件是一個匯編語言文件,完成了

? ? 許多機器級的工作,這個文件利用幾個匯編技巧及一些宏,將一段代碼用于所有可能的中斷)將中斷編號壓入棧,然后跳到一個公共段,而這個公共段

? ? 會調(diào)用在irq.c中定義的do_IRQ函數(shù)

? ? ② do_IRQ中首先會應(yīng)答中斷,這樣中斷控制器就可以繼續(xù)處理其他的事情了,然后該函數(shù)對于給定IRQ號獲得一個自旋鎖,這樣就阻止了任何其他

? ? 的CPU處理這個IRQ,接著清除幾個狀態(tài)位(包括IQR_WAITING),然后尋找這個特定IRQ的處理例程,如果沒有處理例程,就什么也不做,自旋鎖釋放,

? ? 處理任何待處理的軟件中斷,然后do_IRQ返回。

? ? ③ 如果某個中斷注冊了處理例程,則一旦發(fā)生了中斷,函數(shù)handle_IRQ_event會被調(diào)用以便實際調(diào)用處理例程,如果處理例程是慢速類型(即SA_INTERRUPT

? ? 未被設(shè)置),將會重新啟用硬件中斷,并調(diào)用處理例程。然后只是做一些清理工作,接著運行軟件中斷,最后返回到常規(guī)工作,作為中斷的結(jié)果(例如,

? ? 處理例程可以wake_up一個進程),”常規(guī)工作“可能已經(jīng)被改變,所以,從中斷返回時發(fā)生的最后一件事情可能就是一次處理器的重新調(diào)度。

處理中斷例程的參數(shù)及返回值

? ? 中斷處理例程有三個參數(shù):

? ? ① int irq: 中斷號? ? ? ?

? ? ② void dev_id: 是一種客戶數(shù)據(jù)類型(即驅(qū)動程序可用的私有數(shù)據(jù)),一般會為dev_id傳遞一個指向自己設(shè)備的數(shù)據(jù)結(jié)構(gòu)指針,這樣,一個管理

? ? ? ? 若干同樣設(shè)備的驅(qū)動程序在終端處理例程中不需要做任何額外的代碼,就可以找出哪個設(shè)備產(chǎn)生了當前的中斷事件

? ? ③ struct pt_reg *regs:? ? 保存了處理器進入中斷代碼之前的處理器上下文快照,該寄存器可被用來監(jiān)視和調(diào)試,對一般的設(shè)備驅(qū)動程序任務(wù)來說

? ? ? ? 通常不是必需的。

啟用和禁用中斷

? ? 禁用單個中斷

? ? ? ? 禁用某個特定中斷線的中斷產(chǎn)生,主要方法如下:

? ? ? ? ? ? ① void disable_irq(int irq): 該函數(shù)不但會禁止給定的中斷,而且也會等待當前正在執(zhí)行的中斷處理例程完成,如果調(diào)用disable_irq的線程

? ? ? ? ? ? 擁有任何中斷處理例程需要的資源(比如自旋鎖),則系統(tǒng)會死鎖。

? ? ? ? ? ? ② void disable_irq_nosync(int irq): 和disable_irq不同,該函數(shù)是立即返回的。

? ? ? ? ? ? ③ void enable_irq(int irq)

? ? ? ? ? ? ? ? 調(diào)用這些函數(shù)中的任何一個都會更新可編程中斷控制器中指定中斷的掩碼,因而就可以在所有的處理器上禁用或者啟用IRQ。

? ? 禁用所有的中斷

? ? ? ? 以下兩個函數(shù)用于關(guān)閉當前處理器上所有中斷處理:

? ? ? ? void local_irq_save(unsigned long flags)

? ? ? ? void local_irq_disable(void)

? ? ? ? 通過如下函數(shù)打開中斷:

? ? ? ? void local_irq_restore(unsigned logn flags)

? ? ? ? void local_irq_enable(void)

? ? 上半部和下半部

? ? ? ? 由于中斷處理例程需要盡快結(jié)束而不能使中斷阻塞的時間過長同時又需要在一次設(shè)備中斷中需要完成一定數(shù)量的工作,內(nèi)核將中斷處理例程

? ? ? ? 分為兩部分來解決這個問題。

? ? ? ? 上半部: 實際響應(yīng)中斷的例程,也就是用request_irq注冊的中斷例程。

? ? ? ? 下半部: 被上半部調(diào)度,并在稍后更安全的時間內(nèi)執(zhí)行的例程。

? ? ? ? 上半部和下半部最大的不同就是當下半部處理例程執(zhí)行時,所有中斷都是打開的,就是在更安全的時間內(nèi)運行。

? ? ? ? 例如以下一個中斷處理例程:

? ? ? ? ? ? 上半部: 保存設(shè)備的數(shù)據(jù)到一個設(shè)備特定的緩沖區(qū)并調(diào)度它的底半部然后退出

? ? ? ? ? ? 下半部: 執(zhí)行諸如喚醒進程、啟動另外的I/O操作等必要的工作

? ? ? ? Linux內(nèi)核使用tasklet以及工作隊列來實現(xiàn)下半部處理。兩者的優(yōu)缺點如下:

? ? ? ? ① tasklet: 運行非常快,但是必須是原子的

? ? ? ? ② 工作隊列: 具有更高的延遲,但是允許休眠

? ? 中斷共享

? ? ? ? Linux內(nèi)核支持所有總線的中斷共享。

? ? ? ? 安裝共享的處理例程

? ? ? ? ? ? 共享的中斷處理例程也是通過request_irq安裝的,但是存在以下兩點不同:

? ? ? ? ? ? ① 請求中斷時,必須指定flags參數(shù)中的SA_SHIRQ位

? ? ? ? ? ? ② dev_id 參數(shù)必須是唯一的。 任何指向模塊地址空間的指針都可以使用,但dev_id不能設(shè)置成NULL

? ? ? ? 內(nèi)核為每個中斷維護了一個共享處理例程的列表,這些處理例程的dev_id各不相同,就像是設(shè)備的簽名。為什么dev_id不能設(shè)置成NULL?

? ? ? ? 因為如果有兩個驅(qū)動程序在同一個中斷上都注冊NULL作為它們的簽名,那么在卸載的時候引起混淆,當中斷到達時造成內(nèi)核出現(xiàn)oops消息。

? ? ? ? 當請求一個共享中斷時必須滿足以下兩個條件:

? ? ? ? ? ? ① 中斷信號線空閑

? ? ? ? ? ? ② 任何已經(jīng)注冊了該中斷信號線的處理例程也標識了IRQ是共享的。

? ? ? ? 使用共享處理例程的驅(qū)動程序不能使用enable_irq和disable_irq。

? ? ? ? /proc接口和共享的中斷:

? ? ? ? ? ? 共享的中斷處理例程不會對/proc/stat造成影響,但是對于/proc/interrupts而言共享的中斷處理例程會在最后累計顯示多個。

? ? 中斷驅(qū)動的I/O

? ? ? ? 如果與驅(qū)動程序管理的硬件之間的數(shù)據(jù)傳輸因為某種原因被延遲的話,驅(qū)動程序就應(yīng)該實現(xiàn)緩沖。數(shù)據(jù)緩沖區(qū)有助于將數(shù)據(jù)的傳送

? ? ? ? 和接收與系統(tǒng)調(diào)用write和read分離開來,從而提高系統(tǒng)的整體性能。

? ? ? ? 一個好的緩沖機制需要采用中斷驅(qū)動的I/O,這種模式下,一個輸入緩沖區(qū)在中斷時間內(nèi)被填充,并由讀取該設(shè)備的進程取走緩沖區(qū)內(nèi)的數(shù)據(jù);

? ? ? ? 一個輸出緩沖區(qū)由寫入設(shè)備的進程填充,并在中斷時間內(nèi)取走數(shù)據(jù)。

? ? ? ? 要正確進行中斷驅(qū)動的數(shù)據(jù)傳輸,則要求硬件應(yīng)該能按照下面的語義來產(chǎn)生中斷:

? ? ? ? ① 對于輸入來說,當新的數(shù)據(jù)已經(jīng)到達并且處理器準備好接收它時,設(shè)備就中斷處理器。實際執(zhí)行的動作取決于設(shè)備使用的是I/O端口、內(nèi)存

? ? ? ? 映射,還是DMA

? ? ? ? ② 對于輸出來說,當設(shè)備準備好接收新數(shù)據(jù)或者對成功的數(shù)據(jù)傳送進行應(yīng)答時,就要發(fā)出中斷。內(nèi)存映射和具有DMA能力的設(shè)備,通常通過產(chǎn)生

? ? ? ? 中斷來通知系統(tǒng)它們對緩沖區(qū)的處理已經(jīng)結(jié)束。

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

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

  • 進程 創(chuàng)建 創(chuàng)建進程用fork()函數(shù)。fork()為子進程創(chuàng)建新的地址空間并且拷貝頁表。子進程的虛擬地址空間...
    梅花怒閱讀 2,079評論 0 7
  • 陷阱分發(fā) 陷阱(trap)指的是這樣一種機制,當異?;蛑袛喟l(fā)生時,處理器捕捉到一個執(zhí)行線程,并且將控制權(quán)轉(zhuǎn)移到...
    kotw_zjc閱讀 1,412評論 0 0
  • 1 臨界區(qū) 1.1簡介 在早期計算機系統(tǒng)中,只有一個任務(wù)進程在執(zhí)行,并不存在資源的共享與競爭。隨著技術(shù)和需求的飛速...
    Fly晴天里Fly閱讀 9,193評論 2 13
  • 又來到了一個老生常談的問題,應(yīng)用層軟件開發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個問題開始,來談?wù)劜?..
    tangsl閱讀 4,324評論 0 23
  • 文/tangsl(簡書作者) 原文鏈接:http://www.itdecent.cn/p/2b993a4b913e...
    西葫蘆炒胖子閱讀 3,947評論 0 5

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