Windows核心编程【6】小结

Windows核心编程【20】小结 第20章 DLL高级技术 一、DLL模块的显示载入和符号链接 在任何时候,进程中的一个线程可以调用下面两个函数来将一个DLL映射到进程的地址空间中: HMODULE LoadLibrary(PCTSTR pszDLLPathName); HMODULE LoadLibraryEx(PCTSTR pszDLLPathName, HANDLE hFile, DWORD dwFlags) 阅读详情

第六章 线程基础


1、每个进程至少都有一个线程。
2、线程的两个组件:一个是线程的内核对象,操作系统用它管理线程;另一个是线程堆栈,用于维护线程执行时所需的所有函数参数和局部变量。
3、进程是有惰性的,它从来不执行任何东西,只是一个线程的容器。
4、线程只有一个内核对象和一个堆栈,几乎不涉及记录活动,不需要占用多少内存。


一、何时创建线程

1、线程描述了进程内部的一个执行路径。每次初始化进程时,系统都会创建一个主线程。对于MS的C/C++编译器生成的应用程序,该线程会先执行C/C++运行库的启动代码,启动代码调用入口函数,继续执行,直至入口函数返回C/C++运行库的启动代码,后者最终调用ExitProcess。对许多应用程序来说,这个主线程是应用程序唯一需要的线程。
2、忽略省电和发热问题,让CPU空闲着是没有任何道理的。
3、将一个应用程序设计成多线程的,可以使该应用程序更易于扩展。


二、何时不应该创建线程

1、通常应用程序应该有一个用户界面线程,此线程负责创建所有窗口,另外还有一个GetMessage循环。进程中的其它所有线程都是受计算机制约或者I/O限制的工作线程,这些线程永远不会创建窗口。另外,用户界面线程的优先级通常高于工作线程。这样用户界面才能灵敏地响应用户的操作。
2、Windows资源管理器就为每个文件夹的窗口创建了一个独立的线程。这样一来,就可以把文件从一个文件夹复制到另一个文件夹,同时仍然可以查看系统上的其它文件夹。另外,如果Windows资源管理器出现一个bug,正在操作的一个文件夹的线程就可能崩溃。
3、应该合理使用多线程,不要因为一件事情是你能做的就非要去做。


三、编写第一个线程函数

1、每个线程都必须有一个入口函数,这是线程执行的起点。主线程的入口函数:_tmain或_tWinMain。如果想在进程中创建辅助线程,它必须有自己的入口函数,形式如下,DWORD WINAPI ThreadFunc(PVOID pvParam)。
2、线程终止运行,其堆栈的内存也会被释放,线程内核对象的使用计数也会递减。如果使用计数变为0,线程内核对象会被销毁。类似于进程内核对象。
3、主线程的入口函数默认必定命名为main,wmain,WinMain或wWinMain(有一种情况除外,也就是使用/ENTRY:链接器选项来指定另一个函数作为入口函数)。而线程函数可以任意命名,不过需要指定不同的名称,否则编译器/链接器会认为你创建了一个函数的多个链接。
4、由于向主线程传递了字符串参数,所以可以使用入口函数的ANSI/Unicode版本。相反,向线程函数传递的是一个意义由我们而非操作系统来定义的参数。因为,不必担心ANSI/Unicode问题。
5、线程函数必须返回一个值,它会成为该线程的退出代码。
6、线程函数应该尽可能使用函数参数和局部变量。使用静态和全局变量时,多个线程可以同时访问这些变量,这样可能会破坏变量中保存的内容。


四、CreateThread函数

