转载请您注明出处:    http://www.cnblogs.com/lsh123/p/7400625.html

0x01 自旋锁简介

  自旋锁也是一种同步机制,它能保证某个资源只能被一个线程所拥有,这种保护被形象地称做“上锁”。它可以用于驱动程序中的同步处理。初始化自旋锁时,处理解锁状态,这时它可以被程序“获取”。“获取”后的自旋锁处理于锁定状态,不能再被“获取”。

  如果自旋锁已被锁住,这时有程序申请“获取”这个锁,程序则处于“自旋”状态。所谓自旋状态,就是不停地询问是否可以“获取”自旋锁。自旋锁不同于线程中的等待事件,在线程中如果等待某个事件(Event),操作系统会使这个线程进入休眠状态,CPU会运行其他线程;而自旋锁原理则不同,它不会切换到别的线程,而是一直让这个线程“自旋”。因此对自旋锁占用时间不宜过长,否则会导致申请自旋锁的其他线程处于自旋,会浪费CPU时间。

  驱动程序必须在低于或者等于DISPATCH_LEVEL的IRQL级别中使用自旋锁。

0x02 自旋锁操作函数

  自旋锁的结构:
  KSPIN_LOCK SpinLock;
  KSPIN_LOCK实际是一个操作系统相关的无符号整数,32位系统上是32位的unsigned long,64位系统则定义为unsigned __int64。typedef ULONG_PTR KSPIN_LOCK;   (ULONG_PTR就是能够装得下指针的无符号整数,在32位被定义成unsigned long,在64位被定义成unsigned __int64)
  在初始化时,其值被设置为0,为空闲状态。
  参见WRK:

  FORCEINLINE
  VOID
  NTAPI
  KeInitializeSpinLock (
      __out PKSPIN_LOCK SpinLock
      ) 
  {
      *SpinLock = 0;
  }

  关于自旋锁的两个基本操作:获取和释放
  VOID 
  KeAcquireSpinLock(
      IN PKSPIN_LOCK  SpinLock,
      OUT PKIRQL  OldIrql
      );
  VOID 
  KeReleaseSpinLock(
      IN PKSPIN_LOCK  SpinLock,
      IN KIRQL  NewIrql
      );

0x03   WRK源码

  继续查阅WRK看KeAcquireSpinLock获取自旋锁的集体操作:

  

  可以看到,操作对象是第一参数SpinLock,同时也与第二参数IRQL有关。
  进一步找到KeAcquireSpinLockRaiseToDpc的定义:

  

__forceinline
KIRQL
KeAcquireSpinLockRaiseToDpc (
__inout PKSPIN_LOCK SpinLock
) /*++ Routine Description: This function raises IRQL to DISPATCH_LEVEL and acquires the specified
spin lock. Arguments: SpinLock - Supplies a pointer to a spin lock. Return Value: The previous IRQL is returned. --*/ { KIRQL OldIrql; //
// Raise IRQL to DISPATCH_LEVEL and acquire the specified spin lock.
// OldIrql = KfRaiseIrql(DISPATCH_LEVEL);
KxAcquireSpinLock(SpinLock);
return OldIrql;
}

  第一步就是:

  OldIrql = KfRaiseIrql(DISPATCH_LEVEL);

  提升IRQL到DISPATCH_LEVEL,然后调用KxAcquireSpinLock()。如果当前IRQL就是DISPATCH_LEVEL,那么就直接调用KeAcquireSpinLockAtDpcLevel,省去提升IRQL一步。因为线程调度也是发生在DISPATCH_LEVEL,所以提升IRQL之后当前处理器上就不会发生线程切换。单处理器时,当前只能有一个线程被执行,而这个线程提升IRQL至DISPATCH_LEVEL之后又不会因为调度被切换出去,自然也可以实现我们想要的互斥“效果”。(所以说到了这里,如果是单核计算机的话,实际上已经完全达到了互斥上锁的目的了,但应该还需要考虑多核的情况的,所以就有之后的KxAcquireSpinLock函数。)

  进一步查看

  KxAcquireSpinLock函数

__forceinline
VOID
KxAcquireSpinLock (
__inout PKSPIN_LOCK SpinLock
) /*++ Routine Description: This function acquires a spin lock at the current IRQL. Arguments: SpinLock - Supplies a pointer to an spin lock. Return Value: None. --*/ { //
// Acquire the specified spin lock at the current IRQL.
// #if !defined(NT_UP) #if DBG
LONG64 Thread; Thread = (LONG64)KeGetCurrentThread() + 1;
if (InterlockedCompareExchange64((LONG64 *)SpinLock, Thread, 0) != 0)
#else
if (InterlockedBitTestAndSet64((LONG64 *)SpinLock, 0))
#endif
{ KxWaitForSpinLockAndAcquire(SpinLock);
} #else UNREFERENCED_PARAMETER(SpinLock); #endif // !defined(NT_UP) return;
}

  再看KxWaitForSpinLockAndAcquire函数,注释也都写明了这个函数是当首次尝试获取spin lock 失败后被调用,随后这个线程“自旋”,直至获取到spin lock 。

  

