<关键字> 中国移动oneos 、开发环境 、开机自启动、 shell 、单元测试、 源码分析

1、oneos系统

1.1 开发手册

OneOS是中国移动针对物联网领域推出的轻量级操作系统,具有可裁剪、跨平台、低功耗、高安全等特点,支持ARM Cortex-M、MIPS、RISC-V等主流芯片架构,兼容POSIX、CMSIS等标准接口,支持MicroPython语言开发,提供图形化开发工具,能够有效提升开发效率并降低开发成本,集成公共组件,适用于安全易用的物联网产品。

移动官网提供完善的oneos开发文档 https://os.iot.10086.cn/v2/doc/homePage

也可参考RT-Thread的资料,https://www.rt-thread.org/document/site/#/

1.2 开发工具

OneOS开发环境是基于命令行的OneOS-Cube,在对应工程目录下,执行menuconfig配置系统,scons编译构造。具体操作说明:https://os.iot.10086.cn/v2/doc/detailPage/documentHtml?idss=157071776529260544&proId=156799478777782272

1.3 软件框架

OneOS总体架构采用分层设计,主体由驱动、内核、组件、安全框架组成,采用一个轻量级内核加多个系统组件的模式。

与freeRTOS只包括内核相比,oneos支持通用组件或第三方库,尤其对接移动平台方便,遵循 Apache 2.0 license 开源协议,任意使用。

1.4 内核

Oneos内核提供任务管理调度、任务间同步与通信、定时器、内存管理等常用RTOS功能,与常规的RTOS如freeRTOS相比,仅有几点小区别:

1、任务,创建task后需要额外调用才启动,不同于freeRTOS创建所有的task后统一启动调度,全部task都开始执行。这种各task独立启动的,需要注意先后关系,不能task1启动后就向task2的队列发消息,此时队列可能为NULL且发送前没有判断,会导致重启。

2、消息队列,其动态创建和发送接口类似,但接收队列消息需要传入的参数不同,需要传入期望接收的字节大小。巧合的是项目使用的消息队列都是同一个结构体,如果不一致,需要特殊处理。

3、工作队列,将task进行了一定封装,不需要为处理某个事件而新建task,交给系统提前创建的task统一执行,执行结束后触发回调函数,这样多个长时间运行但不经常触发的,可以交给工作队列处理,该功能在UIS8910中系统自带。

4、邮箱,Oneos特有的,可理解为简化版的消息队列。

关于freeRTOS基础可以参考FreeRTOS基础及其应用 (入门简化版)。

1.5 组件

正如前面提到,如何使用oneos,移动官网有详细的说明,本文只是介绍oneos的基本开发流程,分析其部分功能的实现方式,后续以其设备框架、SHELL和单元测试三部分为主。

2、系统移植

2.1 开发流程

基于oneos的开发方式和以往不同,先将原始工程编译生成库,全部复制到oneos工程,再基于oneos系统开发业务逻辑,其开发环境和原始工程开发环境无关。Oneos工程编译生成的bin文件下载到设备,完整版本支持microPython,可以导入python文件直接运行。

可能部分功能比较特殊,使用原始库文件无法实现,例如获取系统某个参数。
在原始工程开发,可以直接将客制化代码插入某个接口拦截,基于oneos开发时尽量避免,但是实在不行也只能这样。这样操作后,原始工程编译成功,但链接肯定失败,但不影响结果,只要输出lib库即可。

2.2 操作系统适配

如果没有原始SDK,要运行oneos,直接将原始库,例如STM32原厂HAL库复制到oneos/thirdparty即可;但是有基础SDK,且SDK是基于其它RTOS开发,则其库要在oneos运行,需要进行适配转换,存在两种方式。

以目前支持cat1网络比较火的两个芯片平台为例。
紫光展锐UIS8910平台使用freeRTOS,且基本开源,因此可以将UIS8910工程中的freeRTOS系统接口,其函数内容替换使用oneos的接口实现。

翱捷ASR1603平台使用threadX,且已封库,因此是在oneos工程,将oneos的系统内核接口函数内容使用ASR1603提供的库实现。

前者UIS8910是oneos直接在底层替换了freeRTOS,相当于只运行了一套较为干净的oneos;后者完全是将两套RTOS的接口互相匹配,且中间并不是一对一替换。

2.3 风险与限制

原始工程开发函数是直接调用,引入oneos框架后,内核适配、驱动框架增加了代码量,运行效率也存在一定损失。
对网络modem相关的操作,oneos使用AT通信,其阻塞方式对原有应用逻辑存在较大影响,不如原始API便捷。

