信号量和自旋锁

转载 2008年09月30日 13:27:00

 信号量和自旋锁

 

内核同步措施
    为了避免并发,防止竞争。内核提供了一组同步方法来提供对共享数据的保护。 目前内核中原子操作多用来做计数使用,其它情况最常用自旋锁、信号量。

 

自旋锁
------------------------------------------------------ 
    自旋锁是专为防止多处理器并发而引入的一种锁,它应用于中断处理等部分。对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁。
    自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被争用(已经被持有)的自旋锁,那么这个任务就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的内核任务便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。
    事实上,自旋锁的初衷就是:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话, 最好使用信号量。
自旋锁的基本形式如下:
    spin_lock(&mr_lock);
    //临界区
    spin_unlock(&mr_lock);
    因为自旋锁在同一时刻只能被最多一个内核任务持有,所以一个时刻只有一个线程允许存在于临界区中。这点很好地满足了对称多处理机器需要的锁定服务。在单处理器上,自旋锁仅仅当作一个设置内核抢占的开关。如果内核抢占也不存在,那么自旋锁会在编译时被完全剔除出内核。
    简单的说,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。另外自旋锁不允许任务睡眠(持有自旋锁的任务睡眠会造成自死锁——因为睡眠有可能造成持有锁的内核任务被重新调度,而再次申请自己已持有的锁),它能够在中断上下文中使用。
    死锁:假设有一个或多个内核任务和一个或多个资源,每个内核都在等待其中的一个资源,但所有的资源都已经被占用了。这便会发生所有内核任务都在相互等待,但它们永远不会释放已经占有的资源,于是任何内核任务都无法获得所需要的资源,无法继续运行,这便意味着死锁发生了。自死琐是说自己占有了某个资源,然后自己又申请自己已占有的资源,显然不可能再获得该资源,因此就自缚手脚了。

 

信号量
------------------------------------------------------
    信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
    信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。
信号量基本使用形式为:
static DECLARE_MUTEX(mr_sem);//声明互斥信号量
if(down_interruptible(&mr_sem))
    //可被中断的睡眠,当信号来到,睡眠的任务被唤醒
    //临界区
up(&mr_sem);

 

信号量和自旋锁区别
------------------------------------------------------
    虽然听起来两者之间的使用条件复杂,其实在实际使用中信号量和自旋锁并不易混淆。注意以下原则:
    如果代码需要睡眠——这往往是发生在和用户空间同步时——使用信号量是唯一的选择。由于不受睡眠的限制,使用信号量通常来说更加简单一些。如果需要在自旋锁和信号量中作选择,应该取决于锁被持有的时间长短。理想情况是所有的锁都应该尽可能短的被持有,但是如果锁的持有时间较长的话,使用信号量是更好的选择。另外,信号量不同于自旋锁,它不会关闭内核抢占,所以持有信号量的代码可以被抢占。这意味者信号量不会对影响调度反应时间带来负面影响。

 

自旋锁对信号量
------------------------------------------------------
需求                       建议的加锁方法
低开销加锁                 优先使用自旋锁
短期锁定                   优先使用自旋锁
长期加锁                   优先使用信号量
中断上下文中加锁           使用自旋锁
持有锁是需要睡眠、调度     使用信号量 
 

 

 

以下部分的来源:kcrazy的纸篓

自旋锁我的理解就好比

    小A,小B,小C,小D 同住一个屋子,可屋子只有一间茅房和一个马桶。他们谁想"便"的时候谁就要把茅房的门锁上,然后占据马桶,比如小A正在占有,聚精会神,非常惬意。碰巧小C此时甚急,但没办法,因为小A已经把门上了锁。于是小B在门口急得打转,即为"自旋"。注意这个"自旋"两个字用的好,小B不是一看门被上锁就回屋睡觉去了,而是在门口"自旋"。... 最终的结果是小A开锁,小B占用。而且在开锁闭锁过程中动作干净利落,不容他人抢在前面。

如此周而复始......

    这里的 小A,B,C,D 即为处理器,茅房的锁即为自旋锁。当其他处理器想访问这个公共的资源的时候就要先获取这个锁。如果锁被占用,则自旋(循环)等待。

小A的聚精会神代表了IRQL为2,开关锁动作快表示为原子操作。

----------------------------------------------------------

不知道我理解的对还是不对,可能这样举例有些不恰当。有理解不对之处希望指点一二,以免误入歧途,悔之晚矣。

----------------------------------------------------------

写了个测试程序测试了一下:

KSPIN_LOCK spinlock;

NTSTATUS DriverEntry(
                    IN PDRIVER_OBJECT   DriverObject,
                    IN PUNICODE_STRING RegistryPath
                     )
{
     NTSTATUS         Status;
     UNICODE_STRING     DeviceName;
     PDEVICE_OBJECT     DeviceObject;
     HANDLE             ThreadHandle;
     KIRQL             oldirql;
     KIRQL             irql;
     ULONG             Processor;
     ULONG             i;

     DeviceObject = NULL;

     RtlInitUnicodeString( &DeviceName, deviceNameBuffer );
    
     Status = IoCreateDevice( DriverObject,
                     0,
                     &DeviceName,
                     FILE_DEVICE_UNKNOWN,
                     0,
                     FALSE,
                     &DeviceObject );
     if ( !NT_SUCCESS(Status) )
     {
         return Status;
     }

     DriverObject->DriverUnload = DriverUnload;

     KeInitializeSpinLock( &spinlock );     // (2)

     PsCreateSystemThread( &ThreadHandle, THREAD_ALL_ACCESS, NULL, NULL, NULL, ThreadRoutine, NULL );

     i = 10000;

     KeAcquireSpinLock( &spinlock, &oldirql );

     while (i--)
     {
         __asm nop
         irql = KeGetCurrentIrql();
         Processor = KeGetCurrentProcessorNumber();
         KdPrint(( "   [%d] CurrentIrql:/t%d", Processor, irql ));
     }

     KeReleaseSpinLock( &spinlock, oldirql );

     return Status;
}

