程式執行緒排程方式

查志強發表於2015-01-08

【原文:http://blog.chinaunix.net/uid-20476365-id-1942505.html

1.程式終止的方式:
(1) 主執行緒的入口函式返回(推薦方式);
(2) 程式主動退出:一個執行緒呼叫ExitProcess;
(3) 程式被動終止:另一個程式呼叫了TerminateProcess;例如通過工作管理員中結束程式。
(4) 程式中的所有執行緒都終止了。
 
 
 
2. 執行緒:
(1) 建立於一個程式的上下文中,在程式的地址空間中執行;
(2) 執行緒是動態的,一個程式至少要有一個執行緒;
(3) 程式中的所有執行緒共享程式的地址空間;
 
2.1 執行緒的組成部分:
(1) 一個核心態的物件,作業系統用來儲存執行緒的資訊;
(2) 執行緒堆疊,用來存放執行緒執行時的函式引數和區域性變數等。
 
2.2 執行緒的執行:
(1) 當一個程式初始化時,系統會建立這個程式的主執行緒;
(2) 這個執行緒最初執行C/C++執行時間庫的啟動程式碼,這些啟動程式碼會呼叫程式的入口函式(main,wmain,WinMain,wWinMain);
(3) GUI程式有一個主UI執行緒,處理訊息迴圈和使用者互動。如果這個執行緒被掛起或忙於處理任務,應用程式會失去相應;
(4) 工作執行緒沒有介面,在後臺工作,例如分析資料、資料庫操作等。
 
2.3 執行緒的終止:
(1) 執行緒函式返回(推薦方式);
(2) 執行緒呼叫ExitThread;
(3) 其他執行緒(可以是其他程式中的執行緒)呼叫TerminateThread;
(4) 該執行緒所在的程式被終止。
 
2.4 執行緒排程:下面的事件會觸發作業系統的執行緒排程(執行緒排程時,OS會執行上下文切換Context Switch,把執行權從當前執行緒轉移到下一個執行緒):
(1) 一個執行緒進入ready狀態,例如這個執行緒被建立或這個執行緒從等待狀態釋放;
(2) 一個執行緒離開執行狀態,例如這個執行緒的時間片結束了,或終止,或放棄執行(sleep),或進入等待狀態;
(3) 一個執行緒的優先順序改變;
(4) 一個執行緒的processor affinity改變。

 
2.5 Context Switch:
(1) 儲存當前執行緒的上下文(Context);
(2) 把當前執行緒放在同一優先順序的執行緒佇列的尾部;
(3) 找到最高優先順序的ready執行緒;
(4) 把這個執行緒從它所在的執行緒中移出,裝載它的上下文(Context),開始執行這個執行緒。
 
2.6 執行緒上下文的內容:
(1) Instruction pointer,記錄執行緒在被掛起前執行到的位置;
(2) 使用者和核心態堆疊指標;
(3) 指向這個執行緒所在的地址空間的指標(Page table diroctory)(e.g:當從程式A中的執行緒B切換到程式C中的執行緒D執行時,需要儲存該值)。
 
2.7 執行緒的優先順序:
Windows使用32個優先順序別,從0到31,數值越大優先順序越高。
(1) 程式priority class:Real-time,High,Above Normal,Normal,Below Normal,Idle;
(2) 執行緒relative thread priority:Time-critical,Highest,Above normal,Highest,Above normal,Normal,Below normal,Lowest,Idle;實際執行時,執行緒的優先順序是由所屬程式的prioriy class(參考級別)和relative thread priority(偏移級別)兩者結合起來來計算的,
(3) 一個系統級別(0),保留給zero page thread;
 
