Windows内核编程(三) 基础概念

基础概念

上下文环境:一名合格的驱动开发者需要时时刻刻知道当前驱动所处的上下文环境,因为内核是共享的,稍不留神直接蓝屏。
驱动入口函数和驱动卸载函数都隶属于进程pid为4的SYSTEM系统进程。其代表的是系统内核。一般来说,内核代码都运行在SYSTEM进程空间中,但是驱动对象的派遣例程一般在发起请求的进程中。

中断请求级别(IRQL):高中断请求级别可以中断低中断请求级别的执行。
下面进行举例:
PASSIVE_LEVEL: 0 应用层线程以及大部分内核函数都处于该级别,可以无限制使用所有的内核API,可以访问分页和非分页内存。
APC_LEVEL:1 异步方法调用(APC) 或页错误处理时处于的级别。可以使用大部分内核API,可以访问分页和非分页内存。
DISPATCH_LEVEL:2 延迟方法调用(DPC)时处于该级别。可以使用特定的内核API,只能访问非分页内存。

不同的虚拟地址可以对应同一个物理地址,这种情况发生的时候,会进行外存置换。
所以说分页内存意味着这个内容可以放到磁盘上,非分页意味着这段内容不能放到磁盘上。

我们在判断当前执行过程所处的中断请求级别时,有两种方法:
一个是静态的,就是WDK帮助文档指明了函数的IRQL。
一个是动态的,某些回调函数他被调用的时候可能IRQL就发生了改变,可能没调用的时候时PASSIVE_LEVEL,调用的时候就成了APC_LEVEL了,这个时候我们可以在回调函数中使用KeGetCurrentIrql()函数来获取当前的IRQL。

驱动异常(蓝屏):
引发蓝屏的原因有很多,高IRQL死锁,内存访问违例,函数堆栈不平衡等。
一个主动引发蓝屏的函数KeBugCheckEx(五个参数);主动引发蓝屏的情况就是,比如:驱动测试的时候,对于一些开发人员感到绝对不可能出现问题的情况设置这个函数,这样运行的过程中出了问题就很明显了。

字符串操作:

RtlInitUnicodeString(
	PUNICODE_STRING DestinationString,
	PCWSTR SourceString
	)

这个函数是将一个以’\0’结尾的WCHAR转换为unicode_string,但是有一点,函数并没有给转换过后的字符串分配空间,而是buffer指向原字符串的首地址。所以改变SourceString,那么转换后的字符串也受影响。

RtlUnicodeStringCopyString(
	PUNICODE_STRING DestinationString
	NTSTRSAFE_PCWSTR pszSrc
	)

这个函数相比于之前的函数添加了拷贝功能,但是给引入头文件“Ntstrsafe.h”。而且还要引入动态链接库。TARGETLIBS = $(DDK_LIB_PATH)\ntstrsafe.lib

链表操作:
WDK里面的链表的定义

typedef struct _LIST_ENTRY{
	struct _LIST_ENTRY *Flink;   //指向后一个
	struct _LIST_ENTRY *Blink;		//指向前一个
} LIST_ENTRY,*PLIST_ENTRY;

这种链表和应用层的链表不太一样,其中不包含实质内容,他是作为结构体成员而引入的。
一般为了方便操作,会定义一个链表头节点,这样使用。

LIST_ENTRY ListHeader = {0};
InitializeListHead(&ListHeader);//初始化头节点,就是将其两个指针指向自己。
//节点插入,插入到头节点后,插入到链表尾部,第一个参数都是头节点,所以我们使用链表的时候千万别忘了头节点。
InsertHeadList(&ListHeader,&entry.ListEntry);
InsertTailList(&ListHeader,&entry.ListEntry);

下面来说一个获取外层结构体首地址的宏,第一个为当前结构体的链表地址,第二个为结构体类型,第三个为结构体中链表的变量名称。

CONTAINING_RECORD(PCHAR Address,TYPE Type,PCHAR Field);

节点的移除有三种方式,移除头节点,尾节点,特定节点。

PLIST_ENTRY RemoveHeadList(PLIST_ENTRY ListHead);
PLIST_ENTRY RemoveTailList(PLIST_ENTRY ListHead);
BOOLEAN RemoveEntryList(PLIST_ENTRY Entry);
isListEmpty()//判断当前链表是不是空链表,空链表就是只有头结点的链表

