久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

深度探索I/O完成端口

 shaolong007 2009-08-03


引言

要 想編寫一個高性能的服務器應用程序,,必須實現(xiàn)一個高效的線程模型。讓太少或者太多的服務器線程來處理客戶的請求,,都可能導致性能問題,。例如,如果一個服務 器創(chuàng)建單個線程來處理所有的請求,,那么客戶端可能長期等待而得不到響應,,因為服務器同一時刻只能忙于處理一個請求。當然單個線程也能并發(fā)處理多個請求,,當I/O操作被啟動時,,它可以從一個請求切換到另一個請求,但是這種結構相當復雜,,并且不能充分利用多處理器的優(yōu)勢,。在另一個極端,服務器可以創(chuàng)建一個大規(guī)模的線程池,,這樣幾乎每一個客戶請求都可以由一個專門的線程來處理,。這種情形通常會導致線程頻繁切換:大量線程被喚醒,,執(zhí)行CPU處理,阻塞等待I/O,,然后在請求完成之后又一次阻塞以等待新的請求,。如果沒有別的情況,太多的線程將導致過多的上下文切換,,因為調(diào)度程序不得不將處理器時間在多個活動線程之間分割,。

服 務器的目標是使線程避免不必要的阻塞,盡量減少上下文切換,。同時,,還要使用多線程來發(fā)揮最大限度的并行。理想的情況是在每一個處理器上運行一個線程來處理 一個客戶請求,,當處理器上的活動線程完成一個請求時,,如果還有其他的請求正在等待,則不阻塞,。為了使這一優(yōu)化處理可以有效的進行,,應用程序必須有一種可行 的方法,使得一個正在處理客戶請求的線程在I/O上阻塞時(例如它在處理過程中需要讀取一個文件時)另外一個等待線程被激活,。

Windows NT 3.5引進了一系列API使得這個目標的實現(xiàn)變得相對容易,。這些API主要聚焦在一個叫完成端口的對象上。在本文中,,首先我將講解完成端口的使用,,然后再深入其內(nèi)部,向你展示Windows NT中完成端口的實現(xiàn)機制,。

 

使用I/O完成端口

應用程序將IoCompletion執(zhí)行體對象當作與多個文件句柄相關的I/O完成的核心,。一旦一個文件與一個完成端口相關聯(lián),任何在此文件上異步I/O操作的完成都會導致一個完成通知包(completion notification packet)加入到完成端口隊列,。一個線程只需簡單的等待一個完成通知包被排隊到此完成端口上,,就可以等待在多個文件上的所有正在進行之中的I/O操作的完成事件。Windows API中的WaitForMultipleObjects 提供了類似的功能,,但完成端口的優(yōu)點在于在系統(tǒng)的協(xié)助下發(fā)揮高效的并發(fā)性,。這里的并發(fā)性可以理解為應用程序主動處理客戶請求的線程的數(shù)量的多少。

當應用程序創(chuàng)建一個完成端口時,,需要設定并發(fā)量,。該數(shù)值指示了在任何給定時候正在運行的與該端口相關聯(lián)的線程的最大數(shù)量。正如前面所提到的,,理想情況是在任何給定的時刻,,系統(tǒng)中每個處理器都有一個線程在運行。Windows利 用與一個端口相關聯(lián)的并發(fā)值參數(shù)來控制一個應用程序中活動線程的數(shù)量,。如果與一個端口相關的活動線程數(shù)達到并發(fā)值,,那么,,在這個端口上等待的線程將不允許 再運行了,。相反,,它將等待某個活動線程處理完當前操作并檢查是否有別的包正在該端口上等待。如果有的話,,該線程只是簡單的抓獲該包然后處理,。在這個過程 中,沒有上下文切換,,CPU得到最大限度的利用,。

下圖1顯示了一個完成端口操作流程的高度圖解??蛻粽埱髮е乱粋€I/O包(IRP)被排隊到完成端口,。操作系統(tǒng)允許不超過并發(fā)量上限(即上面提到的那個并發(fā)值)的多個線程并發(fā)地處理客戶端請求。直到一些活動線程因I/O請求而阻塞,,等待線程才能被激活,。下面我們將做進一步的探討。

 

    

創(chuàng)建完成端口需要調(diào)用Windows API CreateIoCompletionPort

HANDLE CreateIoCompletionPort(
  HANDLE FileHandle,
  HANDLE ExistingCompletionPort,
  DWORD CompletionKey,
  DWORD NumberOfConcurrentThreads
);