2.8 執行緒同步(下面的情況需要做執行緒同步):
(1) 多個執行緒訪問共享資源,在一個時間只有一個執行緒可以訪問共享資源;否則,可能出現的問題是應用程式崩潰;多CPU的情況下尤其要注意這一點;
(2) 一個執行緒需要通知另一個執行緒一項工作已經完成。
 
 
3 程式的虛擬地址空間:
在32位第系統上,程式的虛擬地址空間是4G;預設情況下,使用者態和核心態虛擬地址空間各佔2GB;各個程式的使用者態地址空間是相互獨立的,如下圖所示:

 
3.1 調節使用者態空間大小:
(1) Boot.ini的/3GB選項可以用來調節使用者態虛擬地址空間的大小,把使用者態的地址空間增加到3GB(Win2k,WinXP,Win2k3);
(2) Boot.ini的/USERVA可以指定使用者態地址空間為2GB和3GB之間的一個值(WinXP,Win2K3);
(3) 應用程式的二進位制檔案頭上要有/LARGEADDRESSAWARE標誌,才能訪問高於2GB的地址空間,否則將不能訪問。
 
3.2 使用者態虛擬地址空間的狀態:
(1) Free:這段地址自由,可被分配;
(2) Reserving:這段地址被預留,不會在分配做他用,沒有影射實體記憶體,不能訪問,只有被Commit之後再可以使用;
(3) Commintted:被影射到實體記憶體。
 
3.3 程式使用的記憶體:
(1) Private Bytes:程式的虛擬地址空間中已分配的記憶體(包括實體記憶體和虛擬記憶體Paging File中使用的記憶體的綜合),不包括核其他程式共享的記憶體(e.g:二進位制檔案dll和exe檔案的程式碼);
(2) Virtual Bytes:程式所使用的虛擬地址空間的大小,包括核其他程式共享的記憶體,例如共享動態連結庫;
(3) Working Set:程式所使用的實體記憶體的大小,包括核其他程式共享的記憶體。
 
3.4 記憶體對映檔案:
(1) 程式保留一段虛擬地址空間,把一個檔案中的一部分對映到這段地址空間;可以用這種方式來處理大檔案(size>4GB),使用記憶體對映檔案來處理大檔案,每次只把一部分檔案內容對映到程式的地址空間;
(2) 作業系統使用記憶體對映檔案來裝載.exe和.dll檔案;如果多個程式使用同一個dll檔案,實體記憶體裡只有一份拷貝;多個程式之間共享記憶體:多個程式對映到同一個檔案的用一部分。
 
3.5 實體記憶體和虛擬記憶體(paging file):
(1) paging file用來儲存程式正在使用的修改過的記憶體,例如當系統實體記憶體不足時或者這部分記憶體在很長一段時間內沒有被使用;
(2) 當這部分內容被訪問時,這些內容再被從paging file轉移到實體記憶體(缺頁處理過程)。
 
3.6 缺頁處理(page fault):
(1) 被訪問的頁面不在實體記憶體內,但是在硬碟檔案裡或paging file裡或記憶體對映檔案裡;
(2) 作業系統分配實體記憶體,把所需的訪問的內容從硬碟檔案或paging file或記憶體對映檔案裡讀到實體記憶體裡;
(3) 這個過程對應用程式時透明的。
 
3.7 記憶體訪問流程圖:

 
3.8 執行緒堆疊(stack):
堆疊是執行緒執行時用來儲存函式引數、區域性變數、函式返回地址等資料的一塊記憶體;系統給每個執行緒reserve一塊使用者態執行緒堆疊,並commit其中的一部分記憶體。預設情況下,reserve 1MB堆疊,並commit其中的2個page(2*4KB),如下圖所示(叢高地址向低地址方向寫):

 
3.9 堆(Heap):
分配小塊的記憶體;程式的預設堆(1MB);建立更多的堆(HeapCreate、HeapAlloc、HeapReAlloc);
 
3.10 程式使用記憶體的型別總結:
(1) 虛擬檔案:用於儲存大塊的陣列和資料結構;
(2) 記憶體對映檔案:用於對映可執行檔案、大檔案或多程式之間共享記憶體;
(3) 堆:主要用於大量的小塊記憶體,程式申請記憶體一般是從堆中分配,程式使用的記憶體大部分來自堆,堆實際上是作業系統的堆管理器從虛擬記憶體中分配的;
(4) 堆疊:堆疊儲存執行緒執行時的一些變數、暫存器資訊等,堆疊的空間有限,如果一個執行緒的堆疊用完而且不能再增長,應用程式會崩潰。
 
 
4 動態連結庫(DLL):
(1) DLL裡的內容:函式、資料結構、資源等;
(2) DLL不能直接執行,必須被裝載到一個程式中才能執行;
(3) DLL的image被對映到程式的地址空間,然後程式中的所有執行緒可以呼叫DLL中的函式。
 
