博文

重叠I/O模型(2)(2009-06-15 08:13:00)

摘要:二.获取重叠I/O操作完成结果 当异步I/O请求挂起后,最终要知道I/O操作是否完成。一个重叠I/O请求最终完成后,应用程序要负责取回重叠I/O操作的结果。对于读,直到I/O完成,接收缓冲器才有效(参考IRP缓冲区管理)。对于写,要知道写是否成功,有几种方法可以做到这点,最直接的方法是调用(WSA)GetOverlappedResult,其函数原型如下。 WINBASEAPI BOOL WINAPI GetOverlappedResult( HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait); WINSOCK_API_LINKAGE BOOL WSAAPI WSAGetOverlappedResult( SOCKET s, LPWSAOVERLAPPED lpOverlapped,                                                     LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags); l 参数一为的文件/套接字句柄。 l 参数二为参数一关联的(WSA)OVERLAPPED结构,在调用CreateFile、WSASocket或AcceptEx时指定。 l 参数三指向字节计数指针,负责接收一次重叠发送或接收操作实际传输的字节数。 l   参数四是确定命令是否等待的标志。Wait参数用于决定函数是否应该等待一次重叠操作完成。若将Wait设为TRUE,那么直到操作完成函数才返回;若设......

阅读全文(7561) | 评论:1

重叠I/O模型(1)(2009-06-15 08:12:00)

摘要:一.              重叠I/O的概念及使用 当调用ReadFile和WriteFile时,如果最后一个参数lpOverlapped设置为NULL,那么线程就阻塞在这里,直到读写完指定的数据后,它们才返回。这样在读写大文件的时候,很多时间都浪费在等待ReadFile和WriteFile的返回上面。如果ReadFile和WriteFile是往管道里读写数据,那么有可能阻塞得更久,导致程序性能下降。 为了解决这个问题,windows引进了重叠I/O的概念,它能够同时以多个线程处理多个I/O。其实你自己开多个线程也可以处理多个I/O,但是系统内部对I/O的处理在性能上有很大的优化。它是Windows下实现异步I/O最常用的方式。 Windows为几乎全部类型的文件提供这个工具:磁盘文件、通信端口、命名管道和套接字。通常,使用ReadFile和WriteFile就可以很好地执行重叠I/O。 重叠模型的核心是一个重叠数据结构。若想以重叠方式使用文件,必须用 FILE_FLAG_OVERLAPPED 标志打开它,例如: HANDLE hFile = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);   如果没有规定该标志,则针对这个文件(句柄),重叠I/O是不可用的。如果设置了该标志,当调用ReadFile和WriteFile操作这个文件(句柄)时,必须为最后一个参数提供OVERLAPPED结构: // WINBASE.H typedef struct _OVERLAPPED{         DWORD   Internal;         DWORD   InternalHigh; &nbs......

阅读全文(8767) | 评论:0

套接字的两种模式(2009-06-13 17:27:00)

摘要:   1.同步I/O和异步I/O 当CPU执行代码(当前活动线程)时遇上一个I/O请求(例如调用ReadFile/WriteFile或recv/send)时,系统产生一个中断,当前活动线程阻塞在此,让CPU去完成这个I/O请求,等到完成后,系统再次产生一个中断让原先的程序继续运行。也就说通过中断保持这两者间的同步。可以将终端理解为硬件化的信号量。 这就是所谓的同步I/O,一个线程中只可能同时处理一个I/O请求。因为一个I/O操作是非常耗时的,所以代码挂起后等待I/O完成的这段时间内,这个线程浪费了很多个指令周期。如果要同时反复读写大文件,则同步I/O的效率是很低的。 当然,可以考虑使用多线程来处理。例如在设计服务器时可以使用多线程来处理客户请求,每有一个客户连接请求,就创建一个新线程,专门处理它的通信请求。对于小型服务器来说,这不是问题。对于同时处理成千上万个请求的大型服务器而言,使用多线程是无效的,因为系统能够支持的线程数量毕竟是有限制的。另外一种解决方案就是使用共享负载的线程池,这涉及到异步I/O。 在异步I/O中,当CPU执行你的代码遇上一个I/O请求时,使用一个线程去处理I/O请求,并且当前调用线程并不挂起。如果后续代码和这个I/O有关(例如需要等待I/O操作的结果),那么它就要等到这个I/O操作完成。通常在一个线程中调用WaitForSingleObject/WaitForMultipleObjects、WSAWaitForMultipleEvents、(WSA)GetOverlappedResult等函数就可以得到I/O完成的消息,然后再对数据进行处理。但如果后续的代码和这个I/O操作无关,你就可以以更快的速度执行下去了,而无需等待I/O请求的完成了。这就是异步I/O了。  据此,套接字有锁定和非锁定两种模式。 2.套接字的两种模式    (1)锁定模式套接字 对于锁定模式的套接字而言,调用任何一个Winsock API函数(accept/recv/send)都会耗费或长或短的时间“等待”返回。大多数Winsock应用都是遵照一种“生产者-消费者”模型来编制。在这种模型中,应用程序需要读取(或写入)指定数量的字节,然后再对读取的数据执行一些计算。 在应用程序中,可以为......

