欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
《Essential Linux Device Drivers》第3章 - Inside ...
第3章 內核組件
本章將對一些驅動(dòng)開(kāi)發(fā)相關(guān)的內核組件進(jìn)行講解。我們首先以?xún)群司€(xiàn)程開(kāi)始,它類(lèi)似于用戶(hù)空間的進(jìn)程,通常用于并發(fā)處理。
另外,內核還提供了一些接口,使用它們可以簡(jiǎn)化代碼、消除冗余、增強代碼可讀性并有利于代碼的長(cháng)期維護。本章會(huì )學(xué)習鏈表、哈希鏈表、工作隊列、通知鏈(notifier chain)、完成以及錯誤處理輔助接口等。這些輔助接口經(jīng)過(guò)了優(yōu)化,而且清除了bug,因此你的驅動(dòng)可以繼承這些優(yōu)點(diǎn)。
內核線(xiàn)程
內核線(xiàn)程是一種在內核空間實(shí)現后臺任務(wù)的方式。該任務(wù)可以是繁忙地處理異步事務(wù),也可以睡眠等待某事件的發(fā)生。內核線(xiàn)程與用戶(hù)進(jìn)程相似,唯一的不同是內核線(xiàn)程位于內核空間可以訪(fǎng)問(wèn)內核函數和數據結構。和用戶(hù)進(jìn)程相似,由于可搶占調度的存在,內核現在看起來(lái)也在獨占CPU。很多設備驅動(dòng)都使用了內核線(xiàn)程以完成輔助任務(wù)。例如,USB設備驅動(dòng)核心的khubd內核線(xiàn)程的作用就是監控USB集線(xiàn)器,并在USB被熱插拔的時(shí)候配置USB設備。
創(chuàng )建內核線(xiàn)程
讓我們用一個(gè)例子老學(xué)習內核線(xiàn)程的知識。當我們在開(kāi)發(fā)這個(gè)例子線(xiàn)程的時(shí)候,你也會(huì )學(xué)習到進(jìn)程狀態(tài)、等待隊列的概念,并接觸到用戶(hù)模式輔助函數。當你熟悉內核線(xiàn)程以后,你可以使用它作為在內核中進(jìn)行各種各樣實(shí)驗的媒介。
假定我們的線(xiàn)程要完成這樣的工作:一旦它檢測到某一關(guān)鍵的內核數據結構的健康狀態(tài)極度惡化(譬如,網(wǎng)絡(luò )接受緩沖區的空閑內存低于警戒水位),就激活一個(gè)用戶(hù)模式程序給你發(fā)送一封email或發(fā)出一個(gè)呼機警告。
該任務(wù)比較適合用內核線(xiàn)程來(lái)實(shí)現,原因如下:
(1)它是一個(gè)等待異步事件的后臺任務(wù);
(2)由于實(shí)際的事件偵測由內核的其他部分完成,本任務(wù)也需要訪(fǎng)問(wèn)內核數據結構;
(3)它必須激活一個(gè)用戶(hù)模式的輔助程序,這比較耗費時(shí)間。
內建的內核線(xiàn)程
使用ps命令可以查看系統中正在運行的內核線(xiàn)程(也稱(chēng)為內核進(jìn)程)。內核線(xiàn)程的名字被一個(gè)方括號括起來(lái)了:
bash> ps -ef
UID        PID  PPID  C STIME TTY         TIME CMD
root         1     0  0 22:36 ?       00:00:00 init [3]
root         2     0  0 22:36 ?       00:00:00 [kthreadd]
root         3     2  0 22:36 ?       00:00:00 [ksoftirqd/0]
root         4     2  0 22:36 ?       00:00:00 [events/0]
root        38     2  0 22:36 ?       00:00:00 [pdflush]
root        39     2  0 22:36 ?       00:00:00 [pdflush]
root        29     2  0 22:36 ?       00:00:00 [khubd]
root       695     2  0 22:36 ?       00:00:00 [kjournald]
...
root      3914     2  0 22:37 ?       00:00:00 [nfsd]
root      3915     2  0 22:37 ?       00:00:00 [nfsd]
...
root      4015  3364  0 22:55 tty3    00:00:00 -bash
root      4066  4015  0 22:59 tty3    00:00:00 ps -ef
[ksoftirqd/0]內核線(xiàn)程是實(shí)現軟中斷的助手。軟中斷是由中斷發(fā)起的可以被延后執行的底半部。在第4章《打下基礎》將對底半部和軟中斷進(jìn)行詳細的分析,這里的基本理論是讓中斷處理程序中的代碼越少越好。中斷處理時(shí)間越小,系統屏蔽中斷的時(shí)間會(huì )越短,這會(huì )降低時(shí)延。Ksoftirqd的工作是確保高負荷情況下,軟中斷既不會(huì )饑餓,又不至于壓垮系統。在對稱(chēng)多處理器(SMP)及其上,多個(gè)線(xiàn)程實(shí)例可以并行地運行在不同的處理器上,為了提高吞吐率,系統為每個(gè)CPU都創(chuàng )建了一個(gè)ksoftirqd線(xiàn)程(ksoftirqd/n,其中n代表了CPU序號)。
events/n(其中n代表了CPU序號)實(shí)現了工作隊列,它是另一種在內核中延后執行的手段。內核中期待延遲執行工作的程序可以創(chuàng )建自己的工作隊列,或者使用缺省的events/n工作者線(xiàn)程。第4章也對工作隊列進(jìn)行了深入分析。
pdflush內核線(xiàn)程的任務(wù)是對頁(yè)高速緩沖中的臟頁(yè)進(jìn)行寫(xiě)回(flush out)。頁(yè)高速緩沖會(huì )對磁盤(pán)數據進(jìn)行緩存,為了提供性能,實(shí)際的磁盤(pán)寫(xiě)操作會(huì )一直延遲到pdflush后臺程序將臟數據寫(xiě)回磁盤(pán)才進(jìn)行。當系統中可用的空閑內存低于門(mén)限,或者頁(yè)變成臟頁(yè)后一段時(shí)間。在2.4內核中,這2個(gè)任務(wù)分配被bdflush和kupdated這2個(gè)單獨的線(xiàn)程完成。你可能會(huì )注意到ps的輸出中有2個(gè)pdflush的實(shí)例,如果內核感覺(jué)到現存的實(shí)例已經(jīng)在滿(mǎn)負荷運轉,它會(huì )創(chuàng )建1個(gè)新的實(shí)例以服務(wù)磁盤(pán)隊列。當你的系統有多個(gè)磁盤(pán),而且要頻繁訪(fǎng)問(wèn)它們的時(shí)候,這種方式會(huì )提高吞吐率。
在以前的章節中我們已經(jīng)看到,kjournald是通用內核日志線(xiàn)程,它被EXT3等文件系統使用。
Linux網(wǎng)絡(luò )文件系統(NFS)通過(guò)一套名為nfsd的內核線(xiàn)程實(shí)現。
在被內核中負責監控我們感興趣的數據結構的任務(wù)喚醒之前,我們的例子線(xiàn)程一直會(huì )放棄CPU。在被喚醒后,它激活一個(gè)用戶(hù)模式輔助程序并將恰當的身份代碼傳遞給它。
使用kernel_thread()可以創(chuàng )建內核線(xiàn)程:
ret = kernel_thread(mykthread, NULL,
CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
標記參數定義了父子之間要共享的資源。CLONE_FILES意味著(zhù)打開(kāi)的文件要被貢獻,CLONE_SIGHAND意味著(zhù)信號處理被共享。
清單3.1顯示了例子的實(shí)現。由于內核線(xiàn)程通常是設備驅動(dòng)的助手,它們往往在驅動(dòng)初始化的時(shí)候被創(chuàng )建。但是,本例的內核線(xiàn)程可以在任意合適的位置被創(chuàng )建,例如init/main.c。
這個(gè)線(xiàn)程開(kāi)始的時(shí)候調用daemonize(),它會(huì )執行初始的家務(wù)工作,之后將本線(xiàn)程的父親線(xiàn)程改為kthreadd。每個(gè)Linux線(xiàn)程有一個(gè)父親。如果某個(gè)父進(jìn)程在沒(méi)有等待其所有子進(jìn)程都退出的時(shí)候就死掉了,它的所有子進(jìn)程都會(huì )成為僵死進(jìn)程(zombie process),仍然消耗資源。將父親重新定義為kthreadd可以避免這種情況,并且確保線(xiàn)程退出的時(shí)候能進(jìn)行恰當的清理工作[1]。
[1]在2.6.21及更早的內核中,daemonize()會(huì )通過(guò)調用reparent_to_init()將本線(xiàn)程的父親置為init任務(wù)。
由于daemonize()在默認情況下會(huì )阻止所有的信號,因此,你的線(xiàn)程如果想處理某個(gè)信號,應該調用allow_signal()來(lái)使能它。在內核中沒(méi)有信號處理函數,因此我們使用signal_pending()來(lái)檢查信號的存在并采取適當的行動(dòng)。出于調試目的,清單3.1中的代碼使能了SIGKILL的傳遞,在收到該信號后,本線(xiàn)程會(huì )壽終正寢。
面對更高層次的kthread API(其目的在于超越kernel_thread()),kernel_thread()的地位下降了。以后我們會(huì )分析kthreads。
清單3.1 實(shí)現一個(gè)內核線(xiàn)程
static DECLARE_WAIT_QUEUE_HEAD(myevent_waitqueue);
rwlock_t myevent_lock;
extern unsigned int myevent_id;  /* Holds the identity of the
troubled data structure.
Populated later on */
static int mykthread(void *unused)
{
unsigned int event_id = 0;
DECLARE_WAITQUEUE(wait, current);
/* Become a kernel thread without attached user resources */
daemonize("mykthread");
/* Request delivery of SIGKILL */
allow_signal(SIGKILL);
/* The thread sleeps on this wait queue until it's
woken up by parts of the kernel in charge of sensing
the health of data structures of interest */
add_wait_queue(&myevent_waitqueue, &wait);
for (;;) {
/* Relinquish the processor until the event occurs */
set_current_state(TASK_INTERRUPTIBLE);
schedule();  /* Allow other parts of the kernel to run */
/* Die if I receive SIGKILL */
if (signal_pending(current)) break;
/* Control gets here when the thread is woken up */
read_lock(&myevent_lock);      /* Critical section starts */
if (myevent_id) { /* Guard against spurious wakeups */
event_id = myevent_id;
read_unlock(&myevent_lock); /* Critical section ends */
/* Invoke the registered user mode helper and
pass the identity code in its environment */
run_umode_handler(event_id); /* Expanded later on */
} else {
read_unlock(&myevent_lock);
}
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&myevent_waitqueue, &wait);
return 0;
}
將其編譯入內核并運行,在ps命令的輸出中,你將看到這個(gè)線(xiàn)程mykthread:
bash> ps -ef
UID        PID  PPID C STIME TTY       TIME CMD
root         1     0 0 21:56 ?     00:00:00 init [3]
root         2     1 0 22:36 ?     00:00:00 [ksoftirqd/0]
...
root         111   1 0 21:56 ?     00:00:00 [mykthread]
...
在我們深入探究線(xiàn)程的實(shí)現之前,先寫(xiě)一段代碼,讓它監控我們感興趣的數據結構的“健康狀態(tài)”,一旦發(fā)生問(wèn)題就喚醒mykthread:
/* Executed by parts of the kernel that own the
data structures whose health you want to monitor */
/* ... */
if (my_key_datastructure looks troubled) {
write_lock(&myevent_lock);  /* Serialize */
/* Fill in the identity of the data structure */
myevent_id = datastructure_id;
write_unlock(&myevent_lock);
/* Wake up mykthread */
wake_up_interruptible(&myevent_waitqueue);
}
/* ... */
清單3.1運行在進(jìn)程上下文,而上面的代碼即可以運行于進(jìn)程上下文,又可以運行于中斷上下文。進(jìn)程和中斷上下文通過(guò)內核數據結構通信。在我們的例子中用于通信的是myevent_id和myevent_waitqueue。myevent_id包含了有問(wèn)題的數據結構的身份信息,對它的訪(fǎng)問(wèn)通過(guò)加鎖進(jìn)行了串行處理。
要注意的是只有在編譯時(shí)配置了CONFIG_PREEMPT的情況下,內核線(xiàn)程才是可搶占的。如果CONFIG_PREEMPT被關(guān)閉,如果你運行在沒(méi)有搶占補丁的2.4內核上,如果你的線(xiàn)程不進(jìn)入睡眠狀態(tài),它將使系統凍結。如果你注釋掉清單3.1中的schedule(),并且在內核配置時(shí)關(guān)閉了CONFIG_PREEMPT選項,你的系統將被鎖住。
在第19章《用戶(hù)空間的設備驅動(dòng)》討論調度策略時(shí),你將學(xué)會(huì )怎樣從內核線(xiàn)程獲得軟實(shí)時(shí)響應。
進(jìn)程狀態(tài)和等待隊列
下面的語(yǔ)句是清單3.1中將mykthread置于睡眠狀態(tài)并等待時(shí)間的代碼片段:
add_wait_queue(&myevent_waitqueue, &wait);
for (;;) {
/* ... */
set_current_state(TASK_INTERRUPTIBLE);
schedule();    /* Relinquish the processor */
/* Point A */
/* ... */
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&myevent_waitqueue, &wait);
上面代碼片段的操作依靠了2個(gè)概念:等待隊列和進(jìn)程狀態(tài)。
等待隊列用于存放需要等待事件和系統資源的線(xiàn)程。在被負責偵測事件的中斷服務(wù)程序或另一個(gè)線(xiàn)程喚醒之前,位于等待隊列的線(xiàn)程會(huì )處于睡眠。入列和出列的操作分別通過(guò)調用add_wait_queue()和remove_wait_queue()完成,而喚醒隊列中的任務(wù)則通過(guò)wake_up_interruptible()完成。
一個(gè)內核線(xiàn)程(或一個(gè)常規的進(jìn)程)可以處于如下?tīng)顟B(tài)中的一種:運行(running)、可被打斷的睡眠(interruptible)、不可被打斷的睡眠(uninterruptible)、僵死(zombie)、停止(stopped)、追蹤(traced)和dead。這些狀態(tài)的定義位于include/linux/sched.h:
(1)處于運行狀態(tài)(TASK_RUNNING)的進(jìn)程位于調度器的運行隊列(run queue)中,等待調度器將CPU時(shí)間分給它執行;
(2)處于可被打斷的睡眠狀態(tài)(TASK_INTERRUPTIBLE)的進(jìn)程正在等待一個(gè)事件的發(fā)生,不在調度器的運行隊列之中。當它等待的事件發(fā)生后,或者它被信號打斷,它將重新進(jìn)入運行隊列;
(3)處于不可被打斷的睡眠狀態(tài)(TASK_INTERRUPTIBLE)的進(jìn)程與處于可被打斷的睡眠狀態(tài)的進(jìn)程的行為相似,唯一的區別是信號的發(fā)生不會(huì )導致該進(jìn)程被重新放入運行隊列;
(4)處于停止狀態(tài)(TASK_STOPPED)的進(jìn)程由于收到了某些信號已經(jīng)停止執行;
(5)如果1個(gè)應用程序(如strace)正在使用內核的ptrace支持以攔截一個(gè)進(jìn)程,該進(jìn)程將處于追蹤狀態(tài)(TASK_TRACED);
(6)處于僵死狀態(tài)(EXIT_ZOMBIE)的進(jìn)程已經(jīng)被終止,但是其父進(jìn)程并未等待它完成。一個(gè)退出后的進(jìn)程要么處于EXIT_ZOMBIE狀態(tài),要么處于死亡狀態(tài)(EXIT_DEAD)。
你可以使用set_current_state()來(lái)設置內核線(xiàn)程的運轉狀態(tài)。
現在回到前面的代碼片段。mykthread在等待隊列myevent_waitqueue上睡眠,并將它的狀態(tài)修改為T(mén)ASK_INTERRUPTIBLE,表明了它不想進(jìn)入調度器運行隊列的愿望。對schedule()的調用將導致調度器從運行隊列中選擇一個(gè)新的任務(wù)投入運行。當負責健康狀況檢測的代碼通過(guò)wake_up_interruptible(&myevent_waitqueue)喚醒mykthread后,該進(jìn)程將重新回到調度器運行隊列。而與此同時(shí),進(jìn)程的狀態(tài)也改變?yōu)門(mén)ASK_RUNNING,因此,以便喚醒的動(dòng)作發(fā)生在設置。另外,如果SIGKILL被傳遞給了該線(xiàn)程,它也會(huì )返回運行隊列。之后,一旦調度器從運行隊列中選擇了mykthread線(xiàn)程,它將從Point A開(kāi)始恢復執行。
用戶(hù)模式輔助函數
清單3.1中的mykthread會(huì )通過(guò)調用run_umode_handler()向用戶(hù)空間通告被偵測到的事件:
/* Called from Listing 3.1 */
static void
run_umode_handler(int event_id)
{
int i = 0;
char *argv[2], *envp[4], *buffer = NULL;
int value;
argv[i++] = myevent_handler; /* Defined in
kernel/sysctl.c */
/* Fill in the id corresponding to the data structure
in trouble */
if (!(buffer = kmalloc(32, GFP_KERNEL))) return;
sprintf(buffer, "TROUBLED_DS=%d", event_id);
/* If no user mode handlers are found, return */
if (!argv[0]) return; argv[i] = 0;
/* Prepare the environment for /path/to/helper */
i = 0;
envp[i++] = "HOME=/";
envp[i++] = "PATH=/sbin:/usr/sbin:/bin:/usr/bin";
envp[i++] = buffer; envp[i]   = 0;
/* Execute the user mode program, /path/to/helper */
value = call_usermodehelper(argv[0], argv, envp, 0);
/* Check return values */
kfree(buffer);
}
內核支持這種機制:向用戶(hù)模式的程序發(fā)出請求,讓其執行某些程序。run_umode_handler()通過(guò)調用call_usermodehelper()使用了這種機制。你必須通過(guò)/proc/sys/目錄中的一個(gè)結點(diǎn)來(lái)注冊run_umode_handler()要激活的用戶(hù)模式程序。為了完成此項工作,必須確保CONFIG_SYSCTL(/proc/sys/目錄中的文件全部都被看作sysctl接口)配置選項在內核配置時(shí)已經(jīng)使能,并且在kernel/sysctl.c的kern_table數組中添加一個(gè)入口:
{
.ctl_name     = KERN_MYEVENT_HANDLER, /* Define in
include/linux/sysctl.h */
.procname     = "myevent_handler",
.data         = &myevent_handler,
.maxlen       = 256,
.mode         = 0644,
.proc_handler = &proc_dostring,
.strategy     = &sysctl_string,
},
上述代碼會(huì )導致proc文件系統中產(chǎn)生新的/proc/sys/kernel/myevent_handler結點(diǎn)。為了注冊用戶(hù)模式輔助程序,運行如下命令:
bash> echo /path/to/helper > /proc/sys/kernel/myevent_handler
當mykthread調用run_umode_handler()時(shí),/path/to/helper程序將開(kāi)始執行。
mykthread通過(guò)TROUBLED_DS環(huán)境變量將有問(wèn)題的內核數據結構的身份信息傳遞用戶(hù)模式輔助程序。該輔助程序可以是一段簡(jiǎn)單的腳本,它發(fā)送給你一封包含了從環(huán)境變量搜集到的信息的郵件警報:
bash> cat /path/to/helper
#!/bin/bash
echo Kernel datastructure $TROUBLED_DS is in trouble | mail -s Alert root
對call_usermodehelper()的調用必須發(fā)生在進(jìn)程上下文,而且以root權限運行。它借用下文很快就要討論的工作隊列(work queue)得以實(shí)現。
輔助接口
內核中存在一些有用的輔助接口,這些接口可以有效地減輕驅動(dòng)開(kāi)發(fā)人員的負擔。其中的一個(gè)例子就是雙向鏈表庫的實(shí)現。許多設備驅動(dòng)需要維護和操作鏈表數據結構,內核的鏈表接口函數消除了管理鏈表指針的需要,也使得開(kāi)發(fā)人員無(wú)需調試與鏈表維護相關(guān)的繁瑣問(wèn)題。本節我們將學(xué)會(huì )怎樣使用鏈表(list)、哈希鏈表(hlist)、工作隊列(work queue)、完成函數(completion function)、通知塊(notifier block)和kthreads。
我們可以等效的方式去完成輔助接口提供的功能。譬如,你可以不使用鏈表庫,而是使用自己實(shí)現的鏈表操作函數,你也可以不使用工作隊列而使用內核線(xiàn)程來(lái)進(jìn)行延后的工作。但是,使用標準的內核輔助接口的好處是,它可以簡(jiǎn)化你的代碼、消除冗余、增強代碼的可讀性,并對長(cháng)期維護有利。
由于內核非常龐大,你總是能找到?jīng)]有利用這些輔助機制優(yōu)點(diǎn)的代碼,因此,更新這些代碼也許是一種好的開(kāi)始給內核開(kāi)發(fā)貢獻代碼的方式。
鏈表
為了組成雙向鏈表,可以使用include/linux/list.h文件中提供的函數。首先,你需要在你的數據結構中嵌套一個(gè)list_head結構體:
#include <linux/list.h>
struct list_head {
struct list_head *next, *prev;
};
struct mydatastructure {
struct list_head mylist; /* Embed */
/* ... */                /* Actual Fields */
};
mylist是用于鏈接mydatastructure多個(gè)實(shí)例的鏈表。如果你在mydatastructure數據結構內嵌入了多個(gè)鏈表頭,mydatastructure就可以被鏈接到多個(gè)鏈表中,每個(gè)list_head用于其中的一個(gè)鏈表。你可以使用鏈表庫函數來(lái)在鏈表中增加和刪除元素。
在進(jìn)入細節的討論之前,我們先總結以下鏈表庫說(shuō)提供的鏈表操作接口,如表3.1所示。
表3.1 鏈表操作函數
函數
作用
INIT_LIST_HEAD()
初始化表頭
list_add()
在表頭后增加一個(gè)元素
list_add_tail()
在鏈表尾部增加一個(gè)元素
list_del()
從鏈表中刪除一個(gè)元素
list_replace()
用另一個(gè)元素替代鏈表中的某一元素
list_entry()
遍歷鏈表中的所有結點(diǎn)
list_for_each_entry()/
list_for_each_entry_safe()
被簡(jiǎn)化的鏈表遞歸接口
list_empty()
檢查鏈表是否為空
list_splice()
將2個(gè)鏈表聯(lián)合起來(lái)
為了論證鏈表的用法,我們來(lái)實(shí)現一個(gè)實(shí)例。這個(gè)實(shí)例也可以為理解下一節要討論的工作隊列的概念打下基礎。假設你的內核驅動(dòng)程序需要從某個(gè)入口點(diǎn)開(kāi)始執行一個(gè)艱巨的任務(wù),譬如讓當前線(xiàn)程進(jìn)入睡眠等待狀態(tài)。在該任務(wù)完成之前,你的驅動(dòng)不想被阻塞,因為這會(huì )降低依賴(lài)于它的應用程序的響應速度。因此,當驅動(dòng)需要執行這種工作的時(shí)候,它將相應的函數加入一個(gè)工作函數的鏈表,并延后執行它。而實(shí)際的工作則在一個(gè)內核線(xiàn)程中執行,該線(xiàn)程會(huì )遍歷鏈表,并在后臺執行這些工作函數。驅動(dòng)將工作函數放入鏈表的尾部,而內核則從鏈表的頭部取元素,因此,則包裝了這些放入隊列的工作會(huì )以先進(jìn)先出的原則執行。當然,驅動(dòng)的剩余部分需要被修改以適應這種延后執行的策略。在理解這個(gè)例子之前,你首先要意識到在清單3.5中,我們將使用工作隊列(work queue)接口來(lái)完成相同的工作,而且用工作隊列的方式會(huì )顯得更加簡(jiǎn)單。
首先看看本例中使用的關(guān)鍵的數據結構:
static struct _mydrv_wq {
struct list_head mydrv_worklist; /* Work List */
spinlock_t lock;                 /* Protect the list */
wait_queue_head_t todo;          /* Synchronize submitter
and worker */
} mydrv_wq;
struct _mydrv_work {
struct list_head mydrv_workitem; /* The work chain */
void (*worker_func)(void *);     /* Work to perform */
void *worker_data;               /* Argument to worker_func */
/* ... */                        /* Other fields */
} mydrv_work;
mydrv_wq是一個(gè)針對所有工作發(fā)布者的全局變量。其成員包括一個(gè)指向工作鏈表頭部的指針、一個(gè)用于在發(fā)起工作的驅動(dòng)函數和執行該工作的線(xiàn)程之間進(jìn)行通信的等待隊列。鏈表輔助函數不會(huì )對鏈表成員的訪(fǎng)問(wèn)進(jìn)行保護,因此,你需要使用并發(fā)機制以串行化同時(shí)發(fā)生的指針引用。mydrv_wq的另一個(gè)成員——自旋鎖用于此目的。清單3.2中的驅動(dòng)初始化函數mydrv_init()會(huì )初始化自旋鎖、鏈表頭、等待隊列,并啟動(dòng)工作者線(xiàn)程。
清單3.2 初始化數據結構
static int __init
mydrv_init(void)
{
/* Initialize the lock to protect against
concurrent list access */
spin_lock_init(&mydrv_wq.lock);
/* Initialize the wait queue for communication
between the submitter and the worker */
init_waitqueue_head(&mydrv_wq.todo);
/* Initialize the list head */
INIT_LIST_HEAD(&mydrv_wq.mydrv_worklist);
/* Start the worker thread. See Listing 3.4 */
kernel_thread(mydrv_worker, NULL,
CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
return 0;
}
在查看工作者線(xiàn)程(執行被提交的工作)之前,我們先看看提交者本身。清單3.3給出一個(gè)函數,內核的其他部分可以利用它來(lái)提交工作。該函數調用list_add_tail()來(lái)將一個(gè)工作函數添加到鏈表的尾部,圖3.1顯示了工作隊列的物理結構。
圖3.1 工作函數鏈表
<!--[if !vml]--><!--[endif]-->
清單3.3 提交延后執行的工作
int
submit_work(void (*func)(void *data), void *data)
{
struct _mydrv_work *mydrv_work;
/* Allocate the work structure */
mydrv_work = kmalloc(sizeof(struct _mydrv_work), GFP_ATOMIC);
if (!mydrv_work) return -1;
/* Populate the work structure */
mydrv_work->worker_func = func; /* Work function */
mydrv_work->worker_data = data; /* Argument to pass */
spin_lock(&mydrv_wq.lock);      /* Protect the list */
/* Add your work to the tail of the list */
list_add_tail(&mydrv_work->mydrv_workitem,
&mydrv_wq.mydrv_worklist);
/* Wake up the worker thread */
wake_up(&mydrv_wq.todo);
spin_unlock(&mydrv_wq.lock);
return 0;
}
下面的代碼用于從一個(gè)驅動(dòng)的入口點(diǎn)提交一個(gè)void job(void *)工作函數:
submit_work(job, NULL);
在提交這個(gè)工作函數之后,清單3.3將喚醒工作者線(xiàn)程。清單3.4中工作者線(xiàn)程的結構與上一節討論的標準的內核線(xiàn)程結構相似。該線(xiàn)程調用list_entry()以遍歷鏈表中的所有結點(diǎn),list_entry()返回包含鏈表結點(diǎn)的容器的數據結構。仔細查看清單3.4中如下一行:
struct _mydrv_work, mydrv_workitem);
mydrv_workitem被包含在mydrv_work之中,因此list_entry()返回相應的mydrv_work結構體的指針。傳遞給list_entry()的參數是被嵌入鏈表結點(diǎn)的地址、容器結構體的類(lèi)型、嵌入的鏈表結點(diǎn)字段的名字。
在執行一個(gè)被提交的工作函數之后,工作者線(xiàn)程將通過(guò)list_del()刪除鏈表中的相應結點(diǎn)。注意當工作函數被執行之前,mydrv_wq.lock被釋放,執行完后又被重新獲得。這是因為工作函數可以睡眠,而且新的被調度執行的代碼可能要獲取相同的自旋鎖,則可能導致潛在的死鎖問(wèn)題。
清單3.4 工作者線(xiàn)程
static int
mydrv_worker(void *unused)
{
DECLARE_WAITQUEUE(wait, current);
void (*worker_func)(void *);
void *worker_data;
struct _mydrv_work *mydrv_work;
set_current_state(TASK_INTERRUPTIBLE);
/* Spin until asked to die */
while (!asked_to_die()) {
add_wait_queue(&mydrv_wq.todo, &wait);
if (list_empty(&mydrv_wq.mydrv_worklist)) {
schedule();
/* Woken up by the submitter */
} else {
set_current_state(TASK_RUNNING);
}
remove_wait_queue(&mydrv_wq.todo, &wait);
/* Protect concurrent access to the list */
spin_lock(&mydrv_wq.lock);
/* Traverse the list and plough through the work functions
present in each node */
while (!list_empty(&mydrv_wq.mydrv_worklist)) {
/* Get the first entry in the list */
mydrv_work = list_entry(mydrv_wq.mydrv_worklist.next,
struct _mydrv_work, mydrv_workitem);
worker_func = mydrv_work->worker_func;
worker_data = mydrv_work->worker_data;
/* This node has been processed. Throw it
out of the list */
list_del(mydrv_wq.mydrv_worklist.next);
kfree(mydrv_work);   /* Free the node */
/* Execute the work function in this node */
spin_unlock(&mydrv_wq.lock);  /* Release lock */
worker_func(worker_data);
spin_lock(&mydrv_wq.lock);    /* Re-acquire lock */
}
spin_unlock(&mydrv_wq.lock);
set_current_state(TASK_INTERRUPTIBLE);
}
set_current_state(TASK_RUNNING);
return 0;
}
處于代碼簡(jiǎn)潔的目的,上述例子代碼并不執行錯誤處理。例如,如果清單2調用的kernel_thread()執行失敗,你需要釋放相應的工作結構體的內存。另外,清單3.4中的asked_to_die()也沒(méi)有完成。在偵測到信號或收到了模塊卸載時(shí)release()函數發(fā)出的信息后,它都會(huì )導致循環(huán)終止。
在本節結束之前,我們看一下另一個(gè)有用的鏈表庫方法list_for_each_entry()。使用這個(gè)宏,遍歷操作就變地更為簡(jiǎn)潔,可讀性也會(huì )更好,因為我們不必在循環(huán)內部調用list_entry()。如果你想在循環(huán)內部刪除鏈表元素,需要調用list_for_each_entry_safe()。因為,我們可以將清單3.4中的下列代碼:
mydrv_work = list_entry(mydrv_wq.mydrv_worklist.next,
struct _mydrv_work, mydrv_workitem);
/* ... */
}
替換為:
struct _mydrv_work *temp;
list_for_each_entry_safe(mydrv_work, temp,
&mydrv_wq.mydrv_worklist,
mydrv_workitem) {
/* ... */
}
在本例中,你不能使用list_for_each_entry(),因為你需要在清單3.4的循環(huán)內部刪除由mydrv_work指向的入口。而list_for_each_entry_safe()則通過(guò)傳入的第2個(gè)參數即臨時(shí)變量temp解決了這個(gè)問(wèn)題,其中,temp用戶(hù)保存鏈表中下一個(gè)入口的地址。
哈希鏈表
如果你需要實(shí)現哈希表鏈接數據結構,前文介紹的雙向鏈表實(shí)現并非一種優(yōu)化的方案。這是因為哈希表僅僅需要一個(gè)包含單一指針的鏈表頭。為了減少此類(lèi)應用的內存開(kāi)銷(xiāo),內核提供哈希鏈表(hlist),它是鏈表的變體。鏈表對鏈表頭和結點(diǎn)使用了相同的結構體,但是哈希鏈表則不一樣,它針對鏈表頭和鏈表結點(diǎn)有不同的定義:
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
為了適應單一的哈希鏈表頭指針這一策略,哈希鏈表結點(diǎn)會(huì )維護其前一個(gè)結點(diǎn)的地址,而不是它本身的指針。
哈希表的實(shí)現利用了hlist_head數組,每個(gè)hlist_head牽引著(zhù)一個(gè)雙向的hlist_node鏈表。而一個(gè)哈希函數則被用來(lái)定位目標結點(diǎn)在hlist_head數組中的索引,此后,便可以使用hlist輔助函數(也定義在include/linux/list.h文件中)操作與選擇的索引對應的hlist_node鏈表。fs/dcache.c文件中目錄高速緩沖(dcache)的實(shí)現可以作為一個(gè)例子。
工作隊列
工作隊列是內核中用于進(jìn)行延后工作的一種方式[2]。延后工作在無(wú)數場(chǎng)景下都有用,例如:
[2] 軟中斷和tasklet是內核中用于延后工作的另外2種機制。第4章的表4.1對軟中斷、taskle和工作隊列進(jìn)行了對比分析。
(1)1個(gè)錯誤中斷發(fā)生后,觸發(fā)網(wǎng)絡(luò )適配器重新啟動(dòng);
(2)同步磁盤(pán)緩沖區的文件系統任務(wù);
(3)給磁盤(pán)發(fā)送一個(gè)命令,并跟蹤存儲協(xié)議狀態(tài)機。
工作隊列的作用與清單3.2和3.4中的例子相似,但是,工作隊列可以讓你以更簡(jiǎn)潔的風(fēng)格完成同樣的工作。
工作隊列輔助庫向用戶(hù)呈現了2個(gè)接口數據接口:workqueue_struct和work_struct,使用工作隊列的步驟如下:
1.
創(chuàng )建一個(gè)工作隊列(或一個(gè)workqueue_struct),該工作隊列與一個(gè)或多個(gè)內核線(xiàn)程關(guān)聯(lián)??梢允褂胏reate_singlethread_workqueue()創(chuàng )建一個(gè)服務(wù)于workqueue_struct的內核線(xiàn)程。為了在系統中的每個(gè)CPU上創(chuàng )建一個(gè)工作者線(xiàn)程,可以使用create_workqueue()變體。另外,內核中也存在缺省的針對每個(gè)CPU的工作者線(xiàn)程(events/n,n是CPU序號),可以分時(shí)共享這個(gè)線(xiàn)程而不是創(chuàng )建一個(gè)單獨的工作者線(xiàn)程。根據具體應用的不同,如果你沒(méi)有定義專(zhuān)用的工作者線(xiàn)程,可能會(huì )遭遇性能問(wèn)題。
2.
創(chuàng )建一個(gè)工作元素(或者一個(gè)work_struct)。 使用INIT_WORK()可以初始化一個(gè)work_struct,填充它的工作函數的地址和參數。
3.
將工作元素提交給工作隊列??梢酝ㄟ^(guò)queue_work()將work_struct 提交給一個(gè)專(zhuān)用的work_struct,或者通過(guò)schedule_work()提交給缺省的內核工作者線(xiàn)程。
接下來(lái)我們重新編寫(xiě)清單3.2和3.4,以利用工作隊列接口的優(yōu)點(diǎn),相關(guān)的代碼為清單3.5。原先的內核線(xiàn)程連同自旋鎖、等待隊列,在使用工作隊列接口后都隨風(fēng)飄散了。如果你正在使用缺省的工作者線(xiàn)程的話(huà),對create_singlethread_workqueue()的調用都可以不需要。
清單3.5 使用工作隊列進(jìn)行延后工作
#include <linux/workqueue.h>
struct workqueue_struct *wq;
/* Driver Initialization */
static int __init
mydrv_init(void)
{
/* ... */
wq = create_singlethread_workqueue("mydrv");
return 0;
}
/* Work Submission. The first argument is the work function, and
the second argument is the argument to the work function */
int
submit_work(void (*func)(void *data), void *data)
{
struct work_struct *hardwork;
hardwork = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
/* Init the work structure */
INIT_WORK(hardwork, func, data);
/* Enqueue Work */
queue_work(wq, hardwork);
return 0;
}
如果你使用了工作隊列,你必須將對應模塊設為GPL許可證,否則會(huì )出現連接錯誤。這是因為,內核僅僅將這些函數導出給GPL授權的代碼。如果你查看內核工作隊列的實(shí)現代碼,你將發(fā)現如下的限制表達式:
EXPORT_SYMBOL_GPL(queue_work);
下列語(yǔ)句可用于宣布你的模塊使用GPL copyleft:
MODULE_LICENSE("GPL");
通知鏈
通知鏈(Notifier chains)可用于將狀態(tài)改變信息發(fā)送給請求這些改變的代碼段。與硬編碼不同,notifier提供了一種在感興趣的事件產(chǎn)生時(shí)獲得警告的技術(shù)。Notifier的初始目的是將網(wǎng)絡(luò )事件傳遞給內核中感興趣的部分,但是現在也可用于許多其他目的。內核已經(jīng)為主要的事件預先定義了notifier。這樣的通知的實(shí)例包括:
(1)死亡通知。當內核觸發(fā)了一個(gè)陷阱和錯誤(由oops、缺頁(yè)或斷點(diǎn)命中引發(fā))時(shí)被發(fā)送。例如,如果你正在為一個(gè)醫療等級卡編寫(xiě)設備驅動(dòng),你可能需要注冊自身接受死亡通知,這樣,當內核恐慌發(fā)生時(shí),你可以關(guān)閉醫療電子。
(2)網(wǎng)絡(luò )設備通知。當一個(gè)網(wǎng)絡(luò )接口卡啟動(dòng)和關(guān)閉的時(shí)候被發(fā)送。
(3)CPU頻率通知。當處理器的頻率發(fā)生跳變的時(shí)候,會(huì )分發(fā)這一通知。
(4)Internet地址通知。當偵測到網(wǎng)絡(luò )接口卡的IP地址發(fā)送改變的時(shí)候,會(huì )發(fā)送此通知。
Notifier的應用實(shí)例是drivers/net/wan/hdlc.c中的高級數據鏈路控制(HDLC)協(xié)議驅動(dòng),它會(huì )注冊自己到網(wǎng)絡(luò )設備通知鏈,以偵測載波狀態(tài)的改變。
為了將你的代碼與某通知鏈關(guān)聯(lián),你必須注冊一個(gè)相關(guān)鏈的時(shí)間處理函數。當相應的事件發(fā)生時(shí),事件ID和與通知相關(guān)的參數會(huì )傳遞給該處理函數。為了實(shí)現一個(gè)自定義的通知鏈,你必須另外實(shí)現底層結構,以便當事件被偵測到時(shí),鏈會(huì )被激活。
清單3.6給出了使用預定義的通知和用戶(hù)自定義通知的例子,表3.2則對清單3.6中的通知鏈和它們傳遞的事件進(jìn)行了簡(jiǎn)要的描述,因此,可以對照查看表3.2和清單3.6。
表3.2 通知鏈和它們傳送的事件
通知鏈
描述
Die Notifier Chain (die_chain)
通過(guò)register_die_notifier(),my_die_event_handler()被依附于die_chain死亡通知鏈。為了觸發(fā)my_die_event_handler()的發(fā)生,代碼中引入了一個(gè)冗余的引用,即:
int *q = 0; *q = 1;
當這段代碼被執行的時(shí)候,my_die_event_handler()將被調用,你將看到這樣的信息:
my_die_event_handler: OOPs! at EIP=f00350e7
死亡事件通知將die_args結構體傳給被注冊的事件處理函數。該參數包括一個(gè)指向regs結構體的指針(在發(fā)生缺陷的時(shí)候,用于存放處理器的寄存器)。my_die_event_handler()中打印了指令指針寄存器的內容。
Netdevice Notifier Chain(netdev_chain)
通過(guò)register_netdevice_notifier(),my_dev_event_handler()被依附于網(wǎng)絡(luò )設備通知鏈netdev_chain。通過(guò)改變網(wǎng)絡(luò )接口設備(如以太網(wǎng)ethX和回環(huán)設備lo)的狀態(tài)可以產(chǎn)生此事件:
bash> ifconfig eth0 up
它會(huì )導致my_dev_event_handler()的執行。
net_device結構體的指針被傳給該處理函數作為參數,它包含了網(wǎng)絡(luò )接口的名字,my_dev_event_handler()打印出了該信息:
my_dev_event_handler: Val=1, Interface=eth0
Val=1意味著(zhù)NETDEV_UP事件,其定義在include/linux/notifier.h文件中。
User-Defined Notifier Chain
清單3.6也實(shí)現了一個(gè)用戶(hù)自定義的通知鏈my_noti_chain。假定你希望當用戶(hù)讀取proc文件系統中一個(gè)特定的文件的時(shí)候該事件被產(chǎn)生,可以在相關(guān)的procfs讀函數中加入如下代碼:
blocking_notifier_call_chain(&my_noti_chain, 100, NULL);
當你讀取相應的/proc文件時(shí),my_event_handler()將被調用,如下信息被打印出來(lái):
my_event_handler: Val=100
Val包含了產(chǎn)生事件的ID,本例中為100。該函數的參數沒(méi)有被使用。
清單3.6 通知事件處理函數
#include <linux/notifier.h>
#include <asm/kdebug.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
/* Die Notifier Definition */
static struct notifier_block my_die_notifier = {
.notifier_call = my_die_event_handler,
};
/* Die notification event handler */
int
my_die_event_handler(struct notifier_block *self,
unsigned long val, void *data)
{
struct die_args *args = (struct die_args *)data;
if (val == 1) { /* '1' corresponds to an "oops" */
printk("my_die_event: OOPs! at EIP=%lx\n", args->regs->eip);
} /* else ignore */
return 0;
}
/* Net Device notifier definition */
static struct notifier_block my_dev_notifier = {
.notifier_call = my_dev_event_handler,
};
/* Net Device notification event handler */
int my_dev_event_handler(struct notifier_block *self,
unsigned long val, void *data)
{
printk("my_dev_event: Val=%ld, Interface=%s\n", val,
((struct net_device *) data)->name);
return 0;
}
/* User-defined notifier chain implementation */
static BLOCKING_NOTIFIER_HEAD(my_noti_chain);
static struct notifier_block my_notifier = {
.notifier_call = my_event_handler,
};
/* User-defined notification event handler */
int my_event_handler(struct notifier_block *self,
unsigned long val, void *data)
{
printk("my_event: Val=%ld\n", val);
return 0;
}
/* Driver Initialization */
static int __init
my_init(void)
{
/* ... */
/* Register Die Notifier */
register_die_notifier(&my_die_notifier);
/* Register Net Device Notifier */
register_netdevice_notifier(&my_dev_notifier);
/* Register a user-defined Notifier */
blocking_notifier_chain_register(&my_noti_chain, &my_notifier);
/* ... */
}
通過(guò)BLOCKING_NOTIFIER_HEAD(),清單3.6中的my_noti_chain被定義為一個(gè)阻塞通知,經(jīng)由對blocking_notifier_chain_register()函數的調用,它被注冊。這意味著(zhù)該通知事件處理函數總是在進(jìn)程上下文被調用,也允許睡眠。如果你的通知處理函數允許從中斷上下文調用,你應該使用ATOMIC_NOTIFIER_HEAD()定義該通知鏈并使用atomic_notifier_chain_register()注冊它。
老的通知接口
早于2.6.17的內核版本僅支持一個(gè)通用目的的通知鏈。通知鏈注冊函數notifier_chain_register()內部使用自旋鎖保護,但游走于通知鏈以分發(fā)事件給通知處理函數的函數notifier_call_chain()確是無(wú)鎖的。不加鎖的原因是事件處理函數可能會(huì )睡眠、在運行中注銷(xiāo)自己或在中斷上下文中被調用。但是無(wú)鎖的實(shí)現卻引入了競態(tài),而新的通知API則建立于老的接口之上,其設計中包含了克服此限制的意圖。
完成接口
內核中的許多地方會(huì )激發(fā)一個(gè)單獨的執行線(xiàn)索,之后等待它的完成。完成接口是一個(gè)充分的且簡(jiǎn)單的此類(lèi)編碼的實(shí)現模式。
一些使用場(chǎng)景的例子包括:
(1)你的驅動(dòng)模塊中包含了一個(gè)輔助內核線(xiàn)程。當你卸載這個(gè)模塊時(shí),在模塊的代碼從內核空間被移除之前,release()函數將被調用。release函數中要求內核線(xiàn)程殺死自身,它一直阻塞等待線(xiàn)程的退出。清單3.7實(shí)現了這個(gè)例子。
(2)你正在編寫(xiě)塊設備驅動(dòng)(第14章《塊設備驅動(dòng)》討論)中將設備讀請求排隊的部分。這激活了以單獨線(xiàn)程或工作隊列方式實(shí)現的一個(gè)狀態(tài)機的變更,而驅動(dòng)本身想一直等到該操作完成前才執行下一次操作。drivers/block/floppy.c就是這樣的一個(gè)例子。
(3)一個(gè)應用請求模擬/數字轉換(ADC)驅動(dòng)完成一次數據采樣。該驅動(dòng)初始化一個(gè)轉換請求,接下來(lái)一直等待轉換完成的中斷產(chǎn)生,并返回轉換后的數據。
清單3.7 使用完成接口進(jìn)行同步
static DECLARE_COMPLETION(my_thread_exit);      /* Completion */
static DECLARE_WAIT_QUEUE_HEAD(my_thread_wait); /* Wait Queue */
int pink_slip = 0;                              /* Exit Flag */
/* Helper thread */
static int
my_thread(void *unused)
{
DECLARE_WAITQUEUE(wait, current);
daemonize("my_thread");
add_wait_queue(&my_thread_wait, &wait);
while (1) {
/* Relinquish processor until event occurs */
set_current_state(TASK_INTERRUPTIBLE);
schedule();
/* Control gets here when the thread is woken
up from the my_thread_wait wait queue */
/* Quit if let go */
if (pink_slip) {
break;
}
/* Do the real work */
/* ... */
}
/* Bail out of the wait queue */
__set_current_state(TASK_RUNNING);
remove_wait_queue(&my_thread_wait, &wait);
/* Atomically signal completion and exit */
complete_and_exit(&my_thread_exit, 0);
}
/* Module Initialization */
static int __init
my_init(void)
{
/* ... */
/* Kick start the thread */
kernel_thread(my_thread, NULL,
CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD);
/* ... */
}
/* Module Release */
static void __exit
my_release(void)
{
/* ... */
pink_slip = 1;                        /* my_thread must go */
wake_up(&my_thread_wait);             /* Activate my_thread */
wait_for_completion(&my_thread_exit); /* Wait until my_thread
quits */
/* ... */
}
可以使用DECLARE_COMPLETION()靜態(tài)地定義一個(gè)完成實(shí)例,或者使用init_completion()動(dòng)態(tài)地創(chuàng )建之。而一個(gè)執行線(xiàn)索可以使用complete()或complete_all()來(lái)標識一個(gè)完成。調用者自身則通過(guò)wait_for_completion()等待完成。
在清單3.7中的my_release()函數中,在喚醒my_thread()之前,它通過(guò)pink_slip設置了一個(gè)退出請求標志。接下來(lái),它調用wait_for_completion()等待my_thread()完成其退出。my_thread()函數醒來(lái)后,發(fā)現pink_slip被設置,它進(jìn)行如下工作:
(1)向my_release()函數通知完成;
(2)殺死自身
my_thread()使用complete_and_exit()函數原子性地完成了這2個(gè)步驟。complete_and_exit()關(guān)閉了模塊退出和線(xiàn)程退出之間的那扇窗,而如果使用complete()和exit()函數2步操作的話(huà),此窗口則是開(kāi)放的。
在第11章中,開(kāi)發(fā)一個(gè)遙測設備驅動(dòng)的時(shí)候,我們會(huì )使用完成接口。
Kthread輔助接口
Kthread為原始的線(xiàn)程創(chuàng )建函數添加了一層外衣由此簡(jiǎn)化了線(xiàn)程管理的任務(wù)。
清單3.8使用kthread接口重寫(xiě)了清單3.7。my_init()現在調用kthread_create()而不是kernel_thread(),你可以將線(xiàn)程的名字傳入kthread_create(),而不再需要明確地在線(xiàn)程內調用daemonize()。
Kthread允許你自由地調用內建的由完成接口所實(shí)現的退出同步機制。因此,如清單3.8中my_release()函數所為,你可以直接調用kthread_stop()而不再需要設置pink_slip、喚醒my_thread()并使用wait_for_completion()等待它的完成。相似地,my_thread()可以進(jìn)行一個(gè)簡(jiǎn)潔的對kthread_should_stop()的調用以確認其是否應該退出。
清單3.8 使用Kthread輔助接口完成同步
/* '+' and '-' show the differences from Listing 3.7 */
#include <linux/kthread.h>
/* Assistant Thread */
static int
my_thread(void *unused)
{
DECLARE_WAITQUEUE(wait, current);
-   daemonize("my_thread");
-   while (1) {
+   /* Continue work if no other thread has
+    * invoked kthread_stop() */
+   while (!kthread_should_stop()) {
/* ... */
-     /* Quit if let go */
-     if (pink_slip) {
-       break;
-     }
/* ... */
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&my_thread_wait, &wait);
-   complete_and_exit(&my_thread_exit, 0);
+   return 0;
}
+   struct task_struct *my_task;
/* Module Initialization */
static int __init
my_init(void)
{
/* ... */
-   kernel_thread(my_thread, NULL,
-                 CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
SIGCHLD);
+   my_task = kthread_create(my_thread, NULL, "%s", "my_thread");
+   if (my_task) wake_up_process(my_task);
/* ... */
}
/* Module Release */
static void __exit
my_release(void)
{
/* ... */
-   pink_slip = 1;
-   wake_up(&my_thread_wait);
-   wait_for_completion(&my_thread_exit);
+   kthread_stop(my_task);
/* ... */
}
代替使用kthread_create()創(chuàng )建線(xiàn)程,接下來(lái)再使用wake_up_process()激活它,你可以使用下面的一次調用達到目的:
kthread_run(my_thread, NULL, "%s", "my_thread");
錯誤處理助手
數個(gè)內核函數返回指針值。調用者通常將返回值與NULL對比以檢查是否失敗,但是它們很可能需要更多的信息以分析出確切的錯誤發(fā)生原因。由于內核地址有冗余比特,可以覆蓋它以包含錯誤語(yǔ)義信息。一套輔助函數完成了此功能,清單3.9給出了一個(gè)簡(jiǎn)單的例子。
#include <linux/err.h>
char *
collect_data(char *userbuffer)
{
char *buffer;
/* ... */
buffer = kmalloc(100, GFP_KERNEL);
if (!buffer) { /* Out of memory */
return ERR_PTR(-ENOMEM);
}
/* ... */
if (copy_from_user(buffer, userbuffer, 100)) {
return ERR_PTR(-EFAULT);
}
/* ... */
return(buffer);
}
int
my_function(char *userbuffer)
{
char *buf;
/* ... */
buf = collect_data(userbuffer);
if (IS_ERR(buf)) {
printk("Error returned is %d!\n", PTR_ERR(buf));
}
/* ... */
}
在清單3.9中,如果collect_data()中的kmalloc()失敗,你將獲得如下信息:
Error returned is -12!
但是,如果collect_data()執行成功,它將返回一個(gè)數據緩沖區的指針。
再來(lái)一個(gè)例子,我們給清單3.8中的線(xiàn)程創(chuàng )建代碼添加錯誤處理(使用IS_ERR()和PTR_ERR()):
my_task = kthread_create(my_thread, NULL, "%s", "mydrv");
+  if (!IS_ERR(my_task)) {
+    /* Success */
wake_up_process(my_task);
+  } else {
+    /* Failure */
+    printk("Error value returned=%d\n", PTR_ERR(my_task));
+  }
查看源代碼
ksoftirqd、pdflush和 khubd內核線(xiàn)程代碼分別在kernel/softirq.c, mm/pdflush.c和 drivers/usb/core/hub.c文件中。
kernel/exit.c可以找到daemonize(),以用戶(hù)模式助手的實(shí)現見(jiàn)于kernel/kmod.c文件。
list和hlist庫函數位于include/linux/list.h。在整個(gè)類(lèi)型中都有對它們的使用,因此在大多數子目錄中,都能找到例子。其中的一個(gè)例子是include/linux/blkdev.h中定義的request_queue結構體,它存放磁盤(pán)I/O請求的鏈表。在第14章中我們會(huì )分析此數據結構。
查看www.ussg.iu.edu/hypermail/linux/kernel/0007.3/0805.html可以跟蹤到Torvalds和Andi Kleen之間關(guān)于使用hlist實(shí)現list庫的利弊的爭論。
內核工作隊列的實(shí)現位于kernel/workqueue.c文件,為了理解工作隊列的用法,可以查看drivers/net/wireless/ipw2200.c中PRO/Wireless 2200網(wǎng)卡驅動(dòng)。
內核通知鏈的實(shí)現位于kernel/sys.c和include/linux/notifier.h文件。查看kernel/sched.c和include/linux/completion.h文件可以挖掘完成接口的實(shí)現機理。kernel/kthread.c包含了kthread輔助接口的源代碼,include/linux/err.h則包含了錯誤處理接口的定義。
表3.3給出了本章中所使用的主要的數據結構及其源代碼路徑的總結。表3.4列出了本章中使用的主要內核編程接口及其源代碼路徑。
表3.3 數據結構總結
數據結構
路徑
描述
wait_queue_t
include/linux/wait.h
內核線(xiàn)程欲等待某事件或系統資源時(shí)使用
list_head
include/linux/list.h
用于構造雙向鏈表數據結構的內核結構體
hlist_head
include/linux/list.h
用于實(shí)現哈希表的的內核結構體
work_struct
include/linux/workqueue.h
實(shí)現工作隊列,它是一種在內核中進(jìn)行延后工作的方式
notifier_block
include/linux/notifier.h
實(shí)現通知鏈,用于將狀態(tài)變更信息發(fā)生給請求此變更的代碼段
completion
include/linux/completion.h
用于開(kāi)始某線(xiàn)程活動(dòng)并等待它們完成
表3.4 內核編程接口總結
內核接口
路徑
描述
DECLARE_WAITQUEUE()
include/linux/wait.h
定義一個(gè)等待隊列
add_wait_queue()
kernel/wait.c
將一個(gè)任務(wù)加入一個(gè)等待隊列。該任務(wù)進(jìn)入睡眠狀態(tài),知道它被另一個(gè)線(xiàn)程或中斷處理函數喚醒。
remove_wait_queue()
kernel/wait.c
從等待隊列中刪除一個(gè)任務(wù)。
wake_up_interruptible()
include/linux/wait.h kernel/sched.c
喚醒一個(gè)正在等待隊列中睡眠的任務(wù),將其返回調度器的運行隊列。
schedule()
kernel/sched.c
放棄CPU,讓內核的其他部分運行。
set_current_state()
include/linux/sched.h
設置一個(gè)進(jìn)程的運行狀態(tài),可以是如下?tīng)顟B(tài)中的一種:TASK_RUNNING、TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE、TASK_STOPPED、TASK_TRACED、EXIT_ZOMBIE或EXIT_DEAD.
kernel_thread()
arch/your-arch/kernel/process.c
創(chuàng )建一個(gè)內核線(xiàn)程
daemonize()
kernel/exit.c
激活一個(gè)內核線(xiàn)程(未依附于用戶(hù)資源),并將調用線(xiàn)程的父線(xiàn)程改為kthreadd。
allow_signal()
kernel/exit.c
使能某指定信號的發(fā)起
signal_pending()
include/linux/sched.h
檢查是否有信號已經(jīng)被傳輸。在內核中沒(méi)有信號處理函數,因此,你不得不顯示地檢查信號的發(fā)起
call_usermodehelper()
include/linux/kmod.h kernel/kmod.c
執行一個(gè)用戶(hù)模式的程序
Linked list library functions
include/linux/list.h
看表3.1
register_die_notifier()
arch/your-arch/kernel/traps.c
注冊一個(gè)die通知
register_netdevice_notifier()
net/core/dev.c
注冊一個(gè)netdevice通知
register_inetaddr_notifier()
net/ipv4/devinet.c
注冊一個(gè)inetaddr通知
BLOCKING_NOTIFIER_HEAD()
include/linux/notifier.h
創(chuàng )建一個(gè)用戶(hù)自定義的阻塞性的通知
blocking_notifier_chain_register()
kernel/sys.c
注冊一個(gè)阻塞性的通知
blocking_notifier_call_chain()
kernel/sys.c
將事件分發(fā)給一個(gè)阻塞性的通知鏈
ATOMIC_NOTIFIER_HEAD()
include/linux/notifier.h
創(chuàng )建一個(gè)原子性的通知
atomic_notifier_chain_register()
kernel/sys.c
注冊一個(gè)原子性的通知
DECLARE_COMPLETION()
include/linux/completion.h
靜態(tài)定義一個(gè)完成實(shí)例
init_completion()
include/linux/completion.h
動(dòng)態(tài)定義一個(gè)完成實(shí)例
complete()
kernel/sched.c
宣布完成
wait_for_completion()
kernel/sched.c
一直等待完成實(shí)例的完成
complete_and_exit()
kernel/exit.c
原子性的通知完成并退出
kthread_create()
kernel/kthread.c
創(chuàng )建一個(gè)內核線(xiàn)程
kthread_stop()
kernel/kthread.c
讓一個(gè)內核線(xiàn)程停止
kthread_should_stop()
kernel/kthread.c
內核線(xiàn)程可以使用該函數輪詢(xún)是否其他的執行單元已經(jīng)調用kthread_stop()讓其停止
IS_ERR()
include/linux/err.h
查看返回值是否是一個(gè)出錯碼
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
十四、Linux驅動(dòng)程序開(kāi)發(fā)(10) - 中斷
linux signal 處理
softirq(軟中斷)下半部中tasklet與workqueue的區別
schedule_delayed_work()用法
linux內核--中斷處理程序
深入理解Linux信號機制
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久