锁(二) 信号量 读写信号量 互斥体

xiaoxiao2021-02-27  260

信号量

是用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同的是只有得到信号量的进程才能执行临界区代码。

与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。

linux中信号量的主要操作:

    1,定义信号量  struct semaphore {                                            raw_spinlock_t        lock;                                            unsigned int        count;                                            struct list_head    wait_list;                                  };

                                 struct semaphore sem;

    2,初始化信号量 void sema_init(struct semaphore * sem, int val); 该函数初始化信号量并设置sem的值为val。尽管信号量

          可以被初始化为大于1的值,从而成为一个计数信号量,但是通常是va被初始化为1

          例如: #define init_MUTEX(sem) sema_init(sem, 1)                          等同于  DECLARE_MUTEX(name)

                       #define init_MUTEX_LOCKED(sem) sema_init(sem, 0)       等同于  DECLARE_MUTEX_LOCKED(name)

    3 ,获得信号量 void down(struct semaphore * sem);    该函数用于获得信号量,他会导致睡眠,因此不能再中断上下文使用

           void down_interruptible(struct semaphore * sem); 该函数功能和down类似,因为down()而进入睡眠状态的进程不能被信号

           打断,但down_interruptible()而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值为非0;

           void down_trylock(struct semaphore * sem); 该函数尝试获得信号量sem,如果能够立刻获得,它获得信号量并返回0,否则返

           回非0;

    4,释放信号量  void up(struct semaphore * sem)  该函数释放信号量sem,唤醒等待者。

           使用方法:

                                DECLARE_MUTEX(sem);

                                down(&sem);                             //获取信号量,保护临界区

                                临界区                                          //访问临界资源

                                up(&sem);                                   //释放信号量

关于自旋锁和信号量,都是解决互斥问题的基本手段,面对特定的情况,怎样选择它们主要是根据临界区的性质和系统的特点。

从严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者依赖于后者。在信号量的实现上,为了保证信号量结构存取

的原子性,在多cpu中需要自旋锁来互斥。

信号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份代表进程来争夺

资源的。如果竞争失败,会发生进程上下文切换,当前进程进入睡眠状态,cpu将运行其他进程。鉴于进程上下文切换的开销也

很大,因此,只有当前进程占用资源时间较长时,用信号量才是较好的选择。

当所有要保护的临界区访问时间比较短时,用自旋锁时非常方便的,因为它节省上下文切换的时间。但是cpu得不到自旋锁会在哪

里空转直到其他执行单元解锁为止,所以要求锁不能在临界区里长时间停留,否则会降低系统的效率。

a,当锁不能被获取到时,使用信号量的开销时进程上下文切换时间T_sw,使用自旋锁的开销时等待获取自旋锁(临界区执行时间)

   T_cs,若T_cs较小时宜用自旋锁,若T_cs很大时宜用信号量。

b,信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行

   进程的切换,如果进程被切换出去后,另一个进程企图获取自旋锁,死锁就会发生。

c,信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在信号量和自旋锁之间只能选择自旋

   锁。当然,如果一定要用信号量,则只能通过down_trylock()方式进行,不能获取就立即返回以避免阻塞。

尽管自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候,还可能受到中

断和底半部(BH)的影响。为防止这种影响需要用到自旋锁的衍生锁。

加锁                             spin_lock()                           释放                          spin_unlock() 是自旋锁机制的基础,它们和

关中断                         local_irq_disable()             开中断                      local_irq_enable()

关底半部                     local_bh_disable()             开低半部                  local_bh_enable()

关中断并保存状态字 local_irq_save()                  开中断并恢复状态  local_irq_restore()

spin_lock_irq()            = spin_lock() + local_irq_disable()

spin_unlock_irq()       = spin_unlock() + local_irq_enable() spin_lock_irqsave      = spin_lock() + local_irq_save()

spin_unlock_irqsave = spin_unlock() + local_irq_restore()

读写信号量

读写信号量与信号量的关系和读写自旋锁与自旋锁的关系类似,读写信号量可能引起进程阻塞,但它可允许N个读执行单元同时访问

共享资源,而最多只能有1个写执行单元。因此,读写信号量时一种相对放宽条件的粒度稍大于信号量的互斥机制。

    1,定义信号量  struct rw_semaphore my_rws;

    2,初始化信号量  void rw_sema_init(struct rw_semaphore* my_rws);

    3,读信号量获取  void down_read(struct rw_semaphore * my_rws);

                                     void down_read_trylock(struct rw_semaphore * my_rws);

    4,读信号量释放  void up_read(struct rw_semaphore * my_rws);

    5,写信号量获取  void down_write(struct rw_semaphore * my_rws);

                                     void down_write_trylock(struct rw_semaphore * my_rws);

    4,写信号量释放  void up_write(struct rw_semaphore * my_rws);

互斥体

其实就是信号量的特殊形式。只有0、1两种状态

    1,定义并初始化互斥体    struct mutex m;

                                                  mutex_init(&m);

    2,获取互斥体                    void __sched mutex_lock(struct mutex *lock);

                                                  int __sched mutex_lock_interruptible(struct mutex *lock);

                                                  int __sched mutex_trylock(struct mutex *lock);

          关于mutex_lock()与mutex_lock_interruptible()的区别和down()与down_trylock()的区别完全一致,前者引起的睡眠不能被

          信号打断,而后者可以。mutex_trylock尝试获取mutex,获取不到时不会引起睡眠 。

          互斥体的使用方法和型号量用于互斥的场合完全一样。

    3,释放互斥体                   void __sched mutex_unlock(struct mutex *lock);

自旋锁和信号量对比

转载请注明原文地址: https://www.6miu.com/read-2148.html

最新回复(0)