windows下進程與線程剖析

來源:互聯網
上載者:User

標籤:fun   大小   重要   file   任務   共用   訪問   告訴   inux   

進程與線程的解析

進程:一個正在啟動並執行程式的執行個體,由兩部分組成:

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下進程與線程剖析

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.