windows 驱动开发之自旋锁结构的使用
一丶自旋锁
1.1 简介
? 在内核中有双向链表。 那么也有线程操作。 我们有没有想过,如果在多线程的环境下如何保证双向链表操作数据是安全的那?
其实自旋锁就是用来限制多线程对同一数据资源的访问而设定的。 而内核中的自旋锁与Ring3层的临界区类似。
看看如何使用自旋锁吧。
1.2 使用自旋锁
- 初始化自旋锁
? 自旋锁是内核中提供的一种高IRQL的锁,用同步以独占的方式来访问某个资源。
初始化自旋锁
API
VOID NTAPI KeInitializeSpinLock (_Out_ PKSPIN_LOCK SpinLock);
例子:
KSPIN_LOCK m_spinlock;
KeInitializeSpinLock(&m_spinlock);
它只有一个参数,就是自旋锁的一个值。 我们定义一个自旋锁并且传入进去,他就会给我们进行初始化。
那么以后就是用这个值即可。
-
使用自旋锁
使用自旋锁分别使用以下两个函数即可。
#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值。
看下例子:
KIRQL irql;
KeAcquireSpinLock(&m_spinlock,&irql); //Acquire
//you code.....
KeReleaseSpinLock(&m_spinlock, irql); // Release
1.3 错误的用法
自旋锁使用其实就三个步骤
1.初始化自旋锁(KeinitializeSpinlock)
2.获得自旋锁 (KeAcquireSpinlock)
3.释放自旋锁 (keReleaseSpinLock)
但是这里注意的事情是我们的锁的使用。 看下例子
NTSTATUS MyFunction(){ //假设他是一个线程中频繁调用的函数
KSPIN_LOCK m_spinlock;
KIRQL irql;
KeinitializeSpinLock(&m_spinlock);
KeAcquireSpinlock(&m_spinlock,&irql);
//xxxx code
KeReleaseSpinlock(&m_spinlock,irql);
}
观看上面代码有没有发现问题。 其实问题很大。既然我们使用自旋锁那么自然就不能是堆栈变量。 我们可以将
自旋锁定义为全局变量。 如果在堆栈中那么相当于每次调用你的函数都初始化了一个自旋锁。 根本就起不到同步的
作用。
正确的例子:
NTSTATUS InitSpinlock() {
KeInitializeSpinLock(&g_spinlock);
}
NTSTATUS MyUseSpinlock() {
KIRQL irql;
KeAcquireSpinLock(&g_spinlock,&irql);
//MyCode
KeReleaseSpinLock(&g_spinlock, irql);
}
二丶 链表中使用自旋锁
2.1 简介
? 自旋锁我们可以单独使用也可以配合链表一起使用。 但是如果配合链表一起使用的时候我们就会进行下面的写法。
例子:
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函数 不光可以操作链表也可以操作其它类型数据。然后以我们定义的自旋锁来进行操作。
但是请注意,初始化自旋锁的方法还是一致的。
函数如下:
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
);
例子:
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”特点非常类似,正是由于这个原因,这种自旋锁被称为“队列自旋锁”。
其实跟普通自旋锁相比 出了初始化函数一样,获取锁和释放锁都不一样了。
但是用法原理类似。 所以在使用队列自旋锁的时候一定注意不要和自旋锁混用。
比如等待使用 自旋锁, 释放使用队列自旋锁。
VOID
FASTCALL
KeAcquireInStackQueuedSpinLock (
_Inout_ PKSPIN_LOCK SpinLock,
_Out_ PKLOCK_QUEUE_HANDLE LockHandle
);
VOID
FASTCALL
KeReleaseInStackQueuedSpinLock (
_In_ PKLOCK_QUEUE_HANDLE LockHandle
);
唯一不同的就是多了个 KLOCK_QUERU_HANDLE 类型的自旋锁句柄
例子:
KLOCK_QUEUE_HANDLE handle;
KeAcquireInStackQueuedSpinLock(&g_spinlock,&handle); //参数一的SPIN_LOCK 还是使用KeinitializeSpinlock()初始化即可
KeReleaseInStackQueuedSpinLock(&handle);