tiechui_lesson07_中断级和自旋锁

一、中断级IRQL

高级别可以打断低级别的调用,同级别不能打断同级别的调用。

 中断级在软件层面分为三级,再高的级别是硬件发送的中断。

  • - 0 pass_level
  • - 1 apc_level
  • - 2 dpc_level 只有硬件中断能打断

1.获取中断级

	DbgPrint("当前执行中断级为 %d\n", KeGetCurrentIrql());

2.提高中断级

DPC这个级别是软件层面能提升到的最高权限,ISR(延迟过程调用)当某个硬件设备引发一个高中断的时候,可以把不那么紧急的任务放在DPC里边来执行,同时又把中断级降低。

DPC是一个队列,当CPU处理完高于DPC中断级的任务后就来找这个表,然后挨个拿出来执行,也可以不插队的形式短暂的把我们当前任务请求级提高到DPC。

	KIRQL originIrql = KeGetCurrentIrql();
	DbgPrint("当前执行中断级为 %d\n", KeGetCurrentIrql());

	//提升中断级

	originIrql = KeRaiseIrqlToDpcLevel();

	DbgPrint("提升后请求中断级为 %d\n", KeGetCurrentIrql());

 在使用API函数的时候关注一下中断级别。

当我们在一个DPC例程里边访问一个换页内存的时候,页面在物理内存中时,一切OK。但是当页面被换到磁盘上的pagefile.sys,缺页中断无法打断DPC过程(弹幕大佬:“准确说缺页中断会产生,但页面错误异常处理函数无法回应中断请求”),结果是访问一个无效内存,导致BSOD

DPC编程的时候不要使用换页内存(动态申请的时候有标志)

二、自旋锁 

防止重入,保证资源使用时的唯一性,比如在尝试重入全局变量。自旋锁的本质是一种忙等待,一直在尝试获取,只有在上锁部分动作执行完毕,解锁完成后,才能被其他请求再次获取。(弹幕大佬:“也就是说获取自旋锁的线程中断级更高,被抢占的概率更小,从而能尽可能快的释放锁,避免其他线程长时间忙等待”)。

应用场景:

设置一个全局变量BYTE PhyBuffer[]设置读写函数来试图并发的处理这个变量

//定义全局变量作为读写操作的目标
BYTE PhyBuffer[] = { 0x11,0x11,0x22,0x33 };

NTSTATUS MyRead(PDEVICE_OBJECT	pdevice, PIRP pirp){
    ...
    //
	//	读取全局变量
	//
	RtlCopyMemory(readbuffer,
		PhyBuffer,
		strlen(PhyBuffer));
    ...
}

NTSTATUS MyWrite(PDEVICE_OBJECT	pdevice, PIRP pirp){
    ...
    //
	//	写入全局变量 如果不加锁便会产生异常
	//
	RtlZeroMemory(PhyBuffer, strlen(PhyBuffer));
	RtlCopyMemory(PhyBuffer, writebuffer, strlen(PhyBuffer));
    ...

}

 那么此时就会出现问题,当读还未完成的时候,对全局变量进行写操作那么会产生异常。需要加一些控制那么确定读或写操作的唯一性,只有当读完成后才进行写操作。

自旋锁使用

使用自旋锁来进行保护:

NTSTATUS MyRead(PDEVICE_OBJECT	pdevice, PIRP pirp){
    ...


	//
	//初始化自旋锁
	//
	KIRQL oldirql = 0;
	KeAcquireSpinLock(&spinlock, &oldirql);


	DbgPrint("自旋锁内获取当前中断级 %d\n", KeGetCurrentIrql());

	//
	//	读取全局变量
	//
	RtlCopyMemory(readbuffer,
		PhyBuffer,
		sizeof(PhyBuffer));

	pirp->IoStatus.Status = status;
	pirp->IoStatus.Information = sizeof(PhyBuffer);


	//
	// 释放自旋锁
	//
	KeReleaseSpinLock(&spinlock, oldirql);

    ...
}

来自微软官方文档: 

  • 在持有旋转锁时,切勿实现执行下列任何一项的例程:

    • 导致硬件异常或引发软件异常。

    • 尝试访问可分页内存。

    • 进行会导致死锁或可能导致旋转锁持有时间超过 25 微秒的递归调用。

    • 如果尝试获取另一个旋转锁,则可能导致死锁。

    • 调用违反上述任何规则的外部例程。

一个视频里使用但是不清楚在什么情况下使用的小技巧😣😣😣,据说NDIS源码里边有使用:

block是布尔类型设置初值为FALSE,这个逻辑是:如果没有上锁那么,我们尝试获取自旋锁,中断级提高,这个时候没办法访问分页内存。不过接下来设置了block为TRUE,假设其他获取自旋锁的地方也通过布尔值先做判别,那么其他地方实际上是没有办法获取自旋锁和处理的,相当于该判断中的其他操作还是具有唯一性。然后再释放自旋锁,降低了中断级,这个时候可以访问分页内存,执行完毕后设置布尔量为FALSE,让其他部分代码获取自旋锁。

这个技巧我觉得很容易出错,因为对同一个全局变量或者其他临界资源进行操作的地方,需要使用同样的逻辑来处理,否则就是单纯的上锁解锁。而且这个本质上是逻辑操作,通过布尔量来选择是否执行一些代码,而不是加锁解锁。🤣🤣🤣

让代码跑在DPC级别中

 除了提高自身中断级别外,让代码跑在DPC中的方法:

  • 插入DPC队列

涉及API:

  • KeInitializeDpc
  • KeInsertQueueDpc

KDPC dpcobj = { 0 }; //DPC对象


VOID DpcRoutie(PVOID context)
{
	UNREFERENCED_PARAMETER(context);
	DbgPrint("DpcRoutie DPC is %d\n", KeGetCurrentIrql());

	return;
}

NTSTATUS DriverEntry(){

    ···

    KeInitializeDpc(&dpcobj, DpcRoutie, NULL);	// 初始化DPC对象
    KeInsertQueueDpc(&dpcobj, NULL, NULL);		// 插入DPC队列

    ···

}

三、蓝屏分析 

WIN7可以设置蓝屏的时候转存dump文件

这样蓝屏后可以获取到dump文件进行分析,拖到WinDbg里边可以通过命令来查看具体信息,可以看到堆栈和出错的函数等。

 

 具体使用呢,还得深入学习...

小结

其实看了半天也只是了解到了自旋锁和具体的API,应用场景还不知道。

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值