一、RT-Thread内核框架及功能
1.1 内核框架
在上一讲中,我们对RT-Thread进行了介绍以及如何创建一个RTT的标准工程,接下来我们对RT-Thread的内核进行介绍。
内核是操作系统最基础也是最重要的部分。下图为 RT-Thread 内核架构图,内核处于硬件层之上,内核部分包括内核库、实时内核实现。
内核库是为了保证内核能够独立运行的一套小型的类似C库的函数实现子集。这部分根据编译器的不同自带C库的情况也会有些不同,当使用GNUGCC编译器时,会携带更多的标准C库实现。
实时内核的实现包括:
(1)对象管理。
(2)线程管理及调度器。
(3)线程间通信管理。
(4)时钟管理。
(5)内存管理。
(6)设备管理。
内核最小的资源占用情况是3KB ROM,1.2KB RAMe
1.2 内核功能
1.2.1 线程调度
线程是RT-Thread操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。支持256个线程优先级(也可通过配置文件更改为最大支持32个或8个线程优先级,针对STM32默认配置是32个线程优先级),0优先级代表最高优先级,最低优先级留给空闲线程使用;同时它也支持创建多个具有相同优先级的线程,相同优先级的线程间采用时间片的轮转调度算法进行调度,使每个线程运行相应时间;另外调度器在寻找那些处于就绪状态的具有最高优先级的线程时,所经历的时间是恒定的,系统也不限制线程数量的多少,线程数目只和硬件平台的具体内存相关。
1.2.2 时钟管理
RT-Thread的时钟管理以时钟节拍为基础,时钟节拍是 RT-Thread操作系统中最小的时钟单位。RT-Thread的定时器提供两类定时器机制:第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动停止。第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动的停止定时器否则将永远持续执行下去。
另外,根据超时函数执行时所处的上下文环境,RT-Thread的定时器可以设置为HARD_TIMER模式(硬件定时器)或者SOFT_TIMER模式(软件定时器)。
通常使用定时器定时回调函数(即超时函数),完成定时服务。用户根据自己对定时处理的实时性要求选择合适类型的定时器。
1.2.3 线程间同步
RT-Thread采用信号量、互斥量与事件集实现线程间同步。线程通过对信号量、互斥量的获取与释放进行同步;互斥量采用优先级继承的方式解决了实时系统常见的优先级翻转问题。线程同步机制支持线程按优先级等待或按先进先出方式获取信号量或互斥量。线程通过对事件的发送与接收进行同步;事件集支持多事件的“或触发”和“与触发",适合于线程等待多个事件的情况。
1.2.4 线程间通信
RT-Thread支持邮箱和消息队列等通信机制。邮箱中一封邮件的长度固定为4字节大小;消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。邮箱效率较消息队列更为高效。邮箱和消息队列的发送动作可安全用于中断服务例程中。通信机制支持线程按优先级等待或按先进先出方式获取。
1.2.5 内存管理
RT-Thread支持静态内存池管理及动态内存堆管理。当静态内存池具有可用内存时,系统对内存块分配的时间将是恒定的;当静态内存池为空时,系统将申请内存块的线程挂起或阻塞掉(即线程等待一段时间后仍未获得内存块就放弃申请并返回,或者立刻返回。等待的时间取决于申请内存块时设置的等待时间参数),当其他线程释放内存块到内存池时,如果有挂起的待分配内存块的线程存在的话,则系统会将这个线程唤醒。
动态内存堆管理模块在系统资源不同的情况下,分别提供了面向小内存系统的内存管理算法及面向大内存系统的 SLAB内存管理算法。
还有一种动态内存堆管理叫做memheap,适用于系统含有多个地址可不连续的内存堆。使用memheap可以将多个内存堆“粘贴”在一起,让用户操作起来像是在操作一个内存堆。
1.2.6 I/O设备管理
RT-Thread将 GPIO、I2C、SPl、USB、UART等作为外设设备,统一通过设备注册完成。实现了按名称访问的设备管理子系统,可按照统一的API界面访问硬件设备。在设备驱动接口上,根据嵌入式系统的特点,对不同的设备可以挂接相应的事件。当设备事件触发时,由驱动程序通知给上层的应用程序。
二、RTT内核启动流程
RT-Thread支持多种平台和多种编译器,而rtthread_startup()函数是RT-Thread
规定的统一启动入口。一般执行顺序是:系统先从启动文件开始运行,然后进入RT-
Thread 的启动rtthread_startup0,最后进入用户入口main),如下图所示:
上述就是我们RT-Thread的启动过程,最后也还是会进入到我们的main()函数里面。
下面我们从工程代码里面来详细了解一下
之后跳转到rtthread_startup()函数就能看到我们上述所说的函数啦
最后,再跟大家介绍一下我们的开发中用到最多的用户线程初始化函数rt-application_init(),这个函数最终会调用到我们的main()函数里面。
函数中使用RT_USING_HEAP宏去决定我们是使用动态创建main线程还是使用静态创建线程。
rt_thread_create()函数代表的是使用动态创建的方法;
其中第一个参数“main”代表的是线程的名字,第二个参数main_thread_entry表示的是线程回调函数,第三个参数是线程回调函数的传入参数,这里没有传入参数,所以写的是RT_NULL;第四个参数RT_MAIN_THREAD_STACK_SIZE为线程栈的大小,第五个参数RT_MAIN_THREAD_PRIORITY是线程的优先级,第六个参数20为线程的时钟节拍。
从上面的宏我们可以看出“main”线程的栈大小为2048,优先级为10(数字越小,优先级越高)。
创建完线程之后就可以通过rt_thread_startup()函数去启动线程。
最后我们给大家看一下“main”的回调函数 main_thread_entry(),最后还是回到了我们的主函数main()去运行程序。
这一章就先讲到这里,下一章我会详细介绍一下线程控制的一些知识以及如何使用动态创建线程和静态创建线程。
最后感谢一下千锋物联网。
04-RTT内核启动流程之C阶段_哔哩哔哩_bilibili