DECLSPEC_NOINLINE
ULONG64
KxWaitForSpinLockAndAcquire (
__inout PKSPIN_LOCK SpinLock
) /*++ Routine Description: This function is called when the first attempt to acquire a spin lock
fails. A spin loop is executed until the spin lock is free and another
attempt to acquire is made. If the attempt fails, then another wait
for the spin lock to become free is initiated. Arguments: SpinLock - Supplies the address of a spin lock. Return Value: The number of wait loops that were executed. --*/ { ULONG64 SpinCount = 0; #if DBG LONG64 Thread = (LONG64)KeGetCurrentThread() + 1; #endif //
// Wait for spin lock to become free.
// do {
do {
KeYieldProcessor();
} while (*(volatile LONG64 *)SpinLock != 0); #if DBG } while (InterlockedCompareExchange64((LONG64 *)SpinLock, Thread, 0) != 0); #else } while(InterlockedBitTestAndSet64((LONG64 *)SpinLock, 0)); #endif return SpinCount;
}

  注意到KxAcquireSpinLock函数中还有一个InterlockedBitTestAndSet64函数,这应该是一个64位的函数,我在WRK中没能找到它的定义,但是找到了它的32位版本:

BOOLEAN
FORCEINLINE
InterlockedBitTestAndSet (
IN LONG *Base,
IN LONG Bit
)
{
__asm {
mov eax, Bit
mov ecx, Base
lock bts [ecx], eax
setc al
};
}

  主要的操作是lock bts [ecx], eax这一条指令,这是一条进行位测试并置位的指令。,这里在进行关键的操作时有lock前缀,保证了多处理器安全。InterLockedXXX函数都有这个特点。显然,KxAcquireSpinLock()函数先测试锁的状态。若锁空闲,则*SpinLock为0,那么InterlockedBitTestAndSet()将返回0,并使*SpinLock置位,不再为0。这样KxAcquireSpinLock()就成功得到了锁,并设置锁为占用状态(*SpinLock不为0),函数返回。若锁已被占用,InterlockedBitTestAndSet()将返回1,此时将调用KxWaitForSpinLockAndAcquire()等待并获取这个锁。这也呼应了初始化时SpinLock置0的操作——SPIN_LOCK为0则锁空闲,非0则已被占有。

  现在KeAcquireSpinLock函数获取 spin lock的流程就清晰了,总结一下:

  1.KfRaiseIrql函数将IRQL提升到DISPATCH_LEVEL级别

  2.KxAcquireSpinLock函数申请获取一个spin lock,它先调用了InterlockedBitTestAndSet函数,测试锁的状态。若锁空闲,即spin lock为0,则使spin lock置位,不再为0。,InterlockedBitTestAndSet()返回0,这样KxAcquireSpinLock()就成功得到了锁。若锁已被占用,InterlockedBitTestAndSet()将返回1,此时将调用KxWaitForSpinLockAndAcquire()等待,持续“自旋”知道获取到了这个锁。

//bp KAtomLock!DriverEntry

KSPIN_LOCK __SpinLock;
KIRQL __OldIrql;
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PDEVICE_OBJECT DeviceObject = NULL; DriverObject->DriverUnload = DriverUnload;
SeCreateSpinLock();
return Status;
}
VOID SeCreateSpinLock()
{
KeInitializeSpinLock(&__SpinLock);
HANDLE ThreadHandle[] = { };
ULONG i = ;
PVOID ThreadObject[] = { };
for (i=;i<;i++)
{
PsCreateSystemThread(&ThreadHandle[i], , NULL, NULL, NULL, ThreadProcedure, (PVOID)(i+));
}
for (i = ; i < ; i++)
{
ObReferenceObjectByHandle(ThreadHandle[i], , NULL, KernelMode, &ThreadObject[i], NULL);
}
KeWaitForMultipleObjects(, ThreadObject, WaitAll, Executive, KernelMode, FALSE, NULL, NULL);
for (i = ; i < ; i++)
{
ObDereferenceObject(ThreadObject[i]);
ZwClose(ThreadHandle[i]);
ThreadHandle[i] = NULL;
}
}
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint("DriverUnload()\r\n");
} VOID ThreadProcedure(PVOID ParameterData)
{
PITEM Item;
int Count = ; KeAcquireSpinLock(&__SpinLock, &__OldIrql); //提升DISPATCH_LEVEL
for (Count = ; Count <= ; Count += )
{
DbgPrint("ThreadID%d:%d\r\n",(int)ParameterData,Count);
}
KeReleaseSpinLock(&__SpinLock, &__OldIrql);
PsTerminateSystemThread(STATUS_SUCCESS);
}
 

  