3、系统组件

3.1 编译器关键字

重点介绍section关键字,后续章节都与其有关,section主要作用是将函数或者变量放在指定段中,可在指定的地方取函数执行。

//main.c
//section demo
#include "stdio.h"  int __attribute__((section("my_fun"))) test1(int a,int b)
{  return (a+b);
}int test(int b)
{  return 2*b;
}int __attribute__((section("my_fun"))) test0(int a,int b)
{  return (a*b);
}  int __attribute__((section("my_val"))) chengi;
int __attribute__((section("my_val"))) chengj;  int main(void)
{  int sum,c,j;       chengi=1,chengj=2;  sum=test1(chengi,chengj);  c=test(100);  j=test0(chengi,chengj);  printf("sum=%d,c=%d,j=%d\r\n",sum,c,j);  return 0;
}

编译生成map文件:

gcc -o main.exe main.c -Wl,-Map,my_test.map

my_test.map 文件片段如下:

.text 0x00401460 0xa0 C:\Users\think\ccmGLaeH.o
0x00401460 test
0x0040146a main

.text 0x00401500 0x0 c:/mingw/bin/…/libmingw32.a(CRTglob.o)


my_fun 0x00404000 0x200
[!provide] PROVIDE (___start_my_fun, .)

my_fun 0x00404000 0x1c C:\Users\think\ccmGLaeH.o

0x00404000 test1
0x0040400d test0

[!provide] PROVIDE (___stop_my_fun, .)

.data 0x00405000 0x200

0x00405000 _data_start_ = .

*(.data_cygwin_nocopy)
my_val 0x00406000 0x200

[!provide] PROVIDE (___start_my_val, .)

my_val 0x00406000 0x8 C:\Users\think\ccdMcTrl.o
0x00406000 chengi
0x00406004 chengj

[!provide] PROVIDE (___stop_my_val, .)

.rdata 0x00407000 0x400

分析可见,使用section修饰的函数和变量在自定义的片段,而且是连续存放,这样可根据变量的地址得出与其同段变量的地址,为后续自动初始化等功能提供了基础。

3.2 自动初始化

基于前面section的作用,可以将同类函数指针全部使用同一个段名修饰,然后开机后系统自动检索段内函数指针,逐个执行,对上层应用就是无需主动调用,系统自动初始化。考虑到硬件初始化与应用功能初始化的先后顺序,可以对段名进行分配,map文件按段名排序。自动初始化主体是OS_INIT_EXPORT宏。

typedef os_err_t (*os_init_fn_t)(void);#define OS_INIT_EXPORT(fn, level) \
const os_init_fn_t __os_call_##fn OS_SECTION(".init_call."level) = fn#define OS_BOARD_INIT(fn) OS_INIT_EXPORT(fn, "1")#define OS_PREV_INIT(fn) OS_INIT_EXPORT(fn, "2")#define OS_DEVICE_INIT(fn) OS_INIT_EXPORT(fn, "3")#define OS_CMPOENT_INIT(fn) OS_INIT_EXPORT(fn, "4")#define OS_ENV_INIT(fn) OS_INIT_EXPORT(fn, "5")#define OS_APP_INIT(fn) OS_INIT_EXPORT(fn, "6")

例如shell初始化函数,定义如下:

OS_APP_INIT(sh_system_init);

将宏定义展开

/* 含义是函数指针 __os_call_sh_system_init
*  其指向sh_system_init函数,且该指针编译后放在".init_call.6"段
*/
const os_init_fn_t __os_call_sh_system_init__attribute__((section((".init_call.6")))) = sh_system_init

系统自身也有自定义函数,用来标记起止点函数

OS_INIT_EXPORT(os_init_start, "0");
OS_INIT_EXPORT(os_board_init_start, "0.end");
OS_INIT_EXPORT(os_board_init_end, "1.end");
OS_INIT_EXPORT(os_init_end, "6.end");

最终生成的map文件如下图:

//系统底层在合适的时机调用如下两函数,将指定段区间内的所有函数自动执行void os_board_auto_init(void)
{const os_init_fn_t *fn_ptr_board_init_start;const os_init_fn_t *fn_ptr_board_init_end;const os_init_fn_t *fn_ptr;fn_ptr_board_init_start = &__os_call_os_board_init_start + 1;fn_ptr_board_init_end   = &__os_call_os_board_init_end - 1;for (fn_ptr = fn_ptr_board_init_start; fn_ptr <= fn_ptr_board_init_end; fn_ptr++){(void)(*fn_ptr)();}return;
}static void os_other_auto_init(void)
{const os_init_fn_t *fn_ptr_other_init_start;const os_init_fn_t *fn_ptr_other_init_end;const os_init_fn_t *fn_ptr;fn_ptr_other_init_start = &__os_call_os_board_init_end + 1;fn_ptr_other_init_end   = &__os_call_os_init_end - 1;for (fn_ptr = fn_ptr_other_init_start; fn_ptr <= fn_ptr_other_init_end; fn_ptr++){(void)(*fn_ptr)();}return;
}