1、调用CreateThread函数
HANDLE WINAPI CreateThread(
  __in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in       SIZE_T dwStackSize,
  __in       LPTHREAD_START_ROUTINE lpStartAddress,
  __in_opt   LPVOID lpParameter,
  __in       DWORD dwCreationFlags,
  __out_opt  LPDWORD lpThreadId
);
2、调用CreateThread时,系统会创建一个线程内核对象,并将进程地址空间的内存分配给线程堆栈使用。新线程在与负责创建的那个线程相同的进程上下文中运行。因此,新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同一个进程中其他所有线程的堆栈。这样一来,同一个进程中的多个线程就可以很容易地相互通信。
3、lpThreadAttributes参数指向SECURITY_ATTRIBUTES结构,这个在第三章介绍了。
4、dwStackSize参数指定线程可以为其线程堆栈使用多少地址空间。每个线程都拥有自己的堆栈,当CreateProcess函数开始一个进程的时候,它会在内部调用CreateThread来初始化进程的主线程。对于dwStackSize参数,CreateProcess使用了保存在可执行文本内部的一个值。可以使用链接器/STACK开关来控制这个值,如下所示:
/STACK:[reserve] [,commit]
reserve参数用于设置系统将为线程堆栈预留多少地址空间,默认是1MB。commit参数指定最初应为堆栈的保留区域提交多少物理存储空间,默认是1页。随着线程中的代码开始执行,需要的存储空间可能不止1页。如果线程溢出它的堆栈,会产生异常。系统将捕获这种异常,并将另一个页(或者为commit参数指定的任何大小)提交给保留空间。这样一来,线程堆栈就可以根据需要动态地增大了。调用CreateThread时,如果传入非0值,函数会保留并提交(commit)线程堆栈的所有存储。如果两者都指定了,则取其中较大的那一个。
5、lpStartAddress参数指定希望新线程执行的线程函数的地址。lpParameter参数与最初传给CreateThread函数的lpParameter参数是一样的。通过这个参数,可以讲一个初始值传给线程函数。这个初始值可以是一个数值,也可以是指向一个数据结构(其中包含额外的信息)的指针。创建多个线程时,可以让他们使用同一个函数地址作为起点。这样做完全合法,而且非常有用。
6、由于Windows是一个抢占式的多线程系统(preemptive multithreading system),意味着新的线程和调用CreateThread函数的线程可以同时执行。所以可能出现传过去的lpParameter在原函数中被销毁,而新创建的线程尝试引用产生访问冲突。解决这个问题的一种方法是将变量声明为一个静态变量,是编译器能在应用程序的数据section(而不是线程堆栈)中为变量创建一个存储区域。但是,这会使函数不可重入。换言之,不能创建两个线程来执行同一个函数,因为这两个线程将共享同一个静态变量。为了解决这个问题,可以使用线程同步技术(八九章将讲述到)
7、dwCreationFlags参数指定额外的标志来控制线程的创建。他可以是三个值之一(第五版原书当时为两种)。见MSDN http://msdn.microsoft.com/en-us/library/windows/desktop/ms682453(v=vs.85).aspx


五、终止运行线程

1、线程函数返回,强烈推荐的方式。这是保证线程的所有资源都被正确清理的唯一方式。线程函数中创建的所有C++对象都通过其析构函数被正确销毁;操作系统正确释放线程堆栈使用的内存;操作系统把线程的退出代码(在线程的内核对象中维护)设为线程函数的返回值;系统递减线程的内核对象的使用计数。
2、ExitThread函数,该函数将终止线程的运行,并导致操作系统清理该线程使用的所有操作系统资源。但是,我们的C/C++资源(如C++类对象)不会被销毁。如果是要写C/C++代码,就绝对不要调用ExitThread,应该使用C++运行库函数endthreadex。
3、TerminateThread函数,不同于ExitThread杀死主线程,TerminateThread能杀死任何线程。是异步的,会告诉系统要终止线程,但是在函数返回的时候,线程不能保存已经终止。一个设计良好的应用程序绝不会使用这个函数,因为被终止运行的线程收不到它被杀死的通知。线程无法正确清理,而且不能阻止自己被终止运行。同时要注意的是,使用TerminateThread,除非拥有此线程的进程终止运行,否则系统不会销毁这个线程的堆栈。MS故意以这种方式来实现TerminateThread。否则假如其他还在运行的线程要引用被杀死的那个线程的堆栈上的值,就会引起访问冲突。让被杀死的线程的堆栈保留在内存中,其他的线程就可以继续正常运行。
4、当进程终止运行时,第四章介绍的ExitProcess和TerminateProcess函数,相当于为剩下的每个线程都调用了TerminateThread。
5、当线程终止运行时,线程的退出代码从STILL_ACTIVE变为传给ExitThread或TerminateThread的代码;线程内核对象的状态变为signaled;如果线程是进程中的最后一个活动线程,系统认为进程也终止了;线程内核对象的使用计数递减1。


六、线程内幕

1、对CreateThread函数的一个调用导致系统创建一个线程内核对象。该对象最初的使用计数为2,暂停计数被设为1,退出代码被设为STILL_ACTIVE(0x103),而且对象被设为nonsignaled状态。如下图:


2、每个线程都有其自己的一组CPU寄存器,称为线程的上下文(context)。上下文反映了当线程上次执行时,线程的CPU寄存器的状态。线程的CPU寄存器全部保存在一个CONTEXT结构(在WinNT.h)。CONTEXT结构本身保存在线程内核对象中。
3、指令指针和栈指针寄存器是线程上下文中最重要的两个寄存器。这两个地址都标识了“拥有线程的那个进程”的地址空间的内存。当线程的内核对象被初始化的时候,CONTEXT结构的堆栈指针寄存器被设为pfnStartAddr在线程堆栈中的地址。而指令指针寄存器被设为RtlUserThreadStart函数。
4、线程完全初始化好之后,系统将检查CREATE_SUSPENDED标志是否传给CreateThread函数。如果此标志没有传递,系统将线程的暂停计数递减至0;随后,线程就可以调度给一个处理器去执行。
5、新线程执行RtlUserThreadStart函数的时候,将发生以下事情。围绕线程函数,会设置一个结构化异常处理(SEH)帧。这样一来,线程执行期间所产生的任何异常都能得到系统的默认处理。系统调用我们的线程函数,将传给CreateThread函数的lpParameter参数传给它。线程函数返回时,RtlUserThreadStart调用ExitThread。如果线程产生了一个未被处理的异常,RtlUserThreadStart函数所设置的SEH帧会处理这个异常。通常意味着向用户显示一个消息框,而且当用户关闭此消息框时,RtlUserThreadStart会调用ExitProcess来终止整个进程,而不只是终止有问题的线程。
6、在RtlUserThreadStart内,线程会调用ExitThread或者ExitProcess,这意味着线程永远不能退出此函数;它始终在其内部消亡。同时,RtlUserThreadStart函数是不允许返回的,因为线程堆栈上没有函数返回地址,所以几乎肯定会引起访问冲突。主线程永远不会返回到RtlUserThreadStart函数,因为在之前C/C++运行时启动代码会调用ExitProcess。


七、C/C++运行库注意事项

Windows核心编程【10】小结 第十章 同步设备I/O与异步设备I/O 1、本章介绍的技术可以用来设计高性能、伸缩性好、响应性好,而且健壮的应用。一个伸缩性好的应用程序在处理大量并发操作时和处理少数并发操作时同样高效。 2、在Windows应用程序中,线程是最好的工具,可以用来对工作进行划分。 3、通过I/O完成端口,可以让线程在读取设备和写入设备的时候不必等待设备的响应,从而显著地提高吞吐量。 4、I/O完成端口最初被 阅读详情

相关推荐

PEB及PEB_LDR_DATA结构

