7.linux 设备驱动中的并发控制
要解决多个进程对共享资源的并发访问,并发的访问会导致竞态,竞态问题的解决方式有以下几种:中断屏蔽,原子操作,自旋锁,和信号量等并发控制机制。
中断屏蔽:(但是长时间的屏蔽中断是很危险的)
local_irq_disable() 屏蔽中断
critical section
local_irq_enable() 开中断,其中local_irq_disable,local_irq_enable都是只能禁止和使能本cpu的中断。local_irq_save(flags)除了禁止中断的操作以外,还保存了当前的CPU的中断为的信息,local_irq_restore(flags)执行相反的操作。如果只是想禁止中断的底半部,应使用local_bh_disable()和local_bh_enable()
原子操作(执行过程中不会被其他代码中断,针对整形变量和位进行操作)
void atomic_set(atomic_t *v,int i), atomic_t v=ATOMIC_INIT(0)(定义并初始化为0)
atomic_read(atomic_t *v) 获取原子变量的值 atomic_add(int i, atomic_t *v) atomic_add(int i, atomic_t *v)原子变量的加减 atomic_inc() atomic_dec原子变量自加减
atomic_inc_and_test() atomic_dec_and_test() atomic_sub_and_test() 对原子变量进行自加自减和相减测试结果是否为0
atomic_add_return() atomic_sub_return() atomic_inc_return() atomic_dec_return 对原子变量进行加减操作并返回新的值
set_bit(nr,void * addr) 设置addr地址的第nr位 clear_bit()清除位 change_bit()改变位 test_bit返回相应位 还有test_and_set_bit() test_and_clear_bit() test_and_change_bit()
自旋锁(如果测试结果表明锁仍被占用,程序将在小循环内重复这个测试并设置操作,原地打转)
定义自旋锁 spinlock_t lock 初始化自旋锁 spin_lock_init(lock) 获得自旋锁,如果没有获得,则原地打转等在那 spin_lock(lock) spin_trylock(lock)——结果立即返回,不再原地打转
释放自旋锁 spin_unlock(lock)
自旋锁可以保证临界区不受别的CPU和本CPU内的抢占干扰,但是还是会受到中断和底半部的影响,所以要结合local_irq_disable() loocal_irq_enable()等等构成spin_lock_irq()
spin_unlock_irq() spin_lock_irqsave() spin_unlock_irqrestore() spin_lock_bh() spin_unlock_bh()等
因此在时间很短的时候,用自旋锁是合理的,自旋锁内部不能调用可能引起进程调度的函数,如果阻塞的话就会导致内核的崩溃,比如copy_from_user() copy_to_user,msleep
kmalloc等等
读写自旋锁-P146
它允许多个执行状态读取,但是只有一个写进程
定义和初始化读写自旋锁 rwlock_t my_rwlock=RW_LOCK_UNLOCKED; rwlock_t my_rw_lock; rwlock_init(&my_rwlock)
读锁定
read_lock() read_lock_irqsave() read_lock_irq() read_lock_bh()
读解锁
read_unlock() read_unlock_irqrestore() read_unlock_irq() read_unlock_bh()
写锁定
write_lock() write_lock_irqsave() write_lock_irq() write_trylock() write_lock_bh()
写解锁和读解锁类似
顺序锁(它是对读写锁的一种优化)——P147
读执行单元不会被写执行单元阻塞,即读执行单元在写执行单元对顺序锁保护的共享资源进行写操作时仍然可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行单元完成读操作采取进行写操作,如果在读执行单元在读操作期间,写执行单元以及发生了写操作,那么,读执行单元必须要重新读入数据。
获得顺序锁
write_seqlock() write_tryseqlock() write_seqlock_irqsave() write_seqlock_irq() write_seqlock_bh()
释放顺序锁
write_sequnlock() write_sequnlock()_irqrestore() write_sequnlock_irq() write_sequnlock_bh()
读开始
read_seqbegin() read_seqbegin_irqsave()
重读
read_seqretry() read_seqretry_irqrestore()
读-拷贝-更新(rcu)(首先拷贝一个副本,然后对副本进行修改,然后在适当的时机把原来数据的指针重新指向新的被修改的数据,这个时机就是所有引用该数据的CPU都退出共享数据的操作的时候,这样他允许多个读执行单元和多个写执行单元同时访问被保护的数据,但是写的内容太多的话,就不是很适合了)
读锁定——P148
rcu_read_lock() rcu_read_lock_bh() 实质是禁止内核的抢占调度
读解锁
rcu_read_unlock() rcu_read_unlock_bh()
同步RCU
synchronize_rcu(),用来阻塞写执行单元,知道所有的读执行单元已经完成,写执行单元继续下一步操作。
挂接回调
call_rcu(struct rcu_head *head, void (*func)(struct rcu_head * rcu))
每个CPU维护两个数据结构rcu_data和rcu_bh_data,用于保存回调函数。如函数call_rcu将回调函数注册到rcu_data.在使用rcu时,读执行单元必须提供一个信号给写执行单元以便写执行单元可以确定数据的可以被安全释放和修改的时机。一旦所有的读执行单元告知不在保护的数据结构中 ,那么垃圾收集器就调用回调函数完成最后的数据释放和修改工作。
RCU还增加了链表操作函数的RCU版本
list_add_rcu() list_add_tail_rcu() list_del_rcu() list_replace_rcu() list_for_each_rcu() 还有哈希链表的一些操作——P150
信号量
当获取不到信号量的时候,进程不会原地打转而是进入休眠等待状态。
定义信号量
struct semaphore sem;
初始化信号量
sema_init() define init_MUTEX(sem) sema_init(sem,1) init_MUTEX_LOCKED(sem)
快捷方式 DECLARE_MUTEX(name) DECLARE_MUTEX_LOCKED(name)
获得信号量
down() down_interruptible() 后者可以进入睡眠状态后能被信号打断,信号也会使该函数返回。同时还有down_trylock()
释放信号量
up()
信号量被初始化为0,可以用于同步。
比较好的同步机制——completion——P154
定义完成量 struct completion my_completion;
初始化 init_completion()
快捷方式 DECLARE_COMPLETION()
等待完成量
wait_for_completion()
唤醒完成量
complete() complete_all()
自旋锁和信号量:它们都是互斥手段,但是层次不同,信号量是进程级的,用于多个进程之间对资源的互斥,经常要发生进程上下文的切换,适用于占用时间较长。自旋锁不能在临界区里长时间停留,否则会降低系统的效率。
读写信号量
允许N个读执行单元同时访问共享资源,而最多只能有1个写执行单元。
struct rw_semaphore my_rws;
void init_rwsem()
读信号量获取
down_read() down_read_trylock()
读信号量释放
up_read()
写信号量获取
down_write() down_write_trylock()
写信号量释放 void up_write()
互斥体
定义并初始化它:struct mutex my_mutex; mutex_init(&my_mutex)
下面两个函数用于获取互斥体: mutex_lock() mutex_lock_interruptible() mutex_trylock()
释放互斥体: mutex_unlock()