计算机系统应用教程网站

网站首页 > 技术文章 正文

并发原理系列八:信号量、互斥锁、自旋锁

btikc 2024-10-12 10:47:39 技术文章 7 ℃ 0 评论

1.信号量

1.1 概念

信号量又称为信号灯(semaphore),它是用来协调不同进程间的数据对象的,本质上信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:

   (1) 测试控制该资源的信号量。

   (2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。

   (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。

   (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。

1.2涉及结构、函数

/* 从以上信号量的定义中,可以看到信号量底层使用到了spin lock的锁定机制,这个spinlock主要用来确保对count成员的原子性的操作(count--)和测试(count > 0)。 */
struct semaphore {
        spinlock_t                lock;
        unsigned int             count;
        struct list_head        wait_list;
};

(1)信号量的获取操作

#
(1).void down(struct semaphore *sem);   //废弃


/*  (最常用)
    在保证原子操作的前提下,先测试count是否大于0,
    ~ 如果是说明可以获得信号量,这种情况下需要先将count--,以确保别的进程能否获得该信号量,然后函数返回,其调用者开始进入临界区。
    ~ 如果没有获得信号量,当前进程利用struct semaphore 中wait_list加入等待队列,开始睡眠。
*/
(2).int down_interruptible(struct semaphore *sem);


/* 试图去获得一个信号量,如果没有获得,函数立刻返回1而不会让当前进程进入睡眠状态。 */
(3).int down_trylock(struct semaphore *sem);

(2)信号量的释放操作

/*  如果没有其他线程等待在目前即将释放的信号量上,那么只需将count++即可。如果有其他线程正因为等待该信号量而睡眠,那么调用__up唤醒睡眠的进程*/
void up(struct semaphore *sem);

2.互斥锁

2.1概念

互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section)。因此,在任意时刻,只有一个线程被允许进入这样的代码保护区。mutex实际上是count=1情况下的semaphore。

2.2涉及结构、函数

// 结构
struct mutex {
        /* 1: unlocked, 0: locked, negative: locked, possible waiters */
        atomic_t                  count;
        spinlock_t                wait_lock;
        struct list_head          wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
        struct thread_info        *owner;
        const char                *name;
        void                      *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
        struct lockdep_map         dep_map;
#endif
};
// 定义互斥锁lock
mutex_init(struct mutex* lock)

// 获取
 mutex_lock(struct mutex *lock)

// 释放
 mutex_unlock(struct mutex *lock)

3. 自旋锁

3.1 定义

在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。

~对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。

~但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。

~最大的区别就是自旋锁不会休眠

3.2 涉及结构、函数

(1) 结构

# linux/Spinlock.h
typedef struct spinlock {
          union { //联合
             struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
             struct{
                     u8 __padding[LOCK_PADSIZE];
                     struct lockdep_map dep_map;
             };
#endif
         };
} spinlock_t;

(2)定义和初始化

spinlock_t my_lock = SPIN_LOCK_UNLOCKED; 
void spin_lock_init(spinlock_t *lock); 

(3)操作函数

//加锁一个自旋锁函数
void spin_lock(spinlock_t *lock);                                   //获取指定的自旋锁
void spin_lock_irq(spinlock_t *lock);                               //禁止本地中断获取指定的锁
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);      //保存本地中断的状态,禁止本地中断,并获取指定的锁
void spin_lock_bh(spinlock_t *lock)                                 //安全地避免死锁, 而仍然允许硬件中断被服务


//释放一个自旋锁函数
void spin_unlock(spinlock_t *lock);                                 //释放指定的锁
void spin_unlock_irq(spinlock_t *lock);                             //释放指定的锁,并激活本地中断
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags); //释放指定的锁,并让本地中断恢复到以前的状态
void spin_unlock_bh(spinlock_t *lock);                              //对应于spin_lock_bh


//非阻塞锁
int spin_trylock(spinlock_t *lock);                  //试图获得某个特定的自旋锁,如果该锁已经被争用,该方法会立刻返回一个非0值,
                                                     //而不会自旋等待锁被释放,如果成果获得了这个锁,那么就返回0.
int spin_trylock_bh(spinlock_t *lock);                           
//这些函数成功时返回非零( 获得了锁 ), 否则 0. 没有"try"版本来禁止中断.

//其他
int spin_is_locked(spinlock_t *lock);               //和try_lock()差不多

4.自旋锁&互斥锁对比

信号量/互斥体允许进程睡眠属于睡眠锁,自旋锁则不允许调用者睡眠,而是让其循环等待,所以有以下区别应用

1)、信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因而自旋锁适合于保持时间非常短的情况

2)、自旋锁可以用于中断,不能用于进程上下文(会引起死锁)。而信号量不允许使用在中断中,而可以用于进程上下文

3)、自旋锁保持期间是抢占失效的,自旋锁被持有时,内核不能被抢占,而信号量和读写信号量保持期间是可以被抢占的

5.信号量&互斥锁

使用场所:信号量主要适用于进程间通信,当然,也可用于线程间通信。而互斥锁只能用于线程间通信。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表