Windows APC機制 & 可警告alertable的線程等待狀態

摘要:Windows APC的全稱爲(asynchronous procedure call)翻譯爲中文即「異步過程調用」。《Windows APC機制(一)》、《談談對APC的一點理解》、《線程的Alertable與User APC》主要閱讀了這三篇文章,對APC有了個大概瞭解:網絡

1) APCs容許用戶程序和系統元件在一個進程的地址空間內某個線程的上下文中執行代碼。異步

2) I/O管理器使用APCs來完成一個線程發起的異步的I/O操做。例如:當一個設備驅動調用IoCompleteRequest來通知I/O管理器,它已經結束處理一個異步I/O請求時,I/O管理器排隊一個APC到發起請求的線程。而後線程在一個較低IRQL級別,來執行APC。 APC的做用是從系統空間拷貝I/O操做結果和狀態信息到線程虛擬內存空間的一個緩衝中。async

3) 使用APC能夠獲得或者設置一個線程的上下文和掛起線程的執行。函數

 

談到APC,不可避免的牽涉到QueueUserAPC函數——「QueueUserAPC函數把一個APC對象加入到指定線程的APC隊列中。」從函數名稱,也應該能推測到一個線程其實有兩個APC隊列:用戶APC、系統APC。.net

 

Windows APC函數是被按照先進先出(FIFO)順序放置在一個隊列Queue上面的。同時,用戶APC函數極爲特別,它只有在線程處於「可警告alertable的線程等待狀態」時才能被線程調用。可是,線程一旦開始調用APC函數,就會一次性將全部APC隊列上的函數所有執行完畢。線程

 

那麼,什麼是可警告alertable的線程等待狀態?其實就是線程暫時沒有重要的事情要作,就叫作這個狀態。APC函數通常不會去幹擾(中斷)線程的運行,從上文中知道,一個線程附帶着兩個APC隊列(用戶APC、系統APC),也就至關於這兩個隊列的APC函數都是由「線程自己」來儲備調用的(APC函數就至關於奧運會比賽上的預備選手),只有當線程處於「可警告的線程等待狀態」纔會去調用APC函數(比賽時只有主將沒法上場時,預備選手纔會出現)。翻譯

 

如何衡量線程此時是否有重要的事情要作?對於用戶模式下,能夠調用函數SleepEx、SignalObjectAndWait、WaitForSingleObjectEx、WaitForMultipleObjectsEx、MsgWaitForMultipleObjectsEx均可以使目標線程處於alertable等待狀態(無重要事情要作),從而讓用戶模式APCs執行,緣由是這些函數最終都是調用了內核中的KeWaitForSingleObject,KeWaitForMultipleObjects,KeWaitForMutexObject,KeDelayExecutionThread等函數。可是這裏須要注意的是線程執行Sleep(10)函數時,並非「可警告alertable的線程等待狀態」。想象一個應用場景:客戶端程序每隔5分鐘就和服務端進行一次通訊,實現「心跳」,最簡單的就是使用Sleep(5*60*1000)。那麼這樣一來,這5分鐘內,線程就沉睡了,若是這個時候有比較緊急的網絡IO事件發生怎麼辦呢?線程還在沉睡中,由於5分鐘時間還未到,因此沒法及時處理這些事件。如何解決這個問題呢?那就是使用SleepEx替換Sleep。這個函數比起Sleep就多了一個參數Alertable,表示該線程是「可喚醒的」,就是說,線程雖然等待時間未到,但若是發生一些事件,線程也會及時去處理。這些事件就是:IO完成例程須要執行或者線程有APC須要交付。對象

 

 

對於上述說明,抽取其中的SleepEx做爲例子簡單介紹:blog

SleepEx(
_In_ DWORD dwMilliseconds,
_In_ BOOL bAlertable
);隊列

dwMilliseconds:等待時間,以毫秒爲單位。若是該值爲INFINITE值,則表示無限等待下去;

bAlertable:函數返回方式。若是爲FALSE,除非該函數調用超時,不然該函數不返回。在此期間若是IO完成了回調,完成例程也不會被執行。若是爲TRUE,當該函數調用超時或者IO完成回調時,該函數都會返回——當調用超時時,該函數返回WAIT_OBJECT_0(亦即0);若是返回IO完成回調才返回的話,則返回值爲WAIT_IO_COMPLETION。

 

接下來,舉個APC的實例:

在實例中須要注意三處:①若是APC函數在線程啓動前就已經注入了,那麼線程將會在啓動前——將全部已經注入的APC函數所有執行完畢,才真正執行線程體;②main函數中之因此要使用Sleep(1),是爲了讓線程跑起來之後再執行APC函數。不然,若是全部APC函數都執行完畢了線程才真正跑起來,這時候進入SleepEx無限等待中,而沒有APC例程去觸發它。線程將會卡死在SleepEx處。③當線程退出後,再加入APC函數將不會被執行,由於線程體都已經銷燬了。

 

    #include <stdio.h>
    #include <Windows.h>
    #include <process.h>//_beginthreadex
     
    VOID NTAPI before_thread_running(ULONG_PTR param)
    {
        printf("apc1.\n");
    }
     
    VOID NTAPI thread_running(ULONG_PTR param)
    {
        printf("apc2.\n");
    }
     
    VOID NTAPI after_thread_running(ULONG_PTR param)
    {
        printf("apc3.\n");
    }
     
    unsigned int __stdcall sub_thread(void* context)
    {
        printf("sub_thread begin.\n");
        DWORD ret = SleepEx(INFINITE, TRUE);
        switch (ret)
        {
        case WAIT_OBJECT_0: //Time Out
            printf("WAIT_OBJECT_0\n");
            break;
        case WAIT_IO_COMPLETION: //IO完成,強制退出SleepEx
            printf("WAIT_IO_COMPLETION\n");
            break;
        default:
            break;
        }
        printf("sub_thread end.\n");
        return 0;
    }
     
    int main(int argc, char* argv[])
    {
        HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, sub_thread, NULL, 0, NULL);
        QueueUserAPC(before_thread_running, hThread, NULL);//APC queue(FIFO)
        Sleep(1);
        QueueUserAPC(thread_running, hThread, NULL);
        Sleep(1);
        QueueUserAPC(after_thread_running, hThread, NULL);
        WaitForSingleObject(hThread, INFINITE);//wait sub_thread
        CloseHandle(hThread);
        return 0;
    }

 

apc1.
sub_thread begin.
apc2.
WAIT_IO_COMPLETION
sub_thread end.

 

@qingdujun

 

2017-7-28 in Xi'An ———————————————— 版權聲明:本文爲CSDN博主「qingdujun」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。 原文連接:https://blog.csdn.net/qingdujun/article/details/76223282

相關文章
相關標籤/搜索