自旋锁(Spin Lock)

转载请您注明出处:    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[2] = { 0 };
    ULONG   i = 0;
    PVOID   ThreadObject[2] = { 0 };
    for (i=0;i<2;i++)
    {
        PsCreateSystemThread(&ThreadHandle[i], 0, NULL, NULL, NULL, ThreadProcedure, (PVOID)(i+1));
    }
    for (i = 0; i < 2; i++)
    {
        ObReferenceObjectByHandle(ThreadHandle[i], 0, NULL, KernelMode, &ThreadObject[i], NULL);
    }
    KeWaitForMultipleObjects(2, ThreadObject, WaitAll, Executive, KernelMode, FALSE, NULL, NULL);
    for (i = 0; i < 2; 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 = 0;

    KeAcquireSpinLock(&__SpinLock, &__OldIrql);  //提升DISPATCH_LEVEL
    for (Count = 1; Count <= 20; Count += 1)
    {
        DbgPrint("ThreadID%d:%d\r\n",(int)ParameterData,Count);
    }
    KeReleaseSpinLock(&__SpinLock, &__OldIrql);
    PsTerminateSystemThread(STATUS_SUCCESS);
}

  

时间: 2024-10-14 07:00:57

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

自旋锁&amp;读/写锁

自旋锁 自旋锁(spin lock)是用来在多处理器环境中工作的一种特殊的锁.如果内核控制路径发现自旋锁"开着",就获取锁并继续自己的执行.相反,如果内核控制路径发现由运行在另一个CPU上的内核控制路径"锁着",就在一直循环等待,反复执行一条紧凑的循环指令,直到锁被释放. 一般来说,由自旋锁所保护的每个临界区都是禁止内核抢占的.在单处理器系统上,这种锁本身并不起锁的作用,自旋锁原语仅仅是禁止或启用内核抢占.请注意,在自旋锁忙等期间,内核抢占还是有效的,因此,等待自旋

自旋锁、文件锁、大内核锁

自旋锁(Spin lock) 自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名.其作用是为了解决某项资源的互斥使用.因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远 高于互斥锁.虽然它的效率比互斥锁高,但是它也有些不足之处: 1.自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降

linux驱动开发(十一)linux内核信号量、互斥锁、自旋锁

参考: http://www.360doc.com/content/12/0723/00/9298584_225900606.shtml http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html http://blog.chinaunix.net/uid-25100840-id-3147086.html http://blog.csdn.net/u012719256/article/details/52670098 --

一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁

前续 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(一)——地:起因 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁 平行时空 在复制好上面那一行我就先停下来了,算是先占了个位置,虽然我知道大概要怎么写,不过感觉还是很乱. 我突然想到,既然那么纠结,那么混乱,那么不知所措,我们不如换个视角.记得高中时看过的为数不多的长篇小说<穆斯林的葬礼>,作者是:霍达(女),故事描写了两个发生在不同时代.有着不同的内容却又交错扭结的爱情悲剧,一个是“玉”的故事,一个是“月”

【转】自旋锁及其衍生锁

原文网址:http://blog.chinaunix.net/uid-26126915-id-3032644.html 自旋锁 自旋锁(spinlock)是用在多个CPU系统中的锁机制,当一个CPU正访问自旋锁保护的临界区时,临界区将被锁上,其他需要访问此临界区的CPU只能忙等待,直到前面的CPU已访问完临界区,将临界区开锁.自旋锁上锁后让等待线程进行忙等待而不是睡眠阻塞,而信号量是让等待线程睡眠阻塞.自旋锁的忙等待浪费了处理器的时间,但时间通常很短,在1毫秒以下. 自旋锁用于多个CPU系统中,

本地自旋锁与信号量/多服务台自旋队列-spin wait风格的信号量

周日傍晚,我去家附近的超市(...)买苏打水,准备自制青柠苏打.我感觉我做的比买的那个巴黎水要更爽口.由于天气太热,非常多人都去超市避暑去了,超市也不撵人,这仿佛是他们的策略.人过来避暑了,走的时候难免要买些东西的.就跟非常多美女在公交地铁上看淘宝消磨时光,然后就下单了...这是多么easy一件事,反之开车的美女网购就少非常多.对于超市的避暑者,要比公交车上下单更麻烦些,由于有一个成本问题,这就是排队成本.       其实这是一个典型的多服务台排队问题,可是超市处理的并不好.存在队头拥塞问题.

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

上一篇文章提到AQS是基于CLH lock queue,那么什么是CLH lock queue,说复杂很复杂说简单也简单, 所谓大道至简: CLH lock queue其实就是一个FIFO的队列,队列中的每个结点(线程)只要等待其前继释放锁就可以了. AbstractQueuedSynchronizer是通过一个内部类Node来实现CLH lock queue的一个变种,但基本原理是类似的. 在介绍Node类之前,我们来介绍下Spin Lock,通常就是用CLH lock queue来实现自旋锁

C# lock 语法糖实现原理--《.NET Core 底层入门》之自旋锁,互斥锁,混合锁,读写锁

原文:C# lock 语法糖实现原理--<.NET Core 底层入门>之自旋锁,互斥锁,混合锁,读写锁 在多线程环境中,多个线程可能会同时访问同一个资源,为了避免访问发生冲突,可以根据访问的复杂程度采取不同的措施 原子操作适用于简单的单个操作,无锁算法适用于相对简单的一连串操作,而线程锁适用于复杂的一连串操作 原子操作 修改状态要么成功且状态改变,要么失败且状态不变,并且外部只能观察到修改前或者修改后的状态,修改中途的状态不能被观察到 .NET 中,System.Threading.Inte

多线程中的锁系统(四)-谈谈自旋锁

目录 一:基础 二:自旋锁示例 三:SpinLock 四:继续SpinLock 五:总结 一:基础 内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式.用户模式构造和内核模式构造 优点:cpu利用最大化.它发现资源被锁住,请求就排队等候.线程切换到别处干活,直到接受到可用信号,线程再切回来继续处理请求. 缺点:托管代码->用户模式代码->内核代码损耗.线程上下文切换损耗. 在锁的时间比较短时,系统频繁忙于休眠.切换,是个很大的性能损耗. 自旋锁:原子操作+自循环.通常说的用户构造模式.