计算机各项技术学习的最优原则,应该是先浏览一片森林,再模仿一棵树,所以在学写第一个驱动程序前,有必要把了解一下驱动运行的背景。大至驱动在计算机中的启动顺序和过程,小至驱动开发中内存的使用,都应该有所了解。下面我来整理下今天所看资料中的一些知识。
一。驱动开发中的内存
1.整个内存的分布
虚拟地址0~0x7FFFFFFF范围内的虚拟内存,即低2GB的虚拟地址被称为用户模式地址;而0x80000000~0xFFFFFFFF范围内的虚拟内存,即高2GB的虚拟内存被称为内核模式地址。Windows规定运行在用户态(Ring3层)的程序只能访问用户模式地址,而运行在核心态(Ring0层)的程序可以访问整个4GB的虚拟内存。
Windows的核心代码和Windows的驱动程序加载的位置都在高2GB的内核地址里。Windows在进程切换时,保持内核模式地址是完全相同的,只改变用户模式地址的映射。
2.程序的分布
一段C程序在内存中的大致分布如下图,如果是R3程序,则这段内存在低2G地址内,如果是R0程序,则这段内存在高2G地址。
3.栈的分布
函数调用中栈的分布如下图所示:
应用栈:默认1M,编译指令/stack可改设其他值
内核栈:系统根据CPU架构而定,x86系统上为12KB,x64系统上为24KB
4.内核内存的分配
windows驱动程序使用的内存资源非常珍贵,分配内存时要尽量节约。和应用程序一样,局部变量是存放在栈空间中的,但栈空间没有不很大(x86 12KB),所以驱动程序不适合递归调用或者大型的结构体。需要大型结构体,请在堆中申请。堆中内存申请的函数有以下几个:
PVOID
ExAllocatePool(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
PVOID
ExAllocatePoolWithTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
PVOID
ExAllocatePoolWithQuota(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
PVOID
ExAllocatePoolWithQuotaTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
其中pooltype为枚举变量,有非分页内存和分页内存;
内存间的复制:
RtlCopyMemory(非重叠)
RtlMoveMemory(允许有重叠)
二,驱动的启动执行过程
前面一篇文章windows的启动顺序中提到了IO系统的初始化是在内核初始化阶段的一阶段完成的。大部分设备驱动是在这阶段完成的,也有些设备驱动比这个早,还有一些后面的开发的具有扩展内核功能的驱动则是由用户启动才加载。
系统中安装的驱动程序分以下四种启动类型:“引导-启动”,“系统-启动”,“自动-启动”,“按需-启动”,分别对应注册表中HKLM_local_machine\SYSTEM\CurrentControlSet\ServiceS\<DriverNAME>的键Start值中的0~3.
0型即“引导-启动”是在系统引导阶段由Ntldr程序加载到系统空间的,内核无需加载他们直接初始化。
1即“系统-启动”是在系统初始化阶段1完成的。
2即“自动-启动”是在系统引导后期,由SCM(service control manager services.exe)加载的。
3即“按需-启动”是在系统启动后由用户或者系统模块需要而启动的。
---------------------------------------------------------------------------
至于驱动的调用过程,我们用windows的API ReadFile为例来说明.
1.windows调用ntdll中的ntReadFile
2.ntdll中的ntReadFile进入到内核模式,调用服务中的ntReadFile
3.系统服务中的ntReadFile函数创建IRP_MJ_READ类型的IRP,然后将这个IRP发送到某个驱动程序的派遣函数中。ntReadFile然后去等待一个事件,这时当前线程进入睡眠状态。
4.在派遣函数中会调用IoCompleteRequest函数去设置刚才等待的事件,睡眠线程恢复运行。