同步

玄武OS提供多种同步机制,它们统一称为同步对象

  • 信号量
  • 条件量
  • 事件信号旗
  • 线程栅栏
  • 信号选择器

信号量

描述

信号量通常用于不同代码上下文间的同步,使用PV原语进行操作, 属于操作系统比较底层的同步机制。玄武OS内核的信号量有两种:

  • 管道信号量(PLSMR): 对所有阻塞在其等待队列中的线程按照先进先出(FIFO)的策略调度, 即当信号量可用时,最先进入等待队列中的线程将优先获得信号量。

  • 实时信号量(RTSMR): 对所有阻塞在其等待队列中的线程按照优先级进行调度,即高优先级 的线程总是最先获得信号量,同优先级的线程按先进先出(FIFO)的策略调度。

XWOSAL的API只封装了一种信号量,当系统配置文件中同时配置了 XWUPCFG_SYNC_PLSMRXWUPCFG_SYNC_RTSMR时,优先使用实时信号量。

用法

信号量的创建、初始化与删除、销毁

信号量支持静态初始化与销毁,动态创建与删除两种方式:

  • 静态初始化与销毁

    • 静态是指用户预先定义对象,这些对象在编译时由编译器分配内存。
    • 初始化:xwosal_smr_init()
    • 销毁:xwosal_smr_destroy()
  • 动态创建与删除

    • 动态是指程序在运行时,通过内存分配函数从某个内存区域上申请分配一块内存, 并把这块内存初始化为所需要的对象。使用完毕后,需要释放内存。
    • 创建:xwosal_smr_create()
    • 删除:xwosal_smr_delete()

P操作

P操作是指发布信号量,使得信号量的计数器加1,同时会唤醒信号量等待队列中的一个线程。 被唤醒的线程会进行V操作,取走信号量中的一个信号,使得信号量的计数器减1。

  • API:xwosal_smr_post()
    • 可在中断上下文、中断底半部、线程上下文中使用

V操作

V操作是指取走信号量中的一个信号,使得信号量的计数器减1。

  • API
    • xwosal_smr_trywait()
      • 此API不会等待,只是测试一下信号量,如果失败,就返回-ENODATA
      • 可在中断上下文、中断底半部、线程上下文中使用;
    • xwosal_smr_wait()
      • 只可在线程上下文中使用;
      • 调用的线程若无法获取信号量,将进入“阻塞”状态;
      • 此API可被中断,中断将返回-EINTR
    • xwosal_smr_timedwait()
      • 只可在线程上下文中使用;
      • 调用的线程若无法获取信号量,将进入“阻塞”和“睡眠”状态;
      • 此API可限时等待信号量,超时将以-ETIMEDOUT返回;
      • 此API可被中断,中断将返回-EINTR
    • xwosal_smr_wait_unintr()
      • 只可在线程上下文中使用;
      • 调用的线程若无法获取信号量,将进入“阻塞”状态;
      • 此API不会被中断,因此有可能会妨碍系统休眠等系统事件,要小心使用。

中断操作

中断操作是指玄武OS在处理内核事件时中断线程的“阻塞”“睡眠”, 强制使其进入“就绪”状态,导致线程的“阻塞”“睡眠”API xwosal_smr_wait()以及xwosal_smr_timedwait() 会返回-EINTR,但xwosal_smr_wait_unintr()不会被中断。相关的内核事件:

  • 系统休眠时要求线程冻结
  • 线程从一个CPU迁移到另一个CPU时要求线程冻结

冻结与解冻操作

被冻结的信号量不允许V操作,但可以进行P操作。解冻后,信号量可以重新进行V操作, 同时还可以重新初始化。

  • 冻结API:xwosal_smr_freeze()
  • 解冻API:xwosal_smr_thaw()

绑定与解绑选择器

可以将信号量绑定到一个特定的信号选择器上。当对信号量进行V操作时, 可向信号选择器发送一个“选择信号”

  • 绑定API:xwosal_smr_bind()
  • 解绑API:xwosal_smr_unbind()