系统执行os_other_auto_init时实现了sh_system_init的自动执行,即使应用层没有显示的去调用它。使用符号段的方式实现初始化函数自动执行,应用层修改软件,增加功能启动或者裁剪,对底层代码无需任何改动。

3.3 设备框架

3.3.1 设备模型

一般HAL包括GPIO、UART、ADC等,每个设备节点的类型和控制接口、参数个数及含义完全不同,即使都是GPIO,不同原厂提供的接口也各不相同。设备框架就是在底层封装原始API,然后统一注册到设备节点表,使用时获取节点及其对应的操作接口,这样应用层的代码在风格上比较统一。

应用层需要操作设备时,根据名称查找设备,再使用该提供的API进行操作,无需关注该设备具体对应的端口、状态等细节信息;其风格与linux驱动接近。

3.3.2 设备注册

以I2C设备为例:

#define OS_DEVICE_INFO static OS_SECTION("device_table") const  os_device_info_tOS_DEVICE_INFO asr1603_i2c1_device = {.name = "i2c1",.driver = "ASR1603_I2C_DRIVER",.info = OS_NULL,
};OS_DEVICE_INFO asr1603_i2c2_device = {.name = "i2c2",.driver = "ASR1603_I2C_DRIVER",.info = OS_NULL,
};

所有的设备信息存在device_table段,只是分配设备驱动类型和名称。

OS_DRIVER_INFO asr1603_i2c_driver = {.name = "ASR1603_I2C_DRIVER",.probe = asr1603_i2c_probe, //I2C设备初始化和注册
};OS_DRIVER_DEFINE(asr1603_i2c_driver, "2");#define OS_DRIVER_DEFINE(_driver_, sequence)            \static os_err_t __driver_##_driver_##_init(void)    \{                                                   \return driver_match_devices(&_driver_);         \}                                                   \OS_INIT_EXPORT(__driver_##_driver_##_init, sequence)
//OS_INIT_EXPORT即为前面提到的开机自启动定义宏

开机后自动执行_asr1603_i2c_driver_driver__init,也就是自动将device_table段设备对应的驱动程序asr1603_i2c_probe自动执行,实现了所有设备的初始化,

static int asr1603_i2c_probe(const os_driver_info_t *drv, const os_device_info_t *dev)
{...//所有的 I2C 设备(一种设备有多个)进行初始化if(!strcmp(dev->name, "i2c1")){g_i2c1.id = ASR1603_DEV_I2C1;i2c_p = &g_i2c1;}else if(!strcmp(dev->name, "i2c2")){g_i2c2.id = ASR1603_DEV_I2C2;i2c_p = &g_i2c2;}....asr1603_wrap_i2c_init(i2c_p->id);i2c_p->i2c_bus.ops = &i2c_bus_ops; //底层操作 I2C 的接口,与实际硬件绑定i2c_p->i2c_bus.priv = i2c_p;ret = os_i2c_bus_device_register(&(i2c_p->i2c_bus), dev->name, OS_DEVICE_FLAG_RDWR, &(i2c_p->i2c_bus));return ret;
}

os_i2c_bus_device_register将I2C设备注册到系统设备列表os_device_list,包括其对外接口i2c_ops。

struct os_device_ops
{os_err_t  (*init)   (os_device_t *dev);os_err_t  (*open)   (os_device_t *dev, os_uint16_t oflag);os_err_t  (*close)  (os_device_t *dev);os_size_t (*read)   (os_device_t *dev, os_off_t pos, void *buffer, os_size_t size);os_size_t (*write)  (os_device_t *dev, os_off_t pos, const void *buffer, os_size_t size);os_err_t  (*control)(os_device_t *dev, os_int32_t cmd, void *args);
};

所有设备对外提供接口都类似,部分不支持的为NULL,风格和linux设备驱动一致,这些接口是封装前面i2c_bus_ops提供的硬件特有驱动,这样完成了I2C设备框架与硬件驱动绑定以及自动初始化。

3.3.3 框架应用

应用层使用I2C设备:

os_device_find("i2c1");

获取成功后,正常流程是使用i2c_ops提供的接口操作设备,实际调用也基于i2c_bus_ops封装的接口,可见oneos也不太标准;最佳操作可以参考UART的用法。

3.4 模组连接套件

模组连接套件 Molink (Module link kit),设备通过AT与网络模组交互的接口,内置基带的使用虚拟AT通道。

Molink对单片机加模组的方案非常合适,对内置基带的芯片,反而影响效率,因为其AT是阻塞方式实现,例如扫描周围wifi热点,会导致当前task阻塞几秒钟,这样处理只是为统一API接口,实现MCU+模组和内置基带两种硬件方案的应用代码无缝迁移。

名称高大上,其实就是开机初始化一个大数组,在module_asr1603_create(),不同作用的AT分类,封装AT收发、解析接口。

使用mo-link先获取数组中对应项,使用其支持的API操作AT指令,以阻塞方式运行。对于ASR1603内置基带的,socket没有使用AT方式,而是LWIP接口,这种效率高。

3.5 Shell工具

和linux中shell类似,以命令行触发函数运行,在shell控制口,默认是
OS_CONSOLE_DEVICE_NAME
输入命令,shell task会解析并自动扫描内部函数表,执行函数后输出回应,将结果显示在控制终端上。

Shell对软件调试非常方便,例如调试I2C接口,只需定义:

SH_CMD_EXPORT(test, test_i2c, "test i2c api");

开机后串口输入test字符串,设备即运行test_i2c()函数,其原理如下:

#define SH_CMD_EXPORT(cmd, func, desc)      SH_FUNCTION_EXPORT_CMD(func, __cmd_##cmd, desc)#define SH_FUNCTION_EXPORT_CMD(func, cmd, desc)                         \const char __fsym_##cmd##_name[] = #cmd;                            \const char __fsym_##cmd##_desc[] = desc;                            \OS_USED const sh_syscall_t __fsym_##cmd OS_SECTION("FSymTab") =     \{                                                                   \__fsym_##cmd##_name,                                            \__fsym_##cmd##_desc,                                            \(syscall_func_t)func                                            \};

SH_CMD_EXPORT宏将前面i2c提供的参数转换,在FsymTab段创建一个名为__fsym___cmd_test的结构体,其3个成员分别是字符名,描述和函数体。

OS_APP_INIT(sh_system_init);

开机自启动sh_system_init,创建gs_shell_task任务,接收shell控制口的字符数据,满足一定条件后进入sh_exec,搜索FsymTab段区间变量名,sh_get_cmd_func找到对应函数再执行。

shell工具便于调试,调试复杂功能注意栈空间;但其在数据安全方面存在较大隐患,且占用独立的task和串口,浪费硬件资源,正式发布的软件务必关闭。

3.6 单元测试

类比assert的作用,判断条件为假时触发异常,单元测试与其类似,统计判断结果导致报告。OneOS 开发的单元测试框架atest(and test),和网上开源的差不多。

#define ATEST_TC_EXPORT(name, testcase, init, cleanup, priority)                            \OS_USED static const atest_tc_entry_t gs_atest##testcase OS_SECTION("AtestTcTab") =     \{                                                                                       \#name,                                                                              \init,                                                                               \testcase,                                                                           \cleanup,                                                                            \priority                                                                            \};

其原理就是软件自动执行某一段代码,将运行结果和期望值进行比较并统计,对软件质量的检测效果,取决于单元测试用例的设计水平。该功能与平台无关,适用于新平台首次使用时测试API。

4、 Python开发

基于前面shell的原理,可以按输入的字符串执行与之绑定的函数,如果对字符串进行一定的规则定义,支持自动解析执行,即可实现函数按提供的文本执行。这套文本规则就是python语法,解析器就是MicroPython内核,这样就能实现在嵌入式平台使用python开发。

MicroPython对软件进行天然的分层,严格区驱动层和应用层,实现应用软件的跨平台移植。Oneos集成的就是开源的MicroPython,其源码下载地址是:https://github.com/micropython/micropython

OneOS-MicroPython开发环境:VsCode+NODE+Pymakr
其中.mpy文件混淆加密的工具在MicroPython源码mpy-cross中自行编译。

短期内Python不会作为嵌入式主要开发语言,但掌握其基础也有一定作用。

