当前位置: 首页 > news >正文

安徽企业网站建设公司网络规划设计师备考需要多久

安徽企业网站建设公司,网络规划设计师备考需要多久,做网站个人备案,可画canvas官网1. Windows内部概览 1.1 进程 进程包含以下内容#xff1a; 可执行程序#xff0c;代码和数据私有的虚拟地址空间#xff0c;分配内存时从这里分配主令牌#xff0c;保存进程默认安全上下文#xff0c;进程中的线程执行代码时会用到它私有句柄表#xff0c;保存进程运…1. Windows内部概览 1.1 进程 进程包含以下内容 可执行程序代码和数据私有的虚拟地址空间分配内存时从这里分配主令牌保存进程默认安全上下文进程中的线程执行代码时会用到它私有句柄表保存进程运行过程中的事件、信号量、文件等的句柄一个或多个执行线程 1.2 虚拟内存 每个进程都有自己的私有虚拟且线性的地址空间 地址空间一开始是空的然后可执行映像和ntdll.dll首先被映射进内存中继而是更多的子系统dll 地址空间从0开始但是第一个64KB大小的地址是不可以被使用的一直增长最大值依赖于进程的位数和操作系统的位数 进程用户地址空间最大值规则如下 32位程序32位系统进程用户地址空间大小默认2GB32位程序32位系统通过修改PE头的LARGEADDRESSAWARE标志增加地址空间最大可扩展到3GB32位程序64位系统通过修改PE头的LARGEADDRESSAWARE标志增加地址空间最大可扩展到4GB64位程序64位系统地址空间大小为8TBwin8及以前或128TBwin8.1及以后 虚拟地址可能实际位于物理内存中也有可能位于页面文件中。 如果是位于物理内存中则直接访问数据 如果位于页面文件中则cpu或产生一个page fault异常内存管理器的页错误异常处理程序将会从页面文件中读取数据并复制到物理内存中然后让cpu重新访问 内存以页面为单位进行管理页面的大小由cpu的类型决定。windows系统中默认为4KB小页内存也支持2MBx86/x64/ARM64和4MBARM又叫大页内存 大页内存的优点 直接使用页目录入口Page DIrectory Entry, PDE进行映射不适用页表因此转换速度更快能更好的利用地址转换缓冲区Translation Lookaside BufferTLB一个由CPU维护的近期转换页面的缓存。单个TLB入口能比使用小页面映射更多的内存 大页内存的缺点 大页内存需要在RAM中是连续的因此当内存紧张或非常碎片化时可能会分配失败。 大页内存始终是非分页的并且只能设置读/写保护 1.2.1 页状态 虚拟内存的那每个页面处于如下的三种状态之一 空闲页面未被分配没有任何数据存在对该页面的访问将会引起一个访问违规Access Violation异常已提交页面中已经有数据了可以在不违反该页面的保护属性的情况下进行访问操作已提交页面会被映射在RAM或页面文件中保留页面的地址范围被保留但还未使用将来可能会使用后续进行内存申请时将不会申请到保留部分的页面地址。同样对保留页面访问将触发访问违规Access Violation异常 1.2.2 系统内存 地址空间的较低部分由进程使用较高部分由操作系统使用 32位系统没有配置增加地址空间时操作系统地址空间为高端2GB0x8000,0000 ~ 0xFFFF,FFFF32位系统配置增加地址空间时操作系统地址空间为剩余的地址空间如配置进程最大3GB地址空间那么操作系统地址为高端1GB0xC000,0000 ~ 0xFFFF,FFFF此时最有可能收到影响的就是操作系统的文件系统缓存64位系统win8及以前操作系统使用高端8TB地址空间64位系统win8.1及以后操作系统使用高端128TB地址空间 操作系统地址空间是进程无关的而用户进程的地址空间是进程相关的每个进程都有独立的地址空间其他进程无法访问 操作系统地址都是绝对地址无论从任何进程中看同一个地址内容都是一样的。 从用户模式直接访问系统空间地址会触发访问违规Access violation异常 1.3 线程 线程位于进程中使用进程提供的资源来工作如虚拟内存和内核对象的句柄 线程包含一下内容 当前的访问模式用户模式或内核模式执行上下文处理器的寄存器和执行状态一个或两个栈用于局部变量分配和调用管理线程局部存储TLS数组为存储线程的私有数据提供统一的访问语义基本优先级和当前动态优先级处理器亲和性affinity指定线程可以在哪个处理器上运行 线程的状态包括 运行正在一个逻辑处理器上执行代码就绪所有处理都忙或不可用处于等待被调度执行的状态等待等待某些事件发生以继续处理一旦事件发生线程进入就绪状态 1.3.1 线程栈 每个线程都有栈用于存放局部变量传递函数参数以及在调用函数之前存放返回地址 每个线程至少有一个栈位于系统内核空间内这个栈很小32位系统默认12KB64位系统默认24KB 每个线程还有一个栈位于用户空间中这个栈比较大默认最大1MB 当线程位于运行或就绪状态时内核栈一直驻留在RAM中。用户模式下的栈可能被页换出和所有用户模式的内存一样。 用户模式的栈在起始时只提交一小部分内存可能只有一个页面栈地址空间的其余部分作为保留内存使用一个保护属性PAGE_GUARD标记已提交页面的下一页有时会多于一页指明这是一个警戒页面guard page。当线程需要更多的栈时它会写到警戒页面此时会产生一个异常被内存管理器处理此时内存管理器会移除该页上的警戒保护并提交该页然后设置下一页的警戒保护 线程用户模式的栈大小按如下方式确定 可执行映像在其PE文件头内指定了栈的提交大小和保留大小如果没有指定则使用默认值当用CreateThread及类似函数创建线程时可以在函数参数中指定线程用户栈的大小 1.4 线程服务 应用程序需要进行一些非纯粹计算的操作如分配内存、打开文件、创建线程等最终只能由内核中的代码来完成 打开文件系统服务的执行流程 用户模式调用CreateFile位于kernel32.dll中实现kernel32.dll中进行一些错误检查然后调用NtCreateFileNTDLL.DLL中的一个导出函数NTDLL.DLL是一个基础DLL实现了原生API是位于用户模式的最底层代码。NtCreateFile是一个执行到内核模式的转换API在进行实际的转化之前它首先将一个叫做系统服务号的数字放到CPU寄存器里Intel/AMD体系结构中是EAX然后执行一个特殊的CPU指令x64下是syscallx86下是sysenter来实际转换到内核模式并跳转到一个预定义的被称为系统服务分发器system service dispatcher的例程中系统服务分发器读取EAX寄存器中的值用作系统服务分发表System Service Dispatch Table, SSDT的入口索引跳转到相应的系统服务中对于NtCreateFile来说SSDT中相应的入口会指向I/O管理器的同名NtCreateFile函数且参数相同当系统服务执行完毕后线程会返回到用户模式执行紧接着syscal或sysenter的指令 1.5 系统总体架构 用户进程基于映像文件的普通进程在系统中执行子系统DLL实现子系统API的动态链接库子系统包含众所周知的文件kernel32.dll、user32.dll、gdi32.dll、advapi32.dll等NTDLL.DLL系统范围的DLL实现了Windows的原生API这是用户模式的底层主要提供为系统调用提供内核模式转换还有堆管理、映像加载、部分用户模式线程池功能等服务进程普通的Windows进程它和服务控制管理器SCM在services.exe中实现通信对它的生命周期进行管理执行体执行体位于NtOskrnl.dll内核本体的高层包含了绝大部分的内核代码其中大部分是管理器对象管理器、内存管理器、IO管理器、即插即用管理器、电源管理器、配置管理器等内核内核层实现了最基础和最时间敏感的内核模式操作系统代码包括线程调度、中断、异常分发、互斥量、信号量等设备驱动程序可装载的内核模块具备完全的内核能力Win32k.sys,Windows子系统的内核模式组件本质上是一个内核模块驱动程序处理所有Windows用户界面相关的操作硬件抽象层HAL最接近CPU的硬件之上的一个抽象层使得设备驱动可以通过调用API来工作而不需要知道硬件细节系统进程用于描述那些通常就在那干自己事情的进程如smss.exe、lsass.exe、winlogon.exe、services.exe等子系统进程windows子系统进程运行的映像文件是csrss.exe视为一个助手进程帮助内核对windows系统中运行的进程进行管理。每个会话都会有一个csrss.exe的实例在运行Hyper-V虚拟机管理器位于windows10及以后的版本里如果cpu支持vbs的话vbs提供一个额外的安全层让实际的机器只是一个Hyper-V控制的虚拟机 1.6 句柄和对象 Windows内核提供多种类型的对象以供用户模式进程、内核本身、驱动程序使用。这些对象由对象管理器执行体的一部分在用户模式或内核模式代码请求时创建在内核空间中 对象时引用计数的只有当对象的最后一个引用被释放之后对象才会被销毁并从内存中释放 由于这些对象位于内核空间中用户模式无法直接访问因此通过一种间接的机制来从用户模式访问操作这些对象即句柄 句柄是一个表格的入口索引该表格在进程的基础上维护每个进程都有一个表格表格中的每一项都指向内核中的一个对象。通过Create或Open等函数创建和打开内核中的对象并返回内核对象对应的表格中项的索引 内核代码可以使用句柄或对象的直接指针可以通过ObReferenceObjectByHandle函数将句柄转化为对象的直接指针此时对象的索引将1用完后需要使用ObDerefenceObject将索引-1 句柄的值始终的4的倍数第一个有效的句柄值是40并不是有效的句柄值 1.6.1 对象名称 并非所有的对象都有名称进程和线程对象没有名称它们有标识符文件对象没有名称文件名并非文件对象的名称这是两个概念从用户模式使用名称调用Create函数在此名称的对象不存在时将创建一个对象如果该名称对象已经存在只会打开已经存在的对象此时通用GetLastError将会返回ERROR_ALREADY_EXISTS 提供给Create函数的名称并非对象的最终名称名称的前面会被添加\Sessions\x\BaseNamedObjects\其中x是调用者的会话标识符 如果是0号会话名称的前面会加上\BaseNamedObjects\ 如果调用者在应用容器内一般是通用windows平台uwp进程那么加到前面的字符串将更加复杂包含了唯一的应用容器SID如\Sessions\x\AppContainerNamedObjects\{AppContainerSID} 对象的名称是相对于会话的如果一个对象需要在多个会话间共享可以通过加上前缀Global\在0号会话中创建它应用容器没有使用0号会话名字空间的能力 整个名称空间保留在内存中由对象管理器进行管理 查看对象引用技术的正确方法是使用内核调试器的!trueref命令 2. 内核开发环境 打开测试签名bcdedit /set testsigning on重启生效 安装驱动程序sc create simdrv binpath c:\users\admin\desktop\simdrv.sys type kernel start demand 运行驱动程序sc start simdrv 暂停驱动程序sc pause simdrv 停止驱动程序sc stop simdrv 卸载驱动程序sc delete simdrv 2.1 输出打印信息dbgprint.h #pragma once #include ntddk.h#define _LogMsg(lvl, lvlname, frmt, ...) { \DbgPrintEx( \DPFLTR_IHVDRIVER_ID, \lvl, \[ lvlname ] [irql:%d pid:%-6Iu tid:%-6Iu %s::%-4d] frmt \n, \KeGetCurrentIrql(), \PsGetCurrentProcessId(), \PsGetCurrentThreadId(), \__FILE__, \__LINE__, \__VA_ARGS__ \); \ }#define DbgError(frmt, ...) _LogMsg(DPFLTR_ERROR_LEVEL, erro, frmt, __VA_ARGS__) #define DbgWarning(frmt, ...) _LogMsg(DPFLTR_WARNING_LEVEL, warn, frmt, __VA_ARGS__) #define DbgInfo(frmt, ...) _LogMsg(DPFLTR_INFO_LEVEL, info, frmt, __VA_ARGS__) #define DbgTrace(frmt, ...) _LogMsg(DPFLTR_TRACE_LEVEL, trac, frmt, __VA_ARGS__) #define DbgLog(frmt, ...) _LogMsg(DPFLTR_ERROR_LEVEL, ****, frmt, __VA_ARGS__)3. 内核开发基础 用户模式开发和内核模式开发区别 3.1 使用C开发内核代码 不支持new和delete操作符非默认构造函数中的全局变量将不会被调用可创建init函数在DriverEntry中显式的调用或将类指针定义为全局变量C异常处理try、catch、throw无法通过编译可通过使用SEH来代替无法使用标准C库但是C模板作为一个语言特性是可以在内核中使用的 内核中常用的C特性有 nullptr关键字auto关键字自带进行类型推断模板重载new和delete操作符构造和析构函数 3.2 内核API 内核中Zw开头的函数是NTDLL.DLL中的原生API的镜像是从原生API到执行体实现之间的网关 当调用来自于用户模式时内核中的Nt系列函数会进行合法性检查调用者的信息会以线程为基础保存在每个线程对应的KTHREAD结构中未公开的PreviousMode字段里 当调用来自于内核中时无需进行合法性检查因此可以调用Zw系列函数Zw系列函数会将PreviousMode设置为KernelMode(0)然后调用内核中的Nt系列函数此时Nt系列函数检查PreviousMode发现本次调用来自于内核将跳过安全检查 3.3 函数和错误代码 多数内核API会返回一个状态码来指示操作成功或失败类型是NTSTATUS一个32位的整数 可以通过NT_SUCCESS宏来检查状态码的最高位其代表着成功与否 某些情况下从函数返回的NTSTATUS值最终会返回到用户模式用户模式通过GetLastError得到转换后的值内核中的状态码和用户层的错误值并非对应的 3.4 字符串 内核中常用两种字符串类型wchar_t *或UNICODE_STRING 通常使用一组Rtl系列的函数来操作UNICODE_STRING 内核中也实现了常用的C运行时库的处理函数wcscpy、wcscat、wcslen、wcscpy_s、wcschr、strcpy、strcpy_s等 3.5 动态内存分配 内核中的栈非常小任何大块的内存都必须动态分配 内核提供了两个通用的内存池以供使用 分页池在需要时能够将页面换出的内存池非分页池永远不会换出页面保证一直驻留在RAM里的内存池 驱动程序应当尽可能少使用非分页池除非必须其他情况都应使用分页池 只有三种类型的内存可以被驱动程序分配使用 PagedPool分页内存NonPagedPool非分页内存NonPagedPoolNx没有执行权限的非分页内存 常用内存分配函数 3.6 链表 系统中所有进程使用EPROCESS结构进行管理这些结构使用一个环形双向链表连接在一起其中链表的头部存储在内核变量PsActiveProcessHead中每个进程的EPROCESS.ActiveProcessLinks字段就是一个LIST_ENTRY typedef struct _LIST_ENTRY {struct _LIST_ENTRY *Flink;struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY;通过CONTAINING_RECORD宏可以正确计算出包含LIST_ENTRY字段的结构 常用链表处理函数 3.7 驱动对象DRIVER_OBJECT DRIVER_OBJECT结构在驱动加载时由内核分配并进行部分初始化然后传递给DriverEntry函数进一步初始化 DRIVER_OBJECT的MajorFunction字段是一个指针数组指明了驱动程序支持那些操作 起初MajorFunction数组会被内核初始化成指向内核的内部例程IopInvalidDeviceRequest它会给调用者返回一个错误的状态表明不支持该操作 3.8 设备对象DEVICE_OBJECT 驱动程序无法直接和应用程序通信只能通过驱动程序创建出的设备对象来进行通信 通过CreateFile可以打开一个符号链接也即内核对象并返回一个对象的句柄或指针 所有能够在用户模式直接打开的符号链接都位于对象管理器中名为??的目录下 常用的如C:、Aux、Con等都是合法的符号链接可以被直接打开 其他一些名字很长很复杂的符号链接是基于硬件的驱动程序调用IoRegisterDeviceInterface后由IO系统自动生成的 ??目录中的多数符号链接都指向Device目录下的内部设备名称用户模式不能直接访问这些设备但内核中可以通过IoGetDeviceObjectPointer来访问用户模式只能通过符号链接来访问这些设备 举例 Process Explorer会生成一个\Device\PROCEXP152的设备符号链接名为PROCEXP152。当用户程序想要打开时必须添加\\.\前缀否则会被当作当前目录下的文件来对待。 HANDLE hDevice CreateFile(L\\\\.\\PROCEXP152, GENERIC_WRITE| GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL)创建设备 驱动程序使用IoCreateDevice函数创建设备对象该函数会分配并初始化一个DEVICE_OBJECT结构并返回指针 一个驱动所包含的设备对象的实例位于DRIVER_OBJECT结构的DeviceObject字段中DEVICE_OBJECT的NextDevice字段指向下一个设备对象形成了一个链表 新创建的设备对象是从头部插入链表的因此第一个创建的设备对象保存在链表的最后它的NextDevice为NULL 4. 驱动程序结构 4.1 线程优先级 用户模式线程的最终优先级 线程所在进程的优先级类别SetPriorityClass 该线程的优先级偏移值SetThreadPriority 内核模式下可以调用KeSetPriorityThread直接设置线程的优先级而没有优先级类别的限制 在应用层只有一小部分的优先级能够被直接设置但是在内核层中可以绕开这些限制直接将线程设置为任意的优先级 4.2 DeviceIoControl DeviceIoControl用于和内核设备通信 控制码必须使用CTL_CODE宏来定义 #define CTL_CODE( DeviceType, Function, Method, Access ) ( \((DeviceType) 16) | ((Access) 14) | ((Function) 2) | (Method) \ )DeviceType标识设备的类型FILE_DEVICE_XXX的常数微软规定第三方的值必须从0x8000开始Function一个数字码用于区分不同的操作微软规定第三方驱动需要从0x800开始method输入输出缓冲区的传递方式Access指明这个操作是读还是写FILE_WRITE_ACCESS、FILE_READ_ACCESS、FILE_ANY_ACCESS 4.3 创建设备 创建设备需要调用IoCreateDevice这个API DriverObject设备对象所属的驱动对象DeviceExtensionSize设备扩展缓冲区的大小DeviceName内部的设备名称通常在对象管理器的Device目录下创建DeviceType要创建的设备类型通常和硬件设备相关软件驱动程序设置为FILE_DEVICE_UNKNOWNDeviceCharacteristics设备的属性特征一些标志位的集合Exclusive是否允许同时多次打开设备DeviceObject返回参数用于接收创建好的设备对象指针 如果IoCreateDeive调用成功那么将从非分页内存池中分配DEVICE_OBJECT结构 4.4 IRP初探 IRP是一个半文档化的结构用来表示一个请求 IRP通常来自于执行体中的管理器IO管理器、即插即用管理器、电源管理器对于软件驱动基本上都来自于IO管理器 IRP通常会包括一个或多个IO_STACK_LOCATION结构用于代表设备栈中的每一个设备实例可通过IoGetCurrentIrpStackLocation获得 IoCompleteRequest函数会完成IRP将IRP传送会它的创建者第二个参数是驱动程序提供给客户程序的优先级临时提升数值一般为0 IRP例程中需要返回一个值该值和放到IRP中的值一样 5. 内核机制 5.1 中断请求级别IRQL IRQL是处理器的一个属性 示例由磁盘启动器执行的IO操作在操作执行完成后磁盘驱动器会通过请求中断来通知操作已经完成。此中断连接到中断控制器硬件然后将请求发送到处理器进行处理。哪个线程该执行相关的中断服务例程 每个硬件中断都与一个优先级相联系即IRQL由HAL确定 每个处理器的上下文都有自己的IRQL可以看作寄存器对待 IRQL的执行规则是cpu始终执行最高级IRQL代码比如原本cpu的IRQL级别是0此时一个IRQL是5的中断进来那么cpu就在当前线程的内核栈里保存其目标状态上下文然后将自己的IRQL上升到5并执行与中断相关的中断服务例程ISR。在ISR完成之后将自己的IRQL降低到原来的级别从栈中恢复之前的上下文并执行 重点ISR由中断发生时处理器上正在运行的任一线程处理因此当IRQL2时cpu无法进行上下文切换即无法切换线程 重要IRQL描述如下 PASSIVE_LEVEL(0)正常IRQL用户模式代码总是运行在此级别线程调度正常进行APC_LEVEL(1)用于特殊的内核APC异步过程调用线程调度正常进行DISPATCH_LEVEL(2)线程调度器不会唤醒不允许访问分页内存不允许在内核对象上等待除非等待时间为0设备IRQL用于硬件中断的一段级别范围x64/ARM/ARM64上是3~11在x86上是3-26所有IRQL2规则使用HIGH_LEVEL最高级别中断被一些链表操作的API使用实际值是15x64/ARM/ARM64或31x86 windbg调试时可以通过!irql命令查看当前IRQL可以指定一个CPU号码显示该CPU的IRQL windbg调试时可以通过!idt调试命令查看系统中已注册的中断和其相关联的ISR 5.1.1 提升和降低IRQL 内核模式下KeRaiseIrql提升KeLowerIrql降低 KIRQL oldIrql; KeRaiseIrql(DISPATCH_LEVEL, oldIrql); NT_ASSERT(KeGetCurrentIrql() DISPATCH_LEVEL); //do something KeLowerIrql(oldIrql);如果提升了IRQL请确保在同一个函数中将它降低 5.2 延迟过程调用DPC DPC是一个对象内部封装了一个函数该函数会在IRQL在DISPATCH_LEVEL上调用 DPC由来举例ReadFile 应用层调用ReadFile内核层收到读请求的驱动程序调用文件系统驱动NTFS文件系统驱动继续往下调用直到磁盘驱动程序磁盘驱动程序启动一个对实际磁盘硬件的操作此时硬件会开始工作读取数据硬件完成读取操作后会产生一个中断这会导致与此中断关联的IRS在设备IRQL上执行中断是异步到达的会任意选择一个线程处理中断请求IRS需要做的工作是访问硬件得到操作的结果然后完成初始请求IRP 由于IRS此时运行在设备IRQL上无法执行IoCompleteRequestIRQL DISPATCH_LEVEL因此引入延迟过程调用DPC机制来完成IRP 如果直接在ISR中将当前IRQL降低到DISPATCH_LEVEL然后调用IoCompleteRequest再将IRQL提升到原先的值这样会引起死锁 注册了ISR的驱动程序预先准备好DPC对象在非分页池中分配一个KDPC结构并会一个回调函数调用KeInitializeDpc进行初始化在ISR被调用退出之前ISR调用KeInsertQueueDpc将此DPC排队以请求尽快执行这个DPC每个处理器都有自己的DPC队列KeinsertQueueDpc默认将DPC排到当前处理器的DPC队列中ISR调用结束后在IRQL能降回到PASSIVE_LEVEL之前会查看在处理器的队列上有没有DPC存在如果有处理器会把IRQL降到DISPATCH_LEVEL并以先进先出的方式处理队列中的DPC逐个调用DPC所关联的函数直到队列清空处理器上的DPC执行完毕后IRQL降低为PASSIVE_LEVEL并恢复执行中断到来前被打断的原始代码 由于DPC执行在DISPATCH_LEVEL上因此不会进行线程切换、访问分页内存等 DPC可以通过某些方式控制参阅KeSetImportanceDpc和KeSetTargetProcessorDpc 5.2.1 DPC和时钟一起执行 内核时钟KTIMER允许被设置成未来的某个时间到期可以通过KeWaitForSingleObject来等待或者通过DPC作为回调在始终到期时执行 5.3 异步过程调用APC APC和DPC一样都是封装了回调函数的数据结构 DPC和调用的线程无关而APC和调用的线程相关只有那个线程才能调用APC关联的回调函数 每个线程都有一个相关联的APC队列 APC共有三种类型 用户模式APC这些APC仅在线程进入警戒Alert状态时才在用户模式的PASSIVE_LEVEL上运行。通常在调用SleepEx、WaitForSingleObjectEx、WaitFOtMultipleObjectsEx等类似API进入警戒状态。在警戒状态下线程会检查自己的APC队列如果不是空的其中的APC就会执行直到队列为空普通内核APC 这些APC在内核模式下的PASSIVE_LEVEL中执行能够抢占用户模式代码和用户模式APC特殊内核APC这些APC在内核模式下的APC_LEVEL中执行能够抢占用户模式代码、用户模式APC、普通内核APC这些APC通常被IO系统用来完成IO操作 APC的API在内核模式下是未公开的因此驱动程序一般不会直接使用APC 5.3.1 关键区和警戒区 进入关键区KeEnterCriticalRegion 离开关键区KeLeaveCriticalRegion 关键区会阻止执行用户模式和普通内核APC特殊内核APC除外内核中有些函数需要位于关键区中执行特别是执行体资源 进入警戒区KeEnterGuardedRegion 离开警戒区KeLeaveGuardedRegion 将IRQL提升到APC_LEVEL将禁止所有APC的发送 5.4 结构化异常处理SEH 异常是一种事件和中断类似区别是异常是同步的中断异步的 异常包括除零、断点、页错误、栈溢出、非法指令等 内核中的异常处理程序是基于中断分配表IDT进行调用的IDT中还保存了从中断向量到ISR的映射关系 合法的关键字组合是__try__/__except__和__try__/__finally__ 将EXCEPTION_EXECUTE_HANDLER放到__except__中表示任何异常都会被处理还可以调用GetExceptionCode来检查实际发生的异常进而有选择的处理遇到不想处理的异常可以让内核继续在调用栈上寻找别的处理程序 //使用__try和_except组合 __try {// do something } _except (GetException() STATUS_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {}//使用__try和__finally组合 void foo() {void *p ExAllocatePool(PagedPool, 1024);__try {//do something}__finally {ExFreePool(p);} }SEH还提供了ExRaiseStatus和ExRaiseAccessViolation这样的函数来抛出一个异常 5.4.1 使用C RAII代替__try/__finally templatetypename T void struct kunique_ptr {kunique_ptr(T* p nullptr) : _p(p) {}//remove copy ctor and copy (single owner)kunique_ptr(const kunique_ptr) delete;kunique_ptr operator(const kunique_ptr) delete;//allow ownership transferkunique_ptr(kunique_ptr other) : _p(other._p){other._p nullptr;}kunique_ptr operator(kunique_ptr other){if (other ! this){Release();_p other._p;other._p nullptr;}return *this;}~kunique_ptr(){Release();}operator bool() const{return _p ! nullptr;}T* operator-() const{return _p;}T operator*() const{return *_p;}void Release() {if (_p){ExFreePool(_p);}}private:T* _p; };5.5 线程同步 5.5.1 原子操作用户模式下也可使用 函数描述InterlockedIncrement InterlockedIncrement16 InterlockedIncrement64对32/16/64位的整数原子化加一InterlockedDecrement InterlockedDecrement16 InterlockedDecrement64对32/16/64位的整数原子化减一InterlockedAdd InterlockedAdd64原子化将一个32/64位整数加到一个变量上InterlockedExchange InterlockedExchange8 InterlockedExchange16 InterlockedExchange64原子化的交换两个32/8/16/64位整数InterlockedCompareExchange InterlockedCompareExchange64 InterlockedCompareExchange128原子化比较一个变量和一个值 InterlockedCompareExchange函数家族再无锁编程中使用 5.5.2 等待函数 NTSTATUS KeWaitForSingleObject (PVOID Object,KWAIT_REASON WaitReason,KPROCESSOR_MODE WaitMode,BOOLEAN Alertable,PLARGE_INTEGER Timeout );NTSTATUS KeWaitForMultipleObjects (ULONG Count,PVOID Object[],WaitType,KWAIT_REASON WaitReason,KPROCESSOR_MODE WaitMode,BOOLEAN Alertable,PLARGE_INTEGER Timeout,PKWAIT_BLOCK WaitBlockArray );object要等待的对象WaitReason等待的原因驱动程序通常设置为Executive如果此等待将由用户模式触发设为UserRequestWaitModeUserMode或KernelMode驱动程序一般设置为KernelModeAlertable等待过程中线程是否处于警戒状态。警戒状态下允许传递APC如果WaitMode是UserMode还允许传递用户模式APC驱动程序一般设置为FALSETimeout等待超时时间参数单位是100纳秒负值表示相对时间值正值表示从1601年1月1日午夜起计算的绝对时间值。如果为NULL则一直等待下去Count等待对象的数目Object[]要等待的对象指针数组WaitType指明要等待所有对象编程有信号waitAll还是只要一个对象有信号就行waitAnyWaitBlockArray结构数组用于等待操作的内部管理如果等待对象的数目THREAD_WAIT_OBJECTS目前是3那么这个参数是可选的内核会使用每个线程里内建的数组如果等待对象数目多于这个数驱动程序就必须从非分页池中分配正确大小的结构并在等待结束之后释放它们 KeWaitForSingleObject返回值 返回值描述STATUS_SUCCESS等待完成等待对象变为有信号STATUS_TIMEOUT等待完成超时时间已到 KeWaitForMultipleObject返回值 如果指定WaitAll 返回值描述STATUS_SUCCESS等待完成所有等待对象都变为有信号STATUS_TIMEOUT等待完成超时时间已到 如果指定WaitAny 返回值描述索引值其中一个对象变为有信号了该对象在对象数组中的索引值STATUS_TIMEOUT等待完成超时时间已到 常见可等待对象以及有信号和无信号的含义 5.5.3 可等待对象互斥量KMUTEX 互斥量在自由时是有信号态一旦某个线程调用等待函数等待互斥量成功互斥量变为无信号状态该线程成为互斥量的拥有者 如果某个线程是一个互斥量的拥有者那么此线程是唯一能释放该互斥量的线程一个互斥量能多次被同一线程获取释放的次数也需要和获取的次数一样多只有这样互斥量才能再次变为自由态有信号 使用方法 从非分页池中分配一个KMUTEX、调用KeInitializeMutex初始化互斥量线程中使用等待函数KeWaitForSingleObject或KeWaitForMultipleObjects去等待这个互斥量获取互斥量后的业务代码执行完毕后调用KeReleaseMutex释放该互斥量 如果一个线程没有释放互斥量就死亡了此时内核会显式的释放这个互斥量然后下一个企图获取该互斥量的线程会在等待函数中接收到STATUS_ABANDONED返回值 5.5.3 快速互斥量FAST_MUTEX 快速互斥量是传统互斥量的一个替代提供了更好的性能 快速互斥量并不是一个可等待对象它有自己的获取和释放的API 快速互斥量不能被递归的获取会造成死锁快速互斥量被获取后IRQL会提升到APC_LEVEL这会阻止线程上APC的传递快速互斥量只能无限等待不能设置超时时间快速互斥量并未显露给用户模式用户模式只能使用正式互斥量 多数需要使用互斥量的程序都应当使用快速互斥量除非有必要的理由去使用正式互斥量 使用方法 从非分页池中分配一个AST_MUTEX调用ExInitializeFastMutex初始化快速互斥量线程中使用ExAcquireFastMutex或ExAcquireFastMutexUnsafe来获取快速互斥量获取快速互斥量后的业务代码执行完毕后调用ExReleaseFastMutex或ExReleaseFastMutexUnsafe释放该快速互斥量 5.5.4 可等待对象信号量KSEMAPHORE 使用方法 调用KeInitializeSemaphore初始化信号量设置一个最大值和初始值通常是最大值内部值大于0时信号量处于有信号工作线程调用KeWaitForSingleObject来等待信号量当有信号时将结束等待同时信号量值减一当信号量内部值达到0时信号量变成无信号状态 一个最大值为1的信号量是否相当于互斥量区别在于信号量没有所有权一个线程获得信号量可以被另一个线程释放 5.5.5 可等待对象事件KEVENT 事件封装了一个布尔值的标志要么真有信号要么假无信号 事件有两种类型 通知事件手动重置这种事件被触发后会释放所有正在等待的线程并且事件的状态保持有信号的状态直到被显式重置同步事件自动重置这种事件被触发后最多释放一个线程并且释放之后此事件会自动回到无信号状态 使用方法 从非分页池中分配KEVENT结构并指明事件类型NotificationEvent或SynchronizationEvent和事件初始状态Signaled或non-signaled然后调用KeInitializeEvent进行初始化工作线程调用等待函数等待该事件通过调用KeSetEvent触发事件工作线程结束等待开始执行后续代码通过KeResetEvent或KeClearEvent速度更快因为无需返回之前的状态重置该事件为无信号 5.5.6 执行体资源 执行体资源用于单写多读的场景是一种特殊对象不属于可等待对象 使用方法 从非分页池中分配ERESOURCE结构调用ExInitializeResourceLite初始化该执行体资源写线程调用ExAcquireResourceExclusiveLite获取排他锁用于写读线程调用ExAcquireResourceSharedLite获取共享锁用于读无论读线程还是写线程工作完成后调用ExReleaseResourceList释放锁最后不再使用该执行体资源时调用ExDeleteResourceLite释放执行体资源 调用获取和释放锁的先决条件是必须禁止通常的内核APC可以在获取锁前调用KeEnterCriticalRegion以及释放锁后调用KeLeaveCriticalRegion ERESOURCE resource; void WriteData() {KeEnterCriticalRegion();ExAcquireResourceExclusiveLite(resource, TRUE);//do somethingExReleaseResourceLite(resource);KeLeaveCriticalRegion(); }为了简化上述代码 对于排他锁提供ExEnterCriticalRegionAndAcquireResourceExclusive函数进入关键段然后获得排他锁 对于共享锁提供ExEnterCriticalRegionAndAcquireResourceShared函数进入关键段然后获得共享锁 提供 ExReleaseResourceAndLeaveCriticalRegion释放排他锁或共享锁然后离开关键段 5.6 高IRQL同步 在IRQLDISPATCH_LEVEL时线程不可进行等待无法调用等待函数 由于此时线程调度器无法工作因此不存在线程之间的资源冲突而是存在处理器当前正在运行的线程之间的资源冲突 5.6.1 自旋锁 自旋锁是内存中的一个简单位用于处理器之间的同步 当CPU想要获取自旋锁而它当前并不自由时CPU会一直在自旋锁上自旋即一直进行while空循环直到自旋锁被另一个CPU释放变成自由状态 自旋锁必须只能在IRQL DISPATCH_LEVEL上获取和释放 使用方法 从非分页池中分配一个KSPIN_LOCK结构调用KeInitializeSpinLock初始化自旋锁将自旋锁置于自由状态如果当前IRQL不是DISPATCH_LEVEL那么提升IRQL到DISPATCH_LEVEL调用自旋锁获取函数进行业务代码释放自旋锁 KeAcquireSpinLock获取自旋锁并将IRQL提升至DISPATCH_LEVEL 注释1只能在IRQL为DISPATCH_LEVEL时调用只获取锁而不改变IRQL常用在DPC例程中 注释2常用于对ISR和别的函数之间的同步参数是一个中断对象自旋锁是它的一部分 注释3用于操作基于LIST_ENTRY的链表函数将使用提供给它的自旋锁并将IRQL提升至HIGH_LEVEL 取消自旋锁内核在调用驱动程序注册的取消例程之前会先获得这个自旋锁这是驱动程序释放一个不是由它主动显式申请的自旋锁的唯一情形 5.7 工作项目 分离耗时较长业务到单独的线程中可用PsCreateSystemThread和IoCreateSystemThread(windows8及以上可用) 分离耗时较短业务到内核提供的线程池使用工作项目 工作项目在系统线程池中排队的函数。驱动程序可以分配和初始化工作项目使其指向驱动程序希望执行的函数然后让工作项目在线程池中排队 工作项目类似于DPC但工作项目总是在IRQL_PASSIVE_LEVEL上执行这常被用来在IRQLDISPATCH_LEVEL上执行PASSIVE_LEVEL的操作 创建工作项目两种方式 使用IoAllocateWorkItem分配和初始化工作项目。返回一个指向IO_WORKITEM的指针用完后调用IoFreeWOrkItem释放它使用IoSizeofWorkItem提供的大小动态分配一个IO_WORKITEM然后调用IoInitializeWorkItem初始化在用完工作项目之后调用IoUninitializeWorkItem 这两个函数都接收设备对象作为参数所以需要确保工作项目在排队或执行时驱动程序没有卸载 将工作项目进行排队IoQueueWorkItem void IoQueueWorkItem([in] __drv_aliasesMem PIO_WORKITEM IoWorkItem, //the work item[in] PIO_WORKITEM_ROUTINE WorkerRoutine, //the function to be called[in] WORK_QUEUE_TYPE QueueType, //queue type[in, optional] __drv_aliasesMem PVOID Context //driver-defined value );//驱动程序提供的回调函数WorkerRoutine原型如下 IO_WORKITEM_ROUTINE IoWorkitemRoutine;void IoWorkitemRoutine([in] PDEVICE_OBJECT DeviceObject,[in, optional] PVOID Context ) {...}系统线程池由多个队列基于服务这些工作项目的线程优先级进行区分级别如下 CriticalWorkQueue线程优先级 13 DelayedWorkQueue线程优先级 12 HyperCriticalWorkQueue线程优先级 15 NormalWorkQueue线程优先级 8 BackgroundWorkQueue线程优先级 7 RealTimeWorkQueue线程优先级 18 SuperCriticalWorkQueue线程优先级 14 MaximumWorkQueue CustomPriorityWorkQueue 虽然文档中指明必须使用DelayedWorkQueue但是实际上任何支持的级别都可以使用 另一个用于将工作项目排队的函数IoQueueWorkItemEx使用一个不同的回调函数多了一个参数即工作项本身。用于在工作项目函数需要退出之前释放自身 6. 工作请求包IRP 6.1 IRP简介 IRP由执行体中IO管理器、即插即用管理器、电源管理器之一从非分页池中分配也可由驱动程序分配 IRP遵循谁分配谁释放的原则IRP从不单独分配总是伴随着一个或多个IO_STACK_LOCATION一起分配当一个IRP被分配时调用者必须指明有多少个IO_STACK_LOCATION需要跟IRP一起分配IO_STACK_LOCATION在内存中紧跟IRP后面且每一个IO_STACK_LOCATION对应设备栈中的一个设备对象当收到IRP时需要通过调用IoGetCurrentIrpStackLocation函数获得属于当前驱动使用的IO_STACK_LOCATION 6.2 设备栈 Window的IO系统以设备为中心而非以驱动为中心一个驱动中可以创建多个设备 设备对象被分为三类 PDO物理设备对象由总线驱动程序创建即某些设备位于此总线的设备槽里FDO功能设备对象由真正的驱动程序创建驱动程序由充分了解设备细节的硬件厂商提供FIDO过滤设备对象由过滤驱动程序创建的可选过滤设备 举例PCI网卡驱动安装事件序列如下 PCI总线驱动程序pci.sys识别出某个槽里有设备它创建一个PDO来表示这个事实。总线驱动不知道这是个网卡还是显卡还是别的它只知道有个东西在那里并能从控制器中取得基本信息比如开发商的ID和设备IDPCI总线驱动程序通知pnp管理器在它的总线上发生了一些变化pnp管理器请求由总线驱动程序管理的最新PDO列表pnp管理器现在的工作就是发现并装在这个新PDO的驱动程序发送一个查询给总线驱动请求完整的硬件设备IDpnp管理器得到硬件ID之后在注册表中查阅HKLM\System\CurrentControlSet\Enum\PCI\(硬件ID)如果该PDO的驱动程序已经装载过了就会在这个位置注册pnp管理器就直接装载已经在这个注册表里注册好的驱动程序驱动程序装载并创建FDO但是增加了一个对IoAttachDeviceToDeviceStack的调用将自己置于PDO之上 如果过滤驱动程序在注册表中注册表了那么也会被一起装载 过滤器会在两个地方搜索 HKLM\System\CurrentControlSet\Enum\PCI\(硬件ID)HKLM\System\CurrentControlSet\ControlClasses\{ClassGuid值}硬件ID对应的类别值名称为LowerFilters和UpperFilters 6.3 处理IRP的几种方式 IRP由执行体中的管理器创建管理器只初始化主IRP结构和第一个IO栈的位置然后就把IRP传递给设备栈的最上层设备 包含设备栈最上层设备的驱动程序在相应的分发例程中收到这个IRP调用分发例程分发例程中处理IRP 由于IO管理器只初始化第一个IO栈位置因此每个接收到IRP的设备都需要给下一层设备初始IO栈位置 处理IRP的方式 将IRP向下传递 如果驱动程序的设备不是设备节点中的最后一个驱动程序对此IRP不感兴趣可以将请求传递下去 第一种方法是通过IoCopyIrpStackLocationNext来复制当前的IO栈位置到下一层 第二种方法是通过IoSkipCurrentIrpStackLocation将IRP内部指向当前IO栈位置的指针减一后续调用IoCallDriver时这个指针又会加一从而下一层设备就能看到和当前设备看到一样的IO栈位置了。 调用IoSkipCurrentIrpStackLocation 以确保下一个设备能看到和这个设备一样的信息必须能看到同一个IO栈位置调用IoCallDriver传递IRP给下层设备对象驱动调用IoAttachDeviceToDeviceStack时获得 完全处理此IRP 收到IRP的驱动程序自己可以完全处理此IRP无需往下传播 调用IoCompleteRequest来结束此IRP任何下层的设备都不会看到这个请求 对此IRP进行检查做一些事情比如记录请求或对下一个IO栈位置进行修改然后继续往下传递往下传递请求并在下层设备完成请求时得到通知 任何一层除了最终完成IRP的底层都能设置一个IO完成例程当某个下层完成请求时完成例程就会被调用 调用IoSetCompletion设置一个IO完成例程 开始某种异步IRP处理 驱动程序处理该请求但是如果处理事件较长驱动程序可以将此IRP标记挂起 调用IoMarkIrpPending将此IRP标记挂起分发例程返回STATUS_PENDING调用IoCompleteRequest来完成此IRP 一旦某层调用IoCompleteRequest该IRP被完成IRP将朝着发起IRP的方向进行依次传送。如果注册了完成例程也将会按照先注册的后调用的顺序被逐个调用。 6.4 IRP和IRP_STACK_LOCATION结构解析 IRP结构解析 IoStatus包含IRP的StatusNT_STATUS和一个Information字段意义取决于IRP类型对于读和写是操作中的字节数UserBuffer包含原始的缓冲区指针指向相关IRP的用户缓冲区UserEvent一个事件对象如果客户程序调用是异步的那么客户程序会提供这个事件对象AssociatedIrp联合体 SystemBuffer最常用指向一个系统分配的非分页缓冲区用于有缓冲的IO操作MasterIrp如果本IRP是一个关联IRP那么此成员是指向主IRP的指针。一个主IRP可以有多个关联IRP一旦所有的关联IRP都完成了主IRP就自动完成了IrpCount如果IRP是一个主IRP该成员代表和主IRP关联的IRP数目 Cancel Routine取消例程如果操作被要求取消如用户模式调用CancelIo和CancelIoEx取消例程会被调用MdlAddress指向一个可选的内存描述符列表MDL一种内核数据结构用于描述RAM中的缓冲区主要用于直接IO IRP中的IO_STACK_LOCATION结构解析 MajorFunctionIRP的主功能代码如IRP_MJ_CREATE、IRP_MJ_READ等MinorFunction部分IRP有次功能代码如IRP_MJ_PNP、IRP_MJ_POWER、IRP_MJ_SYSTEM_CONTROL等FileObject此IRP相关联的FILE_OBJECTDeviceObject此IRP相关联的设备对象分发例程会收到一个指向此设备对象的指针因此一般无需访问这个字段CompletionRoutine完成例程由上一层设备对象调用IoSetCompletionRoutine所设置Context传递给完成例程的参数Parameters联合体不同的IRP使用特定的某个操作如IRP_MJ_READ中Parameters.Read结构表示读操作的更多信息 使用IoGetCurrentIrpStackLocation取得当前的IO_STACK_LOCATION 调试IRP可以使用!irpfind命令检索当前非分页池中的IRP使用!irp解析单个irp 6.5 分发例程 分发例程就是通过IRP主功能号连接起来的函数DRVIER_OBJECT结构中的majorFunction字段 分发例程的原型如下 typedef NTSTATUS DRIVER_DISPATCH {_In_ PDEVICE_OBJECT DeviceObject,_Inout_ PIRP Irp };IRP_MJ_CREATE对应用户模式下的CreateFile或内核模式下的ZwCreateFileIRP_MJ_CLOSE对应用户模式下的CloseHandle或内核模式下的ZwCloseIRP_MJ_READ对应读操作通常对应用户模式下的ReadFile或内核模式下的ZwReadFileIRP_MJ_DEVICE_CONTROL对应用户模式下的DeviceIoControl或内核模式下的ZwDeviceIoControlFile内核中还有其他api可以生成IRP_MJ_INTERNAL_DEVICE_CONTROL和IRP_MJ_DEVICE_CONTROL类似但只能从内核调用 6.6 完成请求 一旦驱动程序决定处理IRP意味着必须最终完成它 完成请求指在填写完请求状态和其他信息后调用IoCompleteRequest 示例代码 NTSTATUS MyDispatchRoutine(PDEVICE_OBJECT devObj, PIRP Irp) {Irp-IoStatus.Status STATUS_XXX;Irp-IoStatus.Information NumberOfBytesTransfered;IoCompleteRequest(Irp, IO_NO_INCREMENT);return STATUS_XXX; }在调用IoCompleteRequest后Irp可能就已经被释放掉了,所以在完成Irp之后不要再去使用Irp了如return Irp-Iostatus.Status在处理Irp过程中出现错误时Information字段必须被设置为零而在操作成功时这个字段的含义取决于Irp类型 6.7 访问用户缓冲区 通常分发例程在IRQL0和请求线程的上下文中被调用这意味可以轻松直接访问用户提供的缓冲区因为IRQL0所以可以忽略页错误并且由于时请求线程上下文因此用户模式传递的指针在此处理过程中有效。 但是在一些不便利的情况下将无法直接访问用户缓冲区需要用到缓冲IO和直接IO 6.7.1 缓冲I/O 为了支持缓冲IO的读写操作设备对象必须设置一个标志DeviceObject-Flags | DO_BUFFER_IO; 缓冲IO举例当一个读或写操作到达IO管理器和驱动程序的步骤 IO管理器从非分页内存池中分配一个跟用户缓冲区一样大小的缓冲区并将其指针保存到IRP的AssociatedIrp-SystemBuffer成员中缓冲区大小可以从Parameters.Read.Length或Parameters.Write.Legnth中获得对于写请求而言IO管理器会将用户缓冲区复制到系统缓冲区中直到此时驱动程序的分发例程才会被调用驱动程序无需检查直接访问系统缓冲区因为IO管理器分配至内核地址空间内且是非分页内存池一旦驱动程序完成了这个IRPIoCompleteRequest对于读请求IO管理器就把系统缓冲区复制回用户缓冲区中复制的大小由Irp的IoStatus.Information字段决定由驱动程序设置最终IO管理器释放系统缓冲区 IO管理器是怎样在IoCompleteRequest中将系统缓冲区复制到用户缓冲区中的 答通过将一个特殊内核APC排队到最初发出请求的线程中来实现一旦此线程获得CPU的执行权第一件事就是执行这个APCAPC中执行复制操作 缓冲IO的特点 便于使用只要在设备对象中指明标志后续所以事情都由IO管理器完成总是涉及复制 意味着最好用于小缓存区通常不超过1页 6.7.2 直接I/O 直接IO的目的是允许在任何IRQL和线程中访问用户缓冲区但是不需要在前后进行复制 为了支持直接IO的操作设备对象必须设置一个标志DeviceObject-Flags | DO_DIRECT_IO 直接IO举例当一个读或写操作到达IO管理器和驱动程序的步骤 IO管理器确认用户缓冲区是合法的然后利用页错误将其装入物理内存然后IO管理器通过MmProbeAndLockPages将缓冲区锁定在内存中因此在另行通知之前它不会被换出。这就解决了缓冲区访问的问题之一不能发生页错误IO管理器构造一个内存描述符列表MDL这是一个描述如何将缓冲区映射到RAM的数据结构此数据结构的地址保存在IRP的MdlAddress字段中此时驱动的分发例程被调用分发例程调用MmGetSystemAddressForMdlSafe将MDL映射到系统空间内映射完毕后就能直接读取系统空间地址所描述的内存了一旦驱动完成了请求IO管理器会移除第二个映射映射成系统地址那个释放MDL并将用户缓冲区通过MmUnlockPages解锁因此它就能和其他用户模式内存一样被正常的换出了。 可以多次调用MmGetSystemAddressForMdlSafe。MDL保存有一个标志用来指示系统映射是否已经被执行过了。如果是它仅仅返回已经存在的指针 6.7.3 Neither I/O 在设备对象的标志中既没有设置DO_BUFFERED_IO也没有设置DO_DIRECT_IO的驱动程序使用无I/ONeither I/O方式这单纯表示驱动程序不会从I/O管理器得到任何帮助怎么处理用户缓冲区完全取决于驱动程序自身。 6.8 IRP_MJ_DEVICE_CONTROL用户缓冲区 用户模式下DeviceIoControl函数原型 BOOL DeviceIoControl([in] HANDLE hDevice, //设备或文件句柄[in] DWORD dwIoControlCode, //IOCTL code[in, optional] LPVOID lpInBuffer, //input Buffer[in] DWORD nInBufferSize, //size of input buffer[out, optional] LPVOID lpOutBuffer, //output Buffer[in] DWORD nOutBufferSize, //size of output Buffer[out, optional] LPDWORD lpBytesReturned, //count of bytes actually returned[in, out, optional] LPOVERLAPPED lpOverlapped //for async, operation );访问输入输出缓冲区的方式取决于控制码 #define CTL_CODE( DeviceType, Function, Method, Access ) ( \((DeviceType) 16) | ((Access) 14) | ((Function) 2) | (Method) \ )DeviceType标识设备的类型FILE_DEVICE_XXX的常数微软规定第三方的值必须从0x8000开始Function一个数字码用于区分不同的操作微软规定第三方驱动需要从0x800开始method输入输出缓冲区的传递方式Access指明这个操作是读还是写FILE_WRITE_ACCESS、FILE_READ_ACCESS、FILE_ANY_ACCESS 其中method描述了如何访问输入输出缓冲区 METHOD_NEITHER无需IO管理器的帮助驱动程序自行处理缓冲区用于控制代码自身就是全部信息无需缓冲区的情况下。 输入缓冲区的指针在当前IO栈位置的Parameters.DeviceIoControl.Type3InputBuffer字段中 输出缓冲区的指针在IRP的UserBuffer字段中METHOD_BUFFERED, 输入和输出缓冲区两者均使用缓冲IO。请求开始时IO管理器使用输入输出缓冲区大小的最大值从非分页池中分配系统缓冲区。然后它将输入缓冲区复制到系统缓冲区然后才会调用IRP_MJ_DEVICE_CONTROL的分发例程。请求完成后IO管理器将系统缓冲区复制到输出缓冲区复制的字节数由IoStatus.Information字段指出 系统缓冲区的指针在它常在的地方IRP结构的AssociatedIrp.SystemBufferMETHOD_IN_DIRECT和METHOD_OUT_DIRECT输入缓冲区使用缓冲IO输出缓冲区使用直接IO。唯一区别是 METHOD_IN_DIRECT输出缓冲区可读输出缓冲区也能作为输入使用 METHOD_OUT_DIRECT输出缓冲区可写 Method输入缓冲区输出缓冲区METHOD_NEITHER无无METHOD_BUFFERED缓冲缓冲METHOD_IN_DIRECT缓冲直接METHOD_OUT_DIRECT缓冲直接
http://www.hkea.cn/news/14521169/

