淺嚐非同步IO

2021-06-21 16:09:49 字數 2943 閱讀 3073

關於非同步io

記得幾年前使用mfc程式設計的時候,曾經使用過windows的非同步socket。

當在socket控制代碼上設定好關心的事件(如,可讀、可寫)後,如果事件發生,則指定的視窗會收到乙個指定的訊息。

int wsaasyncselect(socket s, hwnd hwnd, unsigned int wmsg, long levent);

然後視窗例程取得訊息,對socket進行處理(如,recv、send)。

linux也支援類似的非同步io(不侷限於socket),如果事件發生,指定的程序會收到乙個指定的訊號,然後在訊號處理函式裡面可以對fd進行處理。

fcntl(fd, f_setown, getpid());

使用非同步socket模型可以在乙個執行緒中處理多個socket,並且通過訊息佇列(或訊號佇列)將這些處理過程序列化。

相比於傳統的select模型,非同步socket模型在效能上有一定的優勢(每次select操作時,對所有fd的poll操作是很影響效能的)。可能是由於**寫起來不夠結構化,非同步io方式較少被人使用。

但是,實際上上面提到的非同步io並不是真正的非同步io。真正的非同步io應該是:

1、程序對fd進行讀寫,非阻塞;

2、核心負責完成對可讀寫事件的等待,以及讀寫過程,最後把結果通知給程序;

而不是:

1、程序設定關心的事件,非阻塞;

2、核心監聽到事件,然後通知程序;

3、程序呼叫讀寫介面,對fd程序操作;

4、核心完成讀寫操作,返回結果;

真正的非同步io省略了上面的2~3步,省略了一次核心和使用者的切進切出,具有更高的效率。

然而,一直以來,很多作業系統都沒有實現真正的非同步io機制。

實現自己的非同步io

前段時間在學習寫核心模組,作為練習,想做乙個實現非同步io的核心模組。其基本思路是使用乙個核心執行緒來完成對於所有相關聯的fd的讀寫操作。使用者程序進行讀寫時,實際上是向這個核心執行緒新增乙個任務。

這個核心模組註冊了乙個字元裝置(cdev),使用者使用非同步io的方式如下:

1、開啟這個裝置,獲得乙個裝置fd;

fd = open(「/dev/fasync」, o_rdwr);

這時,核心模組生成乙個非同步任務描述物件,存放在返回的fd對應的file->private_data中;

2、通過ioctl介面,將另乙個實際需要讀寫的fd(如:socket)「繫結」到這個裝置fd上;

ioctl(fd, fasync_ioctl_bind, socket);

這時,核心模組將socket資訊新增到fd對應的非同步任務描述物件中;

3、設定socket的f_owner,指定非同步通知的物件,並註冊對應的訊號處理過程;

fcntl(socket, f_setown, getpid());

這是由檔案子系統實現的功能,owner被記錄在socket對應的file結構中;

4、對這個裝置fd進行一次讀寫操作,讀寫操作不阻塞。使用者程式在訊號處理過程中獲知fd的讀寫結果;

read(fd, buffer, size);

這時,核心模組在fd對應的非同步任務描述物件中設定任務為read,及任務相關引數buffer和size。然後將該任務新增到該模組建立的核心工作執行緒中。

5、核心工作執行緒完成對socket的監聽和讀寫。任務完成後向socket對應的owner傳送訊號。

問題及解決辦法

大體的想法就是這樣。但是其中有一點很難實現:使用者傳入buffer是乙個虛擬位址,它與程序的頁表是對應的(如果頁表換了,這個位址也就沒有意義了)。這個位址僅僅在對應的程序上下文中才有效,在這個核心態的工作執行緒中可能是無效的,所以工作執行緒不能通過這個位址來進行讀寫。

核心空間的位址對映在系統初始化時已經生成在init_mm中,但是init_mm中的頁表資訊並不會直接被使用。每乙個程序在建立時,它的mm結構都會在init_mm的基礎上生成。也就是說,每個程序的頁表實際上是繼承了核心的頁表。於是,執行核心**時,並不需要切換頁表,因為每乙個使用者程序都能提供核心所需的頁表。這樣的設計避免了核心和使用者空間切換時的頁表切換。

在上面的設計中,非同步io工作執行緒作為乙個核心執行緒,並沒有自己專用的頁表。它也是使用之前的使用者程序的頁表(當從某個使用者程序a切換到這個核心執行緒時,a的頁表不被切換,繼續被核心執行緒使用)。

當使用者程序a呼叫read的時候,必定是從a切換到核心空間的(實際上這裡還是程序a的上下文),a的頁表還是生效的,所以核心可以使用使用者傳入的buffer。

而在工作執行緒因為socket可讀而被喚醒時,就沒法保證前乙個程序就是a了,這個時候buffer是不能直接使用的。

乙個可行的解決辦法是在接收使用者的呼叫時,將buffer轉成page(page代表了物理頁面)。這個時候,buffer可能還沒有被對映,沒有對應的page,所以需要把它手動建立一下對映。然後,核心模組記錄下這個page,以後就通過它來讀寫buffer。但是,這個方法實現起來相當麻煩,要考慮的邊界條件實在太多了(buffer與page邊界不對齊;buffer跨多個page;buffer可能已經被使用者釋放,但是直接使用page的話卻不知道這個事情;等等……)

後來,在較新的linux核心(2.6.2x)中看到了真正的非同步io——aio,原來linux已經實現了非同步io。(其實,aio早在linux 2.4時就已經被作為核心patch提供了。)

aio提供了專門的系統呼叫(aio_read、aio_write、...),作為非同步io的介面。

aio也是利用核心執行緒來完成讀寫工作的,那麼它是怎麼解決前面提到的讀寫使用者buffer的問題的呢?

aio的做法是記錄下使用者傳入的buffer,以及使用者程序的mm,然後在要訪問buffer之前,將頁表切換成對應使用者頁表(通過乙個叫use_mm函式),於是就可以直接使用buffer了。

在這裡,通過切換頁表來使得使用者傳入的buffer可用,把問題變得簡單了。

可惜use_mm這個函式並沒有匯出符號,不能被核心模組所引用(除非改一下核心),這一招不能用在我的核心模組上面了……

淺嚐非同步IO

關於非同步io 記得幾年前使用mfc程式設計的時候,曾經使用過windows的非同步socket。當在socket控制代碼上設定好關心的事件 如,可讀 可寫 後,如果事件發生,則指定的視窗會收到乙個指定的訊息。int wsaasyncselect socket s,hwnd hwnd,unsigne...

同步IO和非同步IO

同步io和非同步io 簡單的說 同步在程式設計裡,一般是指某個io操作執行完後,才可以執行後面的操作。非同步則是,將某個操作給系統,主線程去忙別的事情,等核心完成操作後通知主線程非同步操作已經完成。i windows同步i o與非同步i o 執行後的效果如下 winxp sp2 vc6.0 4 心得...

同步IO和非同步IO

同步io和非同步io 有兩種型別的檔案io同步 同步檔案io和非同步檔案io。非同步檔案io也就是重疊io。在同步檔案io中,執行緒啟動乙個io操作然後就立即進入等待狀態,直到io操作完成後才醒來繼續執行。而 非同步檔案io方式中,執行緒傳送乙個io請求到核心,然後繼續處理其他的事情,核心完成io請...