可以参考信号选择器的章节获取更多的信息。

示例

  • 应用模块:xwam/example/sync/semaphore
  • 用法:
    • 在配置文件xwbd/电路板名称/cfg/xwam.h中 定义宏XWAMCFG_example_sync_semaphore1
    • 在初始化流程中(例如:xwos_main())调用 example_semaphore_start()启动模块。

配置

/* <cfg/xwos.h> */

/* SMP系统 */
#define XWSMPCFG_SYNC_RTSMR  1   // 是否启用实时信号量功能,取值:1|0
#define XWSMPCFG_SYNC_PLSMR  1   // 是否启用管道信号量功能,取值:1|0
#define XWSMPCFG_SYNC_SMR_MEMSLICE  1   // 是否启用xwsync_smr对象的memslice缓存,
                                        // 取值:1|0

/* UP系统 */
#define XWUPCFG_SYNC_RTSMR  1   // 是否启用实时信号量功能,取值:1|0
#define XWUPCFG_SYNC_PLSMR  1   // 是否启用管道信号量功能,取值:1|0

API参考

  • 头文件:xwos/osal/sync/semaphore.h
  • 注释:见头文件

条件量

描述

玄武OS内核提供的条件量与大部分系统/语言提供的条件量功能类似,例如:

  • POSIX的pthread_cond_t
  • C++的std::condition_variable
  • Java的java.util.concurrent.locks.Condition
  • Python的threading.Condition

条件量常用于多个线程间传递数据。玄武OS内核的条件量还可以用于中断与线程间传递数据。 主要包括两个动作:

  • 线程A等待条件量的”条件”成立而阻塞;
  • 另一个线程B或中断使“条件”成立。为了防止两个上下文竞争“条件”, 通常条件量需要和一个锁一起使用,锁用于保护”条件“不被同时访问。 玄武OS的条件量支持很多种类型的锁:
    • 互斥锁
    • 自旋锁
    • 顺序锁
    • 自定义的加锁与解锁函数

用法

条件量的创建、初始化与删除、销毁

条件量支持静态初始化与销毁,动态创建与删除两种方式:

  • 静态初始化与销毁

    • 静态是指用户预先定义对象,这些对象在编译时由编译器分配内存。
    • 初始化:xwosal_cdt_init()
    • 销毁:xwosal_cdt_destroy()
  • 动态创建与删除

    • 动态是指程序在运行时,通过内存分配函数从某个内存区域上申请分配一块内存, 并把这块内存初始化为所需要的对象。使用完毕后,需要释放内存。
    • 创建:xwosal_cdt_create()
    • 删除:xwosal_cdt_delete()

等待条件量

xwer_t xwosal_cdt_wait(xwid_t cdtid,
                       union xwlk_ulock lock, xwsq_t lktype,
                       void * lkdata, xwsq_t * lkst);

xwer_t xwosal_cdt_timedwait(xwid_t cdtid,
                            union xwlk_ulock lock, xwsq_t lktype,
                            void * lkdata, xwtm_t * xwtm, xwsq_t * lkst);
  • 所有锁统一使用union xwlk_ulock指代,此联合中包含所有锁的定义, 实际只有一个指针的长度,可以代表一个指针,也可表示一个ID, 具体意义由xwsq_t lktype决定:
    • XWLK_TYPE_MTX 互斥锁
      • lock代表互斥锁的ID(访问方式:lock.osal.id), 此ID在创建互斥锁时由xwosal_mtx_create()返回, 也可通过xwosal_mtx_get_id()获取;
      • lockdata无作用,设置为NULL即可;
    • XWLK_TYPE_MTX_UNINTR 不可中断的互斥锁
      • lock代表互斥锁的ID(访问方式:lock.osal.id), 此ID在创建互斥锁时由xwosal_mtx_create()返回, 也可通过xwosal_mtx_get_id()获取;
      • lockdata无作用,设置为NULL即可;
    • XWLK_TYPE_SPLK 自旋锁
      • lock代表自旋锁的指针(访问方式:lock.osal.splk);
      • lockdata无作用,设置为NULL即可;
    • XWLK_TYPE_SQLK_WR 顺序写锁
      • lock代表顺序锁的指针(访问方式:lock.osal.sqlk);
      • lockdata无作用,设置为NULL即可;
    • XWLK_TYPE_SQLK_RDEX 顺序读锁
      • lock代表顺序锁的指针(访问方式:lock.osal.sqlk);
      • lockdata无作用,设置为NULL即可;
    • XWLK_TYPE_CALLBACK 自定义的加锁与解锁函数
      • lock代表指向struct xwlk_cblk的指针(访问方式:lock.cb);
      • lockdata传递给struct xwlk_cblk中的lockunlock函数的参数;
  • 超时的参数xwtm_t * xwtm用法可以参考超时管理
  • lkst指向输出缓冲区,此缓冲区可返回锁的状态:
    • XWLK_STATE_LOCKED函数返回时,已经上锁;
    • XWLK_STATE_UNLOCKED函数返回时,未上锁。

