同步设备IO与异步设备IO
所謂同步IO是指線程在發起IO請求后會被掛起,IO完成后繼續執行。
異步IO是指:線程發起IO請求后并不會掛起而是繼續執行。IO完畢后會得到設備的通知。而IO完成端口就是實現這種通知的很好的一種方式。
CreateFile當然可以創建和打開磁盤文件。但是不要被它的名字所迷惑。它同樣可以打開其他設備。根據傳入參數的不同可以讓CreateFile打開不同的設備。
HANDLE?CreateFile(??LPCTSTR?lpFileName,??DWORD?dwDesiredAccess,??DWORD?dwShareMode,??LPSECURITY_ATTRIBUTES?lpSecurityAttributes,??DWORD?dwCreationDisposition,??DWORD?dwFlagsAndAttributes,??HANDLE?hTemplateFile?? );??psaName既表示設備類型也表示該類設備一個實例。
dwDesiredAccess可以是0,代表我們不希望從設備中讀取或寫入數據,只是想改變它的配置,如時間戳,該值還可以是GENERIC_READ等
0??????????????????????????????????????????????????????????? 不允許讀寫,但可以改變設備屬性。
GENERIC_READ???????????????????????????????? 只讀訪問
GENERIC_WRITE????????????????????????????? ?只寫訪問
GENERIC_READ|GENERIC_WRITE??讀寫訪問
dwSharedMode用來指定共享權限:
0??????????????????????????????????????????????? ??獨占對設備的訪問。如果設備已經打開,我們????的CreateFile會失敗。
FILE_SHARE_READ??????????????????只讀共享,不允許修改內容。如果設備已經以寫入或獨占方式打開,我們的CreateFile會失敗。?
FILE_SHARE_WRITE?????????????????寫共享,不允許讀取內容。如果設備已經以讀取或獨占方式打開,我們的CreateFile會失敗。
FILE_SHARE_READ|FILE_SHARE_WRITE??不關心向設備讀還是寫數據。如果設備已經以獨占方式打開,我們的CreateFile會失敗。
FIEL_SHARE_DELETE?????????????????先將文件標記待刪除,所有對該文件引用的句柄都關閉之后,才將其真正的刪除。
psa指向一個PSECURITY_ATTRIBUTES結構,用來指定安全屬性。只有當我們在具備安全性的文件系統中,如NTFS中創建文件時才會用到此結構。在其他情況下都只需要傳入NULL就可以了,此時會用默認的安全屬性來創建文件,并且返回的句柄是不可繼承的。
dwCreationDisposition參數對文件的含義更重大。它可以是以下值:
CREATE_NEW????????創建一個新文件。如果同名文件存在則失敗。
CREATE_ALWAYS?????文件同名文件存在與否都創建文件。存在時會覆蓋。
OPEN_EXISTING?????打開一個已存在文件。如不存在,則失敗。
OPEN_ALWAYS????????打開一個已存在文件。如不存在,則創建。
TRUNCATE_EXISTING?打開一個已存在文件,將文件大小截斷為0,如果不存在則調用失敗。
dwFlagsAndAttributes有兩個用途:一,允許我們設置一些標志微調與設備的通信。二:如果設備是文件,還可以設置文件屬性。這些標志大多數是一些信號,用來告訴系統我們打算以何種方式來訪問設備,這樣系統就可以對緩存算法進行優化。此處不再介紹。
hFileTemplate,既可以標識一個已經打開的文件句柄,也可以是NULL。如果是一個文件句柄,那么CreateFile會完全忽略dwFlagsAndAttributes參數,轉而使用hFileTemplate標識的文件屬性。此時,hFileTemplate標識的文件句柄必須是一個用GENERIC_READ標志打開的文件。
CreateFile成功的創建或打開設備那會返回設備句柄。否則返回INVALID_HANDLE_VALUE。一定要注意返回值不是NULL哦。
GetFileSizeEx用于得到文件大小。
BOOL GetFileSizeEx(HANDLE hFile,??PLARGE_INTEGER?pliFileSize);??hFile表示一個一打開文件的句柄。pliFileSize表示文件大小。定義如下:
typedef union _LARGE_INTEGER{struct{DWORD LowPart;LONG HighPart; };LONGLONG QuadPart;}LARGE_INTEGER,*PLARGE_INTEGER;它允許我們以一個64位有符號數或者是兩個32位值來表示一個64位數。
另外一個很重要的函數是GetCompressedFileSize:
這個函數返回文件物理大小,而GetFileSizeEx是返回文件邏輯大小。
SetFilePointer的幾點說明:
1:將文件指針設置超過文件大小是可行的,并且可以在該位置寫入數據,這樣文件大小將增大,還可以通過調用SetEndOfFile()增大或者切斷文件
2:如果文件是被FILE_FLAG_NO_BUFFERING打開的,則文件指針只能被設置為扇區大小的整數倍
4.3:SetEndOfFile()可以切斷或增大文件大小
同步異步IO基本函數
typedef?struct?_OVERLAPPED??? {????ULONG_PTR?Internal;?????????//錯誤碼,一旦發出一個異步IO請求,此值被初始化為STATUS_PENDING,如果IO完成,則變為其他值??//我們可以通過宏HasOverlappedIoCompleted(pOverlapped)檢查IO是否完成,這個宏本質就是返回??//return?Internal!=STATUS_PENDING??ULONG_PTR?InternalHigh;?????//IO請求完成時,保存已傳輸的字節數??DWORD?Offset;???????????????//下面解釋??DWORD?OffsetHigh;????HANDLE?hEvent;??????????????//以后解釋?? }OVERLAPPED;??采用異步IO打開文件設備,文件指針是沒有意義的,因為有多個異步操作同時進行,我們無法對文件指針進行同步,所以Offset和OffsetHigh指定從文件那個地方開始執行異步IO操作
如果不是文件設備,必須\將Offset和OffsetHigh設為0
異步IO完成時,會收到一個OVERLAPPED指針,這就是調用異步IO函數傳遞的那個OVERLAPPED指針,我們可以寫一個C++類從OVERLAPPED派生,這樣就能添加更多的數據幾個注意事項:
1:異步IO不會按照你的投遞順序來執行,驅動會選擇他認為最快的方式來組合這些投遞
2:錯誤處理,以文件IO為例,當我們投遞一個異步ReadFile()時,設備驅動程序可能會以同步方式執行,例如如果設備驅動程序發現要讀取的數據在文件緩沖里時,就不會投遞這個異步設備IO,而是直接將數據復制進我們的緩沖區
如果IO是同步方式執行,ReadFile()和WriteFile()返回非零值,如果是異步或者出現錯誤,返回FALSE,調用GetLastError()獲得錯誤碼,錯誤碼列舉一部分
ERROR_IO_PENDING????????????????//異步IO投遞成功??ERROR_INVALID_USER_BUFFER????????? ERROR_NOT_ENOUGH_MEMORY?????????//每個設備驅動程序會在非分頁緩沖池中維護一個固定大小的列表來管理?? //待處理的IO請求,如果這個列表已滿,異步IO就會失敗,返兩個錯誤中的一種,具體返回那個要看設備驅動程序??ERROR_NOT_ENOUGH_QUOTA??????????//某些設備要求我們提供的數據緩存頁面鎖定,也就是不能被喚出內存的頁面,?? //但是系統對一個進程能夠鎖定的頁面數量做了限制,如果超過這個數量,則會返回此錯誤?? //例如如果指定了FILE_FLAG_NO_BUFFERING,就必須滿足頁面鎖定要求?? //可以調用SetProcessWorkingSetSize()來增加進程能鎖定頁面數量的配額??在異步IO完成之前,不能刪除OVERLAPPED結構取消設備中的異步IO請求
1:調用BOOL CancelIo(HANDLE hd);關閉調用線程添加的所有IO請求(完成端口除外)
2:關閉設備句柄,這將取消其所有IO請求
3:線程終止,會取消此線程所有IO請求(完成端口除外)
4:調用BOOL CancelIoEx(HANDLE hd,LPOVERLAPPED pOVerLapped);如果pOVerLapped為0,則取消此線程投遞的所有IO請求,如果不為0,則取消相應的IO請求而不管是不是本線程投遞的
1:觸發設備內核對象 HANDLE?hd=CreateFile(...,FILE_FLAG_OVERLAPPED,...);?? ...//初始化其他數據?? bool??bReadDone=ReadFile(...);//這會將文件句柄設置為未觸發狀態?? DWORD?dwError=GetLastError();??if(bReadDone==false&&(dwError==ERROR_IO_PENDING))?? {??WaitForSingleObject(hd,INFINITE);??...//查看OVERLAPPED結構中錯誤碼和實際傳輸字節數?? }?? else?? {??//此異步IO被同步執行,或者發生了其他錯誤,檢查dwError具體值?? }??這個方法不能投遞多個異步IO,因為設備內核對象觸發時,我們不能確定投遞的那一個IO請求完成了
如果不用這種方式,為了略微提高性能,我們應該關閉操作系統觸發設備內核對象,調用如下函數
BOOL SetFileCompletionNotificationModes(HADNLE hd,UCHAR uFlags);為uFlags傳入FILE_SKIP_SET_EVENT_ON_HANDLE
2:觸發事件內核對象
OVERLAPPED有一個hEvent,它用來標識一個事件內核對象,我們每投遞一個異步IO,就為其添加一個事件內核對象,這樣,當一個異步IO完成時,設備驅動會去檢查這個hEvent的值,如果有值,則會SetEvent(),?我們就可以調用WaitForMultipleObjects()來等待這些事件內核對象,并通過返回值確定是那個異步IO完成了
可提醒IO
1:每個線程都有一個與其相關的隊列,叫做異步過程調用(APC)
2:使用APC執行異步函數如下
CreateFileEx(...,LPOVERLAPPED_COMPLETION_ROUTINE pfn);
pfn別成為完成函數
(WINAPI?*LPOVERLAPPED_COMPLETION_ROUTINE)(??__in????DWORD?dwErrorCode,??__in????DWORD?dwNumberOfBytesTransfered,??__inout?LPOVERLAPPED?lpOverlapped??);??3.系統將已經完成的異步IO投遞到APC隊列中的順序是隨機的
4:調用
SleepEx();?? WaitForSingleObjectEx();?? WaitForMultipleObjectsEx();?? SignalObjectAndWait();?? GetQueuedCompletionStatusEx();?? MsgWaitForMultipleObjectsEx();??中的一個將線程置為可提醒狀態(很多最后一個值需設置為TRUE)
只要APC隊列中有一項,線程就不會被掛起,轉而去執行完成函數,如果APC隊列為空,線程被掛起,直到APC隊列不為空或者等待的內核對象被觸發或者超時或者一個互斥量被遺棄
這6個函數如果是因為APC返回,返回值為WAIT_IO_COMPLETION,GetLastError()也是此值
可提醒IO不會試圖去觸發OVERLAPPED的事件對象,所以我們可以據為己有
可提醒IO的缺點:
發出IO請求的線程必須對其進行處理,即使其他線程處于空閑狀態,效率低下
采用APC通知一個線程優雅退出的方式
//手動添加APC?? DWORD?QueueUserAPC(?????//返回值實際是BOOL值,表示添加成功與否?? __in?PAPCFUNC?pfnAPC,???//回調函數,可以跨進程,那么此函數也應該在相應的地址空間?? __in?HANDLE?hThread,?? __in?ULONG_PTR?dwData???//傳個回調函數的一個參數而已?? );?? //回調函數原型?? VOID?(APIENTRY?*PAPCFUNC)(??__in?ULONG_PTR?dwParam);??//采用APC通知一個線程優雅退出的方式?? void?ThreadFun()?? {??...;??DWORD?dw=WaitForSingleObjectEx(hd,INFINITE,TRUE);??if(dw==WAIT_OBJECT_0)??{??...;??}??else?if(dw==WAIT_IO_COMPLETION)??{??QUIT;??}?? }??
總結
以上是生活随笔為你收集整理的同步设备IO与异步设备IO的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [NOI2013]快餐店
- 下一篇: 如何禁用 Azure 虚拟机的日期时间同