《Windows核心编程》1-8章浅读笔记

第一部分 程序员必读

第一章     对程序错误的处理

当一个Windows函数检测到一个错误时,它会使用一个称为线程本地存储器的机制,将相应的错误代码号码与调用的线程关联起来。当函数返回时,它的返回值就能指明一个错误已经发生。若要确定这是个什么错误,使用GetLastError函数。

使用VOID SetLastError( DWORD dwErrCode ); 函数传递自己认为合适的任何32位值,在线程上设置一个LastError

第二章     字符和字符串处理

UTFUnicode Transformation Format

Windows Vista使用的是UTF-16,除此之外,还有UTF-8以及UTF-32

自从Windows NT起,Windows所有版本都完全用Unicode来构建。

第三章     内核对象

内核对象是一组可以被内核创建、识别和操作的数据结构的总称。操作系统为了管理资源而定义和实现的一组内部数据,这些数据只能被内核创建和修改。

由于内核对象的数据结构只能由内核访问,我们不能直接更改这些结构,所以我们只能利用Windows提供的一组函数,以经过良好定义的方式来操纵这些结构。调用一个会创建内核对象的函数后,函数会返回一个句柄,它标识了创建的对象,句柄可由进程中的任何线程使用。

虽然许多应用程序都不需要关心安全性,但许多Windows函数都要求你传入必要的安全访问信息。以前版本的Windows程序之所以在Vista上不能正常工作,就是因为在实现这些程序时没有充分考虑安全性。

要判断一个对象是不是内核对象,最简单的方式是查看创建这个对象的函数,几乎所有创建内核对象的函数都有一个允许你指定安全属性信息的参数。

进程共享内核对象:对象句柄继承;为对象命名;复制对象句柄。

对象句柄继承:若bInheritHandle设为TRUE,子进程就会继承父进程的可继承的句柄的值。
      
改变句柄的标志:SetHandleInformation,改变内核对象句柄的标志。

为对象命名:许多内核对象的创建函数都要求传入一个PCTSTR pszName参数,这就是内核对象的名称。之后,使用Create***函数或者Open***函数,就可以共享内核对象。
       Terminal Services
(终端服务)命名空间:在正在运行Terminal Services的计算机中,有多个用于内核对象的命名空间。其中一个是全局命名空间,所有客户端都能访问的内核对象要放在这个命名空间中。此外,每个客户端会话都有一个自己的命名空间。采用这个安排之后,可以避免正在运行同一个应用程序的两个或多个会话彼此干扰,一个会话不能访问另一个会话的对象,即使对象的名称相同。
       private
命名空间:可供你在其中创建内核对象的一个目录

复制对象句柄:DuplicateHandle函数获取一个进程的句柄表中的一个记录项,然后在另一个进程的句柄表中创建这个记录项的一个拷贝。

第二部分 完成编程任务

第四章     进程

进程:一个正在运行的程序的一个实例,由内核对象(操作对象用它来管理进程,系统也用它来保存进程统计信息),地址空间(包含所有执行体)组成。

进程要做任何事情,都必须让一个线程在它的上下文中运行。该线程要执行进程地址空间包含的代码。
一个进程可以有多个线程,所有线程都在进程的地址空间中执行代码。
每个线程都有它自己的一组CPU寄存器和它自己的对战。
每个进程至少要有一个线程执行进程地址空间包含的代码。
一个进程创建的时候,系统会自动创建它的第一个线程,这成为主线程。然后,这个线程再创建更多的线程,后者再创建更多的线程。

操作系统会轮流为每个线程调度一些CPU时间,它会采取round-robin方式,为每个线程都分配时间片,从而营造出所有线程都在并发运行的假象。

多核CPU中,操作系统会采用更复杂的算法为线程分配CPU时间。操作系统负责线程的所有管理和调度任务,而程序员不必在自己的代码中做任何特别的事情,即可享受到多处理器系统带来的好处。不过,为了更好的利用这些CPU,你需要在应用程序的算法中多做一些文章。

