自旋锁
XWOS的自旋锁
Categories:
2 分钟阅读
概述
自旋锁是多核系统中为防止多个处理器同时访问公共的内存区域(称为临界区)而引入的一种锁。 当一个CPU获得自旋锁并访问临界区时,其他CPU只能 自旋 等待锁。 所谓 自旋 ,是指不断循环测试 锁 是否已经解开。
自旋锁只是为SMP的场景设计的一种锁机制。在单核(UP)系统中,并不需要自旋锁。 但为了软件接口的统一,实现了虚假的自旋锁,只是对关闭抢占、中断底半部或中断的封装。
自旋锁还伴随其他操作:内存屏障,关闭调度器的抢占,关闭中断底半部,关闭中断等。
局限性
- 自旋锁内包含内存内存屏障操作,内存屏障会降低CPU的性能;
- 自旋锁不区分读和写的情况。
使用自旋锁
自旋锁的初始化
自旋锁是基于原子操作指令实现的,自旋锁结构体很小,核心数据是一个CPU指令能操作的基本数据类型,因此不提供动态创建和删除方法。
用户可以通过 xwos_splk_init()
初始化自旋锁。
多锁
当使用 多个 自旋锁保护临界区时,上锁和解锁顺序必须 保持一致 ,否则会导致死锁。
锁模式
保护 线程 上下文之间的临界区
- 临界区可嵌套,且只对 线程 上下文是安全的。
xwos_splk_lock(&lock1);
/* 临界区1 */
xwos_splk_lock(&lock2);
/* 临界区2 */
xwos_splk_unlock(&lock2);
/* 临界区1 */
xwos_splk_unlock(&lock1);
- 临界区内,只会关闭抢占。可以理解为,在线程层面,临界区内的操作是 原子的 。
- 临界区内的数据只能被 线程 的代码访问。
- 临界区内不能发生 抢占 ,用户不能在临界区内使用会导致 睡眠 与 阻塞 的CAPI。
- CAPI
xwos_splk_lock()
:上锁,关闭抢占,进入临界区xwos_splk_trylock()
:尝试上锁,关闭抢占,尝试进入临界区xwos_splk_unlock()
:解锁,开启抢占,退出临界区
保护 任意 上下文之间的单一临界区
- 临界区对 任意 上下文都是安全的。但临界区必须是单一的,嵌套时会出现以下错误:
xwos_splk_lock_cpuirq(&lock1);
/* 临界区1 */
xwos_splk_lock_cpuirq(&lock2);
/* 临界区2 */
xwos_splk_unlock_cpuirq(&lock2);
/* 临界区1: 错误!中断被打开 */
xwos_splk_unlock_cpuirq(&lock1);
- 临界区内,不但会关闭抢占,还会关闭CPU中断。可以理解为在临界区内的操作是 原子的 ,临界区内的数据能被 任意 代码访问。
- 临界区内不会发生中断,也不能发生 调度 ,用户依然不能在临界区内使用会导致 睡眠 与 阻塞 的CAPI。
- CAPI
xwos_splk_lock_cpuirq()
:上锁,关闭抢占、CPU中断,进入临界区xwos_splk_trylock_cpuirq()
:尝试上锁,关闭抢占、CPU中断,尝试进入临界区xwos_splk_unlock_cpuirq()
:解锁,开启抢占、CPU中断,退出临界区
保护 任意 上下文之间的 嵌套 临界区
- 为了解决关闭中断时临界区嵌套问题,可以使用中断标志保存与恢复的CAPI。
xwos_splk_lock_cpuirqsv(&lock1, &cpuirq1);
/* 临界区1 */
xwos_splk_lock_cpuirqsv(&lock2, &cpuirq2);
/* 临界区2 */
xwos_splk_unlock_cpuirqrs(&loc2, cpuirq2); /* 退出临界区2时中断标志还是为关闭 */
/* 临界区1:不会出现中断被打开的错误 */
xwos_splk_unlock_cpuirqrs(&loc1, cpuirq1);
- 临界区内,不但会关闭抢占,还会关闭CPU中断。可以理解为在临界区内的操作是 原子的 ,临界区内的数据能被 任意 代码访问。
- 临界区内不会发生中断,也不能发生 调度 ,用户依然不能在临界区内使用会导致 睡眠 与 阻塞 的CAPI。
- CAPI
xwos_splk_lock_cpuirqsv()
:上锁,关闭抢占,保存CPU中断标志并关闭,进入临界区xwos_splk_trylock_cpuirqsv()
:尝试上锁,关闭抢占,保存CPU中断标志并关闭,尝试进入临界区xwos_splk_unlock_cpuirqrs()
:解锁,开启抢占,恢复CPU中断,退出临界区
保护 线程 和 指定中断 上下文之间的临界区
- 临界区对 线程 、 指定中断 是安全的。但临界区必须是单一的。
xwos_splk_lock_irqs(&lock, irq_array, num);
/* 临界区 */
xwos_splk_unlock_irqs(&lock, irq_array, num);
- 临界区内,只关闭抢占和指定的中断。可以理解为在线程和指定的中断函数层面,临界区内的操作是 原子的 , 临界区内的数据只能被 线程 和 指定的外设中断 上下文访问。
- 临界区内不会发生指定的中断,也不会发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠 与 阻塞 的CAPI。
- CAPI
xwos_splk_lock_irqs()
:上锁,关闭抢占,关闭部分中断,进入临界区xwos_splk_trylock_irqs()
:尝试上锁,关闭抢占,关闭部分中断,尝试进入临界区xwos_splk_unlock_irqs()
:解锁,开启抢占、开启部分中断,退出临界区
保护 线程 和 指定中断 上下文之间的 嵌套 临界区
- 临界区对 线程 、 指定中断 是安全的。临界区可以嵌套。
xwos_splk_lock_irqssv(&lock1, irq_array, flag1_array, num);
/* 临界区1 */
xwos_splk_lock_irqssv(&lock2, irq_array, flag2_array, num);
/* 临界区2 */
xwos_splk_unlock_irqsrs(&lock2, irq_array, flag2_array, num);
/* 临界区1 */
xwos_splk_unlock_irqsrs(&lock1, irq_array, flag1_array, num);
- 临界区内,只关闭抢占和指定的中断。可以理解为在线程和指定的中断函数层面,
- 临界区内的操作是 原子的 ,临界区内的数据只能被 线程 和 指定的外设中断 上下文访问。
- 临界区内不会发生指定的中断,也不会发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠 与 阻塞 的CAPI。
- CAPI
xwos_splk_lock_irqssv()
:上锁,关闭抢占,保存部分中断标志并关闭,进入临界区xwos_splk_trylock_irqssv()
:尝试上锁,关闭抢占,保存部分中断标志并关闭,尝试进入临界区xwos_splk_unlock_irqsrs()
:解锁,开启抢占、恢复部分中断,退出临界区
保护 线程 和 中断底半部 上下文之间的临界区
- 临界区对 线程 、 中断底半部 是安全的。临界区可嵌套。
xwos_splk_lock_bh(&lock1);
/* 临界区1 */
xwos_splk_lock_bh(&lock2);
/* 临界区2 */
xwos_splk_unlock_bh(&lock2);
/* 临界区1 */
xwos_splk_unlock_bh(&lock1);
- 临界区内,只关闭抢占和中断底半部。可以理解为在线程和中断底半部层面,临界区内的操作是 原子的 , 临界区内的数据只能被 线程 和 中断底半部 上下文访问。
- 临界区内不会发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠 与 阻塞 的CAPI。
- CAPI
xwos_splk_lock_bh()
:上锁,关闭抢占、中断底半部,进入临界区xwos_splk_trylock_bh()
:尝试上锁,关闭抢占、中断底半部,尝试进入临界区xwos_splk_unlock_bh()
:解锁,开启抢占、中断底半部,退出临界区