《Linux4.0設備驅動開發詳解》筆記--第九章:Linux設備驅動中的異步通知與同步I/O

在設備驅動中使用異步通知可使得對設備的訪問可進行時,由驅動主動通知應用程序進行訪問。所以,使用無阻塞I/O的應用程序無需輪詢設備是否可訪問,而阻塞訪問也能夠被相似「中斷」的異步通知所取代。異步通知相似於硬件上的「中斷」概念,比較準確的稱謂是「信號驅動的異步I/O」。css

9.1 異步通知的概念和做用

  • 異步通知:一旦設備就緒,則主動通知應用程序,該應用程序無需查詢設備狀態
  • 幾種通知方式比較:
    • 阻塞I/O :一直等待設備可訪問後開始訪問
    • 非阻塞I/O:使用poll()查詢設備是否訪問
    • 異步通知 :設備主動通知用戶應用程序
    • -

9.2 linux異步通知編程

9.2.1 linux信號

  • 做用:linux系統中,異步通知使用信號來實現
SIGHUP     終止進程     終端線路掛斷
SIGINT     終止進程     中斷進程
SIGQUIT   創建CORE文件終止進程,而且生成core文件
SIGILL   創建CORE文件       非法指令
SIGTRAP   創建CORE文件       跟蹤自陷
SIGBUS   創建CORE文件       總線錯誤
SIGSEGV   創建CORE文件       段非法錯誤
SIGFPE   創建CORE文件       浮點異常
SIGIOT   創建CORE文件       執行I/O自陷
SIGKILL   終止進程     殺死進程
SIGPIPE   終止進程     向一個沒有讀進程的管道寫數據
SIGALARM   終止進程     計時器到時
SIGTERM   終止進程     軟件終止信號
SIGSTOP   中止進程     非終端來的中止信號
SIGTSTP   中止進程     終端來的中止信號
SIGCONT   忽略信號     繼續執行一箇中止的進程
SIGURG   忽略信號     I/O緊急信號
SIGIO     忽略信號     描述符上能夠進行I/O
SIGCHLD   忽略信號     當子進程中止或退出時通知父進程
SIGTTOU   中止進程     後臺進程寫終端
SIGTTIN   中止進程     後臺進程讀終端
SIGXGPU   終止進程     CPU時限超時
SIGXFSZ   終止進程     文件長度過長
SIGWINCH   忽略信號     窗口大小發生變化
SIGPROF   終止進程     統計分佈圖用計時器到時
SIGUSR1   終止進程     用戶定義信號1
SIGUSR2   終止進程     用戶定義信號2
SIGVTALRM 終止進程     虛擬計時器到時

9.2.2 信號的接收

  • 信號捕獲函數signal()
    • 參數:
      • signum:信號值
      • handler:針對signum的處理函數
        • 若爲SIG_IGN:忽略該信號
        • 若爲SIG_DFL:系統默認方式處理
        • 若爲用戶自定義函數:信號被捕獲,該函數被執行
    • 返回值
      • 成功:最後一次爲信號signum綁定的處理函數的handler值
      • 失敗:返回SIG_ERR
    • sigaction()
      • 做用:改變進程接收到特定信號後的行爲
      • 參數
        • signum:信號值
          • 除SIG_KILL及SIG_STOP之外的一個特定有效的信號
        • act:指向結構體sigaction的一個實例的指針
          • 在結構體sigaction中,指定了處理信號的函數,若爲空則進程會以缺省值的方式處理信號
        • oldact:保存原來對應的信號的處理函數,可設爲NULL
int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact);
  • 實例:使用信號實現異步通知
    • 在用戶空間處理設備釋放信號的準備工做
      • 經過F_SETOWN IO控制命令設置設備文件的擁有者爲本進程,以使信號被本進程捕獲
      • 經過F_SETFL IO控制命令設置設備文件以支持FASYNC,及異步通知模式
      • 經過signal()函數鏈接信號和信號處理函數
//啓動信號機制

void sigterm_handler(int sigo)
{

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);
fctcl(STDIN_FILENO,F_SETFL,oflags | FASYNC);
//創建一個死循環,防止程序結束

