目录
在 汇编翻译网址中,左侧是C语言,右侧是汇编语言。一条i++指令,翻译成汇编有三条(黄色底纹)

在使用O2优化后,一条i++指令,只有一条汇编指令。

总结:
翻译译成3条指令,需要加锁;-O2优化后,一条指令不需要加锁,本身就是原子的。
事先不知道用什么优化选项,所以需要加锁保护数据原子执行。
临界区:代码路径能够被并行执行,可以共享写数据;
临界区的资源需要保护:也就是说,它必须以相互排斥的方式单独运行/序列化/运行;
写程序时如何正确的使用锁
锁的粒度:在大项目中,使用太少的锁是个问题。导致性能问题,拥有大量锁实际上对性能有好处,对复杂度不友好。在项目中,使用一个锁保护一个全局变量是比较典型的用法。
简单化:
在CPU0,线程A获取锁A,同时想获取锁B;并发性的,在CPU1线程B获取锁B,同时想获取锁A,导致AB-BA死锁。进一步扩展,可能是AB-BC-CA循环依赖的死锁。
锁A发生在中断上下文中:如果中断发生,中断处理函数尝试获取锁A,导致死锁。因此,在中断上下文中获取的锁必须始终在禁用中断的情况下使用。
遵循锁的顺序:获取-释放。复杂的场景导致死锁,可以使用lockdep来检测。
mutex lock 是睡眠锁,在等待锁时将睡眠,再锁被释放后,内核唤醒等待的进程运行;
spinlock 是不睡眠,一直轮询等待;
睡眠唤醒上下文切换开销,轮询也有开销,比较其时间大小。
2 * t2 > t1
结论
如果临界区等待的时间小于两次上下文切换的时间,使用mutex是不对的,开销太大;
在临界区的时间很小,非阻塞的临界区,使用spinlock比mutex优越;
实际上,如何计算上述的两个时间是不现实的。
一般来说:
那么如何区分程序运行的进程、中断上下文呢?使用PRINT_CTX()宏
- if(in_task())
- In process contex
- else
- Atomic or interrput contex
在人机交互应用程序中,一般的经验,通常应该将进程置于可中断的睡眠状态。
此外,不可中断状态的睡眠更多一些,必须无限期阻塞等待,任务不能打断阻塞的等待。
mutex_lock()是不可打断的, muten_lock_interruptible()是可以打断的;前者的速度快一些,用在临界区非常短的条件下
mutex_trylock(struct mutex *lock)
返回值 1 :锁可用获取到 0: 还在竞争中
- down_[interruptible]
- up()
头文件
动态申请
- spinlock_t lock
- spin_lock_init(&lock)
静态申请
DEFINE_SPINLOCK(lock)
基本用法
- void spin_lock(spinlock_t *lock);
-
- void spin_unlock(spinlock *lock);
内核配置:CONFIG_DEBUG_ATOMIC_SLEEP
- spin_lock()
- schedule_timeout();
- spin_unlock()
其内部引起schedule()
- [ 405.049171] BUG: scheduling while atomic: rdwr_test_secre/895/0x00000002
- [ 405.049935] Modules linked in: miscdrv_rdwr_spinlock(OE)
- [ 405.053302] CPU: 1 PID: 895 Comm: rdwr_test_secre Tainted: G OE 5.0.0+ #13
- [ 405.054026] Hardware name: linux,dummy-virt (DT)
- [ 405.054740] Call trace:
- [ 405.055790] dump_backtrace+0x0/0x528
- [ 405.056319] show_stack+0x24/0x30
- [ 405.056726] __dump_stack+0x20/0x2c
- [ 405.057021] dump_stack+0x25c/0x388
- [ 405.057292] __schedule_bug+0x1d4/0x214
- [ 405.057604] __schedule+0x1d8/0x2214
- [ 405.057820] schedule+0x4e8/0x790
- [ 405.058143] schedule_timeout+0x1bd0/0x1c44
- [ 405.060645] write_miscdrv_rdwr+0x1228/0x1bc8 [miscdrv_rdwr_spinlock]
- [ 405.061225] __vfs_write+0x54/0x90
- [ 405.061935] vfs_write+0x16c/0x2f4
- [ 405.062423] ksys_write+0xb4/0x164
- [ 405.065050] __se_sys_write+0x48/0x58
- [ 405.065653] __arm64_sys_write+0x40/0x48
- [ 405.066150] __invoke_syscall+0x24/0x2c
- [ 405.066533] invoke_syscall+0xa4/0xd8
- [ 405.067189] el0_svc_common+0x100/0x1e4
- [ 405.068025] el0_svc_handler+0x418/0x444
- [ 405.068582] el0_svc+0x8/0xc
硬中断优先级最高,能抢占任何资源
问题在于中断处理程序与read方法在做什么,以及实现方式,有以下三种情景:
第3种方法需要使用spinlock锁,从下图可以看到数据的竞争关系。

void spin_lock_irq(spinlock_t *lock);
其内部禁止中断(本处理器上),使得通过中断产生数据竞争是不可能的。
unsigned long spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
spin_lock_bh(spinlock_t *lock)
它可以禁用本地处理器上的中断,使用自旋锁,从而保护关键部分。
总结:
| 最简单,低开销 | spin_lock()/spin_unlock | 在进程上下文保护临界资源 |
| 中等开销 使用中断禁用 | spin_lock_irq()/spin_unlock_irq() | 在中断发生时,进程和中断上下文有资源竞争 |
| 高开销 最安全 | spin_lock_irqsave()/spin_unlock_irqsave() | spinlock 保存与恢复中断掩码 |
5.8内核中引入 local lock