完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
前言
根据官方的资料,我通过自己的理解进行系统内核的分析。下面的内容类似于笔记,我记录了内核相关的关键点。我会根据官方的资料介绍进行分析和解释,并与之前的知识进行连贯学习。 当前与官方资料对应的页为内核基础页。 1、RT-Thread 的架构 下面是官方的架构图: 我们现在学习的重点在,内核层之下的RT-Thread 内核这一部分。内核层的内容定义为: 内核层: RT-Thread 内核,是 RT-Thread 的核心部分,包括了内核系统中对象的实现,例如多线程及其调度、信号量、邮箱、消息队列、内存管理、定时器等; libcpu/BSP(芯片移植相关文件/ 板级支持包)与硬件密切相关,由外设驱动和 CPU 移植构成。 2、RT-Thread 的内核 RT-Thread 内核介绍: 内核处于硬件层之上,内核部分包括 内核库 和 实时内核实现。内核库是为了保证内核能够独立运行的一套小型的类似 C 库的函数实现子集。这部分根据编译器的不同自带 C 库的情况也会有些不同,当使用 GNU GCC 编译器时,会携带更多的标准 C 库实现。 C 库:也叫 C 运行库(C Runtime Library),它提供了类似 “strcpy”、“memcpy” 等函数,有些也会包括 “printf”、“scanf” 函数的实现。RT-Thread Kernel Service Library 仅提供内核用到的一小部分 C 库函数实现,为了避免与标准 C 库重名,在这些函数前都会添加上 rt_前缀。 实时内核的实现包括:对象管理、线程管理及调度器、线程间通信管理、时钟管理及内存管理等等,内核最小的资源占用情况是 3KB ROM,1.2KB RAM。3、RT-Thread 内核功能部分 线程调度 线程是 RT-Thread 操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。支持 256 个线程优先级(也可通过配置文件更改为最大支持 32 个或 8 个线程优先级,针对 STM32 默认配置是 32 个线程优先级),0 优先级代表最高优先级,最低优先级留给空闲线程使用;同时它也支持创建多个具有相同优先级的线程,相同优先级的线程间采用时间片的轮转调度算法进行调度,使每个线程运行相应时间;另外调度器在寻找那些处于就绪状态的具有最高优先级的线程时,所经历的时间是恒定的,系统也不限制线程数量的多少,线程数目只和硬件平台的具体内存相关。 [tr]关键点说明[/tr]
RT-Thread 的时钟管理以时钟节拍为基础,时钟节拍是 RT-Thread 操作系统中最小的时钟单位。RT-Thread 的定时器提供两类定时器机制:第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动停止。第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动的停止定时器否则将永远持续执行下去。 另外,根据超时函数执行时所处的上下文环境,RT-Thread 的定时器可以设置为 HARD_TIMER 模式或者 SOFT_TIMER 模式。 通常使用定时器定时回调函数(即超时函数),完成定时服务。用户根据自己对定时处理的实时性要求选择合适类型的定时器。 说明:无论是单次触发定时器还是周期定时器,定时器的定时均是通过系统的调度进行实现的,定时器准确度在ms级别还是可以预测的,但是在us级别是无法预测的,因此如果对实时性要求很高,还是不要对系统自带的定时器抱太大希望。最好使用单片机外设的定时器中断的方式,这种硬实时的中断定时时间准确度可以精确到us。线程间同步 线程间通信 内存管理 I/O 设备管理 4、RT-Thread 启动流程 执行顺序是:系统先从启动文件开始运行,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口 main(),如下图所示: 启动函数: int rtthread_startup(void) { rt_hw_interrupt_disable(); //关闭全局中断 /* board level initialization 板级初始化 * NOTE: please initialize heap inside board initialization.注意:请初始化板内堆初始化。 */ rt_hw_board_init(); /* show RT-Thread version 打印 RT-Thread 版本信息*/ rt_show_version(); /* timer system initialization 定时器初始化*/ rt_system_timer_init(); /* scheduler system initialization 调度器初始化*/ rt_system_scheduler_init(); #ifdef RT_USING_SIGNALS /* signal system initialization 信号初始化*/ rt_system_signal_init(); #endif /* create init_thread 由此创建一个用户 main 线程*/ rt_application_init(); /* timer thread initialization 定时器线程初始化*/ rt_system_timer_thread_init(); /* idle thread initialization 空闲线程初始化*/ rt_thread_idle_init(); #ifdef RT_USING_SMP rt_hw_spin_lock(&_cpus_lock); #endif /*RT_USING_SMP*/ /* start scheduler 启动调度器*/ rt_system_scheduler_start(); /* never reach here 不会执行至此*/ return 0; } [tr]关键点说明[/tr]
keil工程编译内存分布 Keil 工程在编译完之后,会有相应的程序所占用的空间提示信息,如下所示: linking... Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124 After Build - User command #1: fromelf --bin.\build\rtthread-stm32.axf--output rtthread.bin ".\build\rtthread-stm32.axf" - 0 Error(s), 0 Warning(s). Build Time Elapsed: 00:00:07 上面提到的 Program Size 包含以下几个部分: [tr]名称说明[/tr]
Total RO Size (Code + RO Data) 53668 ( 52.41kB) Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB) Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB) [tr]名称说明[/tr]
下图是RT-thread studio 编译的程序数据信息输出: linking... Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124 After Build - User command #1: fromelf --bin.\build\rtthread-stm32.axf--output rtthread.bin ".\build\rtthread-stm32.axf" - 0 Error(s), 0 Warning(s). Build Time Elapsed: 00:00:07 编译/链接生成的常见段(section)表 [tr]项目Value[/tr]
根据上面keil 或者 RT-thread studio 编译的的输出信息,都可以算出当前工程占用的RAM和Flash的大小。根据上面的信息keil编译的代码名和RT-thread studio编译的数据分布名称可以对应如下图: 程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。如图 3-3 中左图所示,是可执行映像文件烧录到 STM32 后的内存分布,它包含 RO 段和 RW 段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。 STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。 其中动态内存堆为未使用的 RAM 空间,应用程序申请和释放的内存块都来自该空间。 而一些全局变量则是存放于 RW 段和 ZI 段中,RW 段存放的是具有初始值的全局变量(而常量形式的全局变量则放置在 RO 段中,是只读属性的),ZI 段存放的系统未初始化的全局变量,系统启动后会自动初始化成零(由用户程序或编译器提供的一些库函数初始化成零) 6、RT-Thread 自动初始化机制 自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。 具体用法省略,详细看官方资料 RT-Thread 的自动初始化机制使用了自定义 RTI 符号段,将需要在启动时进行初始化的函数指针放到了该段中,形成一张初始化函数表,在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。 在link.lds脚本文件的内容为:这段是自动初始化机制的编译连接起始地址和结束地址。 11:29:18 **** Incremental Build of configuration Debug for project rt-thread-notes-stm32-m3 **** make -j8 all arm-none-eabi-size --format=berkeley "rtthread.elf" text data bss dec hex filename 50172 1432 3320 54924 d68c rtthread.elf Used Size(B) Used Size(KB) Flash: 51604 B 50.39 KB RAM: 4752 B 4.64 KB 8、RT-Thread 内核对象模型 静态对象和动态对象 RT-Thread 内核采用面向对象的设计思想进行设计,系统级的基础设施都是一种内核对象,例如线程,信号量,互斥量,定时器等。 内核对象分为两类:静态内核对象和动态内核对象
thread1 对象的内存空间,包括线程控制块 thread1 与栈空间 thread1_stack 都是 编译时决定的 ,因为代码中都不存在初始值,都统一放在未初始化数据段中。thread2 运行中用到的空间都是动态分配的,包括线程控制块(thread2_ptr 指向的内容)和栈空间。 静态对象会占用 RAM 空间,不依赖于内存堆管理器,内存分配时间确定。动态对象则依赖于内存堆管理器,运行时申请 RAM 空间,当对象被删除后,占用的 RAM 空间被释放。这两种方式各有利弊,可以根据实际环境需求选择具体使用方式。 关键点:
RT-Thread 采用内核对象管理系统来访问 / 管理所有内核对象,内核对象包含了内核中绝大部分设施,这些内核对象可以是静态分配的静态对象,也可以是从系统内存堆中分配的动态对象。 通过这种内核对象的设计方式,RT-Thread 做到了不依赖于具体的内存分配方式,系统的灵活性得到极大的提高。 RT-Thread 内核对象包括:线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等。对象容器中包含了每类内核对象的信息,包括对象类型,大小等。对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上,如图 RT-Thread 的内核对象容器及链表如下图所示: 下图则显示了 RT-Thread 中各类内核对象的派生和继承关系。对于每一种具体内核对象和对象控制块,除了基本结构外,还有自己的扩展属性(私有属性),例如,对于线程控制块,在基类对象基础上进行扩展,增加了线程状态、优先级等属性。这些属性在基类对象的操作中不会用到,只有在与具体线程相关的操作中才会使用。因此从面向对象的观点,可以认为每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上扩展了与自己相关的属性。 初始化对象 在使用一个未初始化的静态对象前必须先对其进行初始化。初始化对象使用以下接口: void rt_object_init(struct rt_object* object , enum rt_object_class_type type , const char* name) 当调用这个函数进行对象初始化时,系统会把这个对象放置到对象容器中进行管理,即初始化对象的一些参数,然后把这个对象节点插入到对象容器的对象链表中,对该函数的输入参数的描述如下表: [tr]参数描述[/tr]
从内核对象管理器中脱离一个对象。脱离对象使用以下接口: void rt_object_detach(rt_object_t object); 调用该接口,可使得一个静态内核对象从内核对象容器中脱离出来,即从内核对象容器链表上删除相应的对象节点。对象脱离后,对象占用的内存并不会被释放。 分配对象 上述描述的都是对象初始化、脱离的接口,都是面向对象内存块已经有的情况下,而动态的对象则可以在需要时申请,不需要时释放出内存空间给其他应用使用。申请分配新的对象可以使用以下接口: rt_object_t rt_object_allocate(enum rt_object_class_type type , const char* name) 在调用以上接口时,系统首先需要根据对象类型来获取对象信息(特别是对象类型的大小信息以用于系统能够分配正确大小的内存数据块),而后从内存堆中分配对象所对应大小的内存空间,然后再对该对象进行必要的初始化,最后将其插入到它所在的对象容器链表中。对该函数的输入参数的描述如下表: [tr]参数描述[/tr]
对于一个动态对象,当不再使用时,可以调用如下接口删除对象,并释放相应的系统资源: void rt_object_delete(rt_object_t object); 当调用以上接口时,首先从对象容器链表中脱离对象,然后释放对象所占用的内存。对该函数的输入参数的描述下表: [tr]参数描述[/tr]
判断指定对象是否是系统对象(静态内核对象)。辨别对象使用以下接口: rt_err_t rt_object_is_systemobject(rt_object_t object); 调用 rt_object_is_systemobject 接口可判断一个对象是否是系统对象,在 RT-Thread 操作系统中,一个系统对象也就是一个静态对象,对象类型标识上 RT_Object_Class_Static 位置位。通常使用 rt_object_init() 方式初始化的对象都是系统对象。对该函数的输入参数的描述如下表: rt_object_is_systemobject() 的输入参数 [tr]参数描述[/tr]
RT-Thread 的一个重要特性是高度可裁剪性,支持对内核进行精细调整,对组件进行灵活拆卸。 配置主要是通过修改工程目录下的 rtconfig.h 文件来进行,用户可以通过打开 / 关闭该文件中的宏定义来对代码进行条件编译,最终达到系统配置和裁剪的目的,如下: #ifndef RT_CONFIG_H__ #define RT_CONFIG_H__ /* Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) */ /* RT-Thread Kernel RT-Thread 内核部分 */ /* 表示内核对象的名称的最大长度,若代码中对象名称的最大长度大于宏定义的长度, * 多余的部分将被截掉。*/ #define RT_NAME_MAX 8 /* 字节对齐时设定对齐的字节个数。常使用 ALIGN(RT_ALIGN_SIZE) 进行字节对齐。*/ #define RT_ALIGN_SIZE 4 /* 定义系统线程优先级数;通常用 RT_THREAD_PRIORITY_MAX-1 定义空闲线程的优先级 */ #define RT_THREAD_PRIORITY_32 /*设置系统最大优先级,可设置范围 8 到 256,可修改*/ #define RT_THREAD_PRIORITY_MAX 32 /* 定义时钟节拍,为 100 时表示 100 个 tick 每秒,一个 tick 为 10ms */ #define RT_TICK_PER_SECOND 1000 /* 检查栈是否溢出,未定义则关闭 */ #define RT_USING_OVERFLOW_CHECK /* 定义该宏表示开启钩子函数的使用,未定义则关闭 */ #define RT_USING_HOOK /*是否 开启空闲线程钩子功能*/ #define RT_USING_IDLE_HOOK #define RT_IDLE_HOOK_LIST_SIZE 4 /* 定义了空闲线程的栈大小 */ #define IDLE_THREAD_STACK_SIZE 256 /*是否 开启 软件定时器功能*/ #define RT_USING_TIMER_SOFT /*设置软件定时器线程的优先级,默认为 4*/ #define RT_TIMER_THREAD_PRIO 4 /*设置软件定时器线程的栈大小,默认为 512 字节*/ #define RT_TIMER_THREAD_STACK_SIZE 512 /* 定义该宏开启 debug 模式,未定义则关闭 */ #define RT_DEBUG #define RT_DEBUG_COLOR /* Inter-Thread communication */ /* 线程间同步与通信部分,该部分会使用到的对象有信号量、互斥量、事件、邮箱、消息队列、信号等*/ /* 定义该宏可开启信号量的使用,未定义则关闭 */ #define RT_USING_SEMAPHORE /* 定义该宏可开启互斥量的使用,未定义则关闭 */ #define RT_USING_MUTEX /* 定义该宏可开启事件集的使用,未定义则关闭 */ #define RT_USING_EVENT /* 定义该宏可开启邮箱的使用,未定义则关闭 */ #define RT_USING_MAILBOX /* 定义该宏可开启消息队列的使用,未定义则关闭 */ #define RT_USING_MESSAGEQUEUE /* end of Inter-Thread communication */ /* Memory Management 内存管理部分 */ /* 开启静态内存池的使用 */ #define RT_USING_MEMPOOL /* 开启小内存管理算法 */ #define RT_USING_SMALL_MEM /* 开启堆的使用 */ #define RT_USING_HEAP /* end of Memory Management */ /* Kernel Device Object 内核设备对象*/ /* 表示开启了系统设备的使用 */ #define RT_USING_DEVICE /* 定义该宏可开启系统控制台设备的使用,未定义则关闭 */ #define RT_USING_CONSOLE /* 定义控制台设备的缓冲区大小 */ #define RT_CONSOLEBUF_SIZE 256 /* 控制台设备的名称 */ #define RT_CONSOLE_DEVICE_NAME "uart1" /* end of Kernel Device Object */ #define RT_VER_NUM 0x40002 /* end of RT-Thread Kernel */ #define ARCH_ARM #define RT_USING_CPU_FFS #define ARCH_ARM_CORTEX_M #define ARCH_ARM_CORTEX_M3 /* RT-Thread Components */ /* 定义该宏开启自动初始化机制,未定义则关闭 */ #define RT_USING_COMPONENTS_INIT /* 定义该宏开启设置应用入口为 main 函数 */ #define RT_USING_USER_MAIN /* 定义 main 线程的栈大小 */ #define RT_MAIN_THREAD_STACK_SIZE 2048 /* 定义 main 线程的优先级 */ #define RT_MAIN_THREAD_PRIORITY 10 /* C++ features */ /* end of C++ features */ /* Command shell */ /* 定义该宏可开启系统 FinSH 调试工具的使用,未定义则关闭 */ #define RT_USING_FINSH /* 开启系统 FinSH 时:将该线程名称定义为 tshell */ #define FINSH_THREAD_NAME "tshell" /* 开启系统 FinSH 时:使用历史命令 */ #define FINSH_USING_HISTORY /* 开启系统 FinSH 时:对历史命令行数的定义 */ #define FINSH_HISTORY_LINES 5 /* 开启系统 FinSH 时:定义该宏开启使用 Tab 键,未定义则关闭 */ #define FINSH_USING_SYMTAB /* 开启描述功能 */ #define FINSH_USING_DESCRIPTION /* 开启系统 FinSH 时:定义该线程的优先级 */ #define FINSH_THREAD_PRIORITY 20 /* 开启系统 FinSH 时:定义该线程的栈大小 */ #define FINSH_THREAD_STACK_SIZE 4096 /* 开启系统 FinSH 时:定义命令字符长度 */ #define FINSH_CMD_SIZE 80 /* 开启系统 FinSH 时:定义该宏开启 MSH 功能 */ #define FINSH_USING_MSH /* 开启系统 FinSH 时:开启 MSH 功能时,定义该宏默认使用 MSH 功能 */ #define FINSH_USING_MSH_DEFAULT /* 开启系统 FinSH 时:定义该宏,仅使用 MSH 功能 */ #define FINSH_USING_MSH_ONLY /*整数型 最大输入参数数量 10*/ #define FINSH_ARG_MAX 10 /* end of Command shell */ /* Device virtual file system */ /* end of Device virtual file system */ /* Device Drivers */ #define RT_USING_DEVICE_IPC #define RT_PIPE_BUFSZ 512 /*串口驱动框架代码对应的宏定义,这个宏控制串口驱动框架的相关代码是否会添加到工程*/ #define RT_USING_SERIAL /* 接收数据缓冲区默认大小 */ #define RT_SERIAL_RB_BUFSZ 64 /*PIN 驱动代码对应的宏定义,这个宏控制决定 PIN 驱动相关代码是否会添加到工程;*/ #define RT_USING_PIN /* Using USB */ /* end of Using USB */ /* end of Device Drivers */ /* POSIX layer and C standard library */ #define RT_LIBC_USING_TIME /* end of POSIX layer and C standard library */ /* Network */ /* Socket abstraction layer */ /* end of Socket abstraction layer */ /* Network interface device */ /* end of Network interface device */ /* light weight TCP/IP stack */ /* end of light weight TCP/IP stack */ /* AT commands */ /* end of AT commands */ /* end of Network */ /* VBUS(Virtual Software BUS) */ /* end of VBUS(Virtual Software BUS) */ /* Utilities */ /* end of Utilities */ /* end of RT-Thread Components */ /* RT-Thread online packages */ /* IoT - internet of things */ /* Wi-Fi */ /* Marvell WiFi */ /* end of Marvell WiFi */ /* Wiced WiFi */ /* end of Wiced WiFi */ /* end of Wi-Fi */ /* IoT Cloud */ /* end of IoT Cloud */ /* end of IoT - internet of things */ /* security packages */ /* end of security packages */ /* language packages */ /* end of language packages */ /* multimedia packages */ /* end of multimedia packages */ /* tools packages */ /* end of tools packages */ /* system packages */ /* end of system packages */ /* peripheral libraries and drivers */ /* end of peripheral libraries and drivers */ /* miscellaneous packages */ /* samples: kernel and components samples */ /* end of samples: kernel and components samples */ /* end of miscellaneous packages */ /* end of RT-Thread online packages */ /* samples: kernel and components samples */ /* end of samples: kernel and components samples */ #define RT_STUDIO_BUILT_IN #endif 常见宏定义说明 RT-Thread 中经常使用一些宏定义,举例 Keil 编译环境下一些常见的宏定义: 1)rt_inline,定义如下,static 关键字的作用是令函数只能在当前的文件中使用;inline 表示内联,用 static 修饰后在调用函数时会建议编译器进行内联展开。 #define rt_inline static __inline 2)RT_USED,定义如下,该宏的作用是向编译器说明这段代码有用,即使函数中没有调用也要保留编译。例如 RT-Thread 自动初始化功能使用了自定义的段,使用 RT_USED 会将自定义的代码段保留。 #define RT_USED __attribute__((used)) 3)RT_UNUSED,定义如下,表示函数或变量可能不使用,这个属性可以避免编译器产生警告信息。 #define RT_UNUSED __attribute__((unused)) 4)RT_WEAK,定义如下,常用于定义函数,编译器在链接函数时会优先链接没有该关键字前缀的函数,如果找不到则再链接由 weak 修饰的函数。 #define RT_WEAK __weak 5)ALIGN(n),定义如下,作用是在给某对象分配地址空间时,将其存放的地址按照 n 字节对齐,这里 n 可取 2 的幂次方。字节对齐的作用不仅是便于 CPU 快速访问,同时合理的利用字节对齐可以有效地节省存储空间。 #define ALIGN(n) __attribute__((aligned(n))) 6)RT_ALIGN(size,align),定义如下,作用是将 size 提升为 align 定义的整数的倍数,例如,RT_ALIGN(13,4) 将返回 16。 #define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1)) 总结 上述的很多内容是从官网找出的内核基础知识的重点内容,目前只是一些琐碎知识点,其中内核结构还需要进一步的分析。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1934 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1710 浏览 1 评论
1187 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
785 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1752 浏览 2 评论
1992浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
827浏览 4评论
stm32f4下spi+dma读取数据不对是什么原因导致的?
281浏览 3评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
638浏览 3评论
645浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2025-2-3 05:16 , Processed in 0.633761 second(s), Total 42, Slave 36 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号