創(chuàng)建一個完成端口時,,通常對參數(shù)ExistingCompletionPort賦值NULL,, NumberOfConcurrentThreads參數(shù)定義了在完成端口上同時允許執(zhí)行的線程數(shù)量。如果有文件句柄傳遞給FileHandle參數(shù),,則該文件與完成端口關聯(lián)在了一起,。當這個文件上的I/O請求完成時,一個完成通知包將被投遞到完成端口消息隊列中,。另外一個API GetQueuedCompletionStatus是用來獲取排隊完成狀態(tài),,它使調(diào)用線程掛起,直到收到一個完成通知包,。

BOOL GetQueuedCompletionStatus(
  HANDLE CompletionPort,
  LPDWORD lpNumberOfBytesTransferred,
  LPDWORD CompletionKey,
  LPOVERLAPPED* lpOverlapped,
  DWORD dwMiillisecondTimeout
);

完成端口實際上是在管理一個線程池,,它會記錄當前活動(即沒有被I/O等事件阻塞)的線程數(shù)。當有完成通知包到達該端口時,,在該端口上等待的線程按照后進先出(LIFO)的次序被喚醒,,因此最近(most recently)被阻塞的線程就是獲得下一個完成通知包的線程。那些長時間得不到響應的的線程的堆棧將會被從內(nèi)存調(diào)到磁盤交換區(qū)去等待,,當與一個端口關聯(lián)的線程太多超過了當前的處理能力時,,就可以將長時間阻塞的線程占用的內(nèi)存減到最少。

服務器應用程序往往通過網(wǎng)絡端點來接受客戶請求,,而這些網(wǎng)絡端點是由文件句柄來表示的,。這樣的例子包括Windows Sockets 2(Winsock2)套接字或者命名管道。當服務器創(chuàng)建它的通信端點時,,它將這些通信端點與一個完成端口關聯(lián)起來,,并且它的線程通過調(diào)用GetQueuedCompletionStatus來等待此端口上進來的完成通知,。當一個線程在此完成端口上得到一個I/O完成通知包時,它便不再等待,,開始處理I/O結果數(shù)據(jù),,從而變成一個活動的線程。一個線程在處理過程中可能將阻塞很多次,,比如當它需要從磁盤上的文件讀取數(shù)據(jù)時,,或者當它需要與其他的線程同步時。Windows NT檢測到這些活動,,并且識別出該完成端口上至少已經(jīng)有一個活動線程,。因此,當活動線程由于I/O請求而阻塞時,,如果在隊列中存在一個包,,則喚醒另一個正在此完成端口上等待的線程提供處理服務。

微軟的指導原則是,,將并發(fā)值設置成大約等于該系統(tǒng)中處理器的數(shù)目,。但是要注意,一個完成端口上實際活動線程數(shù)量有可能超過設置的并發(fā)值,??紤]并發(fā)值被設置為1的情況,一個客戶請求進來了,,某個線程因為被調(diào)度來處理該請求而變成活動的,。下一個請求到達時,正在該端口上等待的另一個線程卻不允許執(zhí)行,,因為活動的線程數(shù)已經(jīng)達到了設置的并發(fā)上限值,。然后,當活動線程需要等待I/O而阻塞時,,等待的線程將被激活,,當它尚在活動時,上一個線程的I/O完成了,,這使得它繼續(xù)保持活動狀態(tài)(繼續(xù)執(zhí)行數(shù)據(jù)處理服務),。此刻,一直到兩個線程中有一個被阻塞,,并發(fā)值始終是2,,高于設置的并發(fā)上限值1。大多數(shù)時候,,活動線程數(shù)將維持在設置的并發(fā)限制值上,,或者超過一點。

應用程序通過調(diào)用PostQueuedCompletionStatus這個API向完成端口投遞一個自定義的完成通知包,。服務器一般通過該函數(shù)發(fā)送消息通知線程有外部事件發(fā)生,,例如需要溫和的關機,。

 

完成端口內(nèi)部機制

當傳遞NULL值給ExistingCompletionPort參數(shù)來調(diào)用CreateIoCompletionPort來創(chuàng)建完成端口時,將調(diào)用同名的NtCreateIoCompletion系統(tǒng)服務,。實質上,,IoCompletion對象是建立在一個稱為隊列的內(nèi)核同步對象基礎上。系統(tǒng)創(chuàng)建一個完成端口的同時,,在完成端口所分配到的內(nèi)存中初始化一個隊列對象(指向完成端口的指針同時指向了此隊列對象,,因為隊列對象位于完成端口對象內(nèi)存的開始處)。當一個線程調(diào)用CreateIoCompletionPort來創(chuàng)建完成端口時,,第四個參數(shù)NumberOfConcurrentThreads即為隊列的并發(fā)值。NtCreateIoCompletion函數(shù)將調(diào)用KeInitializeQueue系統(tǒng)服務來初始化該端口的消息隊列,。