自旋锁:
自旋锁是内核中提供的一种高IRQL锁,用同步以及独占的方式访问整个资源。

KSPIN_LOCK spin_lock;
KeInitializeSpinLock(&spin_lock);	//初始化一个自旋锁
KIRQL irql;   //中断级别
KeAcquireSpinLock(&spin_lock,&irql);    //会提高中断级别,传入irql返回旧的中断级别
KeReleaseSpinLock(&spin_lock,irql);	//

双向链表使用自旋锁:
链表一旦完成了初始化之后,就可以使用加锁操作来替代普通操作。

ExInterlockedInsertHeadList(			//链表插入操作
	&list_head,
	(PLIST_ENTRY)&list_item,
	&spin_lock
);

队列自旋锁:
队列自旋锁在多cpu的平台的表现比普通自旋锁更好,而且遵循“先来先服务原则”。

KSPIN_LOCK queue_spin_lock = {0}KeInitializeSpinLock(&queue_spin_lock);
KLOCK_QUEUE_HANDLE lock_queue_handle;
//可以看出,队列自旋锁的初始化和普通自旋锁初始化一样,但是他多了一个数据结构KLOCK_QUEUE_HANDLE来唯一标识这个队列自旋锁
KeAcquireInStackQueuedSpinLock(&queue_spin_lock,&lock_queue_handle);
KeReleaseInStackQueuedSpinLock(&lock_queue_handle);

内存分配
首先补充一下应用层编程的内存分配概念,然后来对比他和内核态中的不同。
应用层的内存分配涉及到,堆和虚拟内存的区别就是,堆是基于虚拟内存上更小粒度的分割,这个分割由堆管理器管理,根据开发者需求,堆管理器会申请一页或多页虚拟内存,然后对这部分内存进行更小粒度的内存分割与管理。
内核层也有个相似的东西,叫

PVOID ExAllocatePoolWithTag(
	POOL_TYPE PoolType,   //何种类型内存 PagedPool分页  NonPagedPool非分页
	SIZE_T NumberOfBytes,	//多大
	ULONG Tag);		//标志内存使用者,一般用于问题排查,windbg可以查看各tag标志内存使用情况。

分配的内存类型有讲究,一个是 NonPagedPoolNonPagedPoolExcute 这两个等价,都是可执行类型,这样有很大的安全隐患,可以用NonPagedPoolNx 类型来代替,他只产生可读写的效果。
如果不希望使用tag,那么直接调用ExAllocatePool 也行。

VOID ExFreePoolWithTag(PVOID P,ULONG Tag);  //释放内存

旁视列表
如果需要高频率从系统Pool中申请大小固定的内存,那么使用旁视列表会更有优势。
旁视列表相对于传统内存分配的优点在于,其效率很高。传统的内存分配容易造成内存碎片,而旁视列表分配的内存释放了之后该块儿内存不会是放到OS中的Pool中,如果再次申请,那么这部分内存又被分配出来,这种类似缓存的机制提高了性能,减少了碎片。

void ExInitializeNPagedLookasideList()

注册表:
注册表其实是系统文件在内核内存空间的一个映射, 我们操作注册表其实就是在操作这块空间,然后再写回系统文件中。

缓冲区三种方式:

irp->AssociatedIrp.SystemBuffer
按顺序来说一下,这个是最简单的,直接把三环的内容复制到内核空间里面,效率比较低。
irp->UserBuffer
这个是直接把三环的地址传给内核,直接访问,但是涉及到进程切换问题,如果当前在内核中进行了进程切换,那么页表就变了,那么就访问会出现问题
irp->MDLAddress
这个相比前两者有一定的优势,他把三环的页映射到了内核,搞了一个内核的虚拟地址。这样效率比第一种高,然后也不会出现第二种那个问题。
有些请求咱们并不知道它使用的那种方式,所以
三种方式都写上较好一些。
if(irp->MdlAddress!=NULL){
	buffer = (PBYTE)MmGetSystemAddressForMdlSafe(irp->MdlAddress);
}else{
	buffer = (PBYTE)irp->UserBuffer;
	}
if(buffer == NULL){
	buffer = (PBYTE)irp->AssociatedIrp.SystemBuffer;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值