Linux设备中的并发控制
Linux设备中的并发控制
并发执行的唯一原因是:硬件中断服务。
内核代码是可抢占的。
什么是竞争?:当 2 个执行的线路[有机会操作同一个数据结构(或者硬件资源), 混合的可能性就一直存在。
避免竞争方法:
在你设计驱动时在任何可能的时候记住避免共享的资源. 如果没有并发存取, 就没有竞争情况. 因此小心编写的内核代码应当有最小的共享. 这个想法的最明显应用是避免使用全局变量. 如果你将一个资源放在多个执行线路能够找到它的地方, 应当有一个很强的理由这样做。
全局变量不是共享资源的唯一方法,传递一个指针给内核的其他代码,潜在的创造了一个新的共享形式。
存取管理的常用技术是加锁或者互斥,确保在任何时间内只有一个执行线程可以操作一个共享资源。
当内核代码创建一个会被内核其他部分共享的对象时, 这个对象必须一直存在(并且功能正常)到它知道没有对它的外部引用存在为止。
临界区:在任何给定的时间内只有一个线程可以执行代码。
旗标(信号量):是一个整型值,结合一对函数,一个像进入临界区的进程将调用相关旗标的P,如果旗标的值大于0,这个值将减一并且继续进程。相反,如果旗标的值是0(或者更小),进程必须等待别的进程释放旗标,解锁旗标通过调用V完成,这个函数会增加旗标的值,并且。如果需要,可以唤醒等待的进程。
当旗标初始化为1时,就成了互斥锁。
初始化:void sema_init(struct semaphore *sem, int val) /* val 表示初始化值 */
互斥锁初始化: DECLARE_MUTEX(name); /* 1 name为信号量变量*/
DECLARE_MUTEX_LOCKED(name); /* 0 同上*/
动态初始化(运行时):
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
P函数成为down—或者这个名字的变体:作用是是信号量减一,并且可能使调用者睡眠一会,等到旗标变为可用之后,再对共享资源进行存取。
void down(struct semaphore * sem); /* 递减信号量,并等待需要的时间 */
int down_interruptible(struct semaphore *sem); /* 可被中断的:允许一个在等待用户空间的进程被用户中断 */
int down_trylock(struct semaphore *sem); /* 从不睡眠 */
V函数:void up(struct semaphore *sem); /* 当持有一个旗标时遇到一个错误时,旗标必须在返回错误状态之前释放旗标。
注意:旗标必须在设备对系统的其他部分可用前初始化。
read and write semaphore:
down_read(struct rm_semaphore *sem); -------- down_write(struct rm_semaphore *sem)
int down_read_trylock(struct rw_semaphore *sem); ----- down_write_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem); ---- void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem); //一旦完成改变后允许其他read进入。
what is the meaning of Completions?
允许一个线程告诉另一个线程工作已经完成。
一个completion的创建:
<linux/completion.h>
DDECLARE_COMLETION(my_completion);
动态创建和初始化:
struct completion my_completion;
init_completion( &my_completion);
简单的调用:
void wait_for_completion(struct completion *c) //如果没有人完成这个任务,结果会是一个不可杀死的进程。
一般调用:
void complete(struct completion *c); /* 一个等待进程 */
void complete_all(struct completion *c); /* 多个等待进程 */
what is the meaning of spinlock?
background: 大部分加锁是有一种自旋锁机制来实现;
special case:自旋锁可用在不能睡眠的代码中,例如中断处理;
concept:互斥设备,上锁和解锁;如果锁是可用的, 这个"上锁"位被置位并且代码继续进入临界区. 相反, 如果这个锁已经被别人获得, 代码进入一个紧凑的循环中反复检查这个锁, 直到它变为可用. 这个循环就是自旋锁的"自旋"部分。(简单的形式并不是好的形势)
要求:原子操作;在超线程上避免死锁。
#include<linux/spinlock.h>
初始化定义:spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
或者在运行时使用:void spin_lock_init(spinlock_t *lock);
在进入一个临界区时,代码必须获得需要的lock:
void spin_lock(spinlock_t *lock); /* 由于不可中断 自旋直到锁变为可用*/
释放锁:
void spin_unlock(spinlock_t *lock);
原则上:不能睡眠;不能因为任何原因放弃处理器,除了服务中断(并且有时即便如此也不行);自旋锁必须一直是尽可能短时间得持有。
原因:内核抢占;睡眠可能发生在从用户空间拷贝数据,分配内存的任何操作等等;中断在获得自旋操作之后,但是中断操作中的代码又需要获得这把锁,这就会发生永久的自旋。(避免这个陷阱需要在持有自旋锁时禁止中断)
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_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
陷阱锁:
模糊的规则
加锁顺序规则
细 – 粗 –