阅读全文(3118) | 评论:0

线程的调度方案(2009-06-13 15:37:00)

摘要:时间片(quantum) 前面已经提到,“时间片”就是在Windows检查是否有另外一个同优先级的线程要执行前,线程运行的时间。如果一个线程结束了它的时间片,而又没有另外一个同优先级的线程,Windows重新调度这个线程运行另外一个时间片。每个线程都有一个时间片的数值,它代表了线程可以运行多长时间,直到时间片届满。这个数值不是一个时间的计时长度,而是一个整数,我们称之为“时间片单位(quantum units)”。 缺省的情况下,Windows 2000/XP Professional线程运行2个时钟周期(6个时间片单位),Windows Server版线程运行12个时钟周期(36个时间片单位)。每次时钟中断,时钟中断程序从线程时间片中扣除一个固定的值3(1个时钟周期)。 Windows Server版设置了较长的缺省值是为了减少上下文转换。当有客户请求到来时,服务器应用程序被唤醒后,如果有较长的时间片,它就可以更好的完成这个请求,然后在时间片结束前返回等待状态。 如果线程时间片没有剩余,时间片结束处理进程就会被触发,然后另外一个线程可能被选择执行。当时钟中断发生时,如果系统正处于DPC/Dispatch级别或者更高,例如正在执行一个DPC或者一个中断服务程序,在这种情况下,就算当前的线程在整个时钟中断间隔都没有运行,它的时间片仍然会被减少。如果不是这样做,而设备中断或者DPC又总是刚好发生在时钟间隔中断前,线程的时间片可能因此总不减少。 不同的硬件平台,时钟间隔的长度是不同的。时钟中断的频率是由HAL(硬件层)负责,而不是内核负责。例如,大部分x86单处理器的时钟间隔是10毫秒,大部分x86多处理器的时钟间隔是15毫秒。 每个时钟滴答表示3个时间片单位,而不是一个时间片单位,这种表示方法是为了线程在等待完成的时候,其时间片可以被减少一部分。基本优先级少于14的线程执行了等待的函数,如WaitForSingleObject 或者 WaitForMultipleObjects后,它的时间片就减少1个单位;优先级是14或更高的线程等待后,其时间片会被重新设置。 线程的时间片可以部分被减少的原因是,当线程在时钟间隔计数器激发前进入等待状态,如果没有对其时间片进行调整,那么有可能这个线程的时间片就从不减少。例如:一个线程运行,然后进入等待状态,然后再......

阅读全文(4142) | 评论:0

线程调度的优先级(2009-06-13 14:23:00)

摘要: 概述 Windows 实现了一个由优先级驱动,抢占式的调度系统,也就是最高优先级的可运行的(就绪状态下的)线程总是先运行。 有一种现象称之为“处理器亲合(processor affinity)”,即线程可能受处理器限制,只运行在那些允许它运行的处理器上。缺省的设置是线程可运行在任何可用的处理器上,用户可以通过使用一个Win32的调度函数修改处理器的“亲合性”。 当一个线程被选择运行,它所运行的时间称之为“时间片”。Windows中断这个线程,去查找是否有别的同优先级或更高优先级的线程正在等待执行,或者这个线程的优先级需要被降低,在这之前这个线程运行的时间长度就是一个“时间片”。不同的线程,其时间片的值可以不同,Window 2000专业版和服务器版的时间片的值是不同的。然而,因为Windows实现的是一种抢占式的调度,一个线程可能未完成其时间片。如果另外有一个更高优先级的线程就绪,正在运行的这个线程就可能在未完成其时间片前被抢占。事实上,一个线程甚至会在未开始其时间片前就被抢占了,而要等待下一次被选择运行。 Windows的没有单独的调度模块或程序,调度的代码是在内核中实现的,广泛分布在内核中那些与调度相关的事件发生的地方。这些负责调度的程序被总称为“内核的调度器”。线程调度发生在DPC/Dispatch级别。 以下这些事件发生时会触发线程调度: 变成就绪状态的线程。例如:一个新创建的线程,或者从等待状态释放出来的线程。 因其时间片结束而离开运行状态的线程,它或者结束了,或者进入等待状态。 线程的优先级改变了,是因为系统调用,或者是Windows自己改变了优先级。 正在运行的线程的处理器亲合性改变了。 在每一个上述情况的衔接点,Windows必须决定下一个运行的线程是哪一个。一旦选择了一个新的线程运行,Windows将对其执行一个上下文转换的操作,即保存正在运行的线程的相关的机器状态,装载另一个线程的状态,开始新线程的执行。 Windows的调度是以线程为粒度调度的。调度的决策被严格限制在以线程为基础,并不考虑这个线程属于哪一个进程。当考虑到进程并不运行,而仅为其线程提供资源和运行的上下文环境时,这种方法就有意义了,例如,进程A有10个可运行的线程,进程B有2个可运行的线程,而且这12个线程的优先级别相同,那么,每一......