4.1 DLL的入口函式:
    一個DLL可以指定一個入口函式(DllMain),系統在特定情況下會呼叫DLL的入口函式;下面的情況下系統呼叫DLL的入口函式:
(1) 程式裝載DLL(DLL_PROCESS_ATTACH);
(2) 程式解除安裝DLL(DLL_PROCESS_DETACH);
(3) DLL被裝載之後建立一個新執行緒(DLL_THREAD_ATTACH);
(4) DLL被裝在之後一個執行緒終止(DLL_THREAD_DETACH);
 
4.2 Load-time dynamic linking:
(1) 編譯器把可執行檔案所需的DLL的名字放在可執行檔案頭的Import部分中;
(2) 可執行檔案開始執行時作業系統檢測可執行檔案頭的Import部分,然後定位和裝載所需的Dll。
(3) 如果作業系統不能找到合適的DLL,會給出一個錯誤訊息對話方塊;
 
4.3 Run-time dymnamic linking:
(1) 應用程式執行之後呼叫LoadLibrary或LoadLibraryEx裝載DLL,作業系統定位並裝載DLL;
(2) 如果不能找到DLL或DLL入口函式,則返回false,LoadLibrary或LoadLibrary失敗;
(3) 用GetProcAddress獲得DLL的輸出函式地址,並使用這些函式。
 
4.4 DLL的搜尋順序:
登錄檔鍵:HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode決定DLL的搜尋順序(Windows Vista、Win2K3、WinXP+SP2中,該鍵值為1;在WinXP和Win2K+SP4 中,該鍵值為0):
(1) SafeDllSearchMode=1
    ->可執行檔案所在的目錄;
    ->系統目錄,%SystemRoot%system32;
    ->16位系統目錄;
    ->Windows目錄,%SystemRoot%
    ->程式的當前目錄;
    ->環境變數Path中的目錄;
(2) SafeDllSearchMode=0或Legacy Search Order:
    ->可執行檔案所在的目錄;
    ->程式的當前目錄;
    ->系統目錄,%SystemRoot%system32;
    ->16位系統目錄;
    ->Windows目錄,%SystemRoot%
    ->環境變數Path中的目錄;
 
4.5 Known DLLs
   Known DLLS是保證用LoadLibrary裝載系統DLL時只從特定的系統目錄裝載,防止裝載錯誤的系統DLL。Known DLLs列表儲存在登錄檔HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\KnownDLLs鍵值中;作業系統在裝載一個DLL時會檢查KnownDLLs登錄檔下是否有一樣的登錄檔鍵名,如果是,則裝載%SystemRoot%\System32目錄下對應登錄檔鍵值的DLL。
 
4.6 DLL Hook:
(1) 應用程式註冊hook過程,用於監視一些特定的事件,例如鍵盤按鍵、滑鼠移動、視窗啟用、最小化、最大化、對話方塊裡的輸入等;
(2) Hook DLL被裝載到其他程式裡,一旦遇到上述事件,Hook DLL會執行相應的Hook過程;
(3) 一些惡意軟體會用這種方法竊取使用者輸入的使用者名稱和密碼。
 
 
5 異常處理:
    應用程式執行時可能遇到異常,如果應用程式沒有處理這些異常,應用程式可能崩潰;常見的異常有訪問非法記憶體地址、執行緒堆疊溢位、遇到斷點(程式裡寫有斷點)、執行非法指令(執行緒堆疊被破壞)等。
 
5.1 未處理異常:
   如果執行緒遇到一個未處理異常,Windows unhandled exception filter會被呼叫;讀取HKLM\Software\Microsoft\WindowsNT\CurrentVersion\AeDebug下的登錄檔鍵值Auto和Debugger(如果Auto為1,則自動執行Debugger指定的除錯程式;預設的Debugger是drwtsn32,生成dump檔案);
   如果發生未處理異常,以及Auto是0和Debugger包含"Drwtsn32",系統會檢查HKLM\Software\Microsoft\PCHealth\ErrorReporing決定是否顯示報告錯誤對話方塊。

相關文章