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

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);

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

上一篇:linux shell脚本(持续更新)


下一篇:Windows Azure入门教学系列 (五):使用Queue Storage