驱动程序中的并发控制方法:
一个硬件可能会被多个进程并发使用,例如scull_read的时候被另外一个进程调用的scull_write打断,那么读到的数据就不是以前应该读到的数据,这就需要并发控制
并发控制其实多数是使用信号量来完成,包括如下5种方式:1 信号量,2自旋锁,3读写信号量,4读写自旋锁,5completion机制
信号量操作方法:
1 定义及初始化
struct semaphore sem;
可用void sema_init(struct semaphore *sem, int val);直接创建,其中val为信号量初值。
也可以用两个宏来定义和初始化信号量的值为1或0:
DECLARE_MUTEX(name) : 定义信号量name并初始化为1
DECLARE_MUTEX_LOCKED(name) : 定义信号量name并初始化为0
还可以用下面的函数初始化:
void init_MUTEX(struct semaphore *sem); //初始化信号量的值为1
void init_MUTEX_LOCKED(struct semaphore *sem); //初始化信号量的值为0
2 信号量操作
p操作:
void down(struct semaphore *sem); //用来获取信号量,如果信号量值大于或等于0,获取信号量,否则进入睡眠状态,睡眠状态不可唤醒
void down_interruptible(struct semephore *sem); //用来获取信号量,如果信号量大于或等于0,获取信号量,否则进入睡眠状态,等待信号量被释放后,激活该程。
void down_trylock(struct semaphore *sem); //试图获取信号量,如果信号量已被其他进程获取,则立刻返回非零值,调用者不会睡眠
v操作:
void up(struct semaphore *sem); //释放信号量,并唤醒等待该资源进程队列的第一个进程函数
3 使用方法
定义:struct semaphore sem;
初始化:sema_init(&sem, 1);
获取信号量:if(down_interruptible(&sem))
return -ERESTARTSYS;
释放信号量:up(&sem);
4注意事项
down为深度睡眠,不能被信号中断;浅度睡眠down_interruptible可以被信号中断,返回值可以用于判断被唤醒的原因是由于其他进程执行了up还是收到了信号。
5 实验
在scull_read中加入:ssleep(5);
写入数据:echo yang>./scull0
并发读出数据:cat ./scull0 & cat ./scull0
查看驱动的输出:tail /var/log/syslog
自旋锁的编程实战:
1 自旋锁的特点及与信号量的区别
(1)自旋锁是一个互斥设备,只有两个值:上锁,解锁
(2)上锁spin_lock进入临界区以后,操作系统不能进行任务调度,只会响应中断,且中断结束后也不进行进程调度,而是回到持有自旋锁的进程
(3)如果获取自旋锁的时候已经被上锁,代码会一直检查这个锁,持续占用cpu,且不会进入休眠,导致占用大量cpu资源,所以只适合临界区代码比较短的场合
(4)持有自旋锁以后的进程不要进入休眠,否则无法释放自旋锁,导致系统死锁。
2 自旋锁的操作方法
(1)定义和初始化
spinlock_t my_lock=SPIN_LOCK_UNLOCKED;
void spin_lock_init(spinlock_t *lock);
(2)自旋锁操作函数
void spin_lock(spinlock_t *lock) //获取锁
void spin_unlock(spinlock_t *lock) //释放锁
其他函数
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags)//禁止中断,但是保存interrupt中的flag
void spin_lock_irq(spinlock_t *lock)//期间禁止中断
void spin_lock_bh(spinlock_t *lock)//禁止软件中断,使能硬件中断
int spin_trylock(spinlock_t *lock)
int spin_trylock_bh(spinlock_t *lock)
2 使用方法:
(1)定义
spinlock_t lock;
(2)初始化
spin_lock_init(&lock);
(3)获取自旋锁
spin_lock(&lock);
(4)释放自旋锁
spin_unlock(&lock);
3 读写自旋锁
在控制读和写不能并发执行的前提下,使得多个读可以并发执行声明和初始化
rwlock_t my_rwlock=RW_LOCK_UNLOCKED;
rwlock_t my_rwlock;rwlock_init(&my_rwlock);
操作方法:
void read_lock(rwlock_t *lock)
void read_lock_irqsave(rwlock_t *lock, unsigned long flags)
void read_lock_irq(rwlock_t *lock)
void read_lock_bh(rwlock_t *lock)
void read_unlock(rwlock_t *lock)
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
void read_unlock_irq(rwlock_t *lock)
void read_unlock_bh(rwlock_t *lock)
Competions机制
编写驱动的时候,经常会做如下操作,在当前线程中初始化并启动另外的线程,并且当前现成必须等待被启动的伙伴线程帮助自己完成某些工作后,自己才能继续执行
内核的信号量api针对可获得的情况进行了专门的优化,当按照上述程序的方式来使用信号量api完成工作,则会导致第一个线程几乎必然阻塞在down调用处,因此会导致性能遭受极大的下降
(1)定义,初始化
struct completion my_completion;
init_completion(&my_completion);
(2)等待完成量
void wait_for_completion(struct completion *c)
(3)唤醒完成量
void complete(struct completion *c)
void complete_all(struct completion *c)