阅读全文(5511) | 评论:0

线程的数据结构(2009-06-13 14:08:00)

摘要:除非显式地声明,否则,你可以假设以下讨论的内容既适合用户模式的线程,也适用于内核模式的线程。 在系统级别上,Windows线程是由一个线程块执行体(ETHREAD)来表示的,如图6.7所示。ETHREAD块和它所指向的结构都位于系统地址空间中,唯一的例外是线程环境快(TEB),它位于进程地址空间中。而且,Windows子系统进程(Csrss)为Windows进程中创建的每个线程维护了一个平行结构。另外,对于那些调用了任何一个Windows子系统USER或GDI函数的线程,Windows子系统内核模式部分(Win32k.sys)为它维护了一个称为W32THREAD的数据结构,线程的ETHREAD块指向此结构。 图6.7中的大多数字段的意义已经很明了,不用多说了。第一个字段是内核线程块(KTHREAD)。紧随其后的是线程标识信息、进程标识信息(包括一个指向所有进程的指针,因而可以访问它的环境信息)、安全信息(包括一个指向访问令牌的指针以及身份模拟信息),最后是与LPC消息和待处理I/O请求有关的字段。关于ETHREAD块的信息,你可以使用内核调试器的lkd> dt nt!_ethread(对应进程的是_eprocess)命令来显示其数据结构。 现在让我们来看一看两个关键的线程数据结构:KTHREAD和TEB。KTHREAD块包含了Windows内核为这些正在运行的线程执行线程调度和同步而需要访问的信息。它的布局结构如图6.8所示。 关于KTHREAD块的信息,你可以使用内核调试器的lkd> dt nt!_kthread(对应进程的是_kprocess)命令来显示其数据结构。 图6.9所示的TEB是本节中介绍的唯一位于进程地址空间中的数据结构。 TEB存储了有关映像加载器和各种Windows DLL的环境信息。因为这些组件运行在用户模式下,所以它们需要一个在用户模式下可写的数据结构。为什么该结构位于进程地址空间中而不是位于系统地址空间中呢?因为系统空间只能在内核模式下才可写。通过内核调试器的lkd> dt nt!thread命令你可以看到TEB的地址,同样,你也可以用lkd> dt nt!_teb(对应进程的是_peb)命令来查看TEB的数据结构。 线程的上下文 线程的上下文本质上是一组处理器的寄存器,有正在执行程序中的指针......

阅读全文(4520) | 评论:0

I/O管理器及IRP缓冲区管理(2009-06-13 13:11:00)

摘要:I/O管理器     I/O 管理器定义了有序的结构,或者说是模型,在这个模型里将I/O请求发送给设备驱动程序。I/O系统是包驱动的,大部分I/O请求用I/O请求包(IRP)代表,从一个I/O系统组件传送到另一个组件。(注意:快速I/O不是IRP。)这种设计允许一个应用程序线程可以同时管理多个I/O请求。一个IRP是一个数据结构,包含了完整地描述一个I/O请求的信息。(可以通过Windbg内核调试扩展命令lkd> dt nt!_irp来查看IRP数据结构) I/O管理器创建一个IRP代表一个I/O操作,将指向IRP的指针传递给正确的驱动程序,并且当I/O操作完成时删除IRP。相反,驱动程序收到一个IRP,执行IRP指定的操作,不管是完成操作还是要将IRP传递给另外一个驱动程序进行更进一步的处理,驱动程序都将IRP传回给I/O管理器。 除了创建和删除IRP,I/O管理器还为不同的驱动程序提供公共的代码,驱动程序可以调用这些公共代码进行I/O处理。将I/O管理器中公共的任务合并,可以使驱动程序变得更简单轻便。例如,I/O管理器提供一个函数,允许驱动程序调用其它驱动程序。I/O管理器还管理为I/O请求而设立的缓冲区,为驱动程序提供超时支持,记录操作系统装载了哪个可安装的文件系统。I/O管理器中有将近100个不同的例程可以被设备驱动程序调用。 I/O管理器还提供了灵活的I/O服务,允许环境子系统,如Win32和POSIX,实现它们各自的I/O函数。这些服务包括了用于异步I/O的复杂的服务,用于开发者构造可升级的、高性能的服务器应用程序。 驱动程序提供统一的、模块化的接口,允许I/O管理器可以调用任何驱动程序,而不需要知道驱动程序内部的结构或者是细节的任何信息。前面已经提到,操作系统将所有的I/O请求当作向一个文件发出的请求,驱动程序将发送给虚拟文件的请求转换成硬件特有的请求。驱动程序通过I/O管理器可以互相调用,从而完成对一个I/O请求分层的、各自独立的处理。 除了通常的打开、关闭、读和写函数,Windows 2000 I/O 系统还提供了几个高级特性,如异步,直接,缓冲和分散/集中 I/O。 I/O管理器还负责管理I/O请求缓冲区。 IRP缓冲区管理 当应用程序或者是设备驱动程序通过使用 NtReadFile......

