6.4. 異步通知

2018-02-24 15:49 更新

6.4.?異步通知

盡管阻塞和非阻塞操作和 select 方法的結合對于查詢設備在大部分時間是足夠的, 一些情況還不能被我們迄今所見到的技術來有效地解決.

讓我們想象一個進程, 在低優(yōu)先級上執(zhí)行一個長計算循環(huán), 但是需要盡可能快的處理輸入數(shù)據(jù). 如果這個進程在響應新的來自某些數(shù)據(jù)獲取外設的報告, 它應當立刻知道當新數(shù)據(jù)可用時. 這個應用程序可能被編寫來調用 poll 有規(guī)律地檢查數(shù)據(jù), 但是, 對許多情況, 有更好的方法. 通過使能異步通知, 這個應用程序可能接受一個信號無論何時數(shù)據(jù)可用并且不需要讓自己去查詢.

用戶程序必須執(zhí)行 2 個步驟來使能來自輸入文件的異步通知. 首先, 它們指定一個進程作為文件的擁有者. 當一個進程使用 fcntl 系統(tǒng)調用發(fā)出 F_SETOWN 命令, 這個擁有者進程的 ID 被保存在 filp->f_owner 給以后使用. 這一步對內核知道通知誰是必要的. 為了真正使能異步通知, 用戶程序必須設置 FASYNC 標志在設備中, 通過 F_SETFL fcntl 命令.

在這 2 個調用已被執(zhí)行后, 輸入文件可請求遞交一個 SIGIO 信號, 無論何時新數(shù)據(jù)到達. 信號被發(fā)送給存儲于 filp->f_owner 中的進程(或者進程組, 如果值為負值).

例如, 下面的用戶程序中的代碼行使能了異步的通知到當前進程, 給 stdin 輸入文件:


signal(SIGIO, &input_handler); /* dummy sample; sigaction() is better */
fcntl(STDIN_FILENO, F_SETOWN, getpid());
oflags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);

這個在源碼中名為 asynctest 的程序是一個簡單的程序, 讀取 stdin. 它可用來測試 scullpipe 的異步能力. 這個程序和 cat 類似但是不結束于文件尾; 它只響應輸入, 而不是沒有輸入.

注意, 但是, 不是所有的設備都支持異步通知, 并且你可選擇不提供它. 應用程序常常假定異步能力只對 socket 和 tty 可用.

輸入通知有一個剩下的問題. 當一個進程收到一個 SIGIO, 它不知道哪個輸入文件有新數(shù)據(jù)提供. 如果多于一個文件被使能異步地通知掛起輸入的進程, 應用程序必須仍然靠 poll 或者 select 來找出發(fā)生了什么.

6.4.1.?驅動的觀點

對我們來說一個更相關的主題是設備驅動如何實現(xiàn)異步信號. 下面列出了詳細的操作順序, 從內核的觀點:

    1. 當發(fā)出 F_SETOWN, 什么都沒發(fā)生, 除了一個值被賦值給 filp->f_owner.
    1. 當 F_SETFL 被執(zhí)行來打開 FASYNC, 驅動的 fasync 方法被調用. 這個方法被調用無論何時 FASYNC 的值在 filp->f_flags 中被改變來通知驅動這個變化, 因此它可正確地響應. 這個標志在文件被打開時缺省地被清除. 我們將看這個驅動方法的標準實現(xiàn), 在本節(jié).
    1. 當數(shù)據(jù)到達, 所有的注冊異步通知的進程必須被發(fā)出一個 SIGIO 信號.

雖然實現(xiàn)第一步是容易的--在驅動部分沒有什么要做的--其他的步驟包括維護一個動態(tài)數(shù)據(jù)結構來跟蹤不同的異步讀者; 可能有幾個. 這個動態(tài)數(shù)據(jù)結構, 但是, 不依賴特殊的設備, 并且內核提供了一個合適的通用實現(xiàn)這樣你不必重新編寫同樣的代碼給每個驅動.

Linux 提供的通用實現(xiàn)是基于一個數(shù)據(jù)結構和 2 個函數(shù)(它們在前面所說的第 2 步和第 3 步被調用). 聲明相關材料的頭文件是<linux/fs.h>(這里沒新東西), 并且數(shù)據(jù)結構被稱為 struct fasync_struct. 至于等待隊列, 我們需要插入一個指針在設備特定的數(shù)據(jù)結構中.

驅動調用的 2 個函數(shù)對應下面的原型:


int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
void kill_fasync(struct fasync_struct **fa, int sig, int band);

fasync_helper 被調用來從相關的進程列表中添加或去除入口項, 當 FASYNC 標志因一個打開文件而改變. 它的所有參數(shù)除了最后一個, 都被提供給 fasync 方法并且被直接傳遞. 當數(shù)據(jù)到達時 kill_fasync 被用來通知相關的進程. 它的參數(shù)是被傳遞的信號(常常是 SIGIO)和 band, 這幾乎都是 POLL_IN25.

這是 scullpipe 如何實現(xiàn) fasync 方法的:


static int scull_p_fasync(int fd, struct file *filp, int mode)
{
 struct scull_pipe *dev = filp->private_data;
 return fasync_helper(fd, filp, mode, &dev->async_queue);
}

顯然所有的工作都由 fasync_helper 進行. 但是, 不可能實現(xiàn)這個功能在沒有一個方法在驅動里的情況下, 因為這個幫忙函數(shù)需要存取正確的指向 struct fasync_struct (這里是 與dev->async_queue)的指針, 并且只有驅動可提供這個信息.

當數(shù)據(jù)到達, 下面的語句必須被執(zhí)行來通知異步讀者. 因為對 sucllpipe 讀者的新數(shù)據(jù)通過一個發(fā)出 write 的進程被產(chǎn)生, 這個語句出現(xiàn)在 scullpipe 的 write 方法中.


if (dev->async_queue)
 kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

注意, 一些設備還實現(xiàn)異步通知來指示當設備可被寫入時; 在這個情況, 當然, kill_fasnyc 必須被使用一個 POLL_OUT 模式來調用.

可能會出現(xiàn)我們已經(jīng)完成但是仍然有一件事遺漏. 我們必須調用我們的 fasync 方法, 當文件被關閉來從激活異步讀者列表中去除文件. 盡管這個調用僅當 filp->f_flags 被設置為 FASYNC 時需要, 調用這個函數(shù)無論如何不會有問題并且是常見的實現(xiàn). 下面的代碼行, 例如, 是 scullpipe 的 release 方法的一部分:


/* remove this filp from the asynchronously notified filp's */
scull_p_fasync(-1, filp, 0);

這個在異步通知之下的數(shù)據(jù)結構一直和結構 struct wait_queue 是一致的, 因為 2 種情況都涉及等待一個事件. 區(qū)別是這個 struct file 被用來替代 struct task_struct. 隊列中的結構 file 接著用來存取 f_owner, 為了通知進程.

[25] POLL_IN 是一個符號, 用在異步通知代碼中; 它等同于 POLLIN|POLLRDNORM.

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號