PEB结构如下:(比MSDN上详细多了http://msdn.microsoft.com/en-us/library/windows/desktop/aa813706(v=vs.85).aspx) 这个进程环境块就不罗嗦了,直接贴代码。 typedef struct _PEB { // Size: 0x1D8 /*000*/ UCHAR InheritedAddressSpace; /*001

BetaBin 1万+

Windows内核开发

对话框是一种很特殊的窗口,体现在消息的处理上。

weixin_42284219的博客 1447

Windows核心编程》源代码深度解析与实践

本文还有配套的精品资源,点击获取 简介:《Windows核心编程》这本书由Jeffrey Richter撰写,深入讲解了Windows操作系统的内部机制和编程技术。书中详细阐述了Windows编程核心概念、API和系统服务,旨在提升开发者构建高效、稳定、安全Windows应用的能力。配套光盘源代码提供了一个实践理论知识的重要平台,通过分析这些代码,读者可以加深对线程管理、...

weixin_36122351的博客 1435

窗口的动画显示,蛮好用的

用AnimateWindow函数可以实现多种窗口的多种动态效果 先对该函数做一个简单介绍。 函数原型:BOOL AnimateWindow(HWND hWnd,DWORD dwTime,DWORD dwFlags)。 函数功能: 该函数能在显示与隐藏窗口时产生两种特殊类型的动画效果,即滚动动画...

chuolvyi4659的博客 310

Windows内核开发初步

环境配置并不复杂,只需要按照微软的要求装好VS2019和WDK,就可以在创建新项目中找到Driver。如果用的是Win11,可参考此文:驱动开发Windows11+VS2019环境配置 打开VS->创建新项目->Empty WDM Driver->创建,并删除初始项目Driver Files里面的.inf文件。 接下来新建一个wd.cpp文件,代码为 #include<ntddk.h> void SampleUnload(_In_ PDRIVER_OBJECT DriverO

微小冷的学习笔记 2454

windows内核开发(一)环境搭建

因为大部分 Windows驱动程序都是内核程序,所以这个总结,不区分“驱动编程”与“内核编程” 。同时,也不区分“内核模块”与“驱动程序” 。这两个词汇编译出的.sys 可执行文件。专注于较通用的内核程序的开发,并不介绍针对某种类硬件的,比如声卡、显卡、USB 等的等各种驱动程序的开发。        参考资料:       《寒江独钓:Windows内核编程与信息安全》      《天书夜读-从

明夕何夕0x00 2303

学习《windows核心编程小结

这三周里基本上每天都有4~6小时学习windows核心编程这本上书,就这个系列学习过程,小结一下情况。因为之前的工作内容有多线程相关内容,并且那个时候参考《windows多线程程序设计》已经作了系统的学习,这里就没提那方面的内容;内存映射文件在以前解析数据文件的时候使用过,虽然没有进行过系统学习,翻看了一下应该不是太大的问题;动态连接库已经经过系统的学习,PE文件结构也比较清楚了。至于堆的内容,需

行者无疆的专栏 1862

Windows核心编程【9】小结

第九章 用内核对象进行线程同步 1、在用户模式下进行线程同步的最大好处就是速度非常快。如果关心应用程序的兴宁,那么应该先看看用户模式下的同步机制能否适用。 2、但是用户模式下的同步机制存在一些局限性。例如,对Interlocked系列函数只能对一个值进行操作,它们从来不会把线程切换到等待状态。我们可以用关键段来把线程切换到等待状态,但是它们只能用来对同一个进程中的线程进行同步。此外,在

BetaBin 2804

Windows核心编程【22】小结

第22章 DLL注入和API拦截 在Windows中,每个进程都有自己私有的地址空间。独立的地址空间对开发人员和用户都是非常有利的。对开发人员来说,系统更有可能捕获错误的内存读/写。对用户来说,OS变得更加健壮,因为一个应用程序的错误不会导致其他应用程序或OS崩溃。 APP需要跨越进程边界来访问另一个进程的地址空间的情况如下: 1、想要从另一个进程创建的窗口派生子类窗口。

BetaBin 1939

Windows核心编程【4】小结

第四章进程   一、基本知识 1、  进程的定义,一个正在运行的程序的一个实例。由两部分组成,内核对象(系统保存进程统计信息的地方,用来管理进程)和地址空间(进程的所有执行体、数据段、堆栈等)。 2、  Windows有2大类应用程序。CUI(控制台用户界面)和GUI(图形用户界面),划分界限模糊。其中,关联到了VS集成环境链接器的各种开关。 3、  启动函数在进行一系列初始化(获取新进

BetaBin 3495

Windows核心编程【2】小结

第二章 字符和字符串处理 一、字符编码 1、常规C、C++语言学习中习惯单字节(8个bits,256个字符)ANSI字符编码。 2、双字节字符集(double-byte character,DBCS),一个字符串中的每个字符都由1个或2个字节组成。以日本汉字为例,如果第一个字符在0x81到0x9f之间,或者在0xe0到0xfc之间,就必须检查下一个字节,才能判断出一个完整的汉字。这样

BetaBin 1331

Windows核心编程【1】小结

第一章 错误处理 1、调用Windows函数时,它会先验证你传给它的参数,然后再开始执行任务。如果传入的参数无效,或者由于其他原因导致操作无法执行,则函数的返回值将支出函数在某个方面失败了。 2、每种错误都有三种表示:一个消息ID(一个可在源代码中使用的宏,用于与GetLastError的返回值进行比较)、消息文本(描述错误的英文文本)和一个编号(应该避免使用此编号,尽量使用消

BetaBin 1340

window核心编程小结

1.C r e a t e T h r e a d函数是用来创建线程的Wi n d o w s函数。不过,如果你正在编写C / C + +代码,决不应该调用C r e a t e T h r e a d。相反,应该使用Visual C++运行期库函数_ b e g i n t h r e a d e x.原因比较简单,操作系统并不知道每种语言对堆栈空间的管理,调用CPP的_ b e g i n t

游戏人生的专栏 3613

什么是Windows内核编程

什么是Windows内核编程?上述文字由《天书夜读:从汇编语言到Windows内核编程》作者  谭文  提供    Linux的内核编程大家都是比较熟悉的。而Windows内核编程则不大为一般读者所熟悉。常常有这样的问题:    “你又没有Windows的代码,你如何搞内核编程?”    “除了微软的人,难道还有人做Windows内核吗?”    “Windows内核编程有用吗?”    其实Wi

博文视点(北京)官方博客 8354

WINDOWS核心编程——DLL基础和实操

与将所有的指令通过链接器在生成exe的时候都链接在一起并安排好各个指令的位置不同,windows还提供了动态链接(DLL)技术。一般情况下我们会处于以下原因采用DLL技术:1.扩展了应用程序的特性。DLL可以被动态地载入到进程的地址空间中。2.简化了项目管理,可以让不同的开发团队管理不同的模块。3.有助于节省内存。一个dll可被多个程序共享。多个程序调用同一个dll内的同一个函数时,系统却只需将该...

pokeyode的专栏 9366

解决问题:开启Wireshark之NPF驱动问题

关于新装系统之后,由于我们一系列的开机优化,很多服务都被停掉了,NPE也是一样。 针对以上问题(The NPF driver isn't running...),解决方案如下: (首先确认自己先安装了Winpcap) 命令行运行命令“net start npf”即可,当然,这是Windows Vista之前的。Windows Vista和Windows 7的用户需要自己进入到系统盘:C:\W

BetaBin 1万+
上一篇: ~Windows核心编程【5】小结
下一篇: Windows编程【7】小结
BetaBin
博客等级 码龄15年 155粉丝 121原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值