【驱动开发】003 内存、链表、锁

一、内存申请、释放

     在C开发R3层应用程序中,我们经常要用malloc申请内存,free释放内存;到了内核也有对应的函数申请内存和释放内存。

     内核中微软比较推荐使用的是ExAllocatePoolWithTag和ExFreePoolWithTag,当然还有其他的函数。这对函数比较特殊的一点是有个Tag标签。这个标签的作用就是用来检测内存泄露的。系统根据这个Tag可以大概知道内存泄露的来源,这个标签可以是随意的32位数字,即使冲突也不会有问题。

    下面给一个内核中使用这对函数动态申请内存的小demon:

#include <ntddk.h>

#define MEM_TAG 'MyTg'

VOID DriverUnload(PDRIVER_OBEJCT driver)
{
	IoDeleteDevice(driver->DeviceObject);
	DbgPrint("驱动卸载完成。。。\r\n");
}

NTSTATUS DriverEntry( PDRIVER_OBJECT driver, PUNICODE_STRING reg_path )
{
	NTSTATUS status ;
	UNICODE_STRING dst = {0};
	UNICODE_STRING src = RLT_CONSTANT_STRING(L"Source String !");
	src.Length = src.MaximumLength = wcslen(src.Buffer)*sizeof(WCHAR) ;
	dst.Buffer = (PWCHAR)ExAllocatePoolWithTag(NonPagedPool,src.length, MEM_TAG ) ;  
       // 注意这里不需要再多申请一个1*sizeof(SIZE_T) 来存放结束标识符了,因为UNICODE_STRING最标注的判断就是长度
        if(dst.Buffer == NULL)
	{
		status = STATUS_INSUFFICIENT_RESOURCES ;
<span style="white-space:pre">		</span>return status;
	}
	dst.Length = dst.MaximumLength = src.Length ;

	RtlCopyUnicodeString(&dst,&src);
#if DBG
	DbgPrint("%wZ",&dst);    // 注意打印结构体 %wZ 对应结构体指针;打印字符串(确定是0结尾)%wS dst.buufer
#endif 
	ExFreePoolWithTag(dst.Buffer,MEM_TAG);
<span style="white-space:pre">	</span>return STATUS_SUCCESS;
}
          上面这句话: dst.Buffer = (PWCHAR)ExAllocatePoolWithTag(NonPagedPool,src.length, MEM_TAG ) ;  内核中动态申请内存;第一个参数用来标记申请的内存时候锁定在真实物理内存中。NonPagedPool 当发生缺页中断时候 这些内存不会被置换到硬盘中,永久驻留在物理内存中,直到ExFreePool或者ExFreePoolWitTag被调用,才从物理内存中释放掉。对应的一个是pagedPool 可以被置换到硬盘中。这样,大家应该就可猜出这个参数的意图。当我们申请的内存用来存放要执行的shellCode或者汇编代码的时候,必须放在NonPagePool内存中;当我们申请的空间想用来存放一些信息,比如 文件结构信息 可以用PagedPool。函数第二个参数:用来指定申请内存的长度,单位是字节。注意这个Length = wcslen(str)*sizeof(WCHAR) 这个是UNICODE_STRING 计算方法,也可以再多申请2个字节用来存放‘\0’'\0' 。第三参数就是Tag标签,一个32位的数字。

         内存申请完毕后一定要释放掉。ExFreePoolWithTag(dst.Buffer,MEM_TAG);


二、 链表 LIST_ENTRY

         windows内核中有已经定义好的链表,这样我们就不用自己设计链表,我们只要按照约定调用即可。要注意一点:LIST_ENTRY是一个双向链表。


三、 自旋锁 KSPIN_LOCK

         在内核中必须考虑多线程,否则程序极易发生崩溃引发BSOD。内核引入自旋锁和队列自旋锁。这里我们先讨论自旋锁。

         下面给个LIST_ENTRY 和使用自旋锁的例子。《Windows内核安全与驱动开发》这本书中的LIST_ENTRY代码是有错误的。具体错误(保密),等自己用到这节知识的时候自然会发现书中的错误。

         下面是我写的双向列表和自旋锁的例子:

// list_entry 测试
// KSPIN_LOCK 多线程加锁

#include <ntddk.h>
#include <ntstrsafe.h>
#define  MEM_TAG 'MyTg'
LIST_ENTRY my_list_entry_head ;
KSPIN_LOCK my_list_lock ;

typedef struct{
	LIST_ENTRY list_entry ;
	PFILE_OBJECT file_object;
	PUNICODE_STRING file_name ;
	PLARGE_INTEGER file_length ;
}MY_FILE_INFOR, *PMY_FILE_INFOR ;