!!!特别注意!!!

  • 当使用了带中断管理的上锁函数时,例如:

    • xwosal_splk_lock_cpuirq()
    • xwosal_splk_lock_cpuirqsv()
    • xwosal_splk_lock_irqs()
    • xwosal_sqlk_wr_lock_cpuirq()
    • xwosal_sqlk_wr_lock_cpuirqsv()
    • xwosal_sqlk_wr_lock_irqs()
    • xwosal_sqlk_rdex_lock_cpuirq()
    • xwosal_sqlk_rdex_lock_cpuirqsv()
    • xwosal_sqlk_rdex_lock_irqs()

    xwosal_cdt_timedwait()xwosal_cdt_wait()返回时需要由用户恢复中断, 条件量不会管理中断,但可在关闭中断时使用。

  • 玄武OS内核的条件量与 pthread_cond_t 不同:

    • 返回值为XWOK时才会对锁进行上锁,如果返回小于0的错误码, 则不会对锁进行上锁;
    • 是否上锁的状态由lkst指向的缓冲区返回;
    • pthread_cond_wait()无论如何都会等待互斥锁被上锁时才返回。

单播

“单播”是指唤醒条件量队列中的第一个线程,条件量队列是一个先进先出(FIFO)队列:

  • API:xwosal_cdt_unicast()
    • 此API可在中断上下文、中断底半部、线程上下文中使用;
  • 若条件量绑定了信号选择器,单播会向信号选择器发送“选择信号”。

广播

“广播”是指唤醒条件量队列中的所有线程:

  • API:xwosal_cdt_broadcast()
    • 此API可在中断上下文、中断底半部、线程上下文中使用;
  • 若条件量绑定了信号选择器,广播会向信号选择器发送“选择信号”

冻结与解冻操作

被冻结的信号量不允许V操作,但可以进行P操作。解冻后,信号量可以重新进行V操作, 同时还可以重新初始化。

  • 冻结API:xwosal_cdt_freeze()
  • 解冻API:xwosal_cdt_thaw()

绑定选择器

可以将条件绑定到一个特定的信号选择器上。当广播条件量时,可向信号选择器发送一个 “选择信号”

  • 绑定API:xwosal_cdt_bind()
  • 解绑API:xwosal_cdt_unbind()

可以参考信号选择器的章节获取更多的信息。

示例

  • 应用模块:xwam/example/sync/condition
  • 用法:
    • 在配置文件xwbd/电路板名称/cfg/xwam.h中 定义宏XWAMCFG_example_sync_condition1
    • 在初始化流程中(例如:xwos_main())调用 example_condition_start()启动模块。

配置

/* <cfg/xwos.h> */

/* SMP系统 */
#define XWSMPCFG_SYNC_CDT_MEMSLICE  1   // 是否启用xwsync_cdt对象的memslice缓存,
                                        // 取值:1|0

/* UP系统 */
#define XWUPCFG_SYNC_CDT  1   // 是否启用条件量功能,取值:1|0