VOID
ThreadRoutine( IN PVOID StartContext )
{
     KIRQL     oldirql;
     KIRQL     irql;
     ULONG     Processor;
     ULONG     i;

     i = 10000;

     KeAcquireSpinLock( &spinlock, &oldirql );     // (1)

     while (i--)
     {
         __asm nop
         irql = KeGetCurrentIrql();
         Processor = KeGetCurrentProcessorNumber();

         KdPrint(( "**[%d] CurrentIrql:/t%d", Processor, irql ));
     }

     KeReleaseSpinLock( &spinlock, oldirql );     // (1)
}

---------------------------------------------------------------------------------

首先说明一下我是双核系统,如果是单核的话我想进入自旋锁之后IRQL已经提高到 DPC 级别,第二个线程就跑不起来了。如果他神奇的跑了起来,那一定会发生死锁。

分几种情况测试:

1、就是上边的代码测试

   先抓到锁的先跑,后抓到锁的后跑。并且被锁的期间的IRQL 为 DPC 级别。

2、去掉 标记 (1) 的两行

   结果是两个线程同时跑,一个占处理器 [0] 一个占处理器 [1]

   上锁的那个 IRQL 级别是 DPC 级。

   没上锁的IRQL为 0

   即 自旋锁 并不影响其他处理器的正常运行。除非其他处理器也想获得这个锁。

3、去掉 标记 (2) 的一行(spinlock 是全局变量)

   和 1 的结果相同,因为全局变量默认是初始化为0的。


信号量与自旋锁的比较

sem就是一个睡眠锁.如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将...
  • jeffade
  • jeffade
  • 2012年06月08日 09:13
  • 766

Linux 内核同步之自旋锁与信号量的异同

Linux 设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发访问会导致竞态,linux 提供了多种解决竞态问题的方式,这些方式适合不同的应用场景。   Linux 内核是多进程、多...
  • liuxd3000
  • liuxd3000
  • 2013年02月03日 16:10
  • 1970

自旋锁和信号量区别

在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号...
  • mia_go
  • mia_go
  • 2010年10月24日 10:18
  • 2519

自旋锁与信号量的区别是什么? 分别在什么场景下使用?

自旋锁与信号量作为同步机制,都是用来保护临界区的,但是它们的工作方式完全不一样。 自旋锁只有两种状态,即LOCKED与UNLOCKED。 而信号量既可以作为互斥锁来使用(此时具有0和1两种状态...
  • zhangxin1250
  • zhangxin1250
  • 2014年10月21日 11:30
  • 583

原子操作 信号量 自旋锁 互斥锁

原子操作 信号量 自旋锁 互斥锁
  • xiaofeige567
  • xiaofeige567
  • 2014年10月20日 22:36
  • 1030

原子操作、信号量、读写信号量和自旋锁的区别与联系

一.为什么内核需要同步方法 并发指的是多个执行单元同时,并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量,静态变量等)的访问则很容易导致竞态。 主要竞态发生如下: 1.对称多处...
  • mrdingjie
  • mrdingjie
  • 2012年10月29日 22:33
  • 973

原子操作、信号量、读写信号量和自旋锁

本系列文章分两部分,第一部分详细地介绍了 Linux 内核中的同步机制:原子操作、信号量、读写信号量和自旋锁的API,使用要求以及一些典型示例。第二部分将详细介绍在Linux内核中的另外一些同步机制,...
  • testcs_dn
  • testcs_dn
  • 2015年01月30日 12:48
  • 2200

linux kernel 信号量、互斥锁、自旋锁

1.信号量1.1 概念信号量又称为信号灯(semaphore),本质上信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:    (...
  • u012719256
  • u012719256
  • 2016年09月26日 14:42
  • 2112

信号量与自旋锁

【转】:http://www.cppblog.com/huangjianfu/archive/2009/06/02/86530.html 内核同步措施     为了避免并发,防止竞争。内核...
  • li4850729
  • li4850729
  • 2014年10月10日 22:06
  • 449

Linux常见锁比较--自旋锁、互斥锁、信号量、临界区

自旋锁(SpinLock):锁的目的是为了保护共享资源,实现线程同步。自旋锁区别于其他锁的地方在于若某线程在未获得锁时将不断的询问(判断)自旋锁保持者是否释放了锁(获取锁操作将自旋在那里,不断地申请获...
  • liu_sheng_1991
  • liu_sheng_1991
  • 2016年08月23日 15:36
  • 1890
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章: 信号量和自旋锁
举报原因:
原因补充:

(最多只允许输入30个字)