RT-Thread 解析

目录

系统架构

内核

启动流程

线程管理

时钟管理

线程同步

线程通信

内存管理

> 动态内存堆

>> 小内存管理算法

>> slab管理算法

>> memheap管理算法

> 静态内存池


        RT-Thread 是国内开源的嵌入式实时操作系统,有三种版本:Nano 版(极简内核版)、标准版(内核+组件+软件包)、smart 版(类 linux 版)。笔者初次使用标准版,就被其丝滑的手感打动了,于是就有了这篇文章。整体感受是,RTT 整合了精简的实时内核和类 linux 系统框架,既有 FreeRTOS 的内核精髓又有 linux 的架构支撑。对于熟悉实时系统却不太熟 linux 的同学来说,简直不可多得,原地起飞。

        官方 ENV 工具很好用,menuconfig 裁剪内核、scons 构建工程,爽歪歪。

        国际惯例,先来个流水账,边学边总结,根据心情随便写写哈哈。

系统架构

        看到了吗,除了内核外,组件/软件包太香了有木有,该有的都有了。不过个人感觉,有些东西比如 AirKiss、云接入SDK,纯应用的东西搞进来干啥啊(看到 AirKiss,禁不住就陷入了笔者懵懂青葱岁月的回忆里,那时的少年简单自由,那时的傍晚霞光漫天;少年脚踏单车,迎风歌唱;花儿为我伴舞,鸟儿替我伴奏;六月的晚风在耳畔低语,校园里花香四溢,只待斯人)。

        “设备框架” 类似 linux 中的 /dev,外设需要先注册到框架中,应用层再通过设备框架按注册名称间接操作设备;DFS 也是类似的逻辑,虚拟文件系统接口-> 某具体文件系统接口-> 存储设备读写接口;Finsh 类似 shell,很好用,可以方便自定义命令;...

内核

        object,系统内部资源是按面向对象的方式进行管理,线程、信号量、timer 等都是一个个对象。c 语言方式的面向对象哦。

启动流程

        这部分挺清晰的吧,不知道说啥。

        在 main() 前面进行了各种 init,即所谓自动初始化机制。大概就是通过各种宏比如INIT_BOARD_EXPORT(fn) 将函数 fn 在编译链接阶段放到自定义的 RTI 符号段中,系统初始化阶段就从 RTI 段中调用 fn,从而实现在 main() 之前自动初始化。

线程管理

        抢占式调度器。

        线程个数不设限,最大支持 256 个线程优先级,优先级数值越大优先级越低(idle 线程最低),0 为最高优先级,这点与 FreeRTOS 相反。

        与 FreeRTOS 另一个不同点是,在创建线程时有一个 tick 参数指定线程时间片大小,有啥用呢?当相同优先级线程调度时,该参数指定线程一次调度能够运行的最大时间长度。比如 tick 为 10,则一次调度可以连续运行 10 个 tick;当 tick 为 1,就等效于 FreeRTOS 每个 tick 调度一次。是不是很 nice。

        删除线程时,会将线程从就绪队列中移除,挂到 rt_thread_defunct 僵尸队列上,最后由 idle 线程回收资源,这点跟 FreeRTOS 类似。

        可以设置 idle 线程钩子函数,但注意函数不能导致 idle 线程被挂起。

        可以设置调度器钩子函数,线程切换时被调用。

时钟管理

        系统时间:从系统启动开始计数的时钟节拍(rt_tick)。

        rt_tick 由 cm 内核 SysTick 来实现,时长是 1 / RT_TICK_PER_SECOND 秒。

        硬件定时器,芯片硬件设计的定时器,一般晶振提供时钟, 超时后触发中断。定时精度比较高。

        软件定时器,操作系统层面提供的定时器,以 rt_tick 为单位,可以是单次(oneshot)或周期(periodical)定时。定时时间是 rt_tick 的整数倍,精度不高。

        RTT 定时器有两种模式:HARD_TIMER、SOFT_TIMER。hard 模式,超时函数会在 SysTick 中断里直接被调用,所以要求尽量短、不能有挂起、延迟操作。soft 模式,系统会创建 timer 线程(优先级较高,参考 FreeRTOS),超时函数在线程中被调用。