API参考

  • 头文件:xwos/osal/sync/condition.h
  • 注释:见头文件

事件信号旗

描述

当系统需要处理很多事件,如果每个事件都绑定一个特定的条件量, 每个条件量又由一个线程进行等待处理,会导致系统需要大量内存来创建条件量和线程, 这种方法在小内存的MCU上显然不可行。同时条件量属于操作系统比较底层的功能, 用法虽然灵活但很复杂。因此,玄武OS基于条件量实现了事件信号旗的功能。

事件信号旗使用位图来管理一组事件,位图中的每个位代表一个事件, 当一个或多个事件状态发生变化时,事件对应的位也会发生变化,并唤醒正在等待的线程。 线程唤醒后,就可从事件位图中获取事件的状态。

玄武OS的事件信号旗有以下特点:

  • 线程可以等待位图中的事件位被置1,也可以等待事件位被清0;
  • 线程可以等待位图中的事件位同时被置1(事件与事件之间是逻辑的关系), 也可以等待其中任意一个位被置1(事件与事件之间是逻辑的关系);
  • 线程可以等待位图中的事件位同时被清0(事件与事件之间是逻辑的关系), 也可以等待其中任意一个位被清0(事件与事件之间是逻辑的关系);
  • 线程可以选择是否“消费”事件,“消费”事件是指,当线程等到事件被 唤醒,可以选择是否 清除 事件标志。清除的含义是:
    • 当线程等待的是位图中的事件位被置1,清除是指将这些位清0;
    • 当线程等待的是位图中的事件位被清0,清除是指将这些位置1;
  • 线程可以等待事件标志位发生翻转翻转是指事件标志位由1变为0,或 由0变为1,当线程等待的是标志位发生翻转,不可清除事件标志;
  • 事件位图中的位的最大数量在运行时是固定不变的,但可在编译时进行配置:
    • 多核系统:XWSMPCFG_SYNC_EVT_MAXNUM
    • 单核系统:XWUPCFG_SYNC_EVT_MAXNUM

用法

事件信号旗的创建、初始化与删除、销毁

事件信号旗支持静态初始化与销毁,动态创建与删除两种方式:

  • 静态初始化与销毁

    • 静态是指用户预先定义对象,这些对象在编译时由编译器分配内存。
    • 初始化:xwosal_flg_init()
    • 销毁:xwosal_flg_destroy()
  • 动态创建与删除

    • 动态是指程序在运行时,通过内存分配函数从某个内存区域上申请分配一块内存, 并把这块内存初始化为所需要的对象。使用完毕后,需要释放内存。
    • 创建:xwosal_flg_create()
    • 删除:xwosal_flg_delete()

等待事件

玄武OS提供3个API产生触发事件:

  • xwosal_flg_trywait(): 尝试等一下事件信号旗的触发事件
    • 可在中断上下文、中断底半部、线程上下文中使用;
  • xwosal_flg_wait(): 等待事件信号旗的触发事件
    • 只可线程上下文中使用;
  • xwosal_flg_timedwait(): 限时等待事件信号旗的触发事件
    • 只可线程上下文中使用。
触发条件

当调用等待事件的API时,需要指定一个触发条件(参数:trigger)。 触发条件分为“电平触发”和“边沿”触发。

电平触发

“电平触发”的概念来源于数字电路中,是一种类比,是指事件特定的状态(1或0) 产生唤醒信号,下面的触发条件为电平触发:

  • XWOSAL_FLG_TRIGGER_SET_ALL 所有事件位被置1
  • XWOSAL_FLG_TRIGGER_SET_ANY 任意事件位被置1
  • XWOSAL_FLG_TRIGGER_CLR_ALL 所有事件位被清0
  • XWOSAL_FLG_TRIGGER_CLR_ANY 任意事件位被清0
边沿触发