中国移动oneos框架基础及其组件解析相关推荐

  1. 从程序员到CTO的Java技术路线图 JAVA职业规划 JAVA职业发展路线图 系统后台框架图、前端工程师技能图 B2C电子商务基础系统架构解析...

    http://zz563143188.iteye.com/blog/1877266在技术方面无论我们怎么学习,总感觉需要提升自已不知道自己处于什么水平了.但如果有清晰的指示图供参考还是非常不错的,这样 ...

  2. java职业发展路线图_从程序员到CTO的Java技术路线图 JAVA职业规划 JAVA职业发展路线图 系统后台框架图、前端工程师技能图 B2C电子商务基础系统架构解析...

    http://zz563143188.iteye.com/blog/1877266在技术方面无论我们怎么学习,总感觉需要提升自已不知道自己处于什么水平了.但如果有清晰的指示图供参考还是非常不错的,这样 ...

  3. 【SSM框架系列】Spring-MVC的组件解析

    SpringMVC完整执行流程 用户发送请求至前端控制器DispatcherServlet. DispatcherServlet收到请求调用HandlerMapping处理器映射器. 处理器映射器找到 ...

  4. 中国移动OneOS 3.0物联网操作系统正式发布

    11月28日,由中移物联网有限公司举办的"中国移动OneOS 3.0LTS物联网操作系统发布会暨生态合作论坛"在线上隆重举行.中国工程院院士倪光南.中国移动通信集团有限公司政企事业 ...

  5. data spring 指定时区_Spring 框架基础(05):Mvc架构模式,执行流程详解

    本文源码:GitHub || GitEE 一.SpringMvc框架简介 1.Mvc设计理念 MVC是一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集到一个组件里面, ...

  6. Java熔断框架有哪些_降级熔断框架 Hystrix 源码解析:滑动窗口统计

    降级熔断框架 Hystrix 源码解析:滑动窗口统计 概述 Hystrix 是一个开源的降级熔断框架,用于提高服务可靠性,适用于依赖大量外部服务的业务系统.什么是降级熔断呢? 降级 业务降级,是指牺牲 ...

  7. 中国移动OneOS助力全国大学生物联网竞赛开幕

    本文分享自中移物联网微信公众号:<中国移动OneOS助力全国大学生物联网竞赛开幕>. 近日,2022年全国大学生物联网设计竞赛正式开赛!该项赛事是教育部高等学校计算机类专业教学指导委员会创 ...

  8. Django框架基础知识汇总(有项目版)

    Web框架本质## web系统概念 1. Http,无状态,短连接 2. 浏览器(socket客户端).网站(socket服务端) web框架本质 import socket def handle_r ...

  9. Vue.js 框架基础笔记

    Vue.js 框架基础笔记 1. Vue.js 基本概念 1.1 遇见 Vue.js 1.1.1 为什么要学习 Vue.js 1.1.2 前端开发的复杂化 1.1.3 Vue.js 特点 1.1.4 ...

最新文章

  1. jQuery-1.9.1源码分析系列(四) 缓存系统
  2. 微服务架构下的测试之道
  3. 数据库索引类型及实现方式
  4. 将matpoltlib绘制好的图片从内存中取出
  5. 关于团队开发项目的想法
  6. java基础面试题之:switch的参数类型
  7. vuejs知乎_你也许不知道的Vuejs - 深入浅出响应式系统
  8. HTTP/1.1与HTTP/1.0的区别
  9. flashlite3无法接入网络的解决办法
  10. 求职 IT 少年李文星之死:请务必学会保护自己!
  11. Python机器学习简介
  12. php生成的apk无法安装,xapk怎么安装
  13. Halcon学习(7):颜色识别
  14. steam服务器连接不稳定WIN10,小编操作win10系统steam连接不稳的解决步骤
  15. Win 7 安装office visio
  16. edge microsoff 连不上网_win10电脑连不上网的三种解决方法
  17. [推荐 10 个让你事半功倍的网站]
  18. 安兔兔html5跑分 6s,安兔兔不仅可以跑分,还能帮你辨别真假iPhone 6s
  19. 制造业的发展战略规划
  20. 宅急送项目的第七天笔记!(JBPM工作流)

热门文章

  1. 数学建模如何查找数据?
  2. 嵌入式开发培训学什么?嵌入式开发板知识讲解
  3. Python爬虫获取某个网页所有的a标签中的超链接网址
  4. 算法学习报告-02 遗传算法
  5. 根据RGB值,JSP页面显示相应颜色色块
  6. 《Linux驱动:I2C驱动看这一篇就够了》
  7. 日期计算php,php时间日期计算
  8. 标准化,归一化与训练-测试集数据处理
  9. Java阻塞队列的简单实现
  10. 基于 Nextcloud 的二次开发