上一篇博客简单介绍了一下实时内核库包含的一些内容:线程调度、时钟管理、内存管理、I/O设备管理、线程间同步、线程间通信,在这篇博客中,继续内核基础的相关学习。
rtthread_startup()函数是RT-Thread规定的统一启动入口。一般执行顺序是:系统先从启动文件开始运行,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口main(),如下图所示:
在上图中,先从汇编代码startup_xxx.s开始执行,然后进入rtthread_startup(),开始系统的启动,在启动程序中,主要包括四个部分:
创建main线程的函数:rt_application_init()
调度器启动函数 :rt_system_scheduler_start()
rt_hw_board_init()函数实现系统时钟的设置,为系统提供心跳、串口初始化,将系统输入输出终端绑定在这个串口,后续的系统运行信息就可以通过该串口打印出来了。
在rtthread_startup()中会创建很多线程,但是在调度器启动(启动函数被rtthread_startup调用)之前,这些线程都不会在创建后立马就运行,而是将状态变为就绪态等待系统的调度;待调度启动之后,系统才开始第一个线程的运行,然后根据调度规则(基于优先级的全抢占式多线程调度),选择优先级最高的线程进行执行。
最后进入用户的main函数,开始执行用户代码。
一般MCU包含的存储空间:片内Flash(相当于硬盘)和片内RAM(相当于内存)。编译器会将一个程序分类为好几个部分,分别存储到MCU不同的存储区。在学习资料中,以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包含了一下几个部分:
1、Code: 代码段,存放程序的代码部分;
2、RO-data:只读数据段,存放程序中定义的(常量)
3、RW-data:读写数据段,存放初始化为非0值的全局(变量)
4、ZI-data :0数据段,存放未初始化的全局变量及初始化为0的变量。
另外,在编译生成的.map文件中,有各个函数占用的尺寸和地址,如下:
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)
其中,
在程序运行之前,需要有文件实体被烧录到Flash中,一般是bin文件或者是hex文件,该文件被称作是可执行映像文件。
自动初始化机制:
int rt_hw_usart_init(void) /* 串 口 初 始 化 函 数 */
{
... ...
/* 注 册 串 口 1 设 备 */
rt_hw_serial_register(&serial1, "uart1",
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
uart);
return 0;
}
INIT_BOARD_EXPORT(rt_hw_usart_init); /* 使 用 组 件 自 动 初 始 化 机 制 */
在上述代码中,INIT_BOARD_EXPORT(rt_hw_usart_init)就是自动初始化功能的使用,系统就会自动调用该函数。调用的位置为启动流程图中浅蓝色底的相应位置。同样,流程图中所有浅蓝色为底的函数都表示被自动初始化。说明如下:
RT-Thraed自动初始化机制使用了自定义的RTI符号段(位于内存分布的 RO 段中),将需要在启动时进行初始化的函数指针放到该段中,形成一张初始化函数表,在系统启动过程中遍历该表,并调用表中的函数,达到自动初始化的目的。
用来实现自动初始化功能的宏接口定义详细描述:
初始化顺序 宏接口 描述
1 INIT_BOARD_EXPORT(fn) 非常早期的初始化,此时调度器还未启动
2 INIT_PREV_EXPORT(fn) 主要是用于纯软件的初始化、没有太多依赖的函数
3 INIT_DEVICE_EXPORT(fn) 外设驱动初始化相关,比如网卡设备
4 INIT_COMPONENT_EXPORT(fn) 组件初始化,比如文件系统或者 LWIP
5 INIT_ENV_EXPORT(fn) 系统环境初始化,比如挂载文件系统
6 INIT_APP_EXPORT(fn) 应用初始化,比如 GUI 应用
系统级的基础设施都是一种内核对象,如:线程、邮箱、信号量、互斥量、定时器、消息队列等。
静态对象:通常放在RW和ZI段,系统启动后在程序中初始化;占用RAM空间,不依赖于内存堆管理器,内存分配时间确定。
动态对象:从内存堆中创建,依赖内存堆管理器,手动初始化;运行时申请RAM空间,当对象被删除后,占用的RAM空间被释放。
静态线程对象thread1,动态线程对象thread2,示例代码如下:
/* 线 程 1 的 对 象 和 运 行 时 用 到 的 栈 */
static struct rt_thread thread1;
static rt_uint8_t thread1_stack[512];
/* 线 程 1 入 口 */
void thread1_entry(void* parameter)
{
int i;
while (1)
{
for (i = 0; i < 10; i ++)
{
rt_kprintf("%d\n", i);
/* 延 时 100ms */
rt_thread_mdelay(100);
}
}
}
/* 线 程 2 入 口 */
void thread2_entry(void* parameter)
{
int count = 0;
while (1)
{
rt_kprintf("Thread2 count:%d\n", ++count);
/* 延 时 50ms */
rt_thread_mdelay(50);
}
}
/* 线 程 例 程 初 始 化 */
int thread_sample_init()
{
rt_thread_t thread2_ptr;
rt_err_t result;
/* 初 始 化 线 程 1 */
/* 线 程 的 入 口 是 thread1_entry, 参 数 是 RT_NULL
* 线 程 栈 是 thread1_stack
* 优 先 级 是 200, 时 间 片 是 10 个 OS Tick
*/
result = rt_thread_init(&thread1, "thread1",
thread1_entry, RT_NULL,
&thread1_stack[0],
sizeof(thread1_stack),
200, 10);
/* 启 动 线 程 */
if (result == RT_EOK)
rt_thread_startup(&thread1);
/* 创 建 线 程 2 */
/* 线 程 的 入 口 是 thread2_entry, 参 数 是 RT_NULL
* 栈 空 间 是 512, 优 先 级 是 250, 时 间 片 是 25 个 OS Tick
*/
thread2_ptr = rt_thread_create("thread2",thread2_entry,
RT_NULL, 512, 250, 25);
/* 启 动 线 程 */
if (thread2_ptr != RT_NULL)
rt_thread_startup(thread2_ptr);
return 0;
}
由于采用了面向对象的设计方法,所以RT-Thread的内核对象是可以继承的。与C++中类的继承类似,在对象管理模块中,定义了通用的数据结构,用来保存各种对象的共同属性,各种具体对象加上自己私有的一些属性就可以清楚的表达自己的特征了。这种方式有以下优点:
如上对象控制块rt_object提供了name、type、flag、list属性,由rt_object派生出来了rt_thread、rt_ipc_object、rt_device等对象,由rt_ipc_object派生出信号量、互斥量、邮箱、事件消息队列等对象。
内核对象控制块的数据结构(结构体rt_object):
struct rt_object
{
/* 内 核 对 象 名 称 */
char name[RT_NAME_MAX];
/* 内 核 对 象 类 型 */
rt_uint8_t type;
/* 内 核 对 象 的 参 数 */
rt_uint8_t flag;
/* 内 核 对 象 管 理 链 表 */
rt_list_t list;
};
目前内核对象支持的类型有:
enum rt_object_class_type
{
RT_Object_Class_Thread = 0, /* 对 象 为 线 程 类 型 */
#ifdef RT_USING_SEMAPHORE
RT_Object_Class_Semaphore, /* 对 象 为 信 号 量 类 型 */
#endif
#ifdef RT_USING_MUTEX
RT_Object_Class_Mutex, /* 对 象 为 互 斥 量 类 型 */
#endif
#ifdef RT_USING_EVENT
RT_Object_Class_Event, /* 对 象 为 事 件 类 型 */
#endif
#ifdef RT_USING_MAILBOX
RT_Object_Class_MailBox, /* 对 象 为 邮 箱 类 型 */
#endif
#ifdef RT_USING_MESSAGEQUEUE
RT_Object_Class_MessageQueue, /* 对 象 为 消 息 队 列 类 型 */
#endif
#ifdef RT_USING_MEMPOOL
RT_Object_Class_MemPool, /* 对 象 为 内 存 池 类 型 */
#endif
#ifdef RT_USING_DEVICE
RT_Object_Class_Device, /* 对 象 为 设 备 类 型 */
#endif
RT_Object_Class_Timer, /* 对 象 为 定 时 器 类 型 */
#ifdef RT_USING_MODULE
RT_Object_Class_Module, /* 对 象 为 模 块 */
#endif
RT_Object_Class_Unknown, /* 对 象 类 型 未 知 */
RT_Object_Class_Static = 0x80 /* 对 象 为 静 态 对 象 */换成二进制为1000,0000,十进制为128
};
目前系统最多能够容纳的对象类别数为127个。
内核对象容器数据结构struct rt_object_information。包括:
一个对象由一个rt_object_information结构体管理,每一个这类对象的具体实例都通过链表的形式挂接在object_list上。该类的内存块尺寸由object_size表示出来,所以每一个类所对应的具体实例占有的内存块大小是一样的。
对象初始化
使用的函数为:
void rt_object_init(struct rt_object* object ,
enum rt_object_class_type type ,
const char* name)
object:需要初始化的对象指针,必须指向具体的对象内存块,而不能是空指针或者野指针。
type :rt_object_class_type枚举类型中除RT_Object_Class_Static以外的类型,得到静态对象。
name :对象的名字,名字的最大长度由RT_NAME_MAX指定。
脱离对象
从内核对象管理器中脱离一个对象。注意不是删除对象。
void rt_object_detach(rt_object_t object);
可以使得一个静态内核对象从内核对象容器中脱离出来,即从对象容器链表上删除相应的对象节点。并且对象脱离之后,其占用的内存并没有被释放。
分配对象
对象的初始化以及脱离都是在面向对象内存块已经有的情况下进行的。在使用动态对象的时候,内存空间可以根据需要动态申请或者释放。申请内存接口为:
rt_object_t rt_object_allocate(enum rt_object_class_type type ,
const char* name)
返回值:
删除对象
对于动态对象,可以使用rt_object_delete从对象容器列表中脱离对象,并释放相应对象所占用的内存资源。接口为:
void rt_object_delete(rt_object_t object);
辨别对象
判断指定对象是否是系统对象(静态内核对象,在RT-Thread中,一个系统对象就是一个静态对象)。并且通常使用rt_object_init()方式初始化的对象都是系统对象。对象辨别的接口为:
rt_err_t rt_object_is_systemobject(rt_object_t object);
对于RT-Thread的内核配置是通过修改rtconfig.h文件来进行的。通过在文件中打开或者关闭相应的宏实现对代码的条件编译,最终达到系统配置和剪裁的目的。这种方式也保证了RT-Thread高度的可剪裁性。在实际应用中,系统配置文件 rtconfig.h 是由配置工具自动生成的,无需手动更改。
对于内核的配置,宏打开表示开启,否则表示关闭。配置内容主要包括以下几部分:
#define rt_inline static __inline
#define RT_USED __attribute__((used))
#define RT_UNUSED __attribute__((unused))
#define RT_WEAK __weak
#define ALIGN(n) __attribute__((aligned(n)))
内核基础部分到这就结束了,通过堆内核基础地学习,我们了解到了内核的组成部分(内核库和实时内核),在实时内核中,分别简单介绍了
在后续中,介绍了
系统启动流程:
自动初始化
硬件相关->纯软件->外设->相关组件->系统环境->应用
静态/动态对象
初始化、脱离、分配、删除、辨别。静态动态的区别,对象的继承和派生等
内存分布
RO Size、RW Size、ROM Size等
内核配置
各种宏定义
在接下来的博客中,将详细介绍线程管理。