IRQL
每一个硬件中断都与一个优先级相联系,这个优先级就是IRQL。
IRQL:中断请求级别。一般情况下处理器的IRQL = 0,在用户模式代码执行的时候,他一直是0;内核模式下一般是0,但是也有例外。数字越大,等级越高(中断线路IRQ:当电脑内的周边硬件需要处理器去执行某些工作时,该硬件就会发出一个硬件信号,通知处理器工作,而这个信号就是IRQ。)
比如:磁盘完成执行IO操作之后,磁盘驱动器通过请求中断通知系统操作已经完成,这个中断连接到中断控制器这个硬件,然后将请求发给cpu处理,由指定的线程来执行相关的中断服务例程(ISR)
自旋锁 spinlock
自旋锁是为了解决内核链表读写时存在线程同步问题,解决多线程同步问题必须要用锁,通常使用自旋锁,自旋锁是内核中提供的一种高IRQL锁,用同步以及独占的方式访问某个资源。
并在 SMP 计算机中以 IRQL >= DISPATCH_LEVEL 执行。
这个程序实现了自旋锁保护下的对链表的操作。但是实现的功能很简单,不需要上锁也能完成;主要是展示怎么枷锁,加锁后怎么操作。但是没有多线程实操。
#include <ntifs.h>
#include <ntddk.h>
#include <ntstrsafe.h>
typedef struct _MyStruct
{
ULONG x;
ULONG y;
LIST_ENTRY lpListEntry;
}MyStruct, * pMyStruct;
// 定义全局链表和全局锁
LIST_ENTRY my_list_header;
KSPIN_LOCK my_list_lock;
// 初始化
void Init()
{
// 初始化链表
InitializeListHead(&my_list_header);
// 初始化锁
KeInitializeSpinLock(&my_list_lock);
}
// 函数内使用锁
void function_ins()
{
KIRQL IRQL;
// 加锁
KeAcquireSpinLock(&my_list_lock, &IRQL);
DbgPrint("锁内部执行 \n");
// 释放锁
KeReleaseSpinLock(&my_list_lock, IRQL);
}
NTSTATUS unload(PDRIVER_OBJECT driver)
{
DbgPrint("Driver unload success..");
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING RegistryPath)
{
driver->DriverUnload = unload;
DbgPrint("3 Hello World\n");
// 初始化链表
Init();
// 分配链表空间
pMyStruct testA = (pMyStruct)ExAllocatePool(NonPagedPoolExecute, sizeof(pMyStruct));
pMyStruct testB = (pMyStruct)ExAllocatePool(NonPagedPoolExecute, sizeof(pMyStruct));
// 赋值
testA->x = 100;
testA->y = 200;
testB->x = 1000;
testB->y = 2000;
// 向全局链表中插入数据
if (testA != NULL && testB != NULL)
{
// 在锁的保护下安全的移除节点
ExInterlockedInsertHeadList(&my_list_header, (PLIST_ENTRY)&testA -> lpListEntry, &my_list_lock);
ExInterlockedInsertTailList(&my_list_header, (PLIST_ENTRY)&testB -> lpListEntry, &my_list_lock);
}
// 执行锁的行为
function_ins();
// 移除节点A并放入到remove_entry中
PLIST_ENTRY remove_entry = ExInterlockedRemoveHeadList(&testA->lpListEntry, &my_list_lock);
// 输出链表数据
while (remove_entry != &my_list_header)
{
// 计算出成员距离结构体顶部内存距离
pMyStruct ptr = CONTAINING_RECORD(remove_entry, MyStruct, lpListEntry);
DbgPrint("节点元素X = %d 节点元素Y = %d \n", ptr->x, ptr->y);
// 得到下一个元素地址
remove_entry = remove_entry->Flink;
}
return STATUS_SUCCESS;
}
初始化
KeInitializeSpinLock(&my_list_lock);
用这个api进行初始化,参数就是定义的自旋锁变量。
KeReleaseSpinLock(&my_list_lock, IRQL);
用这个api去释放锁。
使用
自旋锁使用其实就三个步骤
初始化自旋锁(KeinitializeSpinlock)
获得自旋锁 (KeAcquireSpinlock)
释放自旋锁 (keReleaseSpinLock)
结合链表
自旋锁可以单独使用也可以配合链表一起使用。这里参考Windows 驱动开发 - 自旋锁,队列自旋锁,链表自旋锁的使用.-腾讯云开发者社区-腾讯云 (tencent.com)这里的实例代码:
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 init()
{
InitializeListHead(&m_list_head);
KeInitializeSpinLock(&g_spinlock);
return STATUS_SUCCESS;
}
// 假设是线程函数,或者是可能会发生访问冲突的函数
NTSTATUS Insert()
{
// 在非分页池分配IBINARY_INFO结构体
PIBINARY_INFO info1 = reinterpret_cast<PIBINARY_INFO>(
ExAllocatePool(NonPagedPoolExecute, sizeof(IBINARY_INFO)));
if (nullptr != info1)
{
RtlUnicodeStringInit(&info1->m_blobLinks, L"spinlink");
//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;
}
为什么在非分页池分配IBINARY_INFO结构体呢?
- 自旋锁用于保护的临界区数据通常需要稳定的内存地址,内存抖动(分页池(PagedPool)所分配的内存位于系统地址空间,会随着进程地址空间的换页而移动位置,这会导致内存抖动。)会引起各种问题。
- 在DISPATCH_LEVEL或者更高的IRQL获得的自旋锁,不能在分页池分配内存,只能使用非分页池。
锁操作和链表看起来并没有发生交集,代码上面也没有发现什么关联。但是通过我写的代码中的:
// 向全局链表中插入数据
if (testA != NULL && testB != NULL)
{
// 在锁的保护下安全的移除节点
ExInterlockedInsertHeadList(&my_list_header, (PLIST_ENTRY)&testA -> lpListEntry, &my_list_lock);
ExInterlockedInsertTailList(&my_list_header, (PLIST_ENTRY)&testB -> lpListEntry, &my_list_lock);
}
使用了wdk集成的ExInterlockedInsertHeadList
api来实现的,还有很多类似的api ExInterlocked*************