“边沿触发”的概念来源于数字电路中,是一种类比,是指事件状态发生改变 (1变成0或0变成1)时产生的唤醒信号,下面的触发条件为边沿触发:

  • XWOSAL_FLG_TRIGGER_TGL_ALL 所有事件位发生翻转
  • XWOSAL_FLG_TRIGGER_TGL_ANY 任意事件位发生翻转
清除触发事件
  • 当采用“电平触发”时,需要在触发后清除事件标志位,防止事件重复触发,可以 在调用等待事件的API指定参数action为XWOSAL_FLG_ACTION_CONSUMPTION
  • 当采用“边沿触发”时,不需要清除事件标志位。

产生触发事件

玄武OS提供6个API产生触发事件:

  • xwosal_flg_s1m(): 同时设置多个事件标志位
    • 可在中断上下文、中断底半部、线程上下文中使用;
  • xwosal_flg_s1i(): 设置单个事件标志位
    • 可在中断上下文、中断底半部、线程上下文中使用;
  • xwosal_flg_c0m(): 同时清除多个事件标志位
    • 可在中断上下文、中断底半部、线程上下文中使用;
  • xwosal_flg_c0i(): 清除单个事件标志位
    • 可在中断上下文、中断底半部、线程上下文中使用;
  • xwosal_flg_x1m(): 同时翻转多个事件标志位
    • 可在中断上下文、中断底半部、线程上下文中使用;
  • xwosal_flg_x1i(): 翻转单个事件标志位
    • 可在中断上下文、中断底半部、线程上下文中使用。

绑定选择器

可以将事件信号旗绑定到一个特定的信号选择器上,当触发事件产生时, 可向信号选择器发送一个“选择信号”

  • 绑定API:xwosal_flg_bind()
  • 解绑API:xwosal_flg_unbind()

可以参考信号选择器的章节获取更多的信息。

示例

  • 应用模块:xwam/example/sync/flag
  • 用法:
    • 在配置文件xwbd/电路板名称/cfg/xwam.h中 定义宏XWAMCFG_example_sync_flag1
    • 在初始化流程中(例如:xwos_main())调用 example_flag_start()启动模块。

配置

/* <cfg/xwos.h> */

/* SMP系统 */
#define XWSMPCFG_SYNC_EVT           1   // 是否启用事件对象,事件标志旗基于
                                        // 玄武OS内核的事件对象xwsync_evt实现,
                                        // 取值:1|0
#define XWSMPCFG_SYNC_EVT_MAXNUM    32  // 事件位图的最大数量
#define XWSMPCFG_SYNC_EVT_MEMSLICE  1   // 是否启用xwsync_evt对象的memslice缓存,
                                        // 取值:1|0

/* UP系统 */
#define XWUPCFG_SYNC_EVT            1   // 是否启用事件对象,事件标志旗基于
                                        // 玄武OS内核的事件对象xwsync_evt实现,
                                        // 取值:1|0
#define XWUPCFG_SYNC_EVT_MAXNUM     32  // 事件位图的最大数量

API参考

  • 头文件:xwos/osal/sync/flag.h
  • 注释:见头文件

线程栅栏

描述

线程栅栏用于协调多个线程并行工作的同步机制。线程栅栏允许每个线程等待, 直到所有的合作线程都达到某一点,然后从该点继续执行。

玄武OS的线程栅栏有以下特点:

  • 线程栅栏支持的最大线程数量在运行时是固定不变的,但可在编译时进行配置:
    • 多核系统:XWSMPCFG_SYNC_EVT_MAXNUM
    • 单核系统:XWUPCFG_SYNC_EVT_MAXNUM

用法

线程栅栏的创建、初始化与删除、销毁

线程栅栏支持静态初始化与销毁,动态创建与删除两种方式:

  • 静态初始化与销毁

    • 静态是指用户预先定义对象,这些对象在编译时由编译器分配内存。
    • 初始化:xwosal_barrier_init()
    • 销毁:xwosal_barrier_destroy()
  • 动态创建与删除

    • 动态 是指程序在运行时,通过内存分配函数从某个内存区域上申请分配一块内存, 并把这块内存初始化为所需要的对象。使用完毕后,需要释放内存。
    • 创建:xwosal_barrier_create()
    • 删除:xwosal_barrier_delete()