相关文章:

  • 婚纱网站模板素材国外平台卖货
  • 营销网站的设计思路重庆手机网站推广方法
  • 阿里云做的网站怎么备份163企业邮箱注册入口
  • 济南 制作网站 公司吗学做衣服的网站有哪些
  • 京山网站建设泰安市住房和城乡建设局网站
  • 医疗网站前置备案汽车网站建设方案预算
  • 网站开发兼容ie2023网站seo
  • 做网站容易学吗网络推广的优势
  • 虚拟主机做视频网站可以吗招聘网站怎么做市场
  • 青岛网站建设在线网站开发工具需求
  • 做网站怎么每天更新内容深圳互联网公司
  • 甘肃省建设部网站使用经典wordpress编辑器使用手册
  • 做网站大概多少钱php网站开发优势
  • 个人博客网站下载开发直播app多少钱
  • 怎样加快网站收录哈尔滨网站只做
  • 开发区网站开发语言windows 没有wordpress
  • 专业网站设计服务在线咨询做点心的网站
  • 深圳网站设计公司哪家便宜国家企业信用信息系统公示查询官网
  • 安徽网站建设方案开发专注手机网站建设
  • 湛江网站建设制作维护wordpress+企业库插件
  • 石家庄网站排名wordpress自定义页面创建专辑
  • 购买了域名之后怎么做网站公司logo设计在线生成免费设计入口
  • 旅行社电商网站怎么做免费建网上商城
  • 响应式网站开发方案百度竞价推广代运营公司
  • 山东济南做网站公司自己的网站怎么做进销存
  • 仙居网站建设wordpress创始人赚钱吗
  • 建站之星换模板网页设计实验报告实验1
  • 营销网站的建立新洲网站建设
  • 网站上常用字体无域名公司注册
  • 马鞍山做网站的公司软文自助发稿平台