whlie(1);

return 0;

}

9.2.3 信號的釋放 (在設備驅動端釋放信號)

  • 爲了是設備支持異步通知機制,驅動程序中涉及如下3項工做
    • 支持F_SETOWN命令,能在這個控制命令處理中設置filp->f_owner爲對應的進程ID。不過此項工做已由內核完成,設備驅動無須處理
    • 支持F_SETFL命令處理,每當FASYNC標誌改變時,驅動函數中的fasync()函數得以執行。所以,驅動中應該實現fasync()函數
    • 在設備資源中可得到,調用kill_fasync()函數激發相應的信號
    • -
  • 設備驅動中異步通知編程:
    • 處理FASYNC標誌變動函數:fasync_helper()
    • 釋放信號的函數:kill_fasync()
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_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 *ppos)

{
    struct xxx_dev *dev = filp->private_data;
    ...
    //產生異步讀信息
    if(dev->async_queue)
    kill_fasync(&dev->async_queue,GIGIO,POLL_IN);
    ...
}
  • 最後在文件關閉時,要將文件從異步通知列表中刪除
int xxx_release(struct inode *inode,struct file *filp)

{
    //將文件從異步通知列表中刪除
    xxx_fasync(-1,filp,0);
    ...
    return 0;
}

9.4 linux異步I/O

9.4.1 AIO概念與GNU C庫 AIO

9.4.1.1 AIO概念

  • 同步I/O:linux系統中最經常使用的輸入輸出(I/O)模型是同步I/O,在這個模型中,當請求發出後,應用程序就會阻塞,知道請求知足node

  • 異步I/O:I/O請求可能須要與其它進程產生交疊linux

  • Linux 系統中最經常使用的輸入/輸出(I/O)模型是同步 I/O編程

    • 在這個模型中,當請求發出以後,應用程序就會阻塞,直到請求知足爲止
    • 調用應用程序在等待 I/O 請求完成時不須要使用任何中央處理單元(CPU)
    • 在某些狀況下,I/O 請求可能須要與其餘進程產生交疊,可移植操做系統接口(POSIX)異步 I/O(AIO)應用程序接口(API)就提供了這種功能

9.4.1.1 AIO系列API:

  • aio_read–異步讀
    • 做用:請求對一個有效的文件描述符進行異步讀寫操做
      • 請求進行排隊以後會當即返回
      • 這個文件描述符能夠表示一個文件、套接字,甚至管道
    • 參數aiocb:結構體包含了傳輸的全部信息,以及爲AIO操做準備的用戶空間緩存區
    • 返回值
      • 成功:返回0
      • 失敗:返回-1,並設置errno的值
int aio_read( struct aiocb *aiocbp );
  • aio_write–異步寫
    • 做用:請求一個異步寫操做
      • 請求進行排隊以後會當即返回
      • 這個文件描述符能夠表示一個文件、套接字,甚至管道
    • 參數aiocb:結構體包含了傳輸的全部信息,以及爲AIO操做準備的用戶空間緩存區
    • 返回值
      • 成功:返回0
      • 失敗:返回-1,並設置errno的值
int aio_write( struct aiocb *aiocbp );
  • aio_error
    • 做用:肯定請求的狀態
    • 參數aiocb:結構體包含了傳輸的全部信息,以及爲AIO操做準備的用戶空間緩存區
    • 返回值
      • EINPROGRESS:說明請求還沒有完成
      • ECANCELED:說明請求被應用程序取消
      • 失敗:返回-1,並設置errno的值
int aio_error( struct aiocb *aiocbp );
  • aio_return–得到異步操做的返回值
    • 異步 I/O 和標準塊 I/O 之間的另一個區別是不能當即訪問這個函數的返回狀態,由於並無阻塞在 read()調用上
    • 在標準的 read()調用中,返回狀態是在該函數返回時提供的。可是在異步 I/O 中,咱們要使用 aio_return()函數
    • 只有在 aio_error()調用肯定請求已經完成(可能成功,也可能發生了錯誤)以後,纔會調用這個函數
    • 參數aiocb:結構體包含了傳輸的全部信息,以及爲AIO操做準備的用戶空間緩存區
    • 返回值
      • 成功:返回所傳輸的字節數
      • 失敗:返回-1