示例

  • 应用模块:xwam/example/sync/barrier
  • 用法:
    • 在配置文件xwbd/电路板名称/cfg/xwam.h中 定义宏XWAMCFG_example_sync_barrier1
    • 在初始化流程中(例如:xwos_main())调用 example_barrier_start()启动模块。

配置

线程栅栏同事件信号旗一样,在底层基于玄武OS内核的事件对象实现,配置可参考 同步-事件信号旗-配置

API参考

  • 头文件:xwos/osal/sync/barrier.h
  • 注释:见头文件

信号选择器

描述

类似于事件信号旗,信号选择器中包含一幅位图,只不过位图中每个位关联的不再是事件, 而是同步对象。每种同步对象都可绑定到信号选择器并发送选择信号, 使得信号选择器中的对应位被置1,同时使得等待信号选择器的线程被唤醒。

发送信号 对于不同的 同步对象 有不同解释:

  • 信号量:指V操作,对应于APIxwosal_smr_post()
  • 条件量:指广播操作,对应于APIxwosal_cdt_broadcast(),注意,单播操作 xwosal_cdt_unicast()不会向信号选择器发送选择信号
  • 事件信号旗:指事件信号旗位图中任何一位发生改变,对应于API:
    • xwosal_flg_s1m()
    • xwosal_flg_s1i()
    • xwosal_flg_c0m()
    • xwosal_flg_c0i()
    • xwosal_flg_x1m()
    • xwosal_flg_x1i()
  • 线程栅栏:指所有线程抵达栅栏处;
  • 信号选择器:指任何绑定在信号选择器中的同步对象向信号选择器发送了选择信号

信号选择器之间可以依次绑定,形成传递链,将选择信号在传递链上传递。但要注意, 信号选择器不可相互绑定形成循环链,否则会造成无限传递。

img

用法

信号选择器的创建、初始化与删除、销毁

信号选择器支持静态初始化与销毁,动态创建与删除两种方式:

  • 静态初始化与销毁

    • 静态 是指用户预先定义对象,这些对象在编译时由编译器分配内存。
    • 初始化:xwosal_selector_init()
    • 销毁:xwosal_selector_destroy()
  • 动态创建与删除

    • 动态 是指程序在运行时,通过内存分配函数从某个内存区域上申请分配一块内存, 并把这块内存初始化为所需要的对象。使用完毕后,需要释放内存。
    • 创建:xwosal_selector_create()
    • 删除:xwosal_selector_delete()

绑定与解绑

玄武OS中所有同步对象都带有两个相似的API:

/* 绑定 */
xwer_t xwosal_XXX_bind(xwid_t XXXid, xwid_t sltid, xwsq_t pos);

/* 解绑 */
xwer_t xwosal_XXX_unbind(xwid_t XXXid, xwid_t sltid);

同步对象绑定操作又分为独占绑定与非独占绑定:

  • 独占绑定:是指同步对象一旦绑定了信号选择器中的某一位后,其他同步对象 不可再绑定此位;
  • 非独占绑定:是指同步对象一旦绑定了信号选择器中的某一位后,其他同步对象 还可继续再绑定此位。

同步对象采用的绑定方式:

  • 信号量:独占绑定
  • 条件量:非独占绑定
  • 事件信号旗:非独占绑定
  • 线程栅栏:非独占绑定
  • 信号选择器:非独占绑定

示例

  • 应用模块:xwam/example/sync/selector
  • 用法:
    • 在配置文件xwbd/电路板名称/cfg/xwam.h中 定义宏XWAMCFG_example_sync_selector1
    • 在初始化流程中(例如:xwos_main())调用 example_selector_start()启动模块。

配置

信号量选择器同事件信号旗一样,在底层基于玄武OS内核的事件对象实现,配置可参考 同步-事件信号旗-配置

API参考

  • 头文件:xwos/osal/sync/selector.h
  • 注释:见头文件