第一部分 程序员必读
第一章 对程序错误的处理
当一个Windows函数检测到一个错误时,它会使用一个称为线程本地存储器的机制,将相应的错误代码号码与调用的线程关联起来。当函数返回时,它的返回值就能指明一个错误已经发生。若要确定这是个什么错误,使用GetLastError函数。
使用VOID SetLastError( DWORD dwErrCode ); 函数传递自己认为合适的任何32位值,在线程上设置一个LastError。
第二章 字符和字符串处理
UTF:Unicode Transformation Format。
Windows Vista使用的是UTF-16,除此之外,还有UTF-8以及UTF-32。
自从Windows NT起,Windows所有版本都完全用Unicode来构建。
第三章 内核对象
内核对象是一组可以被内核创建、识别和操作的数据结构的总称。操作系统为了管理资源而定义和实现的一组内部数据,这些数据只能被内核创建和修改。
由于内核对象的数据结构只能由内核访问,我们不能直接更改这些结构,所以我们只能利用Windows提供的一组函数,以经过良好定义的方式来操纵这些结构。调用一个会创建内核对象的函数后,函数会返回一个句柄,它标识了创建的对象,句柄可由进程中的任何线程使用。
虽然许多应用程序都不需要关心安全性,但许多Windows函数都要求你传入必要的安全访问信息。以前版本的Windows程序之所以在Vista上不能正常工作,就是因为在实现这些程序时没有充分考虑安全性。
要判断一个对象是不是内核对象,最简单的方式是查看创建这个对象的函数,几乎所有创建内核对象的函数都有一个允许你指定安全属性信息的参数。
进程共享内核对象:对象句柄继承;为对象命名;复制对象句柄。
对象句柄继承:若bInheritHandle设为TRUE,子进程就会继承父进程的可继承的句柄的值。
为对象命名:许多内核对象的创建函数都要求传入一个PCTSTR pszName参数,这就是内核对象的名称。之后,使用Create***函数或者Open***函数,就可以共享内核对象。
复制对象句柄:DuplicateHandle函数获取一个进程的句柄表中的一个记录项,然后在另一个进程的句柄表中创建这个记录项的一个拷贝。
第二部分 完成编程任务
第四章 进程
进程:一个正在运行的程序的一个实例,由内核对象(操作对象用它来管理进程,系统也用它来保存进程统计信息),地址空间(包含所有执行体)组成。
进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程要执行进程地址空间包含的代码。
一个进程可以有多个线程,所有线程都在进程的地址空间中执行代码。
每个线程都有它自己的一组CPU寄存器和它自己的对战。
每个进程至少要有一个线程执行进程地址空间包含的代码。
一个进程创建的时候,系统会自动创建它的第一个线程,这成为主线程。然后,这个线程再创建更多的线程,后者再创建更多的线程。
操作系统会轮流为每个线程调度一些CPU时间,它会采取round-robin方式,为每个线程都分配时间片,从而营造出所有线程都在并发运行的假象。
多核CPU中,操作系统会采用更复杂的算法为线程分配CPU时间。操作系统负责线程的所有管理和调度任务,而程序员不必在自己的代码中做任何特别的事情,即可享受到多处理器系统带来的好处。不过,为了更好的利用这些CPU,你需要在应用程序的算法中多做一些文章。
C/C++启动函数用途简单总结:
WinMain的hInstanceExe参数的实际值是一个基内存地址,在这个位置,系统将执行体文件的映像加载到进程的地址空间中。
为了知道一个EXE或DLL文件被加载到进程地址空间的什么位置,可以使用GetMouduleHandle函数返回一个句柄。该函数有两大重要特性:只检查主调进程的地址空间;调用该函数并向其传递NULL值,会返回进程的地址空间中EXE文件的基地址。
一个线程可以调用以下两个函数来获取和设置其进程的当前驱动器和目录:
DWORD Get CurrentDirectory(…);
BOOL SetCurrentDirectory(…);
目录或文件名称最大字符数MAX_PATH为260。
CreateProcess创建一个进程。
一个线程调用该函数时,系统创建一个进程内核对象,其初始使用计数为1。进程内核对象不是进程本身, 操作系统用来管理这个进程的一个小型数据结构。
终止进程最好的方式,就是主线程的入口函数返回。
第五章 作业
Windows提供了一个作业(job)内核对象,它允许你将进程组合在一起并创建一个“沙箱”来限制进程能够做什么。可以将作业看做进程的容器。
第六章 线程基础
线程由内核对象(操作系统用它来管理线程,以及存放线程统计信息)和线程堆栈(用于维护线程执行时所需的所有函数参数和局部变量)组成。
每个线程都必须有一个入口函数,这是线程执行的起点。主线程的入口函数为_tmain或_tWinMain。如果想在进程中创建辅助线程,它必须有自己的入口函数:
如果想创建一个或多个辅助线程,只需让一个正在运行的线程调用CreateThread函数。
HANDLE CreateThread( PSECURITY_ATTRIBUTES psa,
参数:
第七章 线程调度、优先级和关联性
每个线程都有一个上下文(CONTEXT),其被保存在线程的内核对象中,反映了线程上一次执行时CPU寄存器的状态。
线程调度:
大约每隔20毫秒(GetSystemTimeAdjustment第一个参数的返回值),Windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的值载入CPU寄存器。
线程执行代码,并在进程的地址空间中操作数据。
又过了大约20毫秒,Windows将CPU寄存器存回线程的上下文,线程不再运行。
系统再次检查剩下的可调度线程内核对象,选择另一个线程的内核对象,将该线程的上下文载入CPU寄存器,然后继续。
(载入线程上下文、让线程运行、保存上下文并重复的操作,从系统启动到系统结束)
线程挂起和恢复:
使用CreateThread函数创建线程或CreateProcess创建进程时,系统会创建线程/主线程内核对象,并将其中的暂停计数(suspend count)置为1,这样CPU调度程序会认为该内核对象不可调度,线程得以完成其初始化。
初始化完成后,CreateThread/CreateProcess函数检查调用者是否传递了CREATE_SUSPENDED标志,若是,CreateThread/CreateProcess函数返回,线程依然处于挂起状态,否则,函数将调整线程内核对象的暂停计数值为0,此时该对象变为可调度对象(除非它在等待某事件的发生)。
Windows中不存在挂起和恢复进程的概念,系统从来不会给进程调度CPU时间。
线程使用Sleep函数告诉系统,在一段时间内自己不需要调度了。
超线程:在一颗CPU同时执行多个程序而共同分享一颗CPU内的资源。
每个进程都被赋予0-31(递增)的优先级数,当系统确定给哪个线程分配CPU时,它会首先查看优先级为31的线程,并循环调度。只要有优先级为31的线程可供调度,系统就不会给优先级0-30的线程分配CPU,这种情况称为饥饿。
看起来,这样设计的系统里,低优先级的线程好像永远没有机会运行。但是,任何时刻系统中大多数线程都是不可调度的。例如,进程的主线程调用了GetMessage,而系统看到并没有消息等待处理,它就会暂停这个线程,取消这个线程当前时间片的剩余时间,并立即将CPU分配给另一个等待中的线程。
无论较低优先级的线程是否正在执行,较高优先级的线程会强占较低优先级的线程。
系统启动时,将创建一个名为页面清零线程的特殊线程。这个线程的优先级为0(整个系统中唯一一个优先级为0的线程。页面清零线程负责在没有其他进程需要执行的时候,将系统内存中所有闲置页面清零。
第八章 用户模式下的线程同步
类似InterLockedExchangeAdd ( PLONG , LONG )的InterLocked系列函数能够保证操作是原子操作,不会被打断。
InterLocked系列函数在x86平台上使用时,会在总线上维持一个硬件信号,这个信号会阻止其他CPU访问同一个内存地址。
只让一个线程访问数据,或者只让一个CPU访问数据,就可以完全避免高速缓存行的问题了。
GetLogicalProcessorInfor