链表,全局变量在多线程的使用中可能会存在多个地方同时调用,这时就会引出线程安全问题,这个时候就可以使用锁(不仅仅是自旋锁,当然自旋锁也能解决这个问题,需看实际情况选择)
自旋锁是内核中提供的一种高IRQL锁,用同步以及独占的方式访问某个资源。
下面给出一个自旋锁的申请和初始化:
KSPIN_LOCK SLock;
KeInitializeSpinLock(&SLock);
KeInitializeSpinLock函数没有返回值。下面的代码展示了如何使用自旋锁。在KeAcquireSpinLock和KeReleaseSpinLock之间的代码是只有单线程执行的,其他的线程会停留在KeAcquireSpinLock等候,直到KeReleaseSpinLock被调用。换句话说,只有一个线程能够获得自旋锁。此外,KIRQL是中断级别。KeAcquireSpinLock会提高当前中断级别,将旧的中断级别保存到这个参数中。
VOID SpinLock()
{
KSPIN_LOCK SLock;
KIRQL irql;
KeInitializeSpinLock(&SLock);
KeAcquireSpinLock(&SLock, &irql);
//需要独占资源的代码
KeReleaseSpinLock(&SLock, irql);
}
上面的例子中,自旋锁需定义成全局变量或静态变量才有意义。
如果将自旋锁变量申请成了局部变量,每个线程在执行这个包含局部自旋锁的局部函数是,都会重新初始化一个自旋锁,那么自旋锁就失去了其意义。
双向链表中使用自旋锁:
链表本身并不保证多线程安全性,所以常常需要用到自旋锁。从理论上讲,应该为链表准备自旋锁,并且在操作链表之前,调用KeAcquireSpinLock来获取锁,在操作完成之后,调用KeReleaseSpinLock来释放锁。但是LIST_ENTRY有一系列操作,这些操作并不需要使用者自己调用获取与释放锁,只需要为每个链表定义并初始化一个锁即可。
typedef struct LIST_INFO
{
LIST ENTRY m ListEntry;
WCHAR m_ strFileName[260];
}LIST_INFO, *PLIST_INFO;
LIST_ENTRY mylisthead;
KSPIN LOCK mylistlock;
//链表初始化函数
void MyFileInforInilt()
{
InitializeListHead(&mylisthead);
KeInitializeSpinLock(&mylistlock);
}
LIST_INFO listinfo = { 0 };
ExInterlockedInsertHeadList (&mylisthead,(PLIST ENTRY)&listinfo, &mylistlock);
这里增加了一个自旋锁类型的指针作为参数。在ExInterlockedInsertHeadList中,会自动使用这个自旋锁进行加锁。类似的还有一个加锁的Remove函数,用来移除一个节点,调用方式如下:
PLIST_ENTRY pRemove = NULL;
pRemove = ExInterlockedRemoveHeadList (&mylisthead,&mylist1ock);
这个函数从链表中移除第一个节点,并返回到pRemove中。
列队自旋锁:
列队,遵守先等待先获取的原则(及先进先出)
队列自旋锁的使用和普通自旋锁的使用方法基本一样,初始化自旋锁也是使用KeInitializeSpinLock函数,唯一不同的地方是在获取和释放自旋锁时需要使用新的函数,下面给出一个简例:
全局区:
KSPIN_LOCK SLock;
VOID QueueSpinLock()
{
KeInitializeSpinLock(&SLock);
KeAcquireInStackQueuedSpinLock(&SLock, &KQHandle);
//需要独占资源的代码
KeReleaseInStackQueuedSpinLock(&KQHandle);
}
入口函数
{
KLOCK_QUEUE_HANDLE KQHandle;
QueueSpinLock();
}
在上面的例子中我在强调一次,自旋锁需定义成全局变量或静态变量才有意义。
队列自旋锁的使用增加了一个KLOCK_QUEUE_HANDLE数据结构,这个数据结构唯一地表示一个队列自旋锁。
*注意:列队自旋锁和自旋锁虽然初始化使用的同一个函数,但是获取和释放必须成对使用,不可混用。