1. 程式人生 > >windows下進程與線程剖析

windows下進程與線程剖析

存在 mfc 調用 核心 style 內核對象 tin 內存 dst

進程與線程的解析

進程:一個正在運行的程序的實例,由兩部分組成

1.一個內核對象,操作系統用它來管理進程。內核對象也是系統保存進程統計信息的地方。
2.一個地址空間,其中包含所有可執行文件或DLL模塊的代碼和數據。此外,它還包含動態內存分配,比如線程堆棧和堆的分配。
   進程要做任何事情,都必須讓一個線程在它的上下文中運行。該線程負責執行進程地址空間包含的代碼。事實上,一個進程可以有多個線程,所有線程都在進程的地 址空間中“同時”執行代碼。為此,每個線程都有它自己的一組CPU寄存器和它自己的堆棧。每個進程至少要有一個線程來執行進程地址空間包含的代碼。當系統 創建一個進程的時候,會自動為進程創建第一個線程,這稱為主線程。然後,這個線程再創建更多的線程,後者再創建更多的線程。。。如果沒有線程要執行進程地 址空間包含的代碼,進程就失去了繼續存在的理由。這時,系統會自動銷毀進程及其地址空間。


線程也有兩個部分組成:
一個是線程的內核對象,操作系統用它管理線程。系統還用內核對象來存放線程統計信息的地方。
一個線程棧線程棧默認大小為1M,用於維護線程執行時所需的所有函數參數和局部變量

內核對象又包括:1.計數器

        2.掛起計數器(初始值為0,每掛起一個進程,該計數器加一,每恢復一個進程,該計數器減一,且它的值只可以是非負整數)

        3.信號

進程從來不執行任何東西,它只是一個線程的容器。線程必然是在某個進程的上下文中創建的,而且會在這個進程內部“終其一生”。這意味著線程要在其進程的地址 空間內執行代碼和處理數據。所以,假如一個進程上下文中有兩個以上的線程運行,這些線程將共享同一個地址空間。這些線程可以執行同樣的代碼,可以處理相同 的數據。此外,這些線程還共享內核對象句柄,因為句柄表是針對每一個進程的,而不是針對每一個線程。



對於所有要運行的線程,操作系統會輪流為每個線程調度一些CPU時間。它會采取循環(輪詢或輪流)方式,為每個線程都分配時間片(稱為“量程”),從而營造出所有線程都在“並發”運行的假象。  

每個線程都有一個上下文,後者保存在線程的內核對象中。這個上下文反映了線程上一次執行時CPU寄存器的狀態。大約每隔20ms,Windows都會查看 所有當前存在的線程內核對象。在這些對象中,只有一些被認為是可調度的。Windows在可調度的線程內核對象中選擇一個,並將上次保存在線程上下文中的 值載入CPU寄存器。這一操作被稱為上下文切換。線程執行代碼,並在進程的地址空間中操作數據。又過了大約20ms,Windows將CPU寄存器存回線 程的上下文,線程不再運行。系統再次檢查剩下的可調度線程內核對象,選擇另一個線程的內核對象,將該線程的上下文載入CPU寄存器,然後繼續。載入線程上 下文、讓線程運行、保存上下文並重復的操作在系統啟動的時候就開始,然後這樣的操作會不斷重復,直至系統關閉。

創建進程是用來占空間的,真正幹活的是線程
線程的創建:

用MFC寫一個小例子來介紹一下線程的創建:工作線程??

首先我們先讓進度條跑一下

1 while(1)
2     {
3         m_process.StepIt();
4         Sleep(100);//為了讓進度條更明顯,可以睡一會兒,作用是讓出時間片
5         //因為cpu是輪換時間片的,代表cpu到該線程時它放棄本次時間片,讓cpu先給別人分配任務
6         //註意windows中的sleep的單位是毫秒,而linux中的是秒
7     }

技術分享圖片

我們會發現在進度條跑的時候窗口是不可以移動的,因為現在進程裏幹活的只有這一個線程,它在一段時間內只能幹一件事,為了讓跑進度條的同時也可以移動窗口,這就需要我們再創建一根線程


CreateThread(
LPSECURITY_ATTRIBUTES lpsa,


DWORD cbStack,


LPTHREAD_START_ROUTINE lpStartAddr,


LPVOID lpvThreadParam,


DWORD fdwCreate,


LPDWORD );


其中參數lpStartAddr 是指定希望新線程執行線程函數的地址。


