5.4. Completions 機(jī)制

2018-02-24 15:49 更新

5.4.?Completions 機(jī)制

內(nèi)核編程的一個(gè)普通模式包括在當(dāng)前線程之外初始化某個(gè)動(dòng)作, 接著等待這個(gè)動(dòng)作結(jié)束. 這個(gè)動(dòng)作可能是創(chuàng)建一個(gè)新內(nèi)核線程或者用戶空間進(jìn)程, 對(duì)一個(gè)存在著的進(jìn)程的請(qǐng)求, 或者一些基于硬件的動(dòng)作. 在這些情況中, 很有誘惑去使用一個(gè)旗標(biāo)來同步 2 個(gè)任務(wù), 使用這樣的代碼:


struct semaphore sem; 
init_MUTEX_LOCKED(&sem);
start_external_task(&sem);
down(&sem);

外部任務(wù)可以接著調(diào)用 up(??sem), 在它的工作完成時(shí).

事實(shí)證明, 這種情況旗標(biāo)不是最好的工具. 正常使用中, 試圖加鎖一個(gè)旗標(biāo)的代碼發(fā)現(xiàn)旗標(biāo)幾乎在所有時(shí)間都可用; 如果對(duì)旗標(biāo)有很多競(jìng)爭(zhēng), 性能會(huì)受損并且加鎖方案需要重新審視. 因此旗標(biāo)已經(jīng)對(duì)"可用"情況做了很多的優(yōu)化. 當(dāng)用上面展示的方法來通知任務(wù)完成, 然而, 調(diào)用 down 的線程將幾乎是一直不得不等待; 因此性能將受損. 旗標(biāo)還可能易于處于一個(gè)( 困難的 ) 競(jìng)爭(zhēng)情況, 如果它們表明為自動(dòng)變量以這種方式使用時(shí). 在一些情況中, 旗標(biāo)可能在調(diào)用 up 的進(jìn)程用完它之前消失.

這些問題引起了在 2.4.7 內(nèi)核中增加了 "completion" 接口. completion 是任務(wù)使用的一個(gè)輕量級(jí)機(jī)制: 允許一個(gè)線程告訴另一個(gè)線程工作已經(jīng)完成. 為使用 completion, 你的代碼必須包含 <linux/completion.h>. 一個(gè) completion 可被創(chuàng)建, 使用:


DECLARE_COMPLETION(my_completion); 

或者, 如果 completion 必須動(dòng)態(tài)創(chuàng)建和初始化:


struct completion my_completion;
/* ... */
init_completion(&my_completion);

等待 completion 是一個(gè)簡(jiǎn)單事來調(diào)用:


void wait_for_completion(struct completion *c); 

注意這個(gè)函數(shù)進(jìn)行一個(gè)不可打斷的等待. 如果你的代碼調(diào)用 wait_for_completion 并且沒有人完成這個(gè)任務(wù), 結(jié)果會(huì)是一個(gè)不可殺死的進(jìn)程.[18]

另一方面, 真正的 completion 事件可能通過調(diào)用下列之一來發(fā)出:


void complete(struct completion *c);
void complete_all(struct completion *c);

如果多于一個(gè)線程在等待同一個(gè) completion 事件, 這 2 個(gè)函數(shù)做法不同. complete 只喚醒一個(gè)等待的線程, 而 complete_all 允許它們所有都繼續(xù). 在大部分情況下, 只有一個(gè)等待者, 這 2 個(gè)函數(shù)將產(chǎn)生一致的結(jié)果.

一個(gè) completion 正常地是一個(gè)單發(fā)設(shè)備; 使用一次就放棄. 然而, 如果采取正確的措施重新使用 completion 結(jié)構(gòu)是可能的. 如果沒有使用 complete_all, 重新使用一個(gè) completion 結(jié)構(gòu)沒有任何問題, 只要對(duì)于發(fā)出什么事件沒有模糊. 如果你使用 complete_all, 然而, 你必須在重新使用前重新初始化 completion 結(jié)構(gòu). 宏定義:


INIT_COMPLETION(struct completion c); 

可用來快速進(jìn)行這個(gè)初始化.

作為如何使用 completion 的一個(gè)例子, 考慮 complete 模塊, 它包含在例子源碼里. 這個(gè)模塊使用簡(jiǎn)單的語義定義一個(gè)設(shè)備: 任何試圖從一個(gè)設(shè)備讀的進(jìn)程將等待(使用 wait_for_completion)直到其他進(jìn)程向這個(gè)設(shè)備寫. 實(shí)現(xiàn)這個(gè)行為的代碼是:


DECLARE_COMPLETION(comp);
ssize_t complete_read (struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    printk(KERN_DEBUG "process %i (%s) going to sleep\n",current->pid, current->comm);
    wait_for_completion(&comp);
    printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);
    return 0; /* EOF */
}

ssize_t complete_write (struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
    printk(KERN_DEBUG "process %i (%s) awakening the readers...\n", current->pid, current->comm);
    complete(&comp);
    return count; /* succeed, to avoid retrial */
}

有多個(gè)進(jìn)程同時(shí)從這個(gè)設(shè)備"讀"是有可能的. 每個(gè)對(duì)設(shè)備的寫將確切地使一個(gè)讀操作完成, 但是沒有辦法知道會(huì)是哪個(gè).

completion 機(jī)制的典型使用是在模塊退出時(shí)與內(nèi)核線程的終止一起. 在這個(gè)原型例子里, 一些驅(qū)動(dòng)的內(nèi)部工作是通過一個(gè)內(nèi)核線程在一個(gè) while(1) 循環(huán)中進(jìn)行的. 當(dāng)模塊準(zhǔn)備好被清理時(shí), exit 函數(shù)告知線程退出并且等待結(jié)束. 為此目的, 內(nèi)核包含一個(gè)特殊的函數(shù)給線程使用:


void complete_and_exit(struct completion *c, long retval); 

[18] 在本書編寫時(shí), 添加可中斷版本的補(bǔ)丁已經(jīng)流行但是還沒有合并到主線中.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)