當應用程序再次調(diào)用CreateIoCompletionPort時,,將調(diào)用NtSetInformationFile服務來使參數(shù)一(文件句柄)與參數(shù)二(一個已有的完成端口)關聯(lián)起來。完成通知包FileCompletionInformation包含的信息:CreateIoCompletionPort的參數(shù)二ExistingCompletionPort(已有的完成端口句柄)和參數(shù)三CompletionKey(完成鍵),。NtSetInformationFile通過解引用操作從該文件句柄獲得對應的文件對象,,并且申請一個記錄完成上下文的數(shù)據(jù)結構。這個數(shù)據(jù)結構在NTDDK.H定義如下:

typedef struct _IO_COMPLETION_CONTEXT {
  PVOID Port;
  ULONG Key;
} IO_COMPLETION_CONTEXT, *PIO_COMPLETION_CONTEXT;

最后,,將調(diào)用NtSetInformationFile系統(tǒng)服務設置文件對象中CompletionContext域的值,。當一個異步I/O在一個文件對象上完成時,系統(tǒng)內(nèi)部執(zhí)行具有I/O管理功能的IopCompleteRequest系統(tǒng)服務,,檢查文件對象中的CompletionContext域是否為非NULL,。如果是,則I/O管理器生成一個完成通知包,,通過調(diào)用KeInsertQueue系統(tǒng)服務將完成通知包投遞到完成端口隊列(注意,,完成端口對象和隊列對象是同義的)。

當一個服務器線程調(diào)用GetQueuedCompletionStatus時,,它將調(diào)用NtRemoveIoCompletion系統(tǒng)服務,。在驗證參數(shù)后,并且將完成端口句柄轉換成一個指向該端口的指針后,,NtRemoveIoCompletion調(diào)用KeRemoveQueue,。

正如你所看到的,KeRemoveQueueKeInsertQueue是完成端口模型的兩個引擎級函數(shù),,它們決定阻塞在完成端口上等待I/O完成通知包的線程什么時候被喚醒,。在系統(tǒng)內(nèi)部,隊列對象維護了完成端口上當前活動線程的計數(shù)值,,以及最大的并發(fā)活動線程的數(shù)量,。當一個線程調(diào)用KeRemoveQueue并且當前活動線程數(shù)大于或等于并發(fā)數(shù)上限時,那么該線程將被投放到一個阻塞線程隊列(按LIFO順序)中,,等待系統(tǒng)調(diào)度來獲取并處理完成通知包,。此線程列表掛在隊列對象的外面,,線程的控制塊數(shù)據(jù)結構中有一個指針引用了一個與之相關的隊列對象;如果這個指針為NULL,,則該線程沒有與隊列關聯(lián),。

Windows依賴與線程控制塊中的隊列指針來跟蹤和記錄那些“由于被阻塞在除了完成端口之外的其他事情上而變成不活動”的線程。那些有可能會導致一個線程阻塞的調(diào)度例程(例如KeWaitForSingleObject,,KeDelayExecutionThread等等)要檢查該線程的隊列指針,。如果該指針不為NULL,則這些函數(shù)調(diào)用KiActivateWaiterQueue一個與隊列相關的函數(shù),,它會遞減與該隊列相關聯(lián)的活動線程的計數(shù)值,。如果計數(shù)值遞減到小于設置的并發(fā)值,并且此時至少有一個完成通知包在該隊列中,,那么處于該隊列的線程列表最前面的那個線程被喚醒,,并且把最老的(the oldest)完成通知包交給它處理。相反,,無論何時,,與一個隊列相關聯(lián)的線程在阻塞之后被喚醒時,調(diào)度程序執(zhí)行KiUnwaitThread函數(shù)來增加該隊列上活動線程的計數(shù)值,。

最后,,PostQueuedCompletionStatus這個Windows API將調(diào)用NtSetIoCompletion服務。該函數(shù)只是簡單的調(diào)用KeInsertQueue將自定義的完成通知包插入到完成端口的隊列中,。

 

沒有公開的

     Windows NT的完成端口API提供了一種易于使用和高效的方法最大限度地發(fā)揮服務器的性能——最大限度的減少上下文切換的同時最大限度的提高系統(tǒng)并發(fā)量,。這些API使我們能夠調(diào)用I/O管理器和內(nèi)核提供的一些服務功能。隊列對象可以被設備驅動程序調(diào)用(這些接口盡管沒有公開,,但還是很容易查詢到的),,不過完成端口的API沒有提供相關訪問功能。但是,,如果隊列接口被繼承,,我們完全可以通過編寫隊列處理程序并通過手動設置CompletionContext的值來模擬完成端口模型。

 

原文《Inside I/O Completion Ports》

http://technet.microsoft.com/en-us/sysinternals/bb963891.aspx

    本站是提供個人知識管理的網(wǎng)絡存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導購買等信息,,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,,請點擊一鍵舉報,。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多