C/C++启动函数用途简单总结:
      
获取指向新进程的完整命令行的一个指针;
      
获取指向新进程的环境变量的一个指针;
      
初始化C/C++运行库的全局变量;
      
初始化C运行库内存分配函数(malloccalloc)和其他低级I/O例程使用的堆(heap);
      
调用所有全局和静态C++类对象的构造函数。

WinMainhInstanceExe参数的实际值是一个基内存地址,在这个位置,系统将执行体文件的映像加载到进程的地址空间中。
为了知道一个EXEDLL文件被加载到进程地址空间的什么位置,可以使用GetMouduleHandle函数返回一个句柄。该函数有两大重要特性:只检查主调进程的地址空间;调用该函数并向其传递NULL值,会返回进程的地址空间中EXE文件的基地址。


一个线程可以调用以下两个函数来获取和设置其进程的当前驱动器和目录:
DWORD Get CurrentDirectory(…);
BOOL SetCurrentDirectory(…);
目录或文件名称最大字符数MAX_PATH260

CreateProcess创建一个进程。
一个线程调用该函数时,系统创建一个进程内核对象,其初始使用计数为1。进程内核对象不是进程本身, 操作系统用来管理这个进程的一个小型数据结构。

终止进程最好的方式,就是主线程的入口函数返回。

第五章     作业

Windows提供了一个作业(job)内核对象,它允许你将进程组合在一起并创建一个“沙箱”来限制进程能够做什么。可以将作业看做进程的容器。

第六章     线程基础

线程由内核对象(操作系统用它来管理线程,以及存放线程统计信息)和线程堆栈(用于维护线程执行时所需的所有函数参数和局部变量)组成。

每个线程都必须有一个入口函数,这是线程执行的起点。主线程的入口函数为_tmain_tWinMain。如果想在进程中创建辅助线程,它必须有自己的入口函数:
       DWORD WINAPI ( PVOID pvParam )

如果想创建一个或多个辅助线程,只需让一个正在运行的线程调用CreateThread函数。
HANDLE CreateThread( PSECURITY_ATTRIBUTES psa,
                     DWORD cbStackSize,
                     PTHREAD_START_ROUTINE pfnStartAddr,
                     PVOID pvParam,
                     DWORD dwCreateFlags,
                     PDWORD pdwThreadID ) ;
参数:
    psa
:指向SECURITY_ATTRIBUTES结构的一个指针,安全属性。
       cbStackSize
:指定线程可以为其线程堆栈使用多少地址空间。
       pfnStartAddr
:新线程执行的线程函数的地址。
       pvParam
:线程函数的pvParam与最初传给CreateThread函数的pvParam是一样的。通过这个参数,可以将一个初始值(数值或数据结构指针等)传给线程函数。
       dwCreateFlags
:控制线程创建的额外标志。0为创建后马上调用,CREATE_SUSPENDED为创建并初始化后暂停该线程的运行。
       pdwThreadID
:该值必须是DWORD的一个有效地址(NULL代表你对线程ID没兴趣),CreateThread函数用它来存储系统分配给新线程的ID


 

第七章 线程调度、优先级和关联性

每个线程都有一个上下文(CONTEXT),其被保存在线程的内核对象中,反映了线程上一次执行时CPU寄存器的状态。

线程调度:
大约每隔20毫秒(GetSystemTimeAdjustment第一个参数的返回值),Windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的值载入CPU寄存器。
线程执行代码,并在进程的地址空间中操作数据。
又过了大约20毫秒,WindowsCPU寄存器存回线程的上下文,线程不再运行。
系统再次检查剩下的可调度线程内核对象,选择另一个线程的内核对象,将该线程的上下文载入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访问数据,就可以完全避免高速缓存行的问题了。

GetLogicalProcessorInformation函数可以得知CPU高速缓存行的大小。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值