信号量
是用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同的是只有得到信号量的进程才能执行临界区代码。
与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。
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);
自旋锁和信号量对比