linux内核 信号量与自旋锁、延时函数比较

       在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。

  自旋锁与信号量"类似而不类",类似说的是它们功能上的相似性,"不类"指代它们在本质和实现机理上完全不一样,不属于一类。

  自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的"不类"。

  但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的"类似"。

  鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用;信号量适合于保持时间较长的情况,会只能在进程上下文使用。如果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享资源的访问时间非常短,自旋锁也是好的选择。但是,如 果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。

区别总结如下:
   1、由于争用信号量的进程在等待锁重新变为可用时会睡眠,所以信号量适用于锁会被长时间持有的情况。
   2、相反,锁被短时间持有时,使用信号量就不太适宜了,因为睡眠引起的耗时可能比锁被占用的全部时间还要长。
   3、由于执行线程在锁被争用时会睡眠,所以只能在进程上下文中才能获取信号量锁,因为在中断上下文中(使用自旋锁)是不能进行调度的。
   4、你可以在持有信号量时去睡眠(当然你也可能并不需要睡眠),因为当其它进程试图获得同一信号量时不会因此而死锁,(因为该进程也只是去睡眠而已,而你最终会继续执行的)。
   5、在你占用信号量的同时不能占用自旋锁,因为在你等待信号量时可能会睡眠,而在持有自旋锁时是不允许睡眠的。
   6、信号量锁保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区,因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一进程企图获取本自旋锁,死锁就会发生。
   7、信号量不同于自旋锁,它不会禁止内核抢占(自旋锁被持有时,内核不能被抢占),所以持有信号量的代码可以被抢占,这意味着信号量不会对调度的等待时间带来负面影响。
  除了以上介绍的同步机制方法以外,还有BKL(大内核锁),Seq锁等。
  BKL是一个全局自旋锁,使用它主要是为了方便实现从Linux最初的SMP过度到细粒度加锁机制。
  Seq锁用于读写共享数据,实现这样锁只要依靠一个序列计数器。


sem就是一个睡眠锁.如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。信号量一般在用进程上下文中.它是为了防止多进程同时访问一个共享资源(临界区).

spin_lock叫自旋锁.就是当试图请求一个已经被持有的自旋锁.这个任务就会一直进行 忙循环——旋转——等待,直到锁重新可用(它会一直这样,不释放CPU,它只能用在短时间加锁).它是为了防止多个CPU同时访问一个共享资源(临界区).它一般用在中断上下文中,因为中断上下文不能被中断,也不能被调度.

自旋锁对信号量

需求              建议的加锁方法

低开销加锁           优先使用自旋锁

短期锁定            优先使用自旋锁

长期加锁            优先使用信号量

中断上下文中加锁        使用自旋锁

持有锁是需要睡眠、调度     使用信号量 

进程间的sem.线程间的sem与内核中的sem的功能就很类似.

进程间的sem,线程间的sem功能是一样的.只是线程的sem,它在同一个进程空间,他的初始化,使用更方便.

进程间的sem,就是进程间通信的一部分,使用semget,semop等系统调用来完成.

内核中的sem 被锁定,就等于被调用的进程占有了这个sem.其它进程就只能进行睡眠队列.这与进程间的sem基本一致.

区别

Spin_lock

semaphore

保护的对象

一段代码

一个设备(必要性不强),

一个变量,

一段代码

保护区可被抢占

不可以(会被中断打断)

可以。

可允许在保护对象(代码)中休眠

不可以

可以。但最好不这样。

保护区能否被中断打断

可以,这样容易引发死锁。

最好是关了中断再使用此锁。

因为有可能中断处理例程也需要得到同一个锁。

可以。

其它功能

 

可完成同步,有传达信息的能力。

     

试图占用锁不成功后,进程的表现

不放开CPU,自己自旋。

进入一个等待队列。

释放锁后,还有其它进程等待时,内核如何处理

哪个进程得到运行的权力,它就得到了锁。

从等待队列中选一个出来占用此sem.

内核对使用者的要求

被保护的代码执行时间要短,是原子的,

不能主动的休眠。

不能调用有可以休眠的内核函数。

 

风险

发生死锁

 
 

不允许锁的持有者二次请求同一个锁。

不允许锁的持有者二次请求同一个锁。

信号量在生产者与消费者模式中可以进行同步。

当sem的down和UP分别出现在对立函数中(读,写函数),其实这就是在传达一种信息。表示当前是否有数据可读的信息。

read_somthing()

{

     down(设备)   占用了此设备          此时没有其它人都使用此设备上的所有操作(函数)

     if(有数据)

    {

       读完它。

          ()

    }

    else

     {

              up(设备)

              down(有数据的sem)sem=1表示有数据,为0表示无数据。

      }

}

write_somthing()

{

     down(设备)   占用了此设备          此时没有其它人都使用此设备上的所有操作(函数)

     if(有数据)

    {

          不写。

          up(设备)

          return

    }

    else

     {

              写入数据

              up(有数据的sem)sem=1表示有数据,为0表示无数据。

             up(设备)

            return;

      }

}

总结:

信号量适用于长时间片段,可能会睡眠(挂起调度)  所以只能用在进程上下文,不能用在中断上下文。
自旋锁使用于短时间片段  不会睡眠(挂起调度)抱着cpu不放,  用在中断上下文  但是必须先关闭本地中断,否则很可能因为
获取不到自旋锁又抱着cpu不让别人持有而释放自旋锁从而陷入死锁。

信号量可以用的前提下尽量用信号量,万不得已(中断中)使用自旋锁,短时间片段比较适合自旋锁,调度(进程之间的切换)本身
占用时间,自旋等待的时间很短时就没必要调度了,这时选用自旋锁死抱cpu不放比较好。


时间函数

忙等待(一直消耗cpu)

ndelay、udelay、mdelay

unsigned long delay = jiffies + 100;

while(time_before(jiffies,delay));

睡着等待(不会一直消耗cpu)

msleep()、msleep_interruptible()、ssleep()、interruptible_sleep_on_timeout();

猜你喜欢

转载自blog.csdn.net/lxllinux/article/details/84297145