// 初始化链表头 初始化自旋锁
VOID MyFileInforInit()
{
	InitializeListHead(&my_list_entry_head);
	KeInitializeSpinLock(&my_list_lock);
	/*
	note:链表和自旋锁初始化完成后,采用一系列的加锁操作代替普通的操作
	*/
}

// 向链表中增加一个节点,注意file_name是外面分配的
NTSTATUS MyFileInforAppendNode(
	PFILE_OBJECT file_object,
	PUNICODE_STRING pfile_name,
	PLARGE_INTEGER file_length)
{
	// 给节点申请内存用来存放信息
	PMY_FILE_INFOR my_file_infor = (PMY_FILE_INFOR)ExAllocatePoolWithTag(PagedPool,sizeof(MY_FILE_INFOR),MEM_TAG) ;
	if( my_file_infor == NULL )
	{
		DbgPrint("申请内存失败!\r\n");
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	// 填写信息
	my_file_infor->file_object = file_object ;
	my_file_infor->file_name = pfile_name ;
	my_file_infor->file_length = file_length ;

	// 将数据插入链表,注意这里没有使用锁所以在多线程环境下是不安全的
	//InsertHeadList(&my_list_entry_head,&(my_file_infor->list_entry)) ;
	// 采用自选锁的方式插入 多线程安全
	ExInterlockedInsertHeadList(&my_list_entry_head,&(my_file_infor->list_entry),&my_list_lock);
	return STATUS_SUCCESS;
}

// 遍历整个链表
VOID MyFileInforPrint()
{
	PLIST_ENTRY p ;
	for( p=my_list_entry_head.Flink; p != &(my_list_entry_head);p = p->Flink)
	{
		PMY_FILE_INFOR elem = CONTAINING_RECORD(p,MY_FILE_INFOR,list_entry);
		DbgPrint("FileName = %wZ \r\n",(elem->file_name));
	}
}

// 清空链表 释放内存
VOID MyFileInforFree()
{
	KdPrint(("Dump所有结点:\n"));
	while(!IsListEmpty(&my_list_entry_head))
	{
		// 从头部移除 这种多线程不安全
		// PLIST_ENTRY plist = RemoveHeadList(&my_list_entry_head) ;
		// 采用多线程带锁操作
		PLIST_ENTRY plist = ExInterlockedRemoveHeadList(&my_list_entry_head,&my_list_lock);
		// 从尾部移除
	//	PLIST_ENTRY plist = RemoveTailList(&my_list_entry_head) ;
		PMY_FILE_INFOR p = CONTAINING_RECORD(plist,MY_FILE_INFOR,list_entry);
		// 打印数据
		DbgPrint("FileName = %wZ \r\n",(p->file_name));
		
	}
}


VOID DriverUnload( PDRIVER_OBJECT driver )
{
	DbgPrint("List entry driver unload ...\r\n");
}

NTSTATUS DriverEntry( PDRIVER_OBJECT driver, PUNICODE_STRING reg_path )
{
	INT i ;
	UNICODE_STRING str[6] = {0};
	PWSTR pbuffer = NULL ;
	MyFileInforInit();
	for(i=0; i<6 ;i++)
	{
		
		pbuffer = (LPWSTR)ExAllocatePoolWithTag(PagedPool,128,MEM_TAG);
		RtlStringCbPrintfW(pbuffer,128,L"this is :%d string",i+1);
		RtlInitUnicodeString(&str[i],pbuffer);
		// 添加节点
		MyFileInforAppendNode(NULL,&str[i],NULL);
	}

	// 遍历节点
	MyFileInforPrint();
	// 删除节点
	MyFileInforFree();

	for( i = 0 ;i <6;i++ )
	{
		ExFreePoolWithTag(str[i].Buffer,MEM_TAG);
	}

	driver->DriverUnload = DriverUnload ;
	return STATUS_SUCCESS;
}

四、队列自旋锁

         可以理解为自选锁的升级版。它和普通自选锁相比在多CPU平台上有更高的性能表现,并且遵循队列“First In,First Out”的原则。

简单用法:

// 初始化队列自旋锁

KSPIN_LOCK my_queue_spinlock = {0} ;

KeInitializeSpinLock( &my_queue_spinlock );

// 获取与释放

KLOCK_QUEUE_HANDLE my_lock_queue_handle ;

KeAcquireInStackQueuedSpinLock( &my_queue_spinlock, my_lock_queue_handle  );

// do something ....

KeReleaseInStackQueuedSpinLock(&my_lock_queue_handle);


队列自旋锁使用增加了一个KLOCK_QUEUE_HANDLE的数据结构,这个结构也唯一表示了队列自旋锁。

 

note: 自旋锁和队列自旋锁本质上都是一样,但是两个锁不能交叉使用。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值