歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

支持異步IO的Linux字符設備驅動程序

Linux 2.6內核新引入的AIO(異步IO)機制可以讓應用程序發起多個IO請求,而不用等待IO完成。一般來說,塊設備和網絡設備驅動程序已經是異步的了,無需為支持AIO而做特別的改動;但是字符設備驅動程序卻需要實現新的接口才可以支持AIO。字符設備為支持AIO而需要實現的接口定義在file_operations結構體中:

這是較新版本內核代碼(筆者參考的是2.6.28.6版本的內核代碼)中的定義。《Linux設備驅動開發詳解(第2版)》以及網上搜索到的很多文章給出的接口定義不是這樣的:沒有使用iovec結構體,而是直接使用緩沖區指針char __user* buf,這是較老版本內核代碼的定義。

aio_read和aio_write分別用於執行異步讀寫操作;aio_fsync與fsync類似,用於確保所有數據都寫入到磁盤了,一般不需要實現這個接口。

要注意區分兩種異步機制的差別:

1.異步IO就緒通知機制:適當的時候驅動程序調用kill_fasync()函數,使用信號SIGIO通知應用程序,設備准備就緒,可以執行IO操作了。

2.異步IO機制:應用程序發起多個IO請求,排隊等待處理。異步IO與非阻塞IO的相同點是,應用程序不會阻塞。但是非阻塞IO中,如果設備資源不可用,則驅動程序返回EAGAIN,應用程序需要在適當的時候進行重試,直到資源可用,IO請求成功執行。而使用異步IO時,如果設備資源不可用,IO請求會排隊等待處理,驅動程序返回EIOCBQUEUED,應用程序不需要進行重試。

本文論述如何讓Linux字符設備驅動程序支持第2種異步機制。

1發起IO操作

aio_read和aio_write用於發起異步IO操作。不論設備資源是否可用,這兩個接口都不應該阻塞。如果資源可用,接口應該執行請求的操作,返回操作結果;如果不可用,則應該記錄IO請求,以便隨後設備資源可用時再執行IO操作,然後返回EIOCBQUEUED表示IO操作請求正在排隊等待處理。

《Linux設備驅動開發詳解(第2版)》以及網上搜索到的很多文章給出的例子實際上是不正確的:延遲一段時間之後再返回“同步IO”的結果,這並不是異步IO。實現異步IO能力的要點在於:

l 不能立即執行請求的操作時,記錄IO請求

l 資源可用時再執行記錄下的IO請求,返回操作結果

筆者為《Linux設備驅動開發詳解(第2版)》給出的globalfifo字符設備驅動程序增加了異步IO能力,其中記錄IO請求的代碼如下:

l 第一部分:如果是同步IO,或者可以立即執行請求的IO操作,則進行IO操作。內核代碼定義的宏is_sync_kiocb()表示是否是“同步IO”請求。“同步IO”請求使得必要時可以同步地使用AIO子系統。

l 第二部分:如果第一步進行的操作沒有完成所有的傳輸,則記錄已經處理完成幾個iovec結構體,以及對於余下的第一個待處理的iovec結構體已經傳輸了多少字節。

l 第三部分:分配一個io_struct結構體,用於記錄待處理的IO請求。

l 第四部分:分配一個struct page結構體指針數組,保存輸入參數iovec數組每一項的iov_base字段所對應的物理地址。這一步很重要。iovec結構體的iov_base字段給出的是用戶進程地址,aio_read/aio_write接口在用戶進程上下文中執行,可以通過copy_to_user/copy_from_user與這個地址所指的存儲空間交換數據。但是不能保存這個地址供後續使用,因為後續真正執行IO操作的上下文很可能不是發起IO操作的用戶進程上下文。例如:用戶進程A請求讀取數據,但是當前設備上沒有數據可供讀取,所以這個讀取請求r_req被排隊;一段時間後,用戶進程B寫入了一些數據,驅動程序發現有數據可供讀取了,執行被排隊的讀取請求r_req。然而,驅動程序不能使用發起請求r_req的用戶進程A的上下文來執行讀取操作了,因為進程A並沒有等待請求r_req執行完成。這樣,驅動程序只能在進程B或者其他進程上下文中執行r_req請求的操作,從而不能訪問進程A的進程地址空間中的地址,也就是r_req對應的iovec結構體中的iov_base字段所指的地址。解決這個問題的方法是,將請求r_req所提供的緩沖區地址(iovec結構體的iov_base字段)轉換成物理地址保存起來供後續使用,因為物理地址跟進程上下文無關。第四部分的代碼就是做這個工作的。

l 第五部分:將不能立即執行的異步IO請求放入到待處理IO請求鏈表中。

l 第六部分:如果讀/寫操作傳輸了一些字節,則可以進行寫/讀操作了,執行排隊待處理的IO請求。不過,在這裡執行隊列中待處理的IO請求是不太合適的:隊列中待處理的IO請求可能不是由當前進程發起的,浪費當前進程的處理器時間,執行其他進程的IO請求,這對進程是不公平的。正確的做法應該是:驅動程序創建一個線程(進程?),在自己的線程中執行異步IO請求;或者使用其他內核線程。筆者剛開始學習Linux設備驅動編程,對此不熟悉,所以這部分代碼有待以後改進。

Copyright © Linux教程網 All Rights Reserved