自旋锁(Spin Lock)的更多相关文章

  1. 自旋锁Spin lock与互斥锁Mutex的区别

    POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套常用的API.线程同步(Thread Synchronization)是并行编程中非常重要的通讯手段,其中最典型的应用 ...

  2. Synchronized和Lock, 以及自旋锁 Spin Lock, Ticket Spin Lock, MCS Spin Lock, CLH Spin Lock

    Synchronized和Lock synchronized是一个关键字, Lock是一个接口, 对应有多种实现. 使用synchronized进行同步和使用Lock进行同步的区别 使用synchro ...

  3. 自旋锁解决StackOverflowError案例

    本节笔者分享一个在实际工作中遇到的栈内存溢出(StackOverflowError)问题,以及其解决方案. 问题介绍:笔者负责的一个Java Web项目在启动的时候,需要有一些初始化操作,而接下来的代 ...

  4. LINUX内核笔记:自旋锁

    目录 自旋锁作用与基本使用方法? 在SMP和UP上的不同表现? 自旋锁与上下文 使用spin_lock()后为什么不能睡眠? 强调:锁什么? 参考   1.自旋锁作用与基本使用方法? 与其他锁一样,自 ...

  5. linux内核自旋锁API

    我们大概都了解,锁这种机制其实是为了保护临界区代码的,关于使用和定义,我总结的API如下: #include <linux/spinlock.h> 定义自旋锁 spinlock_t loc ...

  6. 漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼?(转)

    知乎链接:https://zhuanlan.zhihu.com/p/57354304 1. 锁的由来? 学习linux的时候,肯定会遇到各种和锁相关的知识,有时候自己学好了一点,感觉半桶水的自己已经可 ...

  7. (linux)自旋锁及其衍生锁

      自旋锁 毫秒以下. 自旋锁用于多个CPU系统中,在单处理器系统中,自旋锁不起锁的作用,只是禁止或启用内核抢占.在自旋锁忙等待期间,内核抢占机制还是有效的,等待自旋锁释放的线程可能被更高优先级的线程 ...

  8. spin lock自旋锁 双链表操作(多线程安全)(Ring0)

    通过spin lock自旋锁 ,为每个链表都定义并初始化一个锁,在需要向该链表插入或移除节点时不使用前面介绍的普通函数,而是使用如下方法: ExInterlockedInsertHeadList(&a ...

  9. Java并发包源码学习之AQS框架(二)CLH lock queue和自旋锁

    上一篇文章提到AQS是基于CLH lock queue,那么什么是CLH lock queue,说复杂很复杂说简单也简单, 所谓大道至简: CLH lock queue其实就是一个FIFO的队列,队列 ...

随机推荐

  1. 《HTTP 权威指南》笔记:第三章 HTTP 报文

    如果说 HTTP 是因特网的信使,那么 HTTP 报文就是它用来搬东西的包了. 这一章讲述关于 HTTP 报文的相关知识,包括: HTTP 报文的三个组成部分 请求报文以及其各种功能 响应报文以及各种 ...

  2. BGP - 1,基本概念

    1,BGP知识点 a)AS号:私有(64512-65535),公有(0-64511). b)什么时候使用BGP:有数据穿越本AS前往其他AS:本AS有多条到其他AS的连接:必须要做策略.   c)BG ...

  3. vuex之单向数据流

    单向数据流 State State 用来存状态.在根实例中注册了store 后,用 this.$store.state 来访问. Getters Getters 从 state 上派生出来的状态.可以 ...

  4. 2.5 UML顺序图

    相关概念 交互 对象之间为实现某一功能而必须实施的协作过程.动态行为,称为交互 消息 对象间的协作与交流表现为一个对象以某种方式启动另一个对象的活动,这种交流在 UML里被定义为消息 顺序图的建模元素 ...

  5. element upload 一次性上传多张图片(包含自定义上传不走action)

    最重要的都圈出来了

  6. python-django rest framework框架之解析器

    1.解析器 : 对请求的数据进行解析 - 请求体进行解析. 解析器在你不拿请求体数据时 不会调用. class UsersView(APIView): def get(self,request,*ar ...

  7. Spring注解之@Retention

    作用是定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中: package java.lang.annotation; /** * Annotation rete ...

  8. JS 设置盒子div 跳转

    方式一 window.location.href=”url”; 在当前窗口跳转 方式二 window.open(‘url’) 在新窗口跳转 window.open(‘url’,’_self’) 在当前 ...

  9. 在MongoDB中执行查询、创建索引

    1. MongoDB中数据查询的方法 (1)find函数的使用: (2)条件操作符: (3)distinct找出给定键所有不同的值: (4)group分组: (5)游标: (6)存储过程. 文档查找 ...

  10. 牛客第二场A-run

    链接:https://www.nowcoder.com/acm/contest/140/A 来源:牛客网 White Cloud is exercising in the playground. Wh ...