原子操作
linux内核定义了叫做atomic_t的结构体来完成整型数据的原子操作,在使用中原子变量来代替整型变量。
atomic_t lock; 定义lock变量,本身是一个整型变量,内核中提供了大量原子操作的API函数
1,先初始化原子变量
atomic_t lock = ATOMIC_INIT(0); lock 初始化为0
atomic_set(&lock, 10); lock初始化为10
2,对原子变量进行操作,来锁住变量
上面的API函数表就可以对原子变量进行操作
3,释放原子变量
由于在使用原子操作的时候我们可以减1,所以放释放的时候我们需要对原子变量进行加1
自旋锁
原子操作只能对整型变量或者位进行保护,但是,在实际使用环境中怎么可能只有整型或者位这么简单的临界区(共享资源),例如设备结构体就需要被保护,原子操作就无法使用了。
当一个线程访问某个共享资源的时候,首先需要获得相应的锁,锁只能被一个线程所持有,只要这个线程不释放锁,那么其他线程就不能获得此锁。
自旋锁不能长时间持有,因为其他线程也要访问的时候,会一直等待你释放自旋锁,这样会浪费资源,降低系统性能。
被自旋锁保护的临界区一定不能调用任何能够引起休眠的API函数,否则的话可能会引起死锁现象的发生。因为一旦休眠,这个线程就会自动释放CPU使用权,从而线程B开始运行,线程B也想要获得锁,但是锁已经被线程A所使用了,又因为内核抢占被禁止了,线程B无法被调度出去,线程A也就无法运行,锁就没办法释放,就会导致死锁。
spinlock_t lock; //定义自旋锁
中断也会引起死锁的发生,在线程A获得锁之后被中断抢走CPU使用权的时候,中断也想要获得锁,但是锁被线程A占用了,中断会一直申请锁,但是线程A又没办法释放,死锁就发生了,对于这种情况需要先禁止本地中断,Linux 内核提供了相应的 API 函数。
1,初始化自旋锁
spin_lock_init(&gpioled.lock);
2,获取自旋锁
//flags代表中断状态,当被中断抢走时,中断结束会保存当前的状态
spin_lock_irqsave(&dev->lock, flags);
3,释放自旋锁
spin_unlock_irqrestore(&dev->lock, flags);
信号量
Linux 内核也提供了信号量机制,信号量常常用于控制对共享资源的访问。相较于自旋锁,信号量可以使线程进入休眠状态。
- 因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合
- 信号量不能用于中断,以为信号量会引起休眠,中断不能休眠。
- 如果共享资源的持有时间比较短,那么局不适合使用信号量了,因为频繁的休眠,切换线程引起的开销远大于信号量带来的那点优势。
信号量有个信号量值,相当于一栋楼里面有10个房间(线程),这个10也就想当于信号量值为10,当每进一个人的话,信号量值就会减1,当房子都被占用的话,信号量值为0,也就不允许其他人再进去了,只有当其他人出来的时候才可以再进去。
struct semaphore sem; /* 信号量 */
1,初始化信号量
sema_init(&gpioled.sem, 1); //只能一个线程访问
2,获取信号量
down(&gpioled.sem); //不能被中断打断
3,释放信号量
up(&dev->sem); /* 释放信号量,信号量值加1 */
互斥体
将信号量值设为1就可以使用信号量进行互斥访问了,虽然可以通过信号量进行互斥访问,但是linux提供了一个比信号量更加专业的机制来进行互斥,他就是互斥体--mutex。
- mutex可以导致休眠,因此不能再中断中使用mutex,中断中只能使用自旋锁。
- 和信号量一样,mutex保护的临界区可以调用引起阻塞的API函数
- 因为一次只能一个线程可以持有mutex,因此,必须由mutex的持有者释放mutex。
struct mutex lock; /* 互斥体 */
1,初始化互斥体
mutex_init(&gpioled.lock);
2,获取互斥体
mutex_lock(&gpioled.lock); /* 不能被信号打断 */
3,释放互斥体
mutex_unlock(&dev->lock);