写在前面的话:
这个文章很长,主要是对RT-Threadd的教学视频 (视频链接,)的总结和回顾。是一个比较个人化的东西,其实比较专业的内核指导在RTT官网上也有总结(内核链接)。只是视频中有一些比较基础的官网上没提到的地方做下总结。
文章目录
第一章 初识RT-Thread
1.1嵌入式系统
- 嵌入式系统是一种完全嵌入在装置或设备内部、为满足特定需求而设计的计算机系统
- 嵌入式操作系统是应用于嵌入式系统的软件,用来对接嵌入式底层硬件和上层应用。
- 多任务管理
- 任务间通信
- 内存管理
- 定时器管理
- 设备管理
1.2RT-Thread
- 实时操作系统
- 开源、免费;许可证类似FreeRTOS
- 国产嵌入式操作系统,不仅是一个RTOS,还包含网络、文件系统、GUI界面等组件的中间件平台,具有极强的扩展性
- 支持市面上所有的主流编译工具,如IAR、GCC、Keil
- 完成了超过50款MCU上和所有主流CPU架构上的移植工作
1.3目录结构
- RT-Thread内核源码目录
|----+ RT-Thread:
|------src:RT-Thread内核代码文件
|------libcpu:各类芯片/内核移植代码
|------include:RT-Thread内核头文件
|------components:RT-Thread外部组件代码
- 工程源码目录
|-------+ project
|----------application:用户应用代码
|----------drivers:RT-Thread的驱动,不同平台底层驱动的具体实现
|----------kernel-sample:内核例程
|----------Libraries:STM32芯片固件库
|----------rt-thread:RT-Thread源代码
- 工程目录
|-------+ project
|----------Application:用户应用代码
|----------Drivers:RT-Thread的底层驱动代码
|----------STM32_HAL:存放STM32固件库文件【Libraries】
|----------Kernel:存放RT-Thread内核核心代码【src】
|----------CORTEX-M3:存放CORTEX-M3移植代码【libcpu】
|----------DeviceDrives:驱动框架源码【components->d】
|----------finsh:RT-Thread命令行和finsh组件【components->f】
|----------kernel-sample:RT-Thread内核例程
1.4启动分析
int main(void)
{
return 0;
}
无法从main函数中看到RT-Thread的启动过程
- 启动流程
- kernel -->components.c -->
$sub$$main
int $Sub$$main(void)
{
rt_hw_interrupt_disable();//关中断
rtthread_startup();//RTT启动函数
return 0;
}
关于
$Sub$$main
和main之间的说法,我之前有解释过,可以看链接。
- RTT启动入口
int rtthread_startup(void)
{
rt_hw_interrupt_disable();//关中断
/* board level initalization
* NOTE: please initialize heap inside board initialization.
*/
rt_hw_board_init();//硬件平台初始化
/* show RT-Thread version */
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 */
rt_application_init();//很重要,因为用户的任务在这里面创建的
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
/* start scheduler */
rt_system_scheduler_start();//使rtt跑起来
/* never reach here */
return 0;
}
- main就是在app中创建的
void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
tid = rt_thread_create("main", main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(tid != RT_NULL);
#else
rt_err_t result;
tid = &main_thread;
result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(result == RT_EOK);
/* if not define RT_USING_HEAP, using to eliminate the warning */
(void)result;
#endif
rt_thread_startup(tid);
}
第二章 动态内存堆的使用
2.1 回顾堆栈的概念
- 栈(stack):由编译器自动分配释放
- 堆(heap) :一般由程序员分配和释放
int a = 0 ;
char *p1
int main()
{
int b;
char s[] = "abc";
char *p2;
char *p3 = "123456";
static int c = 0;
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
}
- 定义的局部变量都来源于栈空间,栈是由编译器自动分配和释放的,当函数结束,栈空间就释放掉;
- malloc函数申请的都来源于堆空间。
2.2 裸机系统动态内存分配
2.3 RTT中动态内存
在board.c中,需要一个函数事先帮我们配置好动态内存的api。
在board.c中,也就是进行相关内存初始化的时候,会去配置动态内存。会根据实际芯片,或者是板卡上的内存去配置。
rt_system_heap_init((void *)HEAP_BEGIN,(void *)HEAP_END);
//从系统中取到一块内存设置为动态内存区,然后可以通过RTT特定的rt_malloc和rt_free,申请和释放内存
//一般在board.c中调用,在进行板级初始化的时候,配置动态内存,根据芯片或板卡的实际情况配置
函数需要提供一个起始地址HEAP_BEGIN
和结束地址HEAP_END
,中间的这段空间会被系统当作动态内存空间使用。
本节示例使用的芯片,具有64kram空间。
- 从系统中取到一块内存设置为动态内存区,然后可以通过RTT特定的
rt_malloc
和rt_free
,申请和释放内存。 - 一般在board.c中调用,在进行板级初始化的时候,配置动态内存,根据芯片或板卡的实际情况配置
函数原型在men.c
起始地址
#define HEAP_BEGIN ((void *)&Image$$RW_IRAM$$Limit)
HEAP_BEGIN = (void *)&Image$$RW_IRAM$$Limit
-
Image$$RW_IRAM$$Limit
连接器导出的符号,代表ZI段的结束,也就是程序执行去的RAM结束后的地址,反过来就是我们执行去的RAM未使用的区域的起始地址
- 也就是,定义全局变量,或者静态全局变量,所剩下的空间分配给动态内存堆中
通过查看工程中的map文件,可以看到整个工程所用到的静态ram空间用到了22.05k,也就是说,除了RW和ZI外,整个加起来用到了22.05k。那么整个芯片64k的空间,除去22.05k,剩下的这段空间,肯定是有一个起始地址的。这个地址,就是通过宏定义Image$$RW_IRAM$$Limit
表示的。
结束地址
我们一般以片内ram的结束地址,作为结束地址。
Total RW+heap size = MCU total RAM size
2.4 RTT动态内存的使用
- 首先创建一个任务
int dynmem_sample(void)
{
rt_thread_t tid;
/* 创建线程1 */
tid = rt_thread_create("thread1",
thread1_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY,
THREAD_TIMESLICE);
if (tid != RT_NULL)
rt_thread_startup(tid);
return 0;
}
thread1_entry
:任务的入口函数
/* 线程入口 */
void thread1_entry(void *parameter)
{
int i;
char *ptr = RT_NULL; /* 内存块的指针 */
for (i = 0; ; i++)
{
/* 每次分配 (1 << i) 大小字节数的内存空间 */
ptr = rt_malloc(1 << i);
/* 如果分配成功 */
if (ptr != RT_NULL)
{
rt_kprintf("get memory :%d byte\n", (1 << i));
/* 释放内存块 */
rt_free(ptr);
rt_kprintf("free memory :%d byte\n", (1 << i));
ptr = RT_NULL;
}
else
{
rt_kprintf("try to get %d byte memory failed!\n", (1 << i));
return;
}
}
}
使用rt_malloc申请内存,如果申请内存失败,会返回RT_NULL[常量],申请的空间要少于系统空闲的空间才能成功
通过命令行的方式,导出特定的符号
2.5 动态内存注意事项
- 内存复位
当我们每次申请到新的内存块之后,建议对申请到的内存块进行清零操作
因为整个动态内存空间是公用的,申请到的空间内可能有原来的应用留下来的数据,初始值可能不为零。
p = rt_malloc(10);
if(p != RT_NULL)
{
rt_memset(p,0,10);
}
- 内存泄漏
是指程序中已动态分配的堆内存由于某种原因程序未释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
rt_malloc和rt_free配套使用
2.6 其他相关API
void *rt_realloc(void *tmen,rt_size_t newsize)
- 在已分配内存块的基础上重新分配内存块的大小(增加或者缩小)
- 在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断)
如果前面分配了50个字节的内存空间,后面发现不够,想加到100,或者原本分配了50,太大了,需要20就够了
void *rt_calloc(rt_size_t count, rt_size_t size)
- 从内存堆中分配连续内存地址的多个内存块
- malloc的形参只有size 的大小
calloc的形参有count 和size,分配到的空间为count * size ,而且空间连续
第三章 线程的概念
3.1线程的概念
- 在设计一个较为复杂的应用程序时,也通常把一个大型任务分解成多个小任务,然后通过运行这些小任务,最终达到完成大人物的目的
- 在rtt中,与上述小问题对应的程序实体就叫做“线程”(任务),rtt就是一个能对这些小“线程”进行管理和调度的多“线程”操作系统。
- 线程是实现任务的载体,是rtt中最基本的调度单位,她描述了一个任务执行的运行环境,也描述了这个任务的优先等级。
3.2线程组成
RTT中,线程有三部分组成:线程代码(入口函数)、线程控制块,线程堆栈
- 无限循环结构
void thread_entry(void *parameter)
{
whlie(1)
{
/* 等待事件的发生 */
/* 处理事件 */
}
}
while中往往要加入让出CPU使用权的API函数,如果不加,其他线程得不到执行。
- 顺序执行结构
void thread_entry(void *parameter)
{
/* 事务1处理 */
/* 事务2处理 */
}
3.2.1线程控制块
线程控制块是操作系统用于管理线程的一个数据结构,他会存放线程的一些信息,例如,优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构、线程等待时间集合等。
/**
* Thread structure
*/
struct rt_thread
{
/* rt object */
char name[RT_NAME_MAX]; /**< the name of thread */
rt_uint8_t type; /**< type of object */
rt_uint8_t flags; /**< thread's flags */
#ifdef RT_USING_MODULE
void *module_id; /**< id of application module */
#endif
rt_list_t list; /**< the object list */
rt_list_t tlist; /**< the thread list */
/* stack point and entry */
void *sp; /**< stack point */
void *entry; /**< entry */
void *parameter; /**< parameter */
void *stack_addr; /**< stack address */
rt_uint32_t stack_size; /**< stack size */
/* error code */
rt_err_t error; /**< error code */
rt_uint8_t stat; /**< thread status */
/* priority */
rt_uint8_t current_priority; /**< current priority */
rt_uint8_t init_priority; /**< initialized priority */
#if RT_THREAD_PRIORITY_MAX > 32
rt_uint8_t number;
rt_uint8_t high_mask;
#endif
rt_uint32_t number_mask;
#if defined(RT_USING_EVENT)
/* thread event */
rt_uint32_t event_set;
rt_uint8_t event_info;
#endif
#if defined(RT_USING_SIGNALS)
rt_sigset_t sig_pending; /**< the pending signals */
rt_sigset_t sig_mask; /**< the mask bits of signal */
void *sig_ret; /**< the return stack pointer from signal */
rt_sighandler_t *sig_vectors; /**< vectors of signal handler */
void *si_list; /**< the signal infor list */
#endif
rt_ubase_t init_tick; /**< thread's initialized tick */
rt_ubase_t remaining_tick; /**< remaining tick */
struct rt_timer thread_timer; /**< built-in thread timer */
void (*cleanup)(struct rt_thread *tid); /**< cleanup function when thread exit */
/* light weight process if present */
#ifdef RT_USING_LWP
void *lwp;
#endif
rt_uint32_t user_data; /**< private user data beyond this thread */
};
typedef struct rt_thread *rt_thread_t;
3.2.2线程栈
- RTT每个线程都具有独立的栈空间,当线程切换时,系统会将当前线程的上下文保存在线程栈中,当线程要恢复运行时,再从线程栈中读取上下文信息,恢复线程的运行。
- 线程上下文是指线程执行时的环境,具体来说就是【各个变量和数据】,包括所有寄存器变量、堆栈信息、内存信息等。
- 线程栈在形式上是一段连续的内存空间,我们可以通过定义一个数组或者申请一段动态内存来作为线程的栈。
3.3线程创建
- 创建线程
- 创建静态线程
rt_thread_t rt_thread_create(const char* name,
void (*entry)(void* parameter),
void* parameter,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick);
- 创建动态线程
rt_err_t rt_thread_init(struct rt_thread* thread,
const char* name,
void (*entry)(void* parameter),
void* parameter,
void* stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick);
- 静态线程需要实现定义好线程控制块与栈空间
- 动态线程的堆栈 是动态分配的
- 启动线程
rt_err_t rt_thread_startup(rt_thread_t thread);
调用此函数后创建的线程会被加入到线程的就绪队列,执行调度。
3.4创建线程事例
- 动态线程
#include <rtthread.h>
#define THREAD_PRIORITY 25
#define THREAD_STACK_SIZE 512
#define THREAD_TIMESLICE 5
static rt_thread_t tid1 = RT_NULL;
/* 线程1的入口函数 */
static void thread1_entry(void *parameter)
/* 创建线程1,名称是thread1,入口是thread1_entry*/
tid1 = rt_thread_create("thread1",
thread1_entry, RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY, THREAD_TIMESLICE);
/* 如果获得线程控制块,启动这个线程 */
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
- 创建动态线程
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程2入口 */
static void thread2_entry(void *param)
{
}
/* 初始化线程2,名称是thread2,入口是thread2_entry */
rt_thread_init(&thread2,
"thread2",
thread2_ent