1. 程式人生 > >Linux核心開發之非同步通知與非同步I/O(一)

Linux核心開發之非同步通知與非同步I/O(一)

“小王,聽說過錦上添花吧..”我拍拍下王的頭說。

“還錦上添花你,為你上次提的幾個東東,我是頭上長包..”小王氣憤地瞪著我。

“啊,為啥這樣呢,本來還特意拒絕了MM的約會,抽出時間打算給你說點高階的東東,看來現在是不行了”我吃驚道,“這樣吧,這次就給你講些和前邊有關的東西,也不失為錦上添花不是?”。

“好,我也是這麼打算的,就是沒好意思說,今天講些啥呢?”小王暗淡的眼光總算閃了閃。(為啥這麼難受呢,好像跟什麼會嚎叫的特像,哈哈

  那就言歸正傳,今天我們講---Linux裝置驅動程式之非同步通知與非同步I/O.”

  小王,前邊不是講了阻塞與非阻塞訪問,poll()函式等提供的較好的解決裝置訪問的機制,那麼通過這次有關非同步通知整套機制的配合,就更相得益彰,錦上添花了。

啥叫非同步通知:很簡單,一旦裝置準備好,就主動通知應用程式,這種情況下應用程式就不需要查詢裝置狀態,這是不是特像硬體上常提的“中斷的概念”。上邊比較準確的說法其實應該叫做“訊號驅動的非同步I/O”,訊號是在軟體層次上對中斷機制的一種模擬。

“小王,給你一個表現的機會,說說這個和前邊的幾點不同和差異。。”

“嗯,我的理解是這樣的哈,阻塞I/O意味著一直等待裝置可訪問再訪問,非阻塞I/O意味著使用poll()來查詢是否可訪問,而非同步通知則意味著裝置通知應用程式自身可訪問。”看著小王聰明的眼睛和清晰的思路,我也忍不住給予一個鼓勵的微笑啊。

  說的好,我只是想強調一點:上面三種方式,其實本身是沒有優劣的,應該根據不同的應用場景合理選擇罷了。

  說到訊號,在應用程式中,為了捕獲訊號(還捕獲呢, 不就是一個處理嗎)可以使用signal()函式來設定對應的訊號的處理函式。函式原型是

  void (*signal(int signo,void (*func)(int))) (int)   這個看起來費勁吧,不光你,我看著也費勁,沒關係,給你來個例子:

 void sigterm_handler(int signo)
 {
    char data[MAX_LEN];
    int len;
    len=read(STDIN_FILENO, &data,MAX_LEN);
    data[len]=0;
    printf
("Input available:%s\n",data); exit(0); } int main(void) { int oflags; //啟動訊號驅動機制 signal(SIGIO, sigterm_handler); fcntl(STDIN_FILENO, F_SETOWN, getpid()); oflags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); //建立一個死迴圈,防止程式結束 while(1); return 0; }
 看了這段程式碼明白啥意思了吧,我也不多少了,咱們繼續往下走..為了一個使用者在使用者空間中能處理一個裝置釋放的訊號,它必須完成一下3份工作:
 1)通過F_SETOWN控制指令設定裝置檔案的擁有者為本程序,這樣從裝置驅動中發出的訊號才能被本程序收到。
 2)通過F_SETFLIO控制命令設定裝置檔案支援FASYNC,即非同步通知模式。
 3)通過signal()連結訊號和訊號處理函式。
 當然,如果你瞭解linux/Unix訊號機制的話,你可能會問為啥沒說sigaction函式,其實沒關係,作用差不多,想知道的話,自己看書Apue的P261.
 有了訊號的傳送,那麼就一定得有訊號的釋放了:
 在裝置驅動和應用程式的非同步通知互動中,僅僅在應用程式端捕獲訊號是不夠的,因為訊號沒有的源頭是在驅動端,因此要在適當的時機讓裝置驅動釋放訊號。
 為了使裝置支援非同步通知機制,驅動程式中涉及三個操作:
 1)支援F_SETOWN命令,能在這個控制命令處理中設定filp->f_owner為對應的程序ID。不過此項工作已由核心完成,裝置驅動無須處理。
 2)支援F_SETFL命令的處理,每當FASYNC標誌改變時,驅動程式中fasync()函式將得以進行。因此,驅動程式必須實現fasync()函式。
 3)在裝置資源可獲得時,呼叫kill_fasync()函式激發相應的訊號。

驅動程式中上面的三步是和應用程式是一一對應的。如下圖:


裝置驅動中非同步通知程式設計還是比較簡單的,主要就是一些資料結構,和兩個函式:

資料結構:fasync_struct結構體

函式:1)處理FASYNC標誌變更的函式int fasync_helper(int fd, struct file *filp, int mode ,struct fasync_struct **fa);

        2) 釋放訊號用的函式void kill_fasync(struct fasync_struct **fa, int sig, int band);

和其他裝置驅動一樣,一般將fasync_struct放到裝置結構體中。下邊是典型模版:

struct xxx_dev
{
    struct cdev cdev;
    ...
    struct fasync_struct *async_queue;  //非同步結構體
}

而在驅動的fasync()函式中,只需要簡單的將該引數的3個引數以及fasync_struct結構體指標的指標作為第四個引數傳給fasync_helper函式即可.下邊是典型模版:

static int xxx_fasync(int fd, struct file *filp, int mode)
{
    struct xxx_dev *dev = filp->private_data;
    return fasync_helper(fd,filp,mode,&dev->async_queue);
}
一旦裝置資源可以獲得時,應該呼叫kill_fasync()釋放SIGIO訊號,可讀時第三個引數設定為POLL_IN,可寫時第三個引數設定為POLL_OUT,下邊是釋放訊號的典型模版:
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_ops)
{
  struct xxx_dev *dev = filp->private_data;
     ....
     //產生非同步訊號
  if(dev->async_queue)
  {
     kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
  }
  ..
}
最後,在檔案關閉時,即在裝置驅動的release函式中,應呼叫裝置驅動的fasync()函式將檔案從非同步通知的列表中刪除,下邊是裝置驅動的釋放函式的典型模版:
static int xxx_release(struct inode *inode, struct file *filp)
{
    struct xxx_dev *dev = filp->private_data;
    //將檔案從非同步通知列表中刪除
  xxx_fasync(-1,filp,0);
    ...
    return 0;
}

“等等,我知道你明白,你是想向我個例子,我知道你啥意思,沒關係,我下次補上 。”沒等小王,我立刻給她堵上“你啊,心裡幾根小九九,我還不知道啊..”