嫌 sloop 太繁复, sloopLite 来啦-CSDN博客

2025年03月28日 10:01 北京

sloop开源项目仓库链接

https://github.com/sloop-open/sloop

https://gitee.com/gitee_caowent/sloop

开源许可证 MIT

图片

sloop 是一个嵌入式裸机框架,提供一套API,方便管理多种任务,比如超时任务/周期任务/并行任务/互斥任务等。

有 sys_wait 和 sys_wait_bare 这两个非阻塞等待API,支持挂起当前互斥任务,但不阻塞并行任务。

任务采用动态注册机制,有以下优势:

1. 可以在运行时方便地启用和停止任务,停止后没有副作用。

2. 可以替代部分静态调用,简化文件之间的调用耦合。

3. 可以替代部分依赖标志量进行同步的操作,有效降低标志的使用数量,增加代码的可读性。

01

目录结构说明:

/project
├── Bin/                                # 可执行文件目录
├── Libraries/                          # MCU 库文件
│   ├── CMSIS                           # CMSIS
│   └── STM32F2xx_StdPeriph_Driver      # ST标准外设库
├── MDK-ARM/                            # MDK 工程文件
├── user/                               # 用户文件
│   ├── app                             === 应用层文件 ===
│   │   ├── config                      # 用户配置
│   │   │   ├── bl_config.h             # sloop 配置文件
│   │   │   ├── com_config.h            # 串口和can 配置文件
│   │   │   ├── gpio_config.c           # GPIO 配置文件
│   │   │   ├── gpio_config.h           # GPIO 对外API和别名
│   │   │   └── stm32f2xx_conf.h        # ST 外设库配置(不要变更)
│   │   ├── module                      # 整机功能模块,比如伺服电机、编码器
│   │   ├── tasks                       # 任务存放文件夹
│   │   │   ├── task_baseInit.c         # 原始任务
│   │   │   ├── task_demo.c             # 演示任务
│   │   │   └── task_idle.c             # 空闲任务
│   │   ├── common.h                    # 应用层公共包含
│   │   ├── main.c                      # 程序入口 main,在此弱定义任务
│   │   └── main.h                      # 在此声明任务
│   ├── sloop                           === sloop 源码(不要变更)===
│   │   ├── kernel                      # 内核
│   │   │   ├── sloop.c                 # sloop 内核源代码
│   │   │   ├── behavior_log.h          # 行为日志宏
│   │   │   └── console.c               # 控制台源代码
│   │   ├── mcu_interface               # BL移植时,MCU 接口代码
│   │   │   ├── stm32f2xx_it.c          # mcu 中断头文件(不要变更)
│   │   │   └── stm32f2xx_it.h          # mcu 中断源文件(不要变更)建议在驱动中实现 RQHandler,不需要汇总到这里
│   │   ├── RTT                         # 日志支持模块,SEGGER RTT
│   │   ├── sloop.h                     # sloop 用户 API
│   │   └── bl_common.h                 # sloop 用户 宏 API,主要是日志
│   ├── service                         === 为应用层提供服务(不要变更)===
│   │   ├── app_service_pack            # 面向应用层的服务包,与 MCU 无关,完全可移植
│   │   ├── mcu_service_pack            # 基于MCU的服务包,对其他MCU平台不可移植
│   │   └── service_api.h               # 服务包面向应用层,提供给用户的API
└── readme.md   

02

一些设计建议:

1. 开发中要不断思考该业务是不是主要业务,如果是主要业务需要暴露在可以快速直观看到的地方。

2. 业务不应该是混乱分散的,而是有结构的,主次分明的。

3. 尽量不要使用运行时动态连接(函数指针),因为这种方式会破坏静态分析特性。只有在必要的时候才采用,比如抽象业务调用(任务调用)。

4. 用静态调用的方式,不仅具有良好的静态分析性(可以直接跳转),而且运行时的效率也要高,还避免了NULL指针问题。

5. 要控制函数调用深度,在满足设计需求情况下越浅越好,一般不要超过6层。

6. 每一层调用都会消耗栈空间,控制栈深度对于栈溢出风险控制和运行时减少栈入栈出提高运行效率十分有益。

7. 函数名应具有局部辨识度,而且不应太长,不要超过32个字符。能表达核心功能即可,不要太过追求名称的统一(这反而会破坏函数名称的辨识度)。

