顺序锁

XWOS的顺序锁

概述

顺序锁是对 自旋锁 改良后的锁。主要是对读和写的情况进行区分。 顺序锁中包含一个自旋锁和一个顺序值。顺序锁将临界区分为三种:

  • 独占写:任何对顺序锁临界区的 操作都是独占的,每次 操作时,会先上锁自旋锁, 然后增加顺序值,离开临界区时也会再次增加顺序值。言外之意,顺序值增加两次,顺序值增加的次数是 偶数
  • 非独占读:如果多个CPU进行的是 只读 操作,它们可以同时进入 非独占读临界区 。 CPU进入非独占读临界区无需获得自旋锁,但需要先测试顺序值是否为 偶数 ,并记录此时的顺序值。 当退出读临界区时,需要再次读取顺序值,并与之前记录的值进行比较:
    • 如果相等,表示读的结果有效;
    • 如果不相等,则表示读的过程中别的CPU进行了写操作,此次的读操作无效。
  • 独占读:如果希望读临界区不会被写操作无效掉,可以使用独占读的方式, 独占读会排斥其他CPU上的独占写和独占读操作,但不会排斥非独占读, 其他CPU依然可以进入非独占读临界区。

局限性

顺序锁有个缺陷: 者将数据写为空指针时,有可能导致 非独占读 者引用空指针而崩溃。

使用顺序锁

顺序锁的初始化

同自旋锁一样,顺序锁结构体很小,不提供动态创建和删除方法。 用户可以通过 xwos_sqlk_init() 初始化顺序锁。

多锁

当使用 多个 自旋锁保护 写临界区独占读临界区 时,上锁和解锁顺序必须 保持一致 ,否则会导致死锁。

非独占读临界区

如果多个CPU进行的是 只读 操作,它们可以同时进入 非独占读临界区 。 CPU进入非独占读临界区无需获得自旋锁,但需要先测试顺序值是否为 偶数,并记录此时的顺序值。 此操作可通过 xwos_sqlk_rd_begin() 来完成。

当退出读临界区时,需要再次读取顺序值,并与之前记录的值进行比较。 此操作可通过 xwos_sqlk_rd_retry() 完成。

  • 如果相等,表示读的结果有效;
  • 如果不相等,则表示读的过程中别的CPU进行了写操作,此次的读操作无效。
        seq = xwos_sqlk_rd_begin(&lock); /* 进入临界区前先获取顺序值 */
        do {
                /* 非独占读临界区 */
        } while (xwos_sqlk_rd_retry(&lock seq)); /* 测试顺序值是否发生了变化 */

用户也可以通过 xwos_sqlk_get_seq() 读取顺序值,自行比较。

独占读临界区

如果希望读临界区不会被写操作无效掉,可以使用独占读的方式, 独占读会排斥其他CPU上的独占写和独占读操作,但不会排斥 非独占读 , 其他CPU依然可以进入非独占读临界区。

