5.4. Completions 機制

2018-02-24 15:49 更新

5.4.?Completions 機制

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


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

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

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

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


DECLARE_COMPLETION(my_completion); 

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


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

等待 completion 是一個簡單事來調(diào)用:


void wait_for_completion(struct completion *c); 

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

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


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

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

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


INIT_COMPLETION(struct completion c); 

可用來快速進行這個初始化.

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


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 */
}

有多個進程同時從這個設備"讀"是有可能的. 每個對設備的寫將確切地使一個讀操作完成, 但是沒有辦法知道會是哪個.

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


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

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號