lpvThreadParam 參數是線程函數的參數。


線程函數可以執行我們希望他執行的任何任務,函數原型類似於:


DWORD WINAPI ThreadFunc(LPVOID pvParam) {


DWORD dwResult;

return (dwResult);


}






調用CreateThread 時,系統會創建一個線程內核對象。這個線程內核對象不是線程本身,而是一個較小的數據結構,操作系統用這個結構來管理線程。


系統將進程地址空間的內存分配給線程堆棧使用。新線程在與負責創建的那個線程相同的進程上下文中運行。因此,新線程可以訪問進程內核對象的所有句柄、進程中的所有內存以及同一個進程中其他所有線程的堆棧。這樣一來,同一個進程中的多個進程可以很容易地互相通信。




線程可以通過以下4種方法來終止運行。


1.線程函數返回(這是強烈推薦的)。


2.線程通過調用ExitThread函數“殺死”自己(要避免使用這種方法)。


3.同一個進程或另一個進程中的線程調用TerminateThread函數(要避免使用這種方法)。


4.包含線程的進程終止運行(這種方法避免使用)。





Ps:TerminateThread函數是異步的。也就是說,它告訴系統你想終止線程,但在函數返回時,並不保證線程已經終止了。如果需要確定線程已終止運行,還需要調用WaitForSingleObject或類似的函數,並向其傳遞線程的句柄。






線程的初始化


《Windows核心編程》學習筆記(11)– 線程的創建 - fly - 天嗎荇箜




1.對CreateThread函數的一個調用導致系統創建一個線程內核對象。該對象最初的使用計數為2。


(除非線程終止,而且從CreateThread返回的句柄關閉, 否則線程內核對象不會被銷毀。);





2.暫停計數被設為1;


(因為線程的初始化需要時間,我們當然不希望在線程準備好之前就執行它。)





3.退出代碼被設為STILL_ACTIVE (0x103);


(線程終止運行的時候,線程退出代碼從STILL_ACTIVE (0x103)變成傳給ExitThread 或TerminateThread 的代碼);





4.對象被設為nonsignaled(未觸發)狀態。





5.系統分配內存,供線程堆棧使用。然後系統將兩個值寫入新線程堆棧的最上端。寫入線程堆棧的第一個值是傳給


CreateThread函數的pvParam參數的值。緊接在它下方的是傳給CreateThread函數的pfnStartAddr值。


  棧:windows中棧的大小是固定的,棧底在高地址,數據入棧從高地址開始放。





6. 每個線程都有其自己的一組CPU寄存器,稱為線程的上下文(context)。上下文反映了當線程上一 次執行時,線程的


CPU寄存器的狀態。線程的CPU寄存器全部保存在一個CONTEXT結構(在 WinNT.h頭文件中定義)。CONTEXT結構


本身保存在線程內核對象中。





7.指令指針和棧指針寄存器是線程上下文中最重要的兩個寄存器。當線程的內核對象被初始化的時候,CONTEXT結構的堆棧指針寄存器被設為pfnStartAddr(線程執行函數的地址)在線程堆棧中的地址。而指令指針寄存器被設為RtlUserThreadStart函數(線程真正從這裏開始執行)的 地址,此函數是NTDLL.dll模塊導出的。





8.線程完全初始化好之後,系統將檢查CREATE_SUSPENDED標誌是否傳給CreateThread函數。如果此標記沒有傳遞,系統將線程的暫停計數遞增至0;隨後,線程就可以調度給一個處理器去執行。然後,系統在實際的CPU寄存器中加載上一次在線程上下文中保存的值。現在,線程可以在其進程的地址空間中執行代碼並處理數據了。





偽句柄的轉換


HANDLE GetCurrentProcess();


HANDLE GetCurrentThread();





這兩個函數都返回到主調線程的進程或線程內核對象的一個偽句柄(pseudohandle )。它們不會在主調進程的句柄表中新建句柄。而且,調用這兩個函數,不會影響進程或線程內核對象的使用計數。如果調用CloseHandle,將一個偽句柄作為參數傳入,CloseHandle只是簡單地忽略此調 用,並返回FALSE。在這種情況下,GetLastError將返回ERROR_INVALID_HANDLE。


將偽句柄轉換為真正的句柄



有時或許需要一個真正的線程句柄,而不是一個偽句柄。所謂“真正的句柄”,指的是能明確、無歧義地標識一個線程的句柄。來仔細分析下面的代碼:





DWORD WINAPI ParentThread(PVOID pvParam) {


  HANDLE hThreadParent = GetCurrentThread();


  CreateThread(NULL, 0, ChildThread, (PVOID) hThreadParent, 0, NULL);


  // Function continues...


}


DWORD WINAPI ChildThread(PVOID pvParam) {


  HANDLE hThreadParent = (HANDLE) pvParam;


  FILETIME ftCreationTime, ftExitTime, ftKernelTime, ftUserTime;


  GetThreadTimes(hThreadParent,


  &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);


  // Function continues...


}





能看出這個代碼段的問題嗎?其意圖是讓父線程向子線程傳遞一個可以標識父線程的句柄。但是,父線程傳遞的是一個偽句柄,


而不是一個真正的句柄。子線程開始執行時,它把這個偽句柄傳給GetThreadTimes函數,這將導致子線程得到的是它自己的CPU


計時數據,而不是父線程的。之所以會發生這種情況,是因為線程的偽句柄是一個指向當前線程的句柄;換言之,


指向的是發出函數調用的那個線程。





為了修正這段代碼,必須將偽句柄轉換為一個真正的句柄。DuplicateHandle函數可以執行這個轉換:


BOOL DuplicateHandle(


HANDLE hSourceProcess,


HANDLE hSource,


HANDLE hTargetProcess,


PHANDLE phTarget,


DWORD dwDesiredAccess,


BOOL bInheritHandle,


DWORD dwOptions);





正常情況下,利用這個函數,你可以根據與進程A相關的一個內核對象句柄來創建一個新句柄,並讓它同進程B相關。但是,我們可以采取一種特殊的方式來使用它,以糾正前面的那個代碼段的錯誤。糾正過後的代碼如下:





DWORD WINAPI ParentThread(PVOID pvParam) {


HANDLE hThreadParent;


DuplicateHandle(


GetCurrentProcess(), // Handle of process that thread pseudohandle is relative to


GetCurrentThread(), // 父偽句柄


GetCurrentProcess(), // Handle of process that the new, real, thread handle is relative to


&hThreadParent, // Will receive the new, real, handle identifying the parent thread


0, // Ignored due to DUPLICATE_SAME_ACCESS


FALSE, // New thread handle is not inheritable


DUPLICATE_SAME_ACCESS); // New thread handle has same access as pseudohandle





CreateThread(NULL, 0, ChildThread, (PVOID) hThreadParent, 0, NULL);


// Function continues...


}





DWORD WINAPI ChildThread(PVOID pvParam) {


HANDLE hThreadParent = (HANDLE) pvParam;


FILETIME ftCreationTime, ftExitTime, ftKernelTime, ftUserTime;


GetThreadTimes(hThreadParent, &ftCreationTime, &ftExitTime, &ftKernelTime, &ftUserTime);


CloseHandle(hThreadParent);


// Function continues...


}





現在,當父線程執行時,它會把標識父線程的有歧義的偽句柄轉換為一個新的、真正的句柄,後者明確、無歧義地標識了父線程。然後,它將這個真正的句柄傳給CreateThread。當子線程開始執行時,其pvParam參數就會包含這個真正的線程句柄。在調用任何函數時,只要傳入這個句 柄,影響的就將是父線程,而非子線程。 因為DuplicateHandle遞增了指定內核對象的使用計數,所以在用完復制的對象句柄後,有必要 把目標句柄傳給CloseHandle,以遞減對象的使用計數。前面的代碼體現了這一點。調用 GetThreadTimes之後,子線程緊接著調用CloseHandle來遞減父線程對象的使用計數。在這段代 碼中,我假設子線程不會用這個句柄調用其他任何函數。如果還要在調用其他函數時傳入父線程的句柄,那麽只有在子線程完全不需要此句柄的時候,才能調用CloseHandle。





還要強調一點,DuplicateHandle函數同樣可用於把進程的偽句柄轉換為真正的進程句柄,如下所示:


HANDLE hProcess;


DuplicateHandle(


GetCurrentProcess(), // Handle of process that the process pseudohandle is relative to


GetCurrentProcess(), // Process‘ pseudohandle


GetCurrentProcess(),


&hProcess,


0,


FALSE,


DUPLICATE_SAME_ACCESS


)

http://blog.csdn.net/hubinbin595959/article/details/47083019

windows下進程與線程剖析