保护 线程 上下文之间的读临界区

  • 临界区可嵌套,且只对 线程 上下文是安全的。
        xwos_splk_rdex_lock(&lock1);
        /* 独占读临界区1 */
        xwos_splk_rdex_lock(&lock2);
        /* 独占读临界区2 */
        xwos_splk_rdex_unlock(&lock2);
        /* 独占读临界区1 */
        xwos_splk_rdex_unlock(&lock1);
  • 临界区内,只会关闭抢占。可以理解为在线程层面,临界区内的操作是 原子的 , 临界区内的数据只能被 线程 上下文访问。
  • 临界区内不能发生 调度 ,用户不可在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 任意 上下文之间的读临界区

  • 临界区对 任意 上下文都是安全的。但临界区必须是单一的,嵌套时会出现以下错误:
        xwos_splk_rdex_lock_cpuirq(&lock1);
        /* 独占读临界区1 */
        xwos_splk_rdex_lock_cpuirq(&lock2);
        /* 独占读临界区2 */
        xwos_splk_rdex_unlock_cpuirq(&lock2);
        /* 独占读临界区1: 错误!中断被打开 */
        xwos_splk_rdex_unlock_cpuirq(&lock1);
  • 临界区内,不但会关闭抢占,还会把CPU中断也关了。可以理解为在临界区内的操作是 原子的 , 临界区内的数据只能被 任意 上下文访问。
  • 临界区内不会发生中断,也不可能发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 任意 上下文之间的 嵌套 读临界区

  • 为了解决关闭中断时临界区嵌套问题,可以使用中断标志保存与恢复的CAPI。
        xwos_splk_rdex_lock_cpuirqsv(&lock1, &cpuirq1);
        /* 独占读临界区1 */
        xwos_splk_rdex_lock_cpuirqsv(&lock2, &cpuirq2);
        /* 独占读临界区2 */
        xwos_splk_rdex_unlock_cpuirqrs(&loc2, cpuirq2);
        /* 独占读临界区1 */
        xwos_splk_rdex_unlock_cpuirqrs(&loc1, cpuirq1);
  • 临界区内,不但会关闭抢占,还会把CPU中断也关了。可以理解为在临界区内的操作是 原子的 , 临界区内的数据只能被 线程 上下文访问。
  • 临界区内不会发生中断,也不可能发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 线程指定中断 上下文之间的独占读临界区

  • 临界区对 线程指定中断 是安全的。但临界区必须是单一的。
        xwos_splk_rdex_lock_irqs(&lock, irq_array, num);
        /* 独占读临界区 */
        xwos_splk_rdex_unlock_irqs(&lock, irq_array, num);
  • 临界区内,只关闭抢占和指定的中断。可以理解为在线程和指定的中断函数层面, 临界区内的操作是 原子的 ,临界区内的数据只能被 线程指定的外设中断 上下文访问。
  • 临界区内不会发生指定的中断,也不会发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 线程指定中断 上下文之间的 嵌套 读临界区

  • 临界区对 线程指定中断 是安全的。临界区可以嵌套。
        xwos_splk_rdex_lock_irqssv(&lock1, irq_array, flag1_array, num);
        /* 独占读临界区1 */
        xwos_splk_rdex_lock_irqssv(&lock2, irq_array, flag2_array, num);
        /* 独占读临界区2 */
        xwos_splk_rdex_unlock_irqsrs(&lock2, irq_array, flag2_array, num);
        /* 独占读临界区1 */
        xwos_splk_rdex_unlock_irqsrs(&lock1, irq_array, flag1_array, num);
  • 临界区内,只关闭抢占和指定的中断。可以理解为在线程和指定的中断函数层面, 临界区内的操作是 原子的 ,临界区内的数据只能被 线程指定的外设中断 上下文访问。
  • 临界区内不会发生指定的中断,也不会发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 线程中断底半部 上下文之间的读临界区

  • 临界区对 线程中断底半部 是安全的。临界区可嵌套。
        xwos_splk_rdex_lock_bh(&lock1);
        /* 独占读临界区1 */
        xwos_splk_rdex_lock_bh(&lock2);
        /* 独占读临界区2 */
        xwos_splk_rdex_unlock_bh(&lock2);
        /* 独占读临界区1 */
        xwos_splk_rdex_unlock_bh(&lock1);
  • 临界区内,只关闭抢占和中断底半部。 可以理解为在线程和中断底半部层面, 临界区内的操作是 原子的 ,临界区内的数据只能被 线程中断底半部 上下文访问。
  • 临界区内不会发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

读写临界区

任何对顺序锁临界区的 读写 操作都是独占的,每次进入临界区时,会先上锁自旋锁, 然后增加顺序值,离开临界区时也会再次增加顺序值。言外之意,顺序值增加两次,顺序值增加的次数是 偶数