阅读全文(4898) | 评论:0

使用Windbg调试内核(2009-06-12 10:44:00)

摘要:Windbg是微软开发的免费源码级调试工具。Windbg可以用于Kernel模式调试和用户模式调试,还可以调试Dump文件。 1.从http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx下载Install Debugging Tools for Windows 32-bit Version  2.从http://www.microsoft.com/whdc/devtools/debugging/symbolpkg.mspx下载Windows Symbol Packages,安装到D:\Program Files\Symbols  3.启动Windbg,File->Symbol File Path填写以上Symbols安装路径: D:\Program Files\Symbols 4.File->Kernel Debug->Local,在下面执行lkd> !process出现以下错误: NT symbols are incorrect, please fix symbols        这个可能是D:\Program Files\Symbols\exe\ntoskrnl.pdb破损所致。 解决方案:将第3步中的Symbol File Path填写为: D:\Program Files\Symbols; SRV*D:\Program Files\Symbols*http://msdl.microsoft.com/download/symbols 按照这样设置,WinDbg将先从本地文件夹D:\Program Files\Symbols中查找Symbol,如果找不到,则自动从MS的Symbol Server上下载所需要的Symbol,并且在本地D:\Program Files\Symbols文件夹下维持一份拷贝。  5.内核调试 FileàOpenExecutableà可以选择一个可执行文件进行调试;
FileàAttache to a Processà可以选择一个运行中的进程,并对其进行调试; FileàKernel DebugàLocalà可以选择本......

阅读全文(12756) | 评论:3

使用Carlos Antollini封装的ADO2类示例(2009-05-18 21:17:00)

摘要:1.Carlos Antollini编写的Ado封装类,几乎把所有的Ado操作都封装了。Ado2主要提供了CADOCommand,CADODatabase,CADOException,CADOFieldInfo,CADOParameter,CADORecordset,CJetEngine七个类接口,其中最常用的就是CADODatabase和CADORecordset 下面使用Carlos Antollini封装的ADO2类(version 2.20)对MS SQL Server数据库进行操作。 2.新建一个win32 Console Project,将Ado2.h和Ado2.cpp添加到项目中, 并且Project->Settings中选择Use MFC in a shared DLL,因为Ado2需要Afx支持。 3.测试代码如下: // testAdo2.cpp #include "Ado2.h" #include <stdio.h>   void main(void) {        CADODatabase* pAdoDb = new CADODatabase();        _variant_t value;        CString strConnection = "";          try        {               strConnection = "Driver={SQL Server};Server=192.168.89.125;Address=192.168.89.125,1433;Network=dbmssocn;Database=MyDB;Uid=sa;Pwd=******"; // 注意黑体部分根据实际情况填写 ......

阅读全文(5798) | 评论:1

CString的GetBuffer用法(2009-05-10 20:24:00)

摘要:一.函数原型 CString::GetBuffer LPTSTR GetBuffer( int nMinBufLength ); throw( CMemoryException ); Return Value An LPTSTR pointer to the object’s (null-terminated) character buffer. Parameters nMinBufLength The minimum size of the character buffer in characters. This value does not include space for a null terminator. Remarks Returns a pointer to the internal character buffer for the CString object. The returned LPTSTR is not const and thus allows direct modification of CString contents. If you use the pointer returned by GetBuffer to change the string contents, you must call ReleaseBuffer before using any other CString member functions. 二.函数作用及使用范围 对一个CString变量,你可以使用的唯一合法转换符是LPCTSTR,直接转换成非常量指针(LPTSTR-[const] char*)是错误的。正确的得到一个指向缓冲区的非常量指针的方法是调用GetBuffer()方法。 GetBuffer()主要作用是将字符串的缓冲区长度锁定,releaseBuffer则是解除锁定,使得CString对象在以后的代码中继续可以实现长度自适应增长的功能。 CString ::GetBuffer有两个重载版本: LPTSTR GetBuffer( );LPTSTR GetBuffer(int nMinBufferLength); 在第二个版本中,当设定的长度小于原字符串长度时,nMinBufLength = nOldL......

阅读全文(20363) | 评论:2