XWOS移植说明
5 分钟阅读
概述
XWOS的移植包括以下几个要素:
- XWOS移植层(XWOSPL):类似于C++中的抽象接口类,声明接口和实现部分成员函数。
- XWOS移植实现层(XWOSIMPL):类似于C++,提供XWOS移植层(XWOSPL)接口的实现。
- 初始化流程
- 编译
为了提高代码的复用性,XWOS移植实现层(XWOSIMPL)相关的代码又细分为:
- 架构描述层(ADL)
- CPU描述层(CDL)
- SOC描述层(SDL)
- 电路板描述层(BDL)
例如,ARMv7m架构下,ADL目录为 xwcd/soc/arm/v7m/gcc/ ,其中代码对所有基于ARMv7-M的CPU都是复用的。
m3、m4、m7的差异又由CDL目录来描述。相同的CPU内核不同SOC又由SDL目录来描述。相同SOC不同的电路板由BDL目录来描述。
XWOS移植层(XWOSPL)头文件规则
由于C语言并不像C++一样拥有 virtual 关键字,因此有必要设定一个命名规则表达出与C++抽象接口类同样的意思:
xwos/ospl/*.h:XWOS提供给BSP的头文件, 不可 被XWOS自身的头文件包含。- 前缀
xwospl_:类似于C++中的纯虚函数,XWOS不提供缺省实现。BSP中需要提供实现的函数。 - 前缀
xwosplcb_:类似于C++中已实现的成员函数,提供给BSP调用。
- 前缀
xwos/ospl/soc/*.h:其中包含了BSP提供给XWOS的头文件, 可被 XWOS的头文件包含。xwos/ospl/soc/type.h:包含了平台类型的定义xwos/ospl/soc/compiler.h:包含了平台编译器相关的定义xwos/ospl/soc/isa.h:包含了平台指令和架构相关的定义xwos/ospl/soc/lfq.h:包含了无锁队列相关的定义- 前缀
soc_:类似于C++中的纯虚函数,XWOS不提供缺省实现。BSP中需要提供实现的函数。
- 前缀
xwos/ospl/soc/setjmp.h:包含了setjmp.h相关的定义xwlib_setjmp():类似于C++中的虚函数,XWOS提供缺省实现。若不提供覆盖实现,函数默认实现为C标准库中setjmp()。xwlib_longjmp():类似于C++中的虚函数,XWOS提供缺省实现。若不提供覆盖实现,函数默认实现为使用C标准库中longjmp()。
xwos/ospl/soc/spinlock.h:包含了自旋锁相关的定义- 前缀
soc_:类似于C++中的纯虚函数,XWOS不提供缺省实现。BSP中需要提供实现的函数。
- 前缀
xwos/ospl/soc/xwaop.h:包含了原子操作相关的定义- 前缀
xwaop_:类似于C++中的虚函数,XWOS提供了缺省实现。BSP中可提供一个覆盖实现。
- 前缀
xwos/ospl/soc/xwbop.h:包含了位操作相关的定义- 前缀
xwbop_:类似于C++中的虚函数,XWOS提供了缺省实现。BSP中可提供一个覆盖实现。
- 前缀
xwos/ospl/soc/xwsc.h:包含了系统权限与系统调用相关的定义xws64_t xwsc(xwsc_f func, xwreg_t argnum, ...):类似于C++中的纯虚函数。XWOS不提供缺省实现,也可以不用实现。 若不提供实现,系统特权调用xwsc()不可用 。
移植
XWOS的移植,包括:基本类型、编译器、架构指令、无锁队列、setjmp/longjmp、系统调用与系统权限、自旋锁、位操作、原子操作、中断、硬件定时器、调度器、线程本地存储(TLS)。
基本类型
XWOS定义了自己的一套基本类型,所有源码都是围绕基本类型展开的。
- XWOS头文件:
xwos/lib/type.h,被xwos/standard.h包含。 - 接口:
xwos/ospl/soc/type.h - 实现:
xwosimpl_soc_type.h- 将
ARCH_HAVE_xxxx宏定义为1,表明覆盖xxxx的默认定义。 此文件通常位于 ADL 目录,例如xwcd/soc/arm/v7m/gcc/xwosimpl_soc_type.h。
- 将
- 所有基本类型可参考 基本类型
编译器
- XWOS头文件:
xwos/lib/compiler.h,被xwos/standard.h包含。 - 接口:
xwos/ospl/soc/compiler.h - 实现:
xwosimpl_soc_compiler.h,根据所使用的编译器,提供定义。 - 编译器相关的宏定义:
__xwcc_section(s):表明符号属于段s。__xwcc_aligned(x):表明数据的起始地址对齐到x字节处。__xwcc_inline:表明函数是内联函数,需要和static一起使用。__xwcc_packed:表明数据结构体是紧凑分布的,编译器不要做优化对齐处理。__xwcc_must_check:表明函数返回值必须被读取,否则编译器会报警告。__xwcc_unused:表明变量或函数未被使用,用于去除编译警告。__xwcc_noreturn:表明函数不会返回。__xwcc_hot:表明函数在代码中经常被调用,可以帮助某些编译器优化编译。__xwcc_atomic:表明变量是原子的,C11标准中被定义为_Atomic,C99标准中被定义为volatile。__xwcc_likely(x):表明条件x大概率为true,用于帮助编译器优化if..else..的编译。__xwcc_unlikely(x):表明条件x大概率为false,用于帮助编译优化if..else..的编译。__xwcc_alignl1cache:表明数据的起始地址对齐到1级缓存(way-set缓存)的缓存线。__xwcc_alignptr:表明数据的起始地址对齐到指针的尺寸。32位系统,数据对齐到4字节。64位系统,数据对齐到8字节。xwcc_offsetof(type, member):计算member在结构体type中的偏移,等价于标准C库中的offsetof()。
架构指令
CPU架构会提供一些特殊指令,这些指令通常很难用C语言直接表达出。为了方便使用,XWOS封装了部分常见的指令。
- 接口:
xwos/ospl/soc/isa.h - 实现:
xwosimpl_soc_isa.h
这些架构指令包括但不限于:
- XWOS头文件:
xwos/lib/mb.h,被xwos/standard.h包含。- 内存屏障
xwmb_compiler():编译器的内存屏障,防止编译器对数据读写的乱序优化。xwmb_isb():指令同步屏障,用于等待指令流水线中的指令全部执行完毕才重新从指令缓存中取指令。xwmb_ddb():数据依赖屏障,第一次读取内存的数据作为第二次读取的地址,即会产生 数据依赖屏障 , 对应于stdatomic.h中定义的消费序(memory_order_consume)。- 大部分处理器架构都会自动产生数据依赖屏障,不需要额外的指令。此时,
xwmb_ddb()定义为xwmb_compiler()。 - 只有少数CPU才会需要数据依赖屏障指令,例如DEC Alpha。
- 大部分处理器架构都会自动产生数据依赖屏障,不需要额外的指令。此时,
xwmb_rmb():读内存屏障xwmb_wmb():写内存屏障xwmb_mb():读写内存屏障xwmb_dma_rmb():使用DMA时的读内存屏障xwmb_dma_wmb():使用DMA时的写内存屏障xwmb_dma_mb():使用DMA时的内存屏障xwmb_mp_mb():多核系统的内存屏障xwmb_mp_rmb():多核系统的读内存屏障xwmb_mp_wmb():多核系统的写内存屏障xwmb_mp_acquire():多核系统中确保acquire内存序的屏障。acquire内存序通常配合读取指令使用。xwmb_mp_release():多核系统中确保release内存序的屏障。release内存序通常配合存入指令使用。
- 内存屏障
- XWOS头文件:
xwos/standard.h- 调试
soc_bug():通常用于ARCH、CPU、SOC、Board等比较底层的代码出错时的处理。通常定义为断点指令,方便用调试器来调试。
- 调试
无锁队列
XWOS的C库中提供了无锁队列的函数,无锁队列的实现依赖于CPU的原子操作指令。
- XWOS头文件:
xwos/lib/lfq.h - 接口:
xwos/ospl/soc/lfq.h - 实现:
xwosimpl_soc_lfq.h
setjmp/longjmp
XWOS的C库中提供了类似于C标准库中的 setjmp()/longjmp() 函数组合。
其具体实现与OS切换上下文时如何切换寄存器有密切关系,C标准库中的标准实现可能不能满足要求。
若不提供实现,将默认使用C标准库中的标准实现。
- XWOS头文件:
xwos/lib/setjmp.h - 接口:
xwos/ospl/soc/setjmp.h - 实现:
xwosimpl_soc_setjmp.hxwosimpl_soc_setjmp.c
系统调用与系统特权
通常CPU都有两种权限模式:用户和系统。
- 系统模式下可以访问所有的寄存器;
- 用户模式下某些CPU内的寄存器无法被访问(例如开关中断),只能通过特殊指令让CPU进入系统模式才可访问。
XWOS的C库中提供了可切换CPU访问权限的函数 xwsc() 。通过 xwsc() 可以让用户模式暂时拥有系统特权调用某个函数。
xwsc() 并不要求必须被实现。这种情况下,移植XWOS时,所有线程应该只运行在系统权限模式。
- XWOS头文件:
xwos/lib/sc.h - 接口:
xwos/ospl/soc/xwsc.h - 实现:
xwosimpl_soc_xwsc.h
自旋锁
在多核系统中,被多个CPU共同访问的内存区域需要被自旋锁保护,自旋锁的实现依赖于原子操作指令与内存屏障指令。
- XWOS头文件:
xwos/osal/lock/spinlock.h:自旋锁xwos/osal/lock/seqlock.h:自旋锁的改良锁 —— 顺序锁
- 接口:
xwos/ospl/soc/spinlock.h - 实现:
xwosimpl_soc_spinlock.h
位操作
XWOS的C库中提供了位操作的函数集合,为提高效率,部分位操作可使用特殊指令覆盖实现。
- XWOS头文件:
xwos/lib/xwbop.h - 接口:
xwos/ospl/soc/xwbop.h - 实现:
xwosimpl_soc_xwbop.hxwosimpl_soc_xwbop.c
- 基本类型的位操作函数集合:
- 位序镜面翻转:Intel位序(小端)与摩托罗拉位序(大端)之间的转换。
xwbop_rbit8():镜面翻转8位数据的位序xwbop_rbit16():镜面翻转16位数据的位序xwbop_rbit32():镜面翻转32位数据的位序xwbop_rbit64():镜面翻转64位数据的位序
- 大小端反转
xwbop_re16():反转16位数据的字节序xwbop_re16s32():反转16位数据的字节序,并将符号位扩展到32位,返回有符号32位数据xwbop_re32():反转32位数据的字节序xwbop_re32s64():反转32位数据的字节序,并将符号位扩展到64位,返回有符号64位数据xwbop_re64()::反转64位数据的字节序
- 查找被置
1的位xwbop_ffs8():8位数据,从最低有效位开始查找xwbop_fls8():8位数据,从最高有效位开始查找xwbop_ffs16():16位数据,从最低有效位开始查找xwbop_fls16():16位数据,从最高有效位开始查找xwbop_ffs32():32位数据,从最低有效位开始查找xwbop_fls32():32位数据,从最高有效位开始查找xwbop_ffs64():64位数据,从最低有效位开始查找xwbop_fls64():64位数据,从最高有效位开始查找
- 位序镜面翻转:Intel位序(小端)与摩托罗拉位序(大端)之间的转换。
原子操作
XWOS的C库中提供了原子操作的函数集合,原子操作的实现依赖于CPU的原子操作指令。
- XWOS头文件:
xwos/lib/xwaop.h
- 接口:
xwos/ospl/soc/xwaop.h - 实现:
xwosimpl_soc_xwaop.hxwosimpl_soc_xwaop/*.c
- 说明
- XWOS内核会基于8个基本类型(
xws8_t、xwu8_t、xws16_t、xwu16_t、xws32_t、xwu32_t、xws64_t、xwu64_t)实现其他基本类型的原子操作。 因为其他基本类型都是基于这8个基本类型定义出来的。 - 64位原子操作可不提供实现。
- 如果CPU架构比较简单,无原子操作指令,可考虑基于自旋锁来实现原子操作函数。在单核系统,自旋锁的实现只需关闭/打开中断。
- 某些CPU架构只提供与CPU位宽一致的原子操作指令,这种情况下,其他位宽的原子操作指令可考虑基于自旋锁来实现。在单核系统,自旋锁的实现只需关闭/打开中断。
- XWOS内核会基于8个基本类型(
- 基本类型的原子操作函数集合
load():加载(可指定内存序)store():存储(可指定内存序)read():读(内存序:load-require)write():写(内存序:store-release)add():加法运算sub():减法运算rsb():反向减法运算and():与运算or():或运算xor():异或运算teq_then_write():测试原子变量是否与测试值 相等 ,然后 写teq_then_add():测试原子变量是否与测试值 相等 ,然后做 加法 运算teq_then_sub():测试原子变量是否与测试值 相等 ,然后做 减法 运算teq_then_rsb():测试原子变量是否与测试值 相等 ,然后做 反向减法 运算tne_then_write():测试原子变量是否与测试值 不相等 ,然后 写tne_then_add():测试原子变量是否与测试值 不相等 ,然后做 加法 运算tne_then_sub():测试原子变量是否与测试值 不相等 ,然后做 减法 运算tne_then_rsb():测试原子变量是否与测试值 不相等 ,然后做 反向减法 运算tge_then_write():测试原子变量是否 大于等于 测试值,然后 写tge_then_add():测试原子变量是否 大于等于 测试值,然后做 加法 运算tge_then_sub():测试原子变量是否 大于等于 测试值,然后做 减法 运算tge_then_rsb():测试原子变量是否 大于等于 测试值,然后做 反向减法 运算tgt_then_write():测试原子变量是否 大于 测试值,然后 写tgt_then_add():测试原子变量是否 大于 测试值,然后做 加法 运算tgt_then_sub():测试原子变量是否 大于 测试值,然后做 减法 运算tgt_then_rsb():测试原子变量是否 大于 测试值,然后做 反向减法 运算tle_then_write():测试原子变量是否 小于等于 测试值,然后 写tle_then_add():测试原子变量是否 小于等于 测试值,然后做 加法 运算tle_then_sub():测试原子变量是否 小于等于 测试值,然后做 减法 运算tle_then_rsb():测试原子变量是否 小于等于 测试值,然后做 反向减法 运算tlt_then_write():测试原子变量是否 小于 测试值,然后 写tlt_then_add():测试原子变量是否 小于 测试值,然后做 加法 运算tlt_then_sub():测试原子变量是否 小于 测试值,然后做 减法 运算tlt_then_rsb():测试原子变量是否 小于 测试值,然后做 反向减法 运算tgele_then_write():测试原子变量是否在闭区间[l,r],然后 写tgele_then_add():测试原子变量是否在闭区间[l,r],然后做 加法 运算tgele_then_sub():测试原子变量是否在闭区间[l,r],然后做 减法 运算tgele_then_rsb():测试原子变量是否在闭区间[l,r],然后做 反向减法 运算tgelt_then_write():测试原子变量是否在左闭右开区间[l,r),然后 写tgelt_then_add():测试原子变量是否在左闭右开区间[l,r),然后做 加法 运算tgelt_then_sub():测试原子变量是否在左闭右开区间[l,r),然后做 减法 运算tgelt_then_rsb():测试原子变量是否在左闭右开区间[l,r),然后做 反向减法 运算tgtle_then_write():测试原子变量是否在左开右闭区间(l,r],然后 写tgtle_then_add():测试原子变量是否在左开右闭区间(l,r],然后做 加法 运算tgtle_then_sub():测试原子变量是否在左开右闭区间(l,r],然后做 减法 运算tgtle_then_rsb():测试原子变量是否在左开右闭区间(l,r],然后做 反向减法 运算tgtlt_then_write():测试原子变量是否在开区间(l,r),然后 写tgtlt_then_add():测试原子变量是否在开区间(l,r),然后做 加法 运算tgtlt_then_sub():测试原子变量是否在开区间(l,r),然后做 减法 运算tgtlt_then_rsb():测试原子变量是否在开区间(l,r),然后做 反向减法 运算tst_then_op():使用tst()函数测试原子变量,然后使用op()函数操作
- 位图数组的原子操作
xwbmpaop_c0i():将第i位清0xwbmpaop_s1i():将第i位置1xwbmpaop_x1i():翻转第i位xwbmpaop_t1i():测试第i位是否为1xwbmpaop_t0i_then_s1i():测试第i位是否为0,然后把它置1xwbmpaop_t1i_then_c0i():测试第i位是否为1,然后把它清0xwbmpaop_ffs_then_c0i():从最低有效位开始查找第一个为1的位并把它清0xwbmpaop_ffz_then_s1i():从最低有效位开始查找第一个为0的位并把它置1xwbmpaop_fls_then_c0i():从最高有效位开始查找第一个为1的位并把它清0xwbmpaop_flz_then_s1i():从最高有效位开始查找第一个为0的位并把它置1
中断
- XWOS头文件:
xwos/osal/irq.h - 接口:
xwos/ospl/irq.h - 实现:
xwosimpl_irq.hxwosimpl_irq.c
- 中断号:
- XWOS定义了中断号类型
xwirq_t,是一个有符号整数: - 整数和0:表示SOC的外设中断。
- 负数:表示异常。
- XWOS定义了中断号类型
- 中断优先级要求
- 切换上下文的中断为系统中 最低优先级 中断。
- 切换上下文的中断优先级
<=滴答定时器的中断优先级<=调度器服务中断优先级 - 操作系统移植层中需提供实现的函数:
void xwospl_cpuirq_enable_lc(void):开启CPU的中断开关void xwospl_cpuirq_disable_lc(void):关闭CPU的中断开关void xwospl_cpuirq_restore_lc(xwreg_t cpuirq):保存当前CPU的中断开关状态后关闭void xwospl_cpuirq_save_lc(xwreg_t * cpuirq):恢复之前保存的CPU的中断开关状态xwer_t xwospl_irq_get_id(xwirq_t * irqnbuf):获取当前中断的中断号,亦可用于判断上下文xwer_t xwospl_irq_enable(xwirq_t irqn):开启某个外设中断xwer_t xwospl_irq_disable(xwirq_t irqn):关闭某个外设中断xwer_t xwospl_irq_save(xwirq_t irqn, xwreg_t * flag):保存某个外设中断的开关,然后将其关闭xwer_t xwospl_irq_restore(xwirq_t irqn, xwreg_t flag):恢复某个外设中断的开关
硬件定时器
每个CPU都需要一个私有的硬件定时器提供周期性的滴答中断。XWOS的调度、超时、软件定时器都基于滴答中断来实现。
- 接口:
xwos/ospl/syshwt.h - 实现:
xwosimpl_syshwt.hxwosimpl_syshwt.c
- 适配函数:
xwospl_syshwt_init():初始化硬件定时器xwospl_syshwt_start():启动硬件定时器xwospl_syshwt_stop():关闭硬件定时器xwospl_syshwt_get_timeconfetti():返回还有多少纳秒进入下一次定时器中断
调度器
- 接口:
xwos/ospl/skd.h - 实现:
xwosimpl_skd.hxwosimpl_skd.c
- 适配函数:
xwospl_skd_init(struct xwospl_skd * xwskd):初始化调度调度器xwospl_skd_init_stack():初始化调度对象(线程)的栈xwospl_skd_get_id():获取当前CPU的IDxwospl_skd_start():启动调度器xwospl_skd_suspend():暂停调度器,用于电源管理xwospl_skd_resume():继续调度器,用于电源管理xwospl_skd_req_swcx():请求调度xwospl_skd_isr_swcx():切换上下文的中断xwospl_thd_exit_lc():当前CPU上的线程退出xwospl_thd_freeze_lc():冻结当前CPU中正在运行的线程xwospl_thd_outmigrate():将线程迁出其他CPU,并准备迁入其他CPU(仅限多核)xwospl_thd_immigrate():迁移线程至目标CPU(仅限多核)
线程本地存储
从C11标准开始,C语言支持线程本地存储(TLS)。XWOS提供了对关键字 _Thread_local (C11) 以及 thread_local (C2X) 的支持。
- 接口:
xwos/ospl/tls.h - 实现:
xwosimpl_tls.c - 适配函数:
void xwospl_tls_init(struct xwospl_skdobj_stack * stk):在线程栈中初始化TLS。
线程本地存储(TLS)有4种标准模型:global-dynamic, local-dynamic, initial-exec, local-exec。
XWOS只支持 initial-exec 和 local-exec ,默认使用 -ftls-model=local-exec 。
移植XWOS时,需要将 .got 段放入链接脚本中:
.got : {
*(.got.plt) *(.igot.plt) *(.got) *(.igot)
} > code_mr AT> code_mr
链接脚本
XWOS的链接脚本由两部分组成:
- SOC描述层中的
xxx.lds描述SECTIONS- 用户可参考已有的文件进行修改,例如:
- ARMv6M
- STM32F0x:
xwcd/soc/arm/v6m/m0/stm32/f0.lds
- STM32F0x:
- ARMv7M
- STM32H7x:
xwcd/soc/arm/v7m/m7/stm32/h7.lds - S32K3xx:
xwcd/soc/arm/v7m/m7/s32k3xx/s32k3xx.lds - STM32F4x:
xwcd/soc/arm/v7m/m4/stm32/f4.lds - S32K14x:
xwcd/soc/arm/v7m/m4/s32k14x/s32k14x.lds - GD32F30x:
xwcd/soc/arm/v7m/m4/gd32/f30x.lds - STM32F1x:
xwcd/soc/arm/v7m/m3/stm32/f1.lds - GD32F10x:
xwcd/soc/arm/v7m/m3/gd32/f10x.lds
- STM32H7x:
- ARMv8A
- RPi4B:
xwcd/soc/arm64/v8a/a72/bcm2711/soc.lds
- RPi4B:
- PowerPC Embedded
- MPC560xB:
xwcd/soc/powerpc/e200x/e200z0h/mpc560xb/soc.lds
- MPC560xB:
- RISC-V
- GD32VF10x:
xwcd/soc/riscv/nuclei/bumblebee/gd32v/f10x.lds
- GD32VF10x:
- ARMv6M
- 用户可参考已有的文件进行修改,例如:
- 电路板目录中的
brd.lds定义MEMORY。
初始化流程
XWOS提供了一个通用的 启动流程 :
flowchart LR
预初始化阶段 --> 初始化阶段 --> 后初始化阶段 --> 主函数阶段
subgraph 预初始化阶段
direction TB
exc["异常初始化"]
exc["浮点单元初始化"]
exc["内存初始化"]
end
subgraph 初始化阶段
xwos_init["xwos_init()"]
end
subgraph 后初始化阶段
direction LR
device["设备驱动初始化"]
mm["动态内存分配器的初始化"]
end
subgraph 主函数阶段
direction LR
skd_init["初始化调度器: xwos_skd_init_lc()"]
thd["建立线程"]
skd_start["启动调度器: xwos_skd_start_lc()"]
end
XWOS将初始化流程分为四个阶段:
- 预初始化阶段:
xwos_preinit()- 由用户定义。
- 不可访问全局变量,通常用于调用
arch_init(),cpu_init(),soc_init(),加载内存数据。
- 初始化阶段:
xwos_init()- 由XWOS定义。
- 后初始化阶段:
xwos_postinit()- 由用户定义。
- 可访问全局变量,通常用于初始化芯片驱动,初始电源管理、初始化内存管理。
- 主函数阶段:
xwos_main()- 由用户定义。
- 通常用于定义线程,启动调度器。
- 若需要使用libc中的标准函数,还需要初始化libc。依据配置
XWCFG_LIBC选择的libc,选择调用newlibac_init()或picolibcac_init()。 - C++运行环境也会在调用
newlibac_init()或picolibcac_init()时被初始化。
配置
移植XWOS时,用户需要在电路板目录中提供配置 cfg/ 。配置的具体内容可参考 配置 。
集成编译环境
XWOS提供了 构建系统 ,可在Windows、Linux上运行。 用户代码需要使用 玄武模块 进行集成。