保护 线程 上下文之间的读写临界区

  • 临界区只对 线程 上下文是安全的。临界区可嵌套。
        xwos_splk_wr_lock(&lock1);
        /* 临界区1 */
        xwos_splk_wr_lock(&lock2);
        /* 临界区2 */
        xwos_splk_wr_unlock(&lock2);
        /* 临界区1 */
        xwos_splk_wr_unlock(&lock1);
  • 临界区内,只会关闭抢占。可以理解为在线程层面,临界区内的操作是 原子的 , 临界区内的数据只能被 线程 上下文访问。
  • 临界区内不能发生 调度 ,用户不可在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 任意 上下文之间的读写临界区

  • 临界区对 任意 上下文都是安全的。 但临界区必须是单一的,嵌套时会出现以下错误:
        xwos_splk_wr_lock_cpuirq(&lock1);
        /* 临界区1 */
        xwos_splk_wr_lock_cpuirq(&lock2);
        /* 临界区2 */
        xwos_splk_wr_unlock_cpuirq(&lock2);
        /* 临界区1: 错误!中断被打开 */
        xwos_splk_wr_unlock_cpuirq(&lock1);
  • 临界区内,不但会关闭抢占,还会关闭CPU中断。可以理解为在临界区内的操作是 原子的 , 临界区内的数据只能被 任意 上下文访问。
  • 临界区内不会发生中断,也不可能发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 任意 上下文之间的 嵌套 读写临界区

  • 为了解决关闭中断时临界区嵌套问题,可以使用中断标志保存与恢复的CAPI。
        xwos_splk_wr_lock_cpuirqsv(&lock1, &cpuirq1);
        /* 临界区1 */
        xwos_splk_wr_lock_cpuirqsv(&lock2, &cpuirq2);
        /* 临界区2 */
        xwos_splk_wr_unlock_cpuirqrs(&loc2, cpuirq2);
        /* 临界区1 */
        xwos_splk_wr_unlock_cpuirqrs(&loc1, cpuirq1);
  • 临界区内,不但会关闭抢占,还会把CPU中断也关了。可以理解为在临界区内的操作是 原子的 ,临界区内的数据只能被 线程 上下文访问。
  • 临界区内不会发生中断,也不可能发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 线程指定中断 上下文之间的读写临界区

  • 临界区对 线程指定中断 是安全的。但临界区必须是单一的。
        xwos_splk_wr_lock_irqs(&lock, irq_array, num);
        /* 临界区 */
        xwos_splk_wr_unlock_irqs(&lock, irq_array, num);
  • 临界区内,只关闭抢占和指定的中断。可以理解为在线程和指定的中断函数层面, 临界区内的操作是 原子的 ,临界区内的数据只能被 线程指定的外设中断 上下文访问。
  • 临界区内不会发生指定的中断,也不会发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 线程指定中断 上下文之间的 嵌套 读写临界区

  • 临界区对 线程指定中断 是安全的。临界区可以嵌套。
        xwos_splk_wr_lock_irqssv(&lock1, irq_array, flag1_array, num);
        /* 临界区1 */
        xwos_splk_wr_lock_irqssv(&lock2, irq_array, flag2_array, num);
        /* 临界区2 */
        xwos_splk_wr_unlock_irqsrs(&lock2, irq_array, flag2_array, num);
        /* 临界区1 */
        xwos_splk_wr_unlock_irqsrs(&lock1, irq_array, flag1_array, num);
  • 临界区内,只关闭抢占和指定的中断。可以理解为在线程和指定的中断函数层面,临界区内的操作是 原子的 , 临界区内的数据只能被 线程指定的外设中断 上下文访问。
  • 临界区内不会发生指定的中断,也不会发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

保护 线程中断底半部 上下文之间的读写临界区

  • 临界区对 线程中断底半部 是安全的。临界区可嵌套。
        xwos_splk_wr_lock_bh(&lock);
        /* 临界区1 */
        xwos_splk_wr_lock_bh(&lock2);
        /* 临界区1 */
        xwos_splk_wr_unlock_bh(&lock2);
        /* 临界区1 */
        xwos_splk_wr_unlock_bh(&lock1);
  • 临界区内,只关闭抢占和中断底半部。可以理解为在线程和中断底半部层面,临界区内的操作是 原子的 , 临界区内的数据只能被 线程中断底半部 上下文访问。
  • 临界区内不会发生 调度 ,但用户依然不能在临界区内使用会导致 睡眠阻塞 的CAPI。
  • CAPI

CAPI参考