Windows编程基本手册.docx_第1页
Windows编程基本手册.docx_第2页
Windows编程基本手册.docx_第3页
Windows编程基本手册.docx_第4页
Windows编程基本手册.docx_第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

6.3 注意点1.路径分隔符是反斜杠,但是在CreateFile等其他低级的API中正斜杠也可以用,最好避免造成不兼容性,目录和文件名大小不敏感,但是大小写保持,路径名最大为MAX_PATH 260长,但可以通过转义字符指定非常长的名称,如加上 ?及使用Unicode字符避开这个限制,可以长达32K个字符的名称2. Unicode 字符集,使用#define _UNICODE 必须在前语句给出,默认使用的是8位字符,L使用的是16位字符,_T使用通用文本字符,包含tchar.h。1.在所有的头文件之前加入#define UNICODE 和#define _UNICODE #include 2.tchar.h中的通用C库中没有memchr_fgettc,_itot替代itoa_stprintf替代sprintf_tcscpy替代strcpy_ttoi,_totupper,_totlower,以及_ftprintf(输出到文件)3.系统保留CONIN$和CONOUT$为控制台的输入输出,或直接使用GetStdHandle()4.CopyFile(lpExistingName,lpNewFileName,fFailifExists)如果已经有使用新名称的文件存在,那么只有在fFailIfExits等于FALSE 时这个文件才会被替换,CopyFile也复制文件的元数据,比如创建时间5.MoveFile如果新文件已经存在的话,一个进程在一个时间中只能有一个控制台6.利用va_list实现可变参数va_list arg_ptr:定义一个指向个数可变的参数列表指针;va_start(arg_ptr, argN):使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数,说明:argN是位于第一个可选参数之前的固定参数, (或者说,最后一个固定参数;之前的一个参数),函数参数列表中参数在内存中的顺序与函数声明时的顺序是一致的。 如果有一va函数的声明是void va_test(char a, char b, char c, ), 则它的固定参数依次是a,b,c,最后一个固定参数argN为c,因此就是va_start(arg_ptr, c)。va_arg(arg_ptr, type):返回参数列表中指针arg_ptr所指的参数,返回类型为type,并使指针arg_ptr指向参数列表中下一个参数。va_copy(dest, src):dest,src的类型都是va_list,va_copy()用于复制参数列表指针,将dest初始化为src。 va_end(arg_ptr):清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用va_start()、va_copy()恢复arg_ptr。每次调用va_start() / va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() va_end()之内。3. 控制台输入CONIN$ CONOUT$4. 进程之间共享数据的最好方法是使用内存映射5. ExitProcess或ExitThread无法保证正确的清除对象,在入口函数中调用ExitThread只会使主线程停止,如果进程中还有其他线程运行,进程不会终止6. TerminateProcess函数,这种方式终止不会得到任何有关终止运行的通知,终止之前不会将内存中它所拥有的任何东西写回磁盘。该函数是异步执行的,它会告诉系统需要某进程运行终止,但是函数返回时,无法保证这个进程已经终止。如果要确切知道是否已经终止运行要调用WaitForSingleObject函数。7. 所有线程终止,会将进程的退出代码设为最后一个终止运行的线程的终止代码。进程内核对象的存在时间可能大大超过进程的存在时间,即使进程已经终止运行,这些信息也可能还是有用的,可以通过GetExitCodeProcess获得目前已经销毁的进程退出代码如果调用这个函数时进程尚未终止,返回一个STILL_ACTIVE标识符,否则返回数据退出代码值。值得注意的是调用CloseHandle会让系统停止维护进程的统计数据。8. 如果以二进制方式打开,文本方式写,文件的末尾写入会出错,如果以文本方式打开,二进制方式写,文件只能读入一部分的内容6.4进程进程是由一个私有的虚拟地址空间,即进程可以使用的一组虚拟内存地址一个可执行程序,定义了初始的代码和数据,映射到进程的虚拟地址空间中一个已打开句柄的列表,指向各种系统资源,比如信号量,通信端口和文件,该进程中的所有线程都可访问这些系统资源一个被称为访问令牌的安全环境,标识了与该进程关联的用户,安全组和特权一个被称为进程ID的惟一标识符(在内部被称为客户ID)至少一个执行线程 对线程句柄的关闭并不会终止线程,CloseHandle函数只是删除对CreateProcess进行调用的进程内的该线程的引用 ,Windows不维护进程中的父子关系.父子进程可能使用不同的文件指针来访问相同的文件,关闭子进程的句柄不能销毁子进程,只销毁父进程对子进程的访问权.1. 复制句柄,计数代表引用一个对象的不同句柄数量,但它对应用程序不可用,在最后一个句柄被关闭而且引用计数变成零之前,对象不能被销毁.2. 使用GetProcessTimes或GetThreadTimes获得运行时间6.5线程最基本的部件一组代表处理器状态的CPU寄存器的内容两个栈,一个用于当线程在内核模式下执行的时候,另一个用于线程在用户模式下执行的时候一个被称为线程局部存储区(TLS,thread-local storage)的私有存储区域,各个子系统,运行库和DLL都会用到该存储区域一个被称为线程ID的惟一标识符(在内部也被称为客户ID-进程ID和线程ID是在同一个名字空间中生成的,所以它们永远不会重叠)线程也有自已的安全环境,如果多线程服务器应用程序要模仿其客户的安全环境,则往往可以利用线程的安全环境.易失的寄存器,栈和私有存储区域合起来被称为线程的环境(context),因为这些信息随着Windows所在机器的体系结构不同而有所不同,这些数据结构必须是与底层体系结构相关的,通过GetThreadContext可以访问这一信息一个进程中的线程不可能直接引用另一个进程的地址空间,除非第二个进程将它的一部分私有地址空间变成共享内存区(文件映射对象)第一个进程有权打开第二个进程,可以使用ReadProcessMemory和WriteProcessMemory等跨进程的内存函数在默认情况下,线程没有自已访问令牌,也可以包含一个访问令牌,因此单独的线程可以模仿另一个进程的安全环境包括远程Windows系统上运行的进程,而不会影响当前进程中的其他线程6.6虚拟内存对于映射非常大的数据库,在32位系统上,Windows提供了地址窗口扩展(AWE,Address Windowing Extension)的机制,使得32位应用程序可以申请多达64GB物理内存,然后将内存视图或者窗口映射到它的2GB虚拟地址空间中6.7内核模式和用户模式内核模式的操作系统和设备驱动程序共享同一个虚拟地址空间,一旦进入内核模式,操作系统和设备驱动程序的代码可以完全访问系统空间的内存,也可以绕过Windows的安全机制直接访问对象,用户模式到内核模式的切换不会影响线程的调度,大部分图形和窗口系统运行在内核模式下,图形密集的应用程序花在内核模式下的时间比用户模式下的时间多,当Windows无事可做时,它运行在内核模式上.6.8注册表注册表是一个反映内存中易失数据的窗口,比如系统中当前硬件的状态(哪些设备驱动程序已经加载到系统中了,用了哪些资源)以及Windows性能计数器,性能计数器不位于注册表之中,可以通过注册表函数来访问6.9用户模式调试侵入式 通过Windows的DebugActiveProcess,可以检查或修改内存,设断点,可以断开调试连接非侵入式 利用OpenProcess打开被调试进程,使得你可以检查和修改目标进程中的内存,不能设置断点,6.10可移植性分层设计,系统低层是与处理器体系结构相关的,内核(ntoskrnl.exe)和硬件抽象层(HAL,包含在Hat.dll中)绝大部分代码是用C写的,少部分是用C+写的,只有那些需要直接与系统硬件通信的部分(比如中断陷阱处理器,interrupt trap handler),或者对性能极端敏感的部分(如环境切换,context switching)才用汇编写6.11 Windows子系统环境子系统进程(csrss.exe)控制台窗口,创建或删除进程和线程,16位虚拟DOS机(VDM)进程的一部分支持, 内核模式设备驱动程序(Win32k.sys) 窗口管理器(window manager),图形设备接口, 子系统DLL,将已经文档化的Windows API函数翻译成ntoskrnl.exe和Win32k.sys中恰当的且绝大多数未文档化的内核模式系统服务调用.6.12系统进程idle进程,每个CPU一个线程,占用空闲的CPU时间System进程,包含大多数内核模式系统线程smss.exe 会话管理器csrss.exe Windows子系统Winlogon.exe 登录进程services.exe 服务控制管理器和它创建的子服务进程(比如通用服务宿主进程svchost.exe)lsass.exe 本地安全认证服务器每个Windows线程都是从一个从系统提供的函数开始执行的,所以每个Windows进程的0号线程的启动线程都是相同的,6.13停机如果某个进程调用ExitWindowsEx发出停机命令,会有一个消息被发送到给Csrss,指示它执行停机处理,Csrss给Winlogon的一个隐藏窗口发送一个Windows消息,说明要执行系统停机命令,然后WinLogon模仿当前用户的身份,用一些内部标识来调用ExitWIndowsEx,再一次,此调用导致一个消息被发给Csrss,请求执行系统停机命令.这一次Csrss看到请求来自Winlogon,它按照进程的停机级别的反序,来经历当前交互用户的登录会话中的所有进程,每个进程可以调用SetProcessShutdownParameters指定一个停机级别,向系统表明相对于其他进程希望什么时候退出,有效的停机级别位于0-1023,默认级别为640,例如,explorer(默认系统外壳程序)它的停机级别为2,任务管理器停机级别为1,对于每个拥有顶级窗口的进程,csrss给该进程包个包含Windows消息循环的线程发送一个WM_QUERYENDSESSION消息,如果返回TRUE ,系统停机过程继续,然后Csrss给该线程发送WM_ENDSESSION,请求它退出 ,Csrss等待一定的毫秒值,默认为5000毫秒.如果该线程在超时到期之前还未退出,则csrss显示一个终止程序对话框,表明一个程序未能及时停止,让用户选择杀死进程还是取消停机一旦该进程中所有拥有窗口的线程都已经退出了,则csrss终止该进程,并转向当前交互式会话的下一个进程,当用户停机时,许多系统进程仍然在运行,比如smss,winlogon,SCM,lsass,一旦csrss完成了向系统进程传达停机的通知,Winlogon最后调用NtShutdownSystem,从而结束停机过程,NtShutdownSystem,从而结束停机过程,NtShutdownSystem函数又调用NtSetSystemPowerState函数,来协调设备驱动程序和执行体子系统其余部分,NetSetSystemPowerState调用I/O管理器,以便给那些已经请求过停机通知的所有设备驱动程序发送停机I/O包,使得设备驱动程序有机会在Windows退出以前执行任何必要的处理,配置管理器将任何修改过的注册表刷新到磁盘上,内存管理器将所有已修改过的且包含了文件数据的页面写回到它们各自的文件中,I/O管理器会再次被调用,以便告诉文件系统驱动程序,系统正在进行停机,系统停机过程最终会在电源管理器中结束,电源管理器所执行的动作取决于用户指定的是停机,还是关闭。6.14缓存为缓存分配更多的虚拟内存,可以导致越少的解除映射和重新映射操作。6.15崩溃原因运行在内核模式下的设备驱动程序或者操作系统函数引发了一个未被处理的异常,比如内存访问违例 (企图写一个只读页面)调用一个内核支持例程,导致一次重新调度,当中断请求级别为DPC级别时等待一个处于无信号状态的分发器对象在DPC级别的IRQL时,在由页面文件或内存映射文件中的数据上发生了一个页面错误(要求内存管理器等待一个I/O操作发生,但在DPC/Dispach级别或更高级别上不能够进行等待,因为那要求一次重新调度。一个设备驱动程序或操作系统函数显式地使系统崩溃(调用KeBugCheckEx),因为它检测到一个内部条件表明数据受到了破坏,发生硬件错误,比如机器检查或者不可屏蔽中断,崩溃的大部分原因是第三方驱动.,KeBugCheckEx调用任何已注册的设备驱动程序错误检查回调函数,从而让驱动程序有机会停止它们的设备.七.Windows文件系统和字符I/O1.FAT不支持Windows安全性,FAT是惟一支持软盘的文件系统,而且出现在存储卡上.2.在CreateFile和其他低级API的路径名参数中/也可以用,但最好还是使用3.目录和文件是大小写不敏感,但是大小写保持4.用于API函数参数的文件和目录名和长度可以多达255个字符,而路径名的限制是MAX_PATH(260)个字符,使用转义序列也可指定非常长的名称.5.一个点和两个点也是目录名. 6.CreateFile中的属性FILE_ATTRIBUTE_READONLY 应用程序既不能写也不能删除该文件 FILE_FLAG_DELETE_ON_CLOSE 对临时文件有效,当最后一个打开的HANDLE被关闭时,Windows会删除这个文件 7.WriteFile写文件,如果HANDLE的当前位置加上写字节数超过当前文件长度,那么Windows将扩展文件的长度八.高级文件,目录处理与注册表8.1使用重叠结构来指定文件位置 1.RegOpenKeyEx打开一个命名的项 2.RegEnumKeyEx枚举一个打开的注册表项的子项名 3.RegCreateKeyEx 创建新项 4.RegEnumValue 枚举某个打开项下面的值名称及相关数据 5.RegSetValueEx 设置一个打开项中某个命名值的相关数据九. 异常处理使用_try和_catch这样的关键字十. 内存管理,内存映射和DLL 10.1 堆 每个进程有其自已的堆,GetProcessHeap(),使用不同的堆有多种好处,1. 内存映射文件,直接将虚拟内存空间映射到正常的文件中,比使用ReadFile和WriteFile这些文件访问函数快得多,无需缓冲区,共享内存,堆中的动态内存必须物理地分配在一个分页文件中,操作系统会控制页面在物理内存和分页文件之间移动,并且将进程的虚拟地址空间映射到分页文件中,当进程终止时,文件中的物理空间会得到释放.2. 文件映射对象,在一个打开的文件之上映射一个有句柄的Windows内核文件映射对象,然后将文件的所有部分映射到进程的地址空间上,可以对文件映射对象命名,以便其他进程也可访问,从而实现共享内存3. 映射文件与最好的顺序文件处理技术相比,性能改进可达3:1以上,对于超过一半物理内存的文件来说性能优势不存在4. CreateFileMapping,OpenFileMappingMapViewOfFileUnmapViewOfFile关闭映射句柄FlushViewOfFile强制将修改过的数据写入磁盘,因为映射的内存不会立刻被写入文件,再通过传统文件的访问方式(如ReadFile和WriteFile)将得不到文件的连贯性视图,而直接通过共享内存可以保证连贯性5. 在Win32中文件映射对于2GB-3GB的大文件不可能将整个文件映射到虚拟内存空间中,如果在映射文件中使用了带有指针的数据结构,那所有指针都将与由MapViewOfFile所返回的虚拟地址有关,解决方案就是使用基指针,另一个指针的相对实际偏移量.LPTSTR pInFile=NULL;DWORD _based(pInFile) *pSize;TCHAR _based(pInFile) *pIn;10.2动态链接库DLL应该提供版本信息如Utility_4_0.DLL十一.线程和调度XP和2003加入的管理函数有1. GetProcessIdOfThread(仅2003),从线程的句柄找出进程ID,在管理线程或与其他进程的线程交互的程序中可使用这个函数.如果需要的话,可以使用OpenProcess来获取进程句柄.2. GetThreadIOPendingFlag,确定由句柄所标识的线程是否有任何未完成的I/O请求,如线程可能被阻塞于ReadFile操作上,返回的结果是该函数执行时的状态,在任何时候如果目标线程完成或者初始化一个操作,则实际状态可以改变.3. 每个线程都有一个挂起计数,SuspendThread递减挂起计数,4. 在线程中使用C库,其并不是线程安全的,如strtok函数,5. 多线程不只在多处理器有较好的性能,在多个磁盘驱动器或者存储系统中有其他并行能力的情况下也可提升性能.在这种情况下,对不同文件的多I/O操作可并发运行.6. 小心使用高线程优先级和进程优先级,除非有确定的需要,否则最好不用,避免在正常的用户进程中使用实时优先级,内核总是运行准备好执行的线程中优先级最高的那一个,线程接收与其进程优先级类相关的优先级,Windows不是一个实时操作系统,使用REALTIM_PRORITY_CLASS 会妨碍其他重要线程运行.进程可以更改或确定自已的优先级,只要安全权限足够,也可更改或确定其他进程的.线程优先级会随着进程优先级自动改变,Windows可根据线程的行为动态调整线程优先级.使用SetThreadPriorityBoost函数可以启用或禁用这一功能.7. 如果一个运行中的线程的时间片到期但该线程没有等待操作,则执行机构将把它转移到准备好状态,执行Sleep(0)也会将线程从运行中状态转移到准备好状态.8. 不要对父线程和子线程的执行顺序作任何假设,子线程在父线程调用CreateThread返回之前就运行完成是有可能的,反之,子线程也可能很长时间都不运行.9. 确保子线程所需要的所有初始化在CreateThread调用之前都已完成,否则要使用线程挂起,父线程未能初始化子线程所需的数据是造成“竞态条件”的常见原因.10. 确认每个不同的子线程都从线程参数中传递,不要假设子线程会在另一个线程之前完成11. 任何线程在任何时间都可能被抢占或恢复12. 不使用线程优先级来代替显式的同步13. 确保线程有足够大的堆栈,默认的1MB通常已经足够14. 除了显然是并发的才使用多线程以外,其他的只会增加复杂性和性能开销15. 使用大量的线程大量的堆栈会将消耗虚拟内存空间16. Sleep(0)会导致线程放弃时间片的剩余时间,内核将线程从运行中状态转移到准备好状态.有在线程准备好运行的情况下,SwitchToThread函数提供另外一种线程把处理器交给另一个准备好的线程的方法.要使用_beginthreadex来启动一个线程并为libcmt.lib创建线程特定的工作存储,使用_endthreadex替代ExitThread来终止线程,如果使用了CreateThread就会有不同线程访问并修改libcmt.lib线程安全库的正确操作.17. 一个纤程是应用程序可调度的执行单元,而不是由内核来调度的单元,应用程序可创建大量纤程,纤程本身决定下一个要执行的是哪个纤程,纤程有独立的堆栈,但是完全运行于调度它们的线程的上下文中,纤程的管理完全发生在内核外面的用户空间中,可以认为纤程是轻量级的线程,但它们之间有许多不同.纤程要在任何线程上执行,但一次不能运行于两个线程上,所以纤程不应该访问线程特定的数据如TLS.纤程不是抢占式调度的,Windows执行机构并不知道纤程的存在,纤程是在纤程DLL中管理的,完全位于用户空间中.纤程的API由7个函数构成,以如下顺序使用:线程通过调用ConvertThreadToFiber或ConvertThreadToFiberEx来启用纤程操作,而后线程就由一个单一的纤程组成,这一调用提供指向纤程数据的指针,这个指针可以像用于创建线程独特数据的线程参数的用法那样使用.应用程序可使用CreateFiber来创建更多纤程,每个纤程有起始地址,堆栈尺寸以及一个参数,每个新纤程通过地址来标识,而不是句柄.各个纤程可通过调用GetFiberData获取从CreateFiber所接收而来的自已的数据纤程可使用GetCurrentFiber获得自已的标识运行中的纤程通过调用SwitchToFiber将控制让给其他纤程,这个函数需要给出其他纤程的地址,纤程必须显式表明在线程中要运行的下一个纤程DeleteFiber函数删除现有的纤程及其所有关联的数据在XP中增加了新的函数,如ConvertFiberToThread(它释放由ConvertThreadToFiber所创建的资源),以及纤程本地存储.主从调度.一个纤程决定要运行的纤程,这个纤程总是将控制交给主纤程,点对点调度,一个纤程决定下一个要运行的纤程,十二.线程同步Volatile存储可确保变量在被修改之后储存在内存中,而且在使用该变量之前永远从内存中取它来,volatile限定符通知编译器:这个变量可能在任何时间更改值,但是会影响性能,只有在需要的时候才使用它.使用volatile的指导原则,1. 必须是任何由并发线程访问的变量,而且至少被一个线程修改.2. 即使是只读访问,该变量也至少被两个线程访问,并且正确的程序操作要依赖于新值能够立即对所有线程可见.3. 互锁函数(interlocked function的参数需要volatile变量但是使用volatile修饰符也不能确定其他处理器以特定顺序看到修改,一个处理器可能先将值保存在缓存中,而后才提交给内存4. 使用InterlockedIncrement(&N)进行递增操作,但不可连续两次调用这个函数.12.1 线程安全的代码1.尽量不要使用全局变量.2.如果一个函数被许多线程调用,而且有个线程特定的状态值,比如计数器,必须在函数调用之前保持持久,那么将它存储在全局变量中,要将状态值储存在线程专用的数据结构中.3.避免竞态条件,如果在程序的某个点需要保持某些状态,那么可通过等待同步对象来确保状态的确得到保持4.线程不应该更改进程环境,那样会影响所有的线程,一个线程不应该设置标准输入或输出句柄,或者改变环境变量,惟一能这样做的只有主线程.它应该在创建任何其他线程之前做这些更改,所有线程将共享相同的环境5.所有线程共享的变量应该是静态的或者位于全局存储中,并且受到能创建内存屏障的同步或互锁机制的保护.6.确认每个临界代码区域仅有一个入口和出口.12.2同步1)一个线程可通过使用WaitForSingleObject或WaitForMultipleObjects等候线程句柄的方法等待另一个进程或线程的终止.2)文件锁专门用于同步的文件访问3)CRITICAL_SECTION临界区对象是首选机制,永远要确认离开一个CS,如果没能这么做将导致其他线程永远等待,即使拥有该CS的线程终止,临界区拥有一个计数器,确保递归函数有用。可以使用TryEnterCriticalSection,返回TRUE表示成功拥有该CS,返回FALSE,说明临界区代码不安全.确保要由同一个CS变量来守卫.4)使用互锁指令访问和修改的任何变量都是volatile的,除了进入和离开时会创建内存屏障的不需要,但是临界区无法传信给其他线程,没有超时能力.12.3互斥量1.互斥量如果被终止的线程所放弃,会是已传信的,其他线程不会永远被阻塞.2.互斥量等待有超时机制,而CS只能轮询3.创建互斥量的线程可立即指定对该互斥量的拥有权,4.CS几乎总是比互斥量快5.原则如果WaitForSingleObject等待互斥量句柄不带超时,那么调用的线程可能永远阻塞如果线程终止或在离开CS之前终止,那么CS会处于不稳定的状态,之后的行为是不确定的互斥量粒度影响性能,大的临界区的代码区域如果长时间锁住,并发性得不到保证.锁的使用要最小化尽量以文字或表达式的形式将不变式记录到文档12.4 信号量1.将信号量看成是对可用资源数量的表示,可用来控制确切数量的线程醒来,但是如果请求计数递减2存在着阻塞状态12.5 事件当一个事件被传信之后,多个线程可从等待状态同时释放,事件可分为手动复位和自动复位两种。应该避免使用PulseEvent.会打开门并且在一个(自动复位)或所有(手工复位)等待中的线程通过这扇门之后立即关闭,十三.锁,性能以及NT6增强 高度的锁竞争会阻碍良好的性能,而且降低是剧烈的,不会和线程数量呈线性关系,十四.高级线程同步1.只要存在共享变量在一个线程中更改在另一个线程中访问的情况,这个变量就是volatile的,以便确保每个线程都能在内存中对该变量做读写操作,而不是假定该变量保持在对线程特定的寄存器中,但是,不可过度使有volatile,任何函数调用或返回都将确保寄存器的存储,此外,每个同步调用都会建立起内存屏障.2.确认不对同一个事件使用不同的锁,确认等待条件变量之前不变式成立.3. 互锁函数如何运行取决于函数运行的处理器平台,对于x86系列的处理器 ,互锁函数会向总线发出一个硬件信号,防止其他处理器访问同一个内存地址.高速缓存行有32或64个字节组成,并且始终在第32个字节或第64个字节的边界上对齐,高速缓存行的作用是为了提高CPU运行的性能。WaitForMultipleObjects函数是以原子形式运行的,它在检查内核对象状态时,其他任何线程都无法改变对象的状态,这样可以防止出现死锁.微软使用先进先出,即等待时间最长的进程得到这个对象,但是系统中的一些操作有可能改变这个特征,如果正在等待对象的线程暂停运行,那么因为无法对暂停的线程进行调度,所以系统会忘记这个线程正在等待对象,而当线程再次恢复运行时,系统则认为这个线程刚刚开始等待这个对象,因此改变了线程的等待时间特征,微软没有明确说明先进先出算法是如何实现的.在调试一个进程时,达到一个断点时,这个进程中的所有线程都会暂停运行,因此,调试一个进程会使得先进先出算法的结果难于预测.PulseEvent会使事件变为已通知状态,然后立即变为未通知状态,如果在人工重置事件上调用,等待这个事件的所有线程都可以变为可调度线程,如果在自动重置事件上调用,那么只有一个等待这个事件的线程可以变为可调度状态.如果事件发生时没有线程等待这个事件,那么这个函数不起任何作用.因为函数无法知道任何线程的状态,并且无法知道哪个线程将会看到事件发生,并变成可调度线程,所以pulseEvent并不十分有用.WM_TIMER消息属于最低优先级消息,只有进程对列中没有其他消息,才对这条消息进行检索,定时器发出的报时信息将会唤醒正在等待之中的线程.SignalObjectAndWait的优点有两个:通知一个对象等待另一个对象,可以节省很多处理时间,每次调用一个函数,使线程从用户模式代码变成内核模式代码,大约需要1000个CPU处理周期(x86平台上),不使用该函数,一个线程无法知道另一个线程何时处于等待状态,十五.进程间通信1. 命名管道是面对消息的,读进程可以精确地读入由写进程发送的不同长度的消息.2. 命名管道是双向的,面对消息的.3. 可以有多个独立的但具有相同名称的管道实例4. 联网的客户可按名称访问管道.无论两个进程位于同一台机器还是不同台5. 命名管道要比匿名管道更可取,匿名管道可以进行单向的(半双工)基于字节的IPC,对管道的写操作是在内存缓冲区中实现的.如果管道已满,也会阻塞.6. 管道名称是大小写不敏感的,而且可以包含任何除反斜杠之外的字符,使用PeekNamePipe来确定是否有实际需要读取的消息,在不破坏原有消息的基础上读入管道中消息的任意字节,但它不会阻塞,立即返回.7. 邮槽是一个广播机制,它与数据报类似邮槽是单向的邮槽是一对多的关系写者不知道是否对方实际接收到了消息消息长度有限需要的操作服务器使用CreateMailslot使用ReadFile调用等待接收邮槽消息打开邮槽并使用WriteFile写入消息,十六.套接字网络编程/assignments/service-names-port-numbers/service-names-port-numbers.xml查看分配的端口号1. 套接字客户数量没有上限,但命名管道实例的数量可以有限制,取决于第一次对CreateNamePipe的调用.2. 命名管道没有显示的端口号,而是通过名称来区分.3. 两次调用消息接收函数之间,缓冲区内容和状态必须得到保持.4. 每个数据部分以512字节为限,避免消息以碎片来发送十七.Windows服务服务的类型SERVICE_WIN32_OWN_PROCESS 表示Windows服务运行自已的进程SERVICE_WIN32_SHARE_PROCESS 表示Windows服务与其他服务共享一个进程,将许多服务合并到单一的进程中SERVICE_KERNEL_DRIVER 表示Windows设备驱动程序,保留系统使用SERVICE_FILE_SYSTEM_DRIVER 指定Windows文件系统驱动程序,保留系统使用SERVICE_INTERACTIVE_PROCESS 只能与两个SERVICE_WIN32_X值组合使用,但是交互服务有安全危险.十八.异步输入/输出与完成端口1. 多线程I/O,一个线程执行正常的同步I/O,其他线程可继续执行.2. 重叠I/O(带等待),线程在发出读,写或其他I/O操作命令之后继续执行,当线程需要I/O结果才能继续时,它要么等待文件句柄,要么等待ReadFile或WriteFile重叠结构中指定的一个事件.3. 带有完成例程的重叠I/O,系统在完成I/O操作完成时调用线程内一个特定的完成例程回调函数,在XP上重叠的扩展I/O会是复杂的且极少能产生大量性能效益,4. 重叠I/O的后果:I/O操作不会阻塞返回值为FALSE不一定会失败,因为I/O操作很可能尚未完成,在正常情况下,GetLastError()将返回ERROR_IO_PENDING ,表示没有错误.如果传送尚未完成,那么返回的已传送字节数也没有用处程序可能对单个重叠文件句柄发出多个读或写,所以,句柄的文件指针是没有意义的.必须要有另外一种方法为每个读或写指定文件的位置.程序必须要能够等待同步I/O的完成,对同一个句柄上有多个未完成的操作的情况时,必须能够确定哪个操作已经完成。I/O操作不一定按其发出的相同顺序完成十九. UnicodeMicrosoft坚定地支持Unicode,所有需要字符串的COM接口方法 Unicode对应函数Strcat wcscatStrchrwcschrStrcmpwcscmpStrcpywcscpyStrlenwcslen使用sizeof(szBuffer)/sizeof(TCHAR)表示缓冲区大小,使用malloc(nCharacters*sizeof(TCHAR)二十.内核对象 内核的对象的数据结构只能被内核访问,应用程序无法直接在内存中找到这些数据结构. 内核对象的存在时间可以比创建对象的进程长.通过使有计数来确定生命周期.安全性是指:对象管理小组中的任何成员和内核对象的创建者都拥有对这个对象的全部访问权,而其他进程或线程均无权访问该对象,如果忘记调用CloseHandle函数,有可能出现内存泄漏,除非终止运行时,系统将能确保所有内容均正确清除.二十一.线程的基本知识进程是不活泼的,进程从来不执行任何东西,它只是线程依存的地方,线程只有一个内核对象和栈,保留的记录很少,因此需要的内存很少,而进程使用的系统资源比线程多很多,为进程创建一个虚拟地址空间需要许多系统资源.绝对没有道理让CPU闲置,为使CPU处于繁忙状态,可以让其执行各种工作.1. 索引服务,创建低优先级的线程,以便于定期打开磁盘驱动器上的文件并给内容作索引.2. 磁盘碎片整理指令指针和栈指针寄存器是线程上下文中最重要的两个寄存器,线程总是在进程的上下文中运行,当初始化线程的内核对象时,CONTENT结构的栈指针寄存器被设置为线程栈上用来放置pfnStartAddr的地址.指令指针寄存器置为称作BaseThreadStart的未公开(和未输出)函数的地址中,该函数包含在Kernel32.dll模块中_beginthreadex函数只存在于C/C+运行时库的多线程版本中,如果链接到单线程运行时库,就会得到”未转换的外部符号”错误信息每隔20ms左右,Windows都会查看当前存在的所有线程内核对象,只有某些对象是可调度的,Windows选择一个可调度的线程内核对象,将这个内核对象加载到CPU的寄存器中,所加载的值就是上次保存在线程环境中的值,这项操作被称作上下文切换.Windows为抢占式多线程操作系统,不能保证线程能得到整个处理器,无法保证不允许其他线程运行.Windows没有被设计为实时操作系统,除挂起线程外,其他许多正在等待某些事情的发生的线程也是不可调度的,如正在运行的notepad,但不输入任何数据,那么notepad的线程就没有什么事情可做,系统不给闲置的线程分配CPU时间,当移动notepad的窗口时,系统就会自动改变notepad的线程成为可调度的线程,这并不意味notepad线程立即获得CPU时间,只是表示notepad的线程有事可做,系统会设法在某个时间(不久)对它进行调度.一个线程可以挂起若干次也必须唤醒若干次)Sleep系统将会睡眠若干秒时间,线程能否做到在规定时间内被唤醒,取决于系统中正在进行的操作.将0传递给Sleep表示调用线程将自愿放弃剩余的时间片,迫使系统重新调度.如果不存在多个拥有相同优先级的可调度线程,系统将会调度刚刚调用Sleep的线程.SwitchToThread(),系统查看是否存在一个迫切需要CPU时间的线程,如果没有迫切需要CPU时间,这个函数立即返回,允许一个需要资源的线程强制另一个低优先级而且目前拥有该资源的线程放弃该资源.由于抢占式系统的缘故,要获得线程的运行时间使用GetThreadTimes()和GetCurrentThread().一个线程实际有两个环境,用户环境和内核环境,只有线程的用户模式环境才能被GetThreadContext函数返回,当可以调度一个优先级为31的线程时,就不会调用优先级低于31,这个称为渴求调度,当大量的 CPU时间被高优先级的线程占有时,低优先级的线程就无法运行.出现渴求的情况,不过实际上,在某一个时间段内,大多数的线程是不可调度的.在系统引导时,它会创建一个特殊的线程,成为0页线程(zero page thread),该线程的优先级为0,唯一在0优先级上运行的线程,在系统没有任何线程需要执行操作时,0页线程负责将系统中的所有空闲RAM页面置为0.Windows Exploer是在高优先级上运行的.大多数Exploer是暂停的,等待用户的按键,当低优先级线程运行时,系统会让explorer先运行.很大一部分时间,它都无事可做,不占用CPU的时间,保证用户的快速响应.尽量避免实时优先级类的使用,可能会阻止必要的磁盘I/0信息和网络信息的产生,另外,操作系统将无法及时处理键盘和鼠标的输入,只有在需要在很短的时间内响应硬件事件,或者执行某些不能中断的短期服务.进程是不能调度,只有线程才能调度,进程优先级只是一个抽象的概念.通常高优先级的线程大多时候都不应该处于可调度状态.在线程需要执行某种操作时,它能够迅速获得CPU时间,这个时候,线程应该尽可能少地执行CPU指令,并且返回睡眠状态 ,等待再次变成可调度状态.相反,低优先级的线程可以保持可调度状态,执行大量的CPU指令来进行它的操作,如果按照这个原则来做,整个系统就可以正确地对用户作出响应.线程的优先级等级,线程的优先级可能提高,SetProcessPriorityBoost函数负责告诉系统激活或停用进程中的所有线程的优先级提高功能,而SetProcessPriorityBoost则让你激活或停用单个线程的优先级提高功能.Windows使用软亲缘性,如果其他因素,它设法在线程上次运行的那个处理器上再次运行线程,让线程留在单个处理器上,有助于重复使用仍然在处理器的内存缓存数据.二十二.内存管理22.1无效断点分配分区为进程地址空间设置这个分区,是为了帮助程序员捕获无效断点分配(NULL-point assignment)如果进程中的一个线程试图读取这个分区中的数据,或者将数据写入分区,CPU就会引起非法访问,保护这个分区可以有效地帮助发现无效断点分配.Int *pn=(int*)malloc(sizeof(int);*pn=5;由于禁止访问内存的这个分区,因此会发生非法访问现象,并终止这个进程的运行.22.2 64KB禁止进入分区这个分区禁止进入,任何试图访问这个内存分区的操作都是违规的.保留的原因是为了简化操作系统的实现,将内存块的地址和长度传递给Windows函数时,在Windows函数执行操作前,内存块即可生效,22.3物理存储器和页面文件系统将内存页面复制到页面文件,已经将页面文件复制到内存页面的次数越多.访问硬盘的次数就越多,系统的运行也越慢(抖动(thrashing)意味着操作系统将更多的时间花费在将页面在内存中调入调出,而不是把大部分时间用于程序的运行),因此,通过增加计算机的内存,就可以减少运行应用程序时发生的次数,这样必然可以大大提高系统的运行速度,在大多数情况下,增加内存比提高处理器的运行速度更能够提高系统的运行性能.CPU处理准确对齐的数据时,它的运行效率最高,在用数据的大小对内存地址取模,结果为0时,数据是对齐的,例如,WORD类型的值应该总是从能够被2除尽的地址开始,而DWORD类型的值则应该总是从能够被4除尽的地址开始,当CPU试图读取未对齐的数据时,CPU可能产生下面的两种情况之一:它可以产生一个异常条件,也可以执行多次对齐的内存访问,以便将未对齐数据完整地读出.如果CPU多次进行内存访问,应用程序的运行速度就会降低,在最好的情况下,系统访问未对齐的数据所需的时间也是访问对齐数据所需时间的两倍,为了使应用程序获得最佳的性能,编写代码时必须将数据准确对齐.Windows内存管理的方法(1) 虚拟内存,适合管理大型对象或结构数组(2) 内存映射文件,适合管理大数据流以及管理在单个计算机上运行的多个进程之间的数据共享(3) 内存堆,适合管理大量的小对象目前为止,所有Windows的分配粒度都为64KB,因此,如果需要在进程的地址空间中保留从19668992(300*65536+8192)地址开始的区域,系统将会自动将这个地址调整为64KB的倍数,然后保留地址从 19660800(300*65536)开始的区域.物理RAM是一种非常宝贵的资源,应用程序只能分配尚未指明用途的RAM,不应该过多使用AWE,否则进程和其他进程就会过份地在内存与磁盘之间进行页面调度,从而严重影响系统运行性能,如果可用的RAM数量较少,那也会对系统创建进程,线程和其他资源产生负面影响,应用程序可以使用GlobalMemoryStatusEx函数,来监控物理存储器的使用情况.尽管我们需要创建的栈大小只有1MB,栈区域的实际大小却是1MB加128KB,在Windows 98,每次为一个栈保留区域时,系统实际上所保留的区域都比所要求的区域尺寸大128KB,栈的前面有一个64KB的块,捕获栈的上溢条件,栈的后面是另外一个64KB的块.22.4 内存映射文件内存映射文件的目的1. 系统使用内存映射来加载exe和DLL文件,可以大大节省页面文件空间以及应用程序启动运行所需的时间2. 使用内存映射来访问磁盘的数据文件,这样不必对文件执行I/O操作,并且不必对文件内存进行缓存.3. 使用内存映射可以在同一台计算机运行多个线程之间共享数据,Windows其它的共享方式都是基于使用内存映射来实现的.使用内存映射可以对大文件,进行内容的倒序,用户打开这个文件,并告诉系统将虚拟地址空间的一个区域进行倒序,用户告诉系统将文件的第一个字节映射到保留区域的第一个字节中,然后可以访问虚拟内存区域.最大优点,系统管理所有的文件缓存操作,不需要分配任何内存,但是内存映射文件仍然会因为电源

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论