linux 并发控制

竞态通常是在对共享资源的访问的时候产生的。当两个执行线程需要访问相同的数据结构时,就会出现。
所以应该尽量少的使用共享数据。
临界区:在任意的时刻,代码只能被一个线程执行。
休眠:当linux进程到达某个时间点,此时他不能进行任何处理时,它将进入休眠状态,这将把处理器让给其他执行线程直到将来它能够继续完成自己的处理为止。

Linux信号量的实现
头文件<asm/semaphore.h>
信号量的声明和初始化:
直接创建信号量:
void sema_init(struct semaphore *sem, int val);
信号量通常被用于互斥模式:
DECLARE_MUTEX(name);初始化为1
DECLARE_MUTEX_LOCKED(name);初始化为0
在运行时初始化:
void init_MUTEX(struct semapthore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
在linux世界中P操作被称为down,这里指的是减小信号量的值。
void dowm(struce semaphore *sem);减少信号量的值,并在必要时一直等待(进入休眠状态)。
int down_interruptible(struct semaphore *sem);允许等待某个信号量的进程可被用户中断。中断版本几乎是我们始终要用的版本。使用该函数时,如果操作被中断,该函数返回非零值,而调用者不会拥有该信号。对该函数的正确使用需要始终检查返回值,并作出相应的响应。
int down_trylock(struct semaphore *sem);如果信号量不可获得,函数立即返回非零值。
V操作,释放信号量
void up(struct semaphore *sem);
eg:scull中使用信号量
if (down_interruptible(&dev->sem))
  return -ERESTARTSYS;
注意返回值的检查,如果它返回非零值,则说明操作被中断。在这种情况下,通常要做的工作是返回-ERESTARTSYS。在见到这个返回代码后,内核的高层代码要么会从头重新启动该调用,要么会将错误返回给用户。

自旋锁
<linux/spinlock.h>锁的类型为spinlock_t
初始化锁
spinlock_t my_lock = SPIN_LOCK_UNLOCKED;定义的时候初始化
void spin_lock_init(spinlock_t *lock);运行的时候初始化
void spin_lock(spinlock_t *lock);获得锁
void spin_unlock(spinlock_t *lock);释放已经获得锁
所有的自旋锁等待在本质上都是不可中断的。一旦调用了spin_lock,在获得锁之前将一直处于自旋状态。
自旋锁和原子上下文
适用于自旋锁的核心规则是
1.任何拥有自旋锁的代码都必须是原子的,他不能休眠。
2.自旋锁必须在可能的最短时间内拥有,提高系统的效率。
自旋锁函数
void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_bh(spinlock_t *lock);
---------------------------------------
void spin_unlock(spinlock_t *lock);
void spin_unlock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);

如果自旋锁在中断处理函数中被用到,那么在获取该锁之前需要关闭本地中断,spin_lock_irqsave 只是下列动作的一个便利接口:
1 保存本地中断状态
2 关闭本地中断
3 获取自旋锁
解锁时通过 spin_unlock_irqrestore完成释放锁、恢复本地中断到之前的状态等工作
还有一对 spin_lock_irq 和 spin_unlock_irq
如果你确定在获取锁之前本地中断是开启的,那么就不需要保存中断状态,解锁的时候直接将本地中断启用就可以啦

锁陷阱
不明确的规则
1.如果某个获得锁的函数要调用其他同样试图获取这个锁的函数,代码就会死锁。不论是信号量还是自旋锁,都不允许拥有者第二次获得这个锁。
2.想象某个线程锁定了lock1,而其他线程锁定了lock2。这时他们都试图获得另外的那个锁,于是两个线程都将死锁。
解决办法是:在必须获得多个锁时,应该始终以相同的顺序获得。
有帮助的规则:
1.如果我们必须获得一个局部锁,以及一个属于内核更中心位置的锁,则应该首先获得自己的局部锁。
2.如果我们拥有信号量和自旋锁的组合,则必须首先获得信号量;因为拥有自旋锁时调用down导致休眠是个严重的错误。

A&Q:
1.为什么中断中可以使用自旋锁,而不能使用信号量。
自旋锁在自旋的时候不会休眠,不会引起进程调度。(所以可以在中断中使用)
而获取信号量时,再不能立即得到信号量时,进程会休眠,引起进程调度。(所以不能在中断中使用)
2.为什么在拥有自旋锁的时候不能休眠?而拥有信号量时可以休眠。
跟踪一下spin_lock(&mr_lock)的实现
  #define spin_lock(lock) _spin_lock(lock)
  #define _spin_lock(lock) __LOCK(lock)
  #define __LOCK(lock) \
  do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)
留意到“preempt_disable()”,这个调用的功能是“关抢占”(在spin_unlock中会重新开启抢占功能)。从中可以看出,使用自旋锁保护的区域是工作在非抢占的状态;即使获取不到锁,在“自旋”状态也是禁止抢占的。自旋锁保护的区域是工作在非抢占的状态;当拥有自旋锁的进程休眠了,会发生系统调度。试想一下,假如在自旋锁保护的代码中间睡眠,此时发生进程调度,则可能另外一个进程会再次调用spinlock保护的这段代码。而我们现在知道了即使在获取不到锁的“自旋”状态,也是禁止抢占的,而“自旋”又是动态的,不会再睡眠了,也就是说在这个处理器上不会再有进程调度发生了,那么死锁自然就发生了。
同理,在信号量中休眠,另一个试图获得该信号量的进程会休眠,而不会占有CPU,所以可被唤醒休眠的进程得到CPU后唤醒。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值