ssize_t aio_return( struct aiocb *aiocbp );
  • aio_suspend–掛起異步操做,直到異步請求完成爲止
    • 做用:掛起(或阻塞)調用進程,直到異步請求完成爲止,調用者提供了一個 aiocb 引用列表,其中任何一個完成都會致使 aio_suspend()返回
int aio_suspend( const struct aiocb *const cblist[], int n, const struct timespec *timeout );
  • aio_cancel–取消異步請求
    • 做用:容許用戶取消對某個文件描述符執行的一個或全部 I/O 請求
    • 要求:
      • 若是要取消一個請求,用戶需提供文件描述符和 aiocb 引用
        • 函數返回AIO_CANCELED:請求被成功取消
        • 函數返回AIO_NOTCANCELED:請求完成
      • 若是要取消對某個給定文件描述符的全部請求,用戶須要提供這個文件的描述符以及一個對 aiocbp 的 NULL 引用
        • 函數返回AIO_CANCELED:代表全部的請求都取消了
        • 函數返回AIO_NOTCANCELED:代表至少有一個請求沒有被取消
        • 函數返回AIO_ALLDONE:代表沒有一個請求能夠被取消
      • 使用 aio_error()來驗證每一個 AIO 請求
        • aio_error()返回-1而且設置了errno被設置爲ECANCELED:代表某個請求已經被取消了
int aio_cancel( int fd, struct aiocb *aiocbp );
  • lio_listio–同時發起多個傳輸(一次系統調用能夠啓動大量的I/O操做)
    • 做用:這個函數很是重要,它使得用戶能夠在一個系統調用(一次內核上下文切換)中啓動大量的 I/O 操做
    • 參數
      • mode:能夠是 LIO_WAIT 或 LIO_NOWAIT
        • LIO_WAIT 會阻塞這個調用,直到全部的 I/O 都完成爲止
        • 在操做進行排隊以後,LIO_NOWAIT 就會返回
      • list :是一個 aiocb 引用的列表,最大元素的個數是由 nent 定義的
        • 若是 list 的元素爲 NULL,lio_listio()會將其忽略。
int lio_listio( int mode, struct aiocb *list[], int nent, struct sigevent *sig );

9.4.2 Linux內核AIO與libaio

  • linux AIO也能夠由內核空間實現,異步I/O是linux2.6之後版本內核的標準特性
  • 對於塊設備,AIO能夠一次性發出大量的read/write調用而且經過通用塊層的I/O調度來得到更好的性能,用戶也能夠減小過多的同步負載
  • 對網絡設備而言,在socket層面上,也可使用AIO,讓CPU和網卡的收發充分交疊以改善吞吐性能
  • 用戶空間中通常要結合libaio來進行內核AIO的系統調用
io_setup( )

//Initializes an asynchronous context for the current process

io_submit( )

//Submits one or more asynchronous I/O operations

io_getevents( )

//Gets the completion status of some outstanding asynchronous I/O operations

io_cancel( )

//Cancels an outstanding I/O operation

io_destroy( )

//Removes an asynchronous context for the current process

9.4.3 AIO與設備驅動

  • 用戶空間調用io_submit()以後,對應於用戶傳遞的每一個iocb結構,內核會生成一個與之對應的kiocb結構
  • 經過is_sync_kiocb判斷某kiocb是否爲同步I/O請求api

    • 若是是返回真,表示爲異步I/O請求
  • 字符設備:必須明確應支持AIO(極少數是異步I/O操做)緩存

  • 字符設備驅動程序中file_operations 包含 3 個與 AIO 相關的成員函數,
ssize_t (*aio_read) (struct kiocb *iocb, char *buffer, size_t count, loff_t offset);

ssize_t (*aio_write) (struct kiocb *iocb, const char *buffer, size_t count, loff_t offset);

int (*aio_fsync) (struct kiocb *iocb, int datasync);
  • 塊設備和網絡設備:自己是異步的
相關文章
相關標籤/搜索