8. 业务配置和业务的执行loop,应该放在一个任务文件或模块文件下。

9. 按键响应这些会直接导致业务跳转的地方(可以叫做业务路由),业务路由需要集中管理和暴露,不能分散,否则将极不利于业务流的分析。

10. 尽量不要使用标志量来进行模块间的交互,建议采用函数封装操作接口替代标志量。因为往往函数的意图比标志量清晰。

11. 如果抽象层次过高,开发者可能需要花费较多时间去理解每一层的具体实现,尤其是当功能较简单时,过度的抽象可能适得其反。

12. 尽量避免使用extern关键字,如果可能应当完全禁止使用extern。用函数来作为模块之间的交互接口,而不是全局变量。

13. 如果是同一文件中函数之间的交互,可以使用标志量,更轻量。因为在同一文件,容易定位,不太会引起混乱。

关于中断处理的最佳实践:

1. sloop主循环可以看成一个单线程,称为主线程。

2. 中断可以理解为“硬件触发的超高优先级的短暂线程”。

3. 中断适合快速处理事件,而主线程适合持续执行任务。

4. 中断里只做“最小、最关键”的事情,比如读取传感器、清除中断标志位。

5. 中断必须快速执行完成。

6. 复杂逻辑交给主线程处理,推荐使用sys_task_once()把中断触发的业务逻辑下放到主线程处理。

7. 避免中断嵌套,否则可能导致系统崩溃或不可预测的延迟,所以NVIC配置成了不可抢占。

8. 中断是异步处理,应该尽快处理完成,切回主线程继续同步处理各项事务。

以下是常见的非法内存操作,可导致不可预测的行为:

1. 未初始化的变量

{

    int x;

    /* x 未初始化,输出不可预测的值 */

    printf("%d", x);

}

2. 对常量进行修改

char* str = "Hello";

/* 错误,字符串常量不可修改 */

str[0] = 'h';

3. 数组越界写入

int arr[5] = {1, 2, 3, 4, 5};

/* 超出数组范围,越界写入 */

arr[10] = 100;

/* 可能导致其他模块或函数工作异常 */

4. 空函数指针调用

pfunc func = NULL;

/* 未定义行为,通常会导致崩溃 */

func();

5. 未处理的空指针解引用

int* ptr = NULL;

/* 空指针解引用,程序崩溃或行为不确定 */

*ptr = 10;

6. 缓冲区溢出(Buffer Overflow)

char buffer[10];

/* 超出缓冲区的大小 */

strcpy(buffer, "This is a very long string");

/* 可能导致其他模块或函数工作异常 */

char buffer[10];

/* 不安全,可能溢出 */

sprintf(buffer, "This is a string");

7. 内存泄漏(Memory Leak)

int* ptr = malloc(sizeof(int));

/* 没有调用 free(ptr),导致内存泄漏 */

8. 递归深度过深导致栈溢出

void recursive_function() {

    /* 没有终止条件,导致栈溢出 */

    recursive_function();

}

安全性与内存管理的关系:

C语言中的内存管理责任落在开发者身上,编译器和操作系统对内存的管理并不像在高级语言(如Java或Python)中那么自动化。

因此,内存管理错误(如访问非法内存、越界、空指针、内存泄漏等)往往是导致程序崩溃或安全漏洞的根本原因。

预防非法内存操作的方法:

  • 初始化指针:始终将指针初始化为NULL或指向有效内存区域,避免使用未初始化的指针。

  • 检查指针有效性:在解引用指针之前,始终检查它是否为NULL,避免空指针解引用。

  • 边界检查:确保对数组和缓冲区的访问不会超出其边界,可以使用sizeof或明确的边界值进行检查。

  • 使用安全的库函数:避免使用易受缓冲区溢出攻击影响的函数(如strcpy()、gets()等),使用更安全的替代函数(如strncpy()、fgets()等)。

03

系统控制台,可以在RTT终端执行预设命令,比如输入reboot执行重启,输入m、M或menu出菜单。

RTT设置:

input->"end of line"->none

input->"echo input"->all enable

控制台内置命令汇总:

功能 命令 简写 数字调用 参数
c1:  重启 reboot r/R 0
c2:  查询版本 version v/V 1
c3:  查询当前任务数据 task t/T 2 命令示例:"task" 打印一次,"task on" 10Hz打印,"task off" 关闭10Hz打印
c4:  查询 cpu 负载 cpu c/C 3 命令示例:"cpu" 打印一次,"cpu on" 10Hz打印,"cpu off" 关闭10Hz打印
c5:  串口发送 uart 命令示例:ASCLL 模式:"uart6 hello",HEX 模式:"uart6 -hex 68 65 6C 6C 6F"
c6:  CAN发送 can 命令示例:ASCLL 模式:"can1 -id 1 hello",HEX 模式:"can1 -id 1 -hex 68 65 6C 6C 6F"
c7:  GPIO输出和 gpio输入回显开关 gpio 命令示例:命令示例:"gpio pin_beep H", "gpio input on" or "gpio input off"

命令 -h: 可查看命令使用举例,比如uart -h

使用注意:

1. 超时任务和周期任务 以回调函数名称作为ID,故同一时刻,同一个回调函数不支持开启多种超时或者周期任务。

开启多个,最新的会覆盖旧的。同一个回调即开启超时又开启周期,是允许的。

2. 重置超时任务,只需重新调用 sys_timeout_start。超时任务到达后,会自动释放任务,没有副作用。关闭后,可以重新设置超时时间。

3. 所有需要在主线程轮询的任务,必须通过 sys_task_start 注册调用(不允许在 main 函数 while(1) 下添加其他函数),这样才能保证 sys_wait 非阻塞等待功能正常。

4. sys_wait 和 sys_wait_bare 只能在互斥任务中调用,使用次数不限,不允许嵌套调用(嵌套日志会报错)。

不建议在互斥任务的驱动中和互斥任务的子函数中调用,容易出现嵌套。保证不嵌套的情况下可以使用。

5. sys_wait 和 sys_wait_bare 调用会阻塞当前任务,但不影响其他并行任务执行。相当于挂起当前任务。

可以用sys_wait_break中断等待,中断后返回1,建议应用层直接返回,不再执行等待后的业务。

可以用sys_wait_continue忽略等待,返回0,继续执行等待后的业务。

6. sys_wait推荐使用方法

等待前的业务,进入 wait 后开始阻塞

if(sys_wait(10))

    return;

等待完成后的业务

7. 建议不要使用 sys_delay,特别是周期调用,会大大降低系统性能。sys_delay_us <100us ,可以安全调用。

8. 在互斥任务顶层,可以使用 sys_wait ,达到 sys_delay 的相同效果。

9. 互斥任务是主要的业务执行场景,并行任务是辅助服务。

10. 在所有任务操作中,调用stop后,任务被释放,没有副作用。

11. NVIC 分组已经配置为 16 级响应,不支持抢占,他处不应再调用 NVIC_PriorityGroupConfig。

12. 配置外设中断时,预优先级恒为0,子优先级默认设为宏 PRIO_DEFAULT,如遇到响应延迟,再调整。

中断优先级分配表
SysTick_IRQn PRIO_HIGHEST 0
外设中断 PRIO_DEFAULT 5
最低优先级  PRIO_LOWEST 15
main 主线程 16(相当于)

13. 新增互斥任务需要在 main.h 声明,在 main.c 中弱定义,在用户创建的 task 文件中实现。

14. 在 bl_config.h 中可以开启看门狗,默认不启用。

15. 在 bl_config.h 中开启行为日志功能,默认启用。

开启行为日志后,会对系统 API 进行打印增强。即在调用系统 API 时会有相应日志输出。

这对分析系统工作流程会有帮助。

16. 互斥任务有三个阶段:

a1. 初始化阶段(_INIT 和 _FREE)

a2. 运行阶段 _RUN;之后

a3. 运行终止(_FREE; 和 _RUN;)

17._INIT _FREE和_RUN必须实现,为空没关系。因为任务跳转依赖它。

18. systick和tim7两个外设,内核已占用,应用层不要再使用。

19. sloop不适用运行时间长的任务,比如做连续FFT,时间超过1ms。

如果是这种任务建议用状态机拆分或者直接拆分成更小的任务。拆分后,小任务的执行周期应小于10us。

20. 推荐使用V5版本编译器,速度更快。编译优化等级,推荐使用 -O2。

04

更新日志:

2025-3-11

feat:控制台新增GPIO输出命令

feat:控制台新增gpio输入回显开关

2025-3-10

feat:精简GPIO API

2025-3-3

feat:全新的gpio配置模板

optimiz:移除系统API:sys_wait_for 和 sys_delay_nonblock

