前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Windows 驱动开发 - 自旋锁,队列自旋锁,链表自旋锁的使用.

Windows 驱动开发 - 自旋锁,队列自旋锁,链表自旋锁的使用.

作者头像
IBinary
发布2021-09-10 10:51:46
1.6K0
发布2021-09-10 10:51:46
举报
文章被收录于专栏:逆向技术逆向技术

目录

windows 驱动开发之自旋锁结构的使用

一丶自旋锁

1.1 简介

​ 在内核中有双向链表。 那么也有线程操作。 我们有没有想过,如果在多线程的环境下如何保证双向链表操作数据是安全的那?

其实自旋锁就是用来限制多线程对同一数据资源的访问而设定的。 而内核中的自旋锁与Ring3层的临界区类似。

看看如何使用自旋锁吧。

1.2 使用自旋锁

  • 初始化自旋锁

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

初始化自旋锁

API

代码语言:javascript
复制
VOID NTAPI KeInitializeSpinLock (_Out_ PKSPIN_LOCK SpinLock);
例子:
KSPIN_LOCK m_spinlock;
KeInitializeSpinLock(&m_spinlock);

它只有一个参数,就是自旋锁的一个值。 我们定义一个自旋锁并且传入进去,他就会给我们进行初始化。

那么以后就是用这个值即可。

  • 使用自旋锁 使用自旋锁分别使用以下两个函数即可。
代码语言:javascript
复制
#define KeAcquireSpinLock(SpinLock, OldIrql) \						//获得自旋锁
    *(OldIrql) = KeAcquireSpinLockRaiseToDpc(SpinLock)
   
VOID KeReleaseSpinLock (_Inout_ PKSPIN_LOCK SpinLock,_In_  KIRQL NewIrql);//释放自旋锁

在这里我们使用Acquire函数来获取自旋锁,但是我使用的WDK已经变成了宏其实根本函数调用的就是 KeAcquireSpinLockRaiseToDpc(SpinLock)

我们使用宏即可。

说下参数

参数1是你初始化好的 KSPIN_LOCK的值 参数2就是会返回的一个IRQL值。 所以我们需要定义一个IRQL 并且以地址形式传递给函数

KeReleaseSpinLock 函数的参数与 Acquire唯一不同的就是第二个参数。 代表的是你给一个IRQL值。

看下例子:

代码语言:javascript
复制
KIRQL irql;
KeAcquireSpinLock(&m_spinlock,&irql);		//Acquire
//you code.....
KeReleaseSpinLock(&m_spinlock, irql);       // Release

1.3 错误的用法

自旋锁使用其实就三个步骤

1.初始化自旋锁(KeinitializeSpinlock)

2.获得自旋锁 (KeAcquireSpinlock)

3.释放自旋锁 (keReleaseSpinLock)

但是这里注意的事情是我们的锁的使用。 看下例子

代码语言:javascript
复制
NTSTATUS MyFunction(){                       //假设他是一个线程中频繁调用的函数
	KSPIN_LOCK m_spinlock;
	KIRQL irql;
	KeinitializeSpinLock(&m_spinlock);
	KeAcquireSpinlock(&m_spinlock,&irql);
	//xxxx code
	KeReleaseSpinlock(&m_spinlock,irql);
}

观看上面代码有没有发现问题。 其实问题很大。既然我们使用自旋锁那么自然就不能是堆栈变量。 我们可以将

自旋锁定义为全局变量。 如果在堆栈中那么相当于每次调用你的函数都初始化了一个自旋锁。 根本就起不到同步的

作用。

正确的例子:

代码语言:javascript
复制
NTSTATUS InitSpinlock() {
	KeInitializeSpinLock(&g_spinlock);
}
NTSTATUS MyUseSpinlock() {
	
	KIRQL irql;
	KeAcquireSpinLock(&g_spinlock,&irql);

	//MyCode
	KeReleaseSpinLock(&g_spinlock, irql);

}

二丶 链表中使用自旋锁

2.1 简介

​ 自旋锁我们可以单独使用也可以配合链表一起使用。 但是如果配合链表一起使用的时候我们就会进行下面的写法。

例子:

代码语言:javascript
复制
typedef struct _IBINARY_INFO {
	UNICODE_STRING m_blobLinks;
	LIST_ENTRY m_listentry;
	//other
}IBINARY_INFO,*PIBINARY_INFO;
LIST_ENTRY m_list_head;
KSPIN_LOCK g_spinlock;
NTSTATUS MyInitListHead() {
	InitializeListHead(&m_list_head);
	KeInitializeSpinLock(&g_spinlock);
	return STATUS_SUCCESS;
}
NTSTATUS Insert()        //假设是线程函数
{
	KIRQL Irql;
	KeAcquireSpinLock(&g_spinlock, &Irql);//获得锁
	PIBINARY_INFO info1 = reinterpret_cast<PIBINARY_INFO>(
		ExAllocatePool(NonPagedPoolExecute, sizeof(IBINARY_INFO)));
	if (nullptr != info1)
	{
		RtlUnicodeStringInit(&info1->m_blobLinks, L"https://www.cnblogs.com/iBinary/");
		InsertHeadList(&m_list_head, &info1->m_listentry);
	}
	//测试
	PIBINARY_INFO pCur = CONTAINING_RECORD(m_list_head.Flink ,IBINARY_INFO, m_listentry);
	UNICODE_STRING ustr = pCur->m_blobLinks;
	DbgPrint("the bloblink is %wZ \r\n", ustr);
	KeReleaseSpinLock(&g_spinlock, Irql);//释放
	return STATUS_SUCCESS;
}