线程同步

        信号量、互斥量、事件集(event)。

        先说下,多线程获取同一个同步量时,一般有两种分配方式: RT_IPC_FLAG_FIFO 先排队的线程先获得;RT_IPC_FLAG_PRIO 优先级高的线程先获得。

        信号量sem,RTT 里的信号量是计数信号量(FreeRTOS),没有二值信号量。可以初值 1,通过 take-release 的方式当锁使用。

        互斥量mutex,RTT 里的互斥量是递归的,可以被同个线程多次 take,注意 take-release 个数匹配;另外,互斥量使用优先级继承算法解决了优先级翻转问题:例如优先级 A>B>C,当 C 占用 mutex,当 A 获取 mutex 时临时将 C 优先级提高到跟 A 一样高(所有尝试获取的线程中优先级最高的那个),当 C 释放后,执行 A 再执行 B,C 优先级恢复初始值。防止 A 被 C 卡住,C 被 B 卡住的情况。

        事件集event,RTT 使用 32bit 无符号整数表示事件集,每 bit 代表一个事件。比信号量功能更丰富,线程只等待、接收它关注的事件,可以一个线程等待多个事件的到来(事件间可以使用"与"/"或"逻辑触发线程),也可以多个线程等待一个事件的到来。事件是不累积的,多次 send 某个事件只要该 bit 没有被 clear,就只是1bit 被置位。

线程通信

        邮箱、消息队列、信号。

        邮箱mb,每封邮件固定大小 4 字节。若消息较大,可以将 buffer 指针作为邮件发到邮箱。

        消息队列mq,需设置消息最大长度,内部缓冲区。发送消息时,是将外部 buffer 内容拷贝到内部消息块,然后挂到消息队列的队尾;发送紧急消息时,会将该消息挂到消息队列队首。接收消息时,将内部消息块内容拷贝到外部 buffer。中断服务例程可以发送但不能接收消息。当消息长度是 4 字节时,消息队列等同于邮箱。

        信号signal,本质是软中断,用作线程间的异步通知。假设线程 1 需要对信号进行处理,首先线程 1 安装一个信号并解除阻塞,并在安装的同时设定了对信号的异常处理方式(三种:自定义处理函数;忽略不做处理;系统默认处理函数);然后其他线程可以给线程 1 发送信号,触发线程 1 对该信号的处理。当信号被传递给线程 1 时,如果它正处于挂起状态,那会把状态改为就绪状态去处理对应的信号。如果它正处于运行状态,那么会在它当前的线程栈基础上建立新栈帧空间去处理对应的信号。

        一般可以不显式使用 rt_signal_wait() 来阻塞等待,直接 install -> unmask 就可以等待其他线程调用 kill 发来的中断信号,触发异常处理函数。

内存管理

> 动态内存堆

        以下 3 种算法只能选其一。两个有意思(用)的函数:

        void rt_malloc_sethook(void (*hook)(void *ptr, rt_size_t size)); // 设置的钩子函数会在内存分配完成后进行回调。
        void rt_free_sethook(void (*hook)(void *ptr)); // 设置的钩子函数会在调用内存释放完成前进行回调。

>> 小内存管理算法

        一般用于小于 2MB 内存的系统。

        算法比较简单,一条双向链表连接所有内存块,malloc 时使用首次匹配(额外需要 12 字节数据头),free 时若前后相邻内存块空闲则合并成大的空闲块。

>> slab管理算法

        适用系统资源比较丰富的系统,提供一种近似多内存池管理算法的快速算法。将相同大小的内存块链在一起组成一个内存池,内存堆由多个内存块大小不一样的内存池构成。malloc 时从内存块大小适合的内存池中取出一个。

>> memheap管理算法

        适用于系统存在多个内存堆的情况,将多个内存 “粘贴” 在一起,形成一个大的内存堆。

> 静态内存池

        RTT 的内存池支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的申请线程唤醒。

        看这个函数就知道咋用了吧...

未完待续...

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值