optimiz:简化demo逻辑

feat:readme.txt 加入mdk工程

2025-2-27

feat:readme里增加一些设计建议

feat:新增常见的内存非法操作提示

2025-2-26

feat:uart1~6都验证通过,控制台提供uart发送支持

2025-2-19

feat:hardfault增加栈解析,抛出疑似出错代码的位置(即地址)

2025-2-13

feat:readme新增目录结构说明

2025-2-8

feat:新增任务周期统计,控制台 task on 命令可查看。

2025-2-7

feat:使用注意 添加 关于中断处理的最佳实践。

feat:新增us级延时 sys_delay_us 和 sys_get_us, 通过tim7 精确定时的,但不产生中断。

2025-2-2

optimiz:修改打印日志api 名称,比如 sys_printf_func 修改为 sys_prt_withFunc。

feat:命令增加 -h 查看命令使用举例,比如 uart -h。

2025-1-27

feat:通讯口串口和can配置集成在 com_config.h。service_api.h 汇总了服务层api。

2025-1-24

feat:控制台新增并行任务和周期任务查询,开启行为日志才能生效。

feat:控制台串口和can支持发送16进制。命令格式 uart -hex 30 31 ……,不带 -hex 默认发ASCLL码。

2025-1-23

feat:can支持 FIFO

2025-1-20

feat:控制台新增查询mcu负载命令。

feat:移除 API sys_cycle_flag_start。

feat:软定时任务不再跑在ms中断,改到主线程loop中。有助于降低栈空间,减少栈入栈出,提升系统性能和稳定性。

feat:中断改为16级响应优先级,不再支持抢占,有助于降低栈空间,减少栈入栈出,提升系统性能和稳定性。

也不会再有死锁问题。但要求中断处理要干净利索,处理实时数据,快速退出。

2025-1-16

feat:串口接收 DMA + FIFO OK

feat:base_init 改成任务 task_baseInit。即始祖任务。

2025-1-15

feat:串口发送 DMA + FIFO OK

feat:重构互斥任务,原静态调用改为动态调用。

feat:新增 _FREE 和 _RUN,方便在任务结束时,释放资源。比如周期任务。

2025-1-14

feat:串口支持DMA收发

feat:控制台新增查询当前任务命令task。互斥任务需实现INIT,才支持查询。

2025-1-13

feat:新增idle空闲任务,互斥任务没事可做可停留在idle。

feat:新增 task_end 互斥任务回收函数。

它会在跳转到其他任务前,释放一些在当前任务中开启的资源,比如周期任务,所以你不需要在每处 sys_goto 前做释放操作。

弱定义,根据需求实现,可以不实现。

feat:在周期任务基础上新增多次任务,可指定执行次数和间隔时间。可用于蜂鸣器多次鸣叫、串口多次定时重发。

feat:新增 sys_wait_bare 非阻塞裸等待,直到 break or continue。无需传入等待条件。适用多条件。

相当于挂起,外部通过 sys_wait_break 或 sys_wait_continue 打断。

feat:新增 sys_is_waiting,可以查询等待状态。

feat:控制台新增查询版本命令version ,可以通过 sys_set_version 设置版本。

2025-1-10

feat:新增蜂鸣器驱动,支持多次鸣叫

feat:新增运行灯驱动

2025-1-9

新增参数保存驱动和演示,默认以Flash为存储介质,可配置为EEP(EEP有32字节限制)。

2025-1-6

新增系统控制台,可以在RTT终端执行预设命令,比如输入reboot执行重启。输入m出菜单。

支持输入参数,如test -p 6.88

控制台可用于初期在线调试硬件,在线调试运动等。

2025-1-4

新增单次任务API sys_task_once,只执行一次,迟滞到loop里执行。可用于中断中复杂逻辑下放执行。

2024-12-31

新增可配置启用的看门狗,超时时间1s,默认不启用。

新增行为日志,可在 bl_config.h 开启,利于分析业务工作流。

日志新增时间戳[day h:m:s.ms]

2024-12-30

新增系统心跳,在 terminal1 窗口打印,可用于判断死机与否。

2024-12-25

精简互斥任务

新增错误日志,打印源码位置 sys_error

新增变量打印宏 sys_prt_var

2024-12-20

V1.0

sloop开源项目仓库链接

https://github.com/sloop-open/sloop

https://gitee.com/gitee_caowent/sloop

开源许可证 MIT

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