可以实现我们想要的功能,但是有没有发现其实 自旋锁与我们的链表并没有关联。只是我们认为他是可以控制我们链表的。

按照理论来说应该集成到一起。 所以 WDK也给我提供了。他就是Exinterlockedxxx函数 不光可以操作链表也可以操作其它类型数据。然后以我们定义的自旋锁来进行操作。

但是请注意,初始化自旋锁的方法还是一致的。

函数如下:

代码语言:javascript
复制
PLIST_ENTRY
FASTCALL
ExInterlockedInsertHeadList (						//以锁的形态加入链表数据 前两个参数与InsertHeadlist一样。
    _Inout_ PLIST_ENTRY ListHead,                   
    _Inout_ __drv_aliasesMem PLIST_ENTRY ListEntry,
    _Inout_ _Requires_lock_not_held_(*_Curr_) PKSPIN_LOCK Lock   //最后一个参数就是你的SPIN_LOCK的锁
    );
    
另一个就是删除
    NTKERNELAPI
PLIST_ENTRY
FASTCALL
ExInterlockedRemoveHeadList (
    _Inout_ PLIST_ENTRY ListHead,
    _Inout_ _Requires_lock_not_held_(*_Curr_) PKSPIN_LOCK Lock
    );    

例子:

代码语言:javascript
复制
NTSTATUS Insert()
{
	
	
	PIBINARY_INFO info1 = reinterpret_cast<PIBINARY_INFO>(
		ExAllocatePool(NonPagedPoolExecute, sizeof(IBINARY_INFO)));
	if (nullptr != info1)
	{
		RtlUnicodeStringInit(&info1->m_blobLinks, L"https://www.cnblogs.com/iBinary/");
		//InsertHeadList(&m_list_head, &info1->m_listentry);
		ExInterlockedInsertHeadList(&m_list_head, &info1->m_listentry, &g_spinlock);;  //此位置
	}
	//测试
	PIBINARY_INFO pCur = CONTAINING_RECORD(m_list_head.Flink ,IBINARY_INFO, m_listentry);
	UNICODE_STRING ustr = pCur->m_blobLinks;
	DbgPrint("the bloblink is %wZ \r\n", ustr);
	//KeReleaseSpinLock(&g_spinlock, Irql);
	ExInterlockedRemoveHeadList(&m_list_head, &g_spinlock);  //此位置
	return STATUS_SUCCESS;
}

三丶队列自旋锁

3.1 简介

​ 队列自旋锁在Windows XP系统之后被引入,和普通自旋锁相比,队列自旋锁在多CPU平台上具有更好的性能表现,并且遵守“first-come first-served”原则,即:队列自旋锁遵守“谁先等待,谁先获取自旋锁”的原则。其过程和队列的“First in First out”特点非常类似,正是由于这个原因,这种自旋锁被称为“队列自旋锁”。

其实跟普通自旋锁相比 出了初始化函数一样,获取锁和释放锁都不一样了。

但是用法原理类似。 所以在使用队列自旋锁的时候一定注意不要和自旋锁混用。

比如等待使用 自旋锁, 释放使用队列自旋锁。

代码语言:javascript
复制
VOID
FASTCALL
KeAcquireInStackQueuedSpinLock (
    _Inout_ PKSPIN_LOCK SpinLock,
    _Out_ PKLOCK_QUEUE_HANDLE LockHandle
    );
    
    
VOID
FASTCALL
KeReleaseInStackQueuedSpinLock (
    _In_ PKLOCK_QUEUE_HANDLE LockHandle
    );

唯一不同的就是多了个 KLOCK_QUERU_HANDLE 类型的自旋锁句柄

例子:

代码语言:javascript
复制
KLOCK_QUEUE_HANDLE handle;
KeAcquireInStackQueuedSpinLock(&g_spinlock,&handle);  //参数一的SPIN_LOCK 还是使用KeinitializeSpinlock()初始化即可
KeReleaseInStackQueuedSpinLock(&handle);
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-09-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • windows 驱动开发之自旋锁结构的使用
    • 一丶自旋锁
      • 1.1 简介
      • 1.2 使用自旋锁
      • 1.3 错误的用法
      • 二丶 链表中使用自旋锁
      • 2.1 简介
    • 三丶队列自旋锁
      • 3.1 简介
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档