windows线程与进程的关系

来源:互联网 发布:mac必备软件 知乎 编辑:程序博客网 时间:2024/06/03 23:40
线程与进程的关系


一般将进程定义成一个正在运行的程序的一个实例,它由以下两部分构成。


一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方。 
一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。


  进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程负责执行进程地址空间包含的代码。事实上,一个进程可以有多个线程,所有线程都在进程的地 址空间中“同时”执行代码。为此,每个线程都有它自己的一组CPU寄存器和它自己的堆栈。每个进程至少要有一个线程来执行进程地址空间包含的代码。当系统 创建一个进程的时候,会自动为进程创建第一个线程,这称为主线程。然后,这个线程再创建更多的线程,后者再创建更多的线程。。。如果没有线程要执行进程地 址空间包含的代码,进程就失去了继续存在的理由。这时,系统会自动销毁进程及其地址空间。






线程也有两个组成部分:


一个是线程的内核对象,操作系统用它管理线程。系统还用内核对象来存放线程统计信息的地方。 
一个线程栈,用于维护线程执行时所需的所有函数参数和局部变量。


进程从来不执行任何东西,它只是一个线程的容器。线程必然是在某个进程的上下文中创建的,而且会在这个进程内部“终其一生”。这意味着线程要在其进程的地址 空间内执行代码和处理数据。所以,假如一个进程上下文中有两个以上的线程运行,这些线程将共享同一个地址空间。这些线程可以执行同样的代码,可以处理相同 的数据。此外,这些线程还共享内核对象句柄,因为句柄表是针对每一个进程的,而不是针对每一个线程。


对于所有要运行的线程,操作系统会轮流为每个线程调度一些CPU时间。它会采取循环(轮询或轮流)方式,为每个线程都分配时间片(称为“量程”),从而营造出所有线程都在“并发”运行的假象。  


每个线程都有一个上下文,后者保存在线程的内核对象中。这个上下文反映了线程上一次执行时CPU寄存器的状态。大约每隔20ms,Windows都会查看 所有当前存在的线程内核对象。在这些对象中,只有一些被认为是可调度的。Windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的 值载入CPU寄存器。这一操作被称为上下文切换。线程执行代码,并在进程的地址空间中操作数据。又过了大约20ms,Windows将CPU寄存器存回线 程的上下文,线程不再运行。系统再次检查剩下的可调度线程内核对象,选择另一个线程的内核对象,将该线程的上下文载入CPU寄存器,然后继续。载入线程上 下文、让线程运行、保存上下文并重复的操作在系统启动的时候就开始,然后这样的操作会不断重复,直至系统关闭。


线程的创建


CreateThread(


  LPSECURITY_ATTRIBUTES lpsa,


  DWORD cbStack,


  LPTHREAD_START_ROUTINE lpStartAddr,


  LPVOID lpvThreadParam,


  DWORD fdwCreate,


  LPDWORD );


其中参数lpStartAddr 是指定希望新线程执行线程函数的地址。


lpvThreadParam 参数是线程函数的参数。


线程函数可以执行我们希望他执行的任何任务,函数原型类似于:


DWORD WINAPI ThreadFunc(PVOID 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


)

0 0
原创粉丝点击