《Windows核心编程》读书心得——线程(线程基础)(4)

来源:互联网 发布:网络抽奖合法吗 编辑:程序博客网 时间:2024/06/05 23:24

 

线程的组成:

线程内核对象和线程堆栈(系统从进程的地址空间中分配内存给线程栈使用,因为线程没有自己的地址空间)。

线程还有自己的一组CPU寄存器。


线程1M的栈空间是从进程栈空间中分配的,每个线程占用不同的一块栈空间。如下图:




 

线程的创建:

CreateThread()的参数如下:

psa内核对象的安全属性(传入NULL,表示使用默认安全属性)。

cbStackSize指定线程栈可使用多少地址空间。

线程栈的空间大小,除了通过cbStackSize来设置,也可以通过链接器的/STACK开关来设置。格式如下:

/STACK [reserve] [,commit]

reserve:线程栈的内存空间(默认为1MB);

commit:线程栈的物理存储器大小(默认为1个页面)。

链接器会把该参数嵌入到.exe.dllPE头文件中。

cbStackSize参数设置为0,系统将根据/STACK来设置默认的栈空间大小;

cbStackSize参数为非0值,分配栈空间的时候,按照cbStatckSize/STACK中比较大的值来分配

pfnStartAddr线程函数的地址;

pvParam线程开始执行时,将该参数传给线程函数(给线程函数传一个初始值);

dwCreateFlags

值为0:线程创建后立即调度;

值为CREATE_SUSPENDED:线程创建后挂起(可以在线程函数执行之前,更改线程属性,再唤醒它);

pdwThreadID线程ID,线程创建成功,将ID传给该参数(一般传入NULL,表示不想知道线程的ID)。

 

线程的终止:

ExitThread()、TerminateThread()函数。

若调用ExitThread来终止线程,该线程的线程栈会被销毁;

若调用TerminateThread来终止线程,只有当创建该线程的进程退出时,线程栈才被销毁。

线程终止时,将执行以下操作:

(1)      释放属于该线程的用户对象句柄(线程有两个用户对象:窗口和挂钩);

(2)      设置线程退出代码;

(3)      线程内核对象变为触发状态;

(4)      如果它是进程的最后一个活动线程,那么进程也随之终止;

(5)      线程内核对象计数减1

 

线程的CPU寄存器:

又称线程上下文(context),上下文反映了当线程上一次执行时,线程的CPU寄存器状态。(线程的CPU寄存器,保存在一个CONTEXT结构中,而CONTEXT结构本身保存在线程的内核对象中)。

上下文中最重要的两个寄存器:指令指针寄存器、栈指针寄存器。

 

RtlUserThreadStart()函数:

原型:VOID  RtlUserThreadStartPTHREAD_START_ROUTINE pfnStartAddr PVOID pvParam);

线程内核对象初始化的时候,会调用该函数:

CONTEXT结构的堆栈指针寄存器被设定为pfnStartAddr在线程堆栈中的地址。

指令指针寄存器设定为RtlUserThreadStart函数的地址。

RtlUserThreadStart函数就是线程的启动函数,执行过程类似于进程的启动函数。

pvParam参数:就是CreateThread()函数传入的那个参数;

如果线程出现一个未处理异常,将会弹出消息框,并最终通过ExitProcess来终止整个进程,而不只是终止本线程。

 

_beginthreadex()函数:

每个线程有自己专用的_tiddata内存块,是从C/C++运行库堆栈上分配的。线程函数的地址保存在_tiddata

_beginthreadex函数内部会调用CreateThread函数。

(建议使用_beginthreadex函数,而不用CreateThread来创建线程)

 

_endthreadex()函数:

获取_tiddata内存块地址,并将其释放;

调用ExitThread销毁线程。

(不鼓励使用_endthreadex函数来终止线程,但至少比ExitThread要好点。因为ExitThread不释放_tiddata内存)

 

伪句柄:

所谓“真正的句柄”,是指能明确、无歧义地标示一个线程的句柄。但有些函数返回的是伪句柄(如GetCurrentProcessGetCurrentThread)。

伪句柄有以下特点:

伪句柄指向的对象会根据调用者的不同而改变。因此,不能从一个调用者中获取伪句柄,然后传给另一个调用者调用,因为这将导致两个调用者调用的并不是同一个对象。

调用伪句柄,并不会增加内核对象的使用计数。调用CloseHandle()函数并不会关闭内核对象,只是简单的忽略,并返回FALSE

传递伪句柄的方法是:将该句柄复制,再传递(用DuplicateHandle)。

 

原创粉丝点击