线程管理之线程调度器
前言
基本信息
名称 | 描述说明 |
---|---|
RT-Thread Studio 软件版本 | 版本: 1.1.3 |
RT-Thread 系统版本 | 4.0.2 |
STM32CubeIDE 软件版本 | 1.4.0 |
STM32芯片型号 | STM32F013VG |
前言说明
前面的讲解都是线程的基础知识,下面以线程调度器为中心进行线程管理的分析。
线程调度器启动前执行的相关流程
系统启动的最后一步就是启动线程调度器。放到最后启动调度器是因为要把前面需要使用的线程初始化之后才可以进行线程调度(切换)和执行。下面是系统的启动流程(rtthread_startup函数),下面会对这个流程中的关键点进行分析:
说明:
- RT-thread系统的启动是从entry函数开始进入rtthread_startup函数。
- 这个启动的流程基本上固定,用户可以将自己线程的初始化代码放到main线程初始化的逻辑中。要在板级初始化之后,线程调度器启动之前完成用户线程初始化。
线程调度器
在 RT-Thread 中,线程是实现任务的载体,它是 RT-Thread 中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级,重要的任务可设置相对较高的优先级,非重要的任务可以设置较低的优先级,不同的任务还可以设置相同的优先级,轮流运行。
当线程运行时,它会认为自己是以独占 CPU 的方式在运行,线程执行时的运行环境称为上下文,具体来说就是各个变量和数据,包括所有的寄存器变量、堆栈、内存信息等。而线程调度器的作用就是实现这些线程之间的逻辑控制,是非常重要的一个模块。
线程调度器初始化和启动
调度器初始化-rt_system_scheduler_init函数
void rt_system_scheduler_init(void)
{
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("start scheduler: max priority 0x%02x\n",RT_THREAD_PRIORITY_MAX));
/*初始化线程优先级链表,默认下一个节点和上一个节点均指向自身地址*/
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&rt_thread_priority_table[offset]);
}
/*初始化就绪优先级组 initialize ready priority group */
rt_thread_ready_priority_group = 0;
/*初始化僵尸队列(资源未回收、处于关闭状态的线程队列) 默认下一个节点和上一个节点均指向自身地址initialize thread defunct */
rt_list_init(&rt_thread_defunct);
}
说明:
在调度器初始化中,对线程就绪链表、就绪优先级组号、关闭线程链表进行了初始化(其中有多核的宏定义部分暂不分析)。
启动线程调度器-rt_system_scheduler_start函数
下面是rt_system_scheduler_start函数的代码:
/**
* @ingroup SystemInit
* This function will startup scheduler. It will select one thread
* 此函数将启动调度程序。它将选择一个线程
* 使用最高优先级,然后切换到它。with the highest priority level, then switch to it.
*/
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
rt_ubase_t highest_ready_priority;
/*获取当前最高优先级的线程*/
to_thread = _get_highest_priority_thread(&highest_ready_priority);
#ifdef RT_USING_SMP
to_thread->oncpu = rt_hw_cpu_id();
#else
rt_current_thread = to_thread; /*将当前运行的线程设置为获取到的最高优先级线程*/
#endif /*RT_USING_SMP*/
rt_schedule_remove_thread(to_thread);/*将当前线程从线程链表删除*/
to_thread->stat = RT_THREAD_RUNNING;/*修改当前线程运行状态,设置为正在运行状态*/
/* switch to new thread */
#ifdef RT_USING_SMP
rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp, to_thread);
#else
rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);//切换到当前设置的线程开始运行
#endif /*RT_USING_SMP*/
/* never come back */
}
根据上面的代码,线程调度器的启动步骤是:
- 获取到当前最高优先级线程
- 将当前线程从线程就绪链表删除
- 修改当前线程运行状态,设置为正在运行状态
- 切换到当前设置的线程开始运行
如果要理解上述步骤的用意,还是要完整的了解线程调度的流程。
_get_highest_priority_thread函数获取当前最高优先级线程的控制块
下面是 _get_highest_priority_thread函数的代码:
static struct rt_thread* _get_highest_priority_thread(rt_ubase_t *highest_prio)
{
register struct rt_thread *highest_priority_thread;
register rt_ubase_t highest_ready_priority;
#if RT_THREAD_PRIORITY_MAX > 32
register rt_ubase_t number;
number = __rt_ffs(rt_thread_ready_priority_group) - 1;
highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
#else//获取当前最高就绪的优先级组编号
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#endif
/*获取最高优先级线程 get highest ready priority thread */
highest_priority_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
*highest_prio = highest_ready_priority;
return highest_priority_thread;
}
代码说明1:
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
下面是宏定义展开:
int __rt_ffs(int value)
{
return __builtin_ffs(value);
}
返回rt_thread_ready_priority_group的最后一位1的是从后向前第几位,比如7368(1110011001000)返回4,这是获取当前最高优先级组编号
代码说明2:
highest_priority_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
这是个宏定义,也可以看做函数,用于根据结构体元素地址返回rt_thread结构体首地址。
宏定义展开:
/**
* rt_container_of - return the member address of ptr, if the type of ptr is the
* struct type.如果ptr的类型是struct类型,则返回ptr的成员地址。
*/
#define rt_container_of(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
参数 | 说明 |
---|---|
rt_thread_priority_table[highest_ready_priority].next | 表示当前最高优先级组表中的就绪链表中的 第一个线程就绪链表节点指针 |
struct rt_thread | 表示要返回的结构体类型 |
tlist | 表示输入的成员类型 |
这样根据最高优先级组编号,查找到最高优先级组,再从最高优先级组中找到第一个线程就绪链表的地址,获取到当前对应的线程控制块首地址,就可以获取到最高优先级线程的控制块。之后就可以进行后续的步骤。
线程调度器启动流程
下图是线程的初始化时被插入到就绪链表(rt_thread_priority_table)的过程:
启动线程调度器会启动一个优先级最高的线程,但是普通的线程的启动后(执行rt_thread_startup()函数后)还要到下一次进入SysTick_Handler中断函数中处理才能正式启动。
线程调度过程
根据线程切换的方式分为线程到线程切换和中断到线程切换。将切换的主动和被动进行区分,可以得出以下表格的内容
线程到线程切换:
情况描述 | 主动或被动放弃CPU |
---|---|
调用sleep,delay函数使线程放弃CPU | 主动放弃 |
调用suspend使线程挂起 | 主动放弃 |
线程的时间片耗尽 | 被动放弃 |
系统产生中断,线程暂时失去CPU,中断例程执行完后没进行线程切换 | 被动暂时放弃 |
中断到线程切换:
情况描述 | 主动或被动放弃CPU |
---|---|
系统产生中断,线程暂时失去CPU,中断例程执行完后进行了线程切换 | 被动放弃 |
当前线程的时间片用完或者该线程主动要求让出处理器资源时,它将不再占有处理器,调度器会选择相同优先级的下一个线程执行。线程调用这个接口后,这个线程仍然在就绪队列中。线程让出处理器使用下面的函数接口:
rt_err_t rt_thread_yield(void);
调用该函数后,当前线程首先把自己从它所在的就绪优先级线程队列中删除,然后把自己挂到这个优先级队列链表的尾部,然后激活调度器进行线程上下文切换(如果当前优先级只有这一个线程,则这个线程继续执行,不进行上下文切换动作)。
rt_thread_yield() 函数 和 rt_schedule() 函数比较相像,但在有相同优先级的其他就绪态线程存在时,系统的行为却完全不一样。执行 rt_thread_yield() 函数后,当前线程被换出,相同优先级的下一个就绪线程将被执行。而执行 rt_schedule() 函数后,当前线程并不一定被换出,即使被换出,也不会被放到就绪线程链表的尾部,而是在系统中选取就绪的优先级最高的线程执行(如果系统中没有比当前线程优先级更高的线程存在,那么执行完 rt_schedule() 函数后,系统将继续执行当前线程)。
与优先级相关的参数:
在启动线程函数rt_thread_startup中有如下代码:
/* 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; /**< 线程优先级对应的组号: current_priority >> 3 */
rt_uint8_t high_mask; /**< 线程位号掩码: (1 << 位号) 位号: (current_priority & 7) */
#endif
rt_uint32_t number_mask; /**< 组号掩码: (1 << 组号) */
在线程rt_thread_startup内,系统会调用rt_thread_resume函数将线程立即运行,而在rt_thread_resume函数中,系统会调用rt_schedule_insert_thread函数将线程加入到调试器中
接着在rt_schedule_insert_thread函数中,系统会操作线程就绪表rt_thread_ready_table将其插入就绪表最前面和修改线程就绪优先级组rt_thread_ready_priority_group的值,如下代码所示:
/* set priority mask */
#if RT_THREAD_PRIORITY_MAX > 32
rt_thread_ready_table[thread->number] |= thread->high_mask;
#endif
rt_thread_ready_priority_group |= thread->number_mask;
在优先级组小于等于32时,rt_thread_ready_priority_group (就绪优先级组号)的值等于 自身与thread->number_mask(组号掩码)相或。
因此与一个线程优先级相关的参数: rt_thread_priority_table(线程就绪表)和rt_thread_ready_priority_group (线程就绪优先级组号)
这两个参数在初始化和线程启动时被设置好。线程调度时会用到。
接下来看一个时间片耗尽时线程调度的例子。
举例:时间片耗尽时线程切换的调度
线程调度器的切换和管理是由rt_schedule函数来管理的。而系统的心跳是由SysTick定时器来控制的,SysTick_Handler函数正是系统心跳的处理函数,这是最简单系统线程切换场景。
下面是SysTick_Handler函数代码:
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
HAL_IncTick();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
下面是rt_tick_increase函数代码:
void rt_tick_increase(void)
{
struct rt_thread *thread;
/* increase the global tick */
#ifdef RT_USING_SMP
rt_cpu_self()->tick ++;
#else
++ rt_tick;
#endif
/*检查时间片 check time slice */
thread = rt_thread_self(); //获取当前正在执行的线程的控制块
-- thread->remaining_tick; //减去一次线程运行剩余的tick值
if (thread->remaining_tick == 0)
{
/*更改为原来初始化的tick值 change to initialized tick */
thread->remaining_tick = thread->init_tick;
thread->stat |= RT_THREAD_STAT_YIELD;
/*放弃CPU使用权 yield */
rt_thread_yield();
}
/* 检查定时器 check timer */
rt_timer_check();
}
上面的代码执行的步骤是:
- 获取当前正在执行的线程的控制块
- 减去一次线程运行剩余的tick值
- 判断是否剩余tick值为0 如果为0,则将当前线程剩余tick值改为初始化时的tick值,将当前线程状态增加一个调度状态,进入调度器处理函数-rt_schedule函数
- 检查定时器
下面是线程状态值的定义,下面会用到与之相关的判断
线程状态定义 | 描述 | 16进制值 | 2进制值 |
---|---|---|---|
RT_THREAD_INIT | 初始状态 | 0x00 | 0000 |
RT_THREAD_READY | 就绪状态 | 0x01 | 0001 |
RT_THREAD_SUSPEND | 挂起状态 | 0x02 | 0010 |
RT_THREAD_RUNNING | 运行状态 | 0x03 | 0011 |
RT_THREAD_BLOCK | 阻塞状态(与挂起相同) | RT_THREAD_SUSPEND | |
RT_THREAD_CLOSE | 关闭状态 | 0x04 | 0100 |
RT_THREAD_STAT_MASK | 线程状态掩码值 | 0x07 | 0111 |
RT_THREAD_STAT_YIELD | 调度状态 | 0x08 | 1000 |
RT_THREAD_STAT_YIELD_MASK | 调度状态掩码值(与调度状态相同) |
rt_schedule函数
执行完rt_thread_startup函数后,当前线程状态被设置为 RT_THREAD_SUSPEND(挂起状态),此时线程还没开始被CPU执行。
假设这是系统启动调度器后第一次进入tick中断中执行rt_schedule函数。
下面是rt_schedule函数代码:
void rt_schedule(void)
{
rt_base_t level;
struct rt_thread *to_thread;
struct rt_thread *from_thread;
/* 关闭中断 disable interrupt */
level = rt_hw_interrupt_disable();
/*检查调度程序是否已启用,调度器是否加锁 check the scheduler is enabled or not */
if (rt_scheduler_lock_nest == 0)
{
rt_ubase_t highest_ready_priority;//最高优先级组编号
if (rt_thread_ready_priority_group != 0) //如果线程优先级就绪组编号不为0,表示存在线程在就绪列表
{
/*需要 将from_thread 插入就绪队列 need_insert_from_thread: need to insert from_thread to ready queue */
int need_insert_from_thread = 0;
//获取当前最高优先级组下的最靠前就绪的线程控制块
to_thread = _get_highest_priority_thread(&highest_ready_priority);
//判断当前 正在运行的线程 是否正在运行状态
if ((rt_current_thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING)
{ //如果当前 正在运行的线程 优先级 高于 当前就绪优先级
if (rt_current_thread->current_priority < highest_ready_priority)
{
to_thread = rt_current_thread;
}//如果当前 正在运行的线程 优先级 等于 当前就绪优先级 且
else if (rt_current_thread->current_priority == highest_ready_priority && (rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) == 0)
{
to_thread = rt_current_thread;
}
else //其它情况为需要 将 from_thread 插入就绪队列
{
rt_current_thread->stat &= ~RT_THREAD_STAT_YIELD_MASK;//去掉当前线程调度状态
need_insert_from_thread = 1;
}
}
if (to_thread != rt_current_thread)//如果目标线程与当前线程不同 则需要切换线程
{
/*如果目标线程与当前线程不同 if the destination thread is not the same as current thread */
rt_current_priority = (rt_uint8_t)highest_ready_priority;
from_thread = rt_current_thread;
rt_current_thread = to_thread;
RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));
if (need_insert_from_thread)
{
rt_schedule_insert_thread(from_thread);
}
//将to_thread从就绪队列删除
rt_schedule_remove_thread(to_thread);
to_thread->stat = RT_THREAD_RUNNING | (to_thread->stat & ~RT_THREAD_STAT_MASK);
/* switch to new thread */
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
("[%d]switch to priority#%d "
"thread:%.*s(sp:0x%08x), "
"from thread:%.*s(sp: 0x%08x)\n",
rt_interrupt_nest, highest_ready_priority,
RT_NAME_MAX, to_thread->name, to_thread->sp,
RT_NAME_MAX, from_thread->name, from_thread->sp));
#ifdef RT_USING_OVERFLOW_CHECK
_rt_scheduler_stack_check(to_thread);
#endif
if (rt_interrupt_nest == 0)
{
extern void rt_thread_handle_sig(rt_bool_t clean_state);
//将CPU控制权从from_thread切换到to_thread
rt_hw_context_switch((rt_ubase_t)&from_thread->sp,
(rt_ubase_t)&to_thread->sp);
/*开启中断响应 enable interrupt */
rt_hw_interrupt_enable(level);
#ifdef RT_USING_SIGNALS
/* check stat of thread for signal */
level = rt_hw_interrupt_disable();
if (rt_current_thread->stat & RT_THREAD_STAT_SIGNAL_PENDING)
{
extern void rt_thread_handle_sig(rt_bool_t clean_state);
rt_current_thread->stat &= ~RT_THREAD_STAT_SIGNAL_PENDING;
rt_hw_interrupt_enable(level);
/* check signal status */
rt_thread_handle_sig(RT_TRUE);
}
else
{
rt_hw_interrupt_enable(level);
}
#endif
goto __exit;
}
else
{
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));
//将CPU控制权从from_thread切换到to_thread
rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp,
(rt_ubase_t)&to_thread->sp);
}
}
else
{
rt_schedule_remove_thread(rt_current_thread);//将rt_current_thread从就绪队列删除
rt_current_thread->stat = RT_THREAD_RUNNING | (rt_current_thread->stat & ~RT_THREAD_STAT_MASK);//rt_current_thread添加运行状态
}
}
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
__exit:
return;
}
rt_schedule函数的流程图
例子1:test线程切换到空闲线程
以空闲线程切换到test线程为例子,进行程序执行情况的描述。
test线程优先级为4,tick值为10,空闲线程优先级为31,tick值32.
执行步骤如下:
- 当test线程tick值减至0时,将tick值重新设置为初始化的值,将test线程状态添加一个调度状态,进入rt_thread_yield函数后,再进入rt_schedule函数。
- 关闭CPU中断响应,判断是否将调度器加锁,加锁了则直接开启中断响应并退出,未加锁继续执行。
- 判断当前就绪优先级组是否有就绪的线程,如果不存在则直接开启中断响应并退出,存在则继续执行。
- 获取目前就绪优先级组中优先级最高的线程的线程控制块
- 判断当前test线程是否有运行状态,当前test线程的状态值为(0B1011)是有运行态的。继续执行判断。
- 判断当前线程的优先级(4)是否高于目前就绪状态的线程优先级(空闲线程优先级为31),因此将to_thread赋值为 rt_current_thread。
- 由于to_thread的值为 rt_current_thread,将执行将当前线程从就绪列表删除,并将当前状态添加运行状态。
- 调度结束。
例子2:test线程切换到main线程
以空闲线程切换到test线程为例子,进行程序执行情况的描述。
test线程优先级为4,tick值为10,main线程优先级为10,tick值20.
执行步骤如下:
- 当test线程tick值减至0时,将tick值重新设置为初始化的值,将test线程状态添加一个调度状态,进入rt_thread_yield函数后,再进入rt_schedule函数。
- 关闭CPU中断响应,判断是否将调度器加锁,加锁了则直接开启中断响应并退出,未加锁继续执行。
- 判断当前就绪优先级组是否有就绪的线程,如果不存在则直接开启中断响应并退出,存在则继续执行。
- to_thread赋值为目前就绪优先级组中优先级最高的线程的线程控制块(为main线程)
- 判断当前执行的test线程是否有运行状态,当前test线程的状态值为(0B1010)不在运行状态。继续执行判断。
- 判断to_thread和rt_current_thread是否一致,目前不一致,则需要进行切换。
- 执行切换前的变量赋值,将rt_current_priority(当前线程优先级)设置为to_thread的线程优先级,将from_thread设置为rt_current_thread的值(为当前执行的test线程控制块),将rt_current_thread的值设置为to_thread(为main线程)。
- 调用钩子函数(目前不存在线程切换时的钩子函数,因此没有什么执行)
- 判断是否开启了插入from_thread线程的标志位,前面并没有进入这段代码,因此没有开启。跳过
- 将 to_thread(main线程)从就绪列表删除,给to_thread的状态添加运行状态标志。
- 检查如果当前线程切换过去后,此时堆栈是否溢出,如果溢出则打印信息。
- 判断是否给线程调度器加锁,没有加锁则执行线程切换。
- 执行CPU栈指针切换,将栈指针从from_thread线程切换到to_thread线程,切换过程要保存from_thread线程的完整栈信息到栈数组。
- 切换完毕,开启中断响应,结束退出。
总结
上述的内容只是将线程调度器在进行线程调度时候的逻辑进行了分析,与线程调度器相关的还有线程优先级的计算方法,由于篇幅原因,放到后面分析。
知识点
获取结构体首地址的宏 rt_container_of/rt_list_entry
#define rt_list_entry(node, type, member) \
rt_container_of(node, type, member)
#define rt_container_of(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
说明:
rt_list_entry 这个宏的作用是通过结构体成员的地址,返回结构体的地址
参数 | 说明 |
---|---|
参数 node | 结构体成员的地址 |
参数 type | 结构体的类型 |
参数 member | 结构体的成员 |
&((type *)0->member) 的作用是求成员 member 在 type 结构体中的相对偏移量
例如结构体
struct test_struct
{
int num1;
int num2;
float fl;
};
成员 num1 的相对偏移量是 0,成员 num2 的相对偏移量是 4,成员 fl 的相对偏移量是 8。所以使用 ptr 的地址,减去结构体成员的相对地址,得到的就是结构体的地址。
双向链表
RT-thread线程链表管理采用的是双向链表,下面是双向链表代码定义:
/**
* Double List structure
*/
struct rt_list_node
{
struct rt_list_node *next; /**< point to next node. */
struct rt_list_node *prev; /**< point to prev node. */
};
typedef struct rt_list_node rt_list_t; /**< Type for lists. */
元素 | 说明 |
---|---|
prev | 引用域 ,保存直接前驱结点的地址 |
data | 数据域 ,把存储据元素本身信息的域叫结点的数据域 |
next | 引用域,保存直接后继结点的地址 |
链表初始化
将前后引用域都指向同一个地址
rt_inline void rt_list_init(rt_list_t *l)
{
l->next = l->prev = l;
}
双向链表后插入
/**
* @brief insert a node after a list
*
* @param l list to insert it
* @param n new node to be inserted
*/
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
l->next->prev = n;
n->next = l->next;
l->next = n;
n->prev = l;
}
下图是节点N插入到L的后面:
双向链表前插入
/**
* @brief insert a node before a list
*
* @param n new node to be inserted
* @param l list to insert it
*/
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
l->prev->next = n;
n->prev = l->prev;
l->prev = n;
n->next = l;
}
下图是节点N插入到L的前面:
移除链表n结点
/**
* @brief remove node from list.
* @param n the node to remove from the list.
*/
rt_inline void rt_list_remove(rt_list_t *n)
{
n->next->prev = n->prev;
n->prev->next = n->next;
n->next = n->prev = n;
}
检查是否为空链表
/**
* @brief tests whether a list is empty
* @param l the list to test.
*/
rt_inline int rt_list_isempty(const rt_list_t *l)
{
return l->next == l;
}
高效位运算 __builtin_系列函数
•int __builtin_ffs (unsigned int x)
返回x的最后一位1的是从后向前第几位,比如7368(1110011001000)返回4。
•int __builtin_clz (unsigned int x)
返回前导的0的个数。
•int __builtin_ctz (unsigned int x)
返回后面的0个个数,和__builtin_clz相对。
•int __builtin_popcount (unsigned int x)
返回二进制表示中1的个数。
•int __builtin_parity (unsigned int x)
返回x的奇偶校验位,也就是x的1的个数模2的结果。
此外,这些函数都有相应的usigned long和usigned long long版本,只需要在函数名后面加上l或ll就可以了,比如int __builtin_clzll。
线程调度器启动的步骤说明(补充)
下面的内容是为了完整说明线程调度器启动过程中其它与线程调度关系不大的流程的简单说明。
板级初始化
下面是板级初始化代码:
RT_WEAK void rt_hw_board_init()
{
extern void hw_board_init(char *clock_src, int32_t clock_src_freq, int32_t clock_target_freq);
/* Heap initialization */
#if defined(RT_USING_HEAP) //初始化系统堆
rt_system_heap_init((void *) HEAP_BEGIN, (void *) HEAP_END);
#endif
//初始化系统时钟
hw_board_init(BSP_CLOCK_SOURCE, BSP_CLOCK_SOURCE_FREQ_MHZ, BSP_CLOCK_SYSTEM_FREQ_MHZ);
/*设置shell控制台输出设备 Set the shell console output device */
#if defined(RT_USING_DEVICE) && defined(RT_USING_CONSOLE)
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
/*板底层硬件初始化 Board underlying hardware initialization */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
}
rt_system_heap_init初始化系统堆
这是系统堆管理的初始化,当前采用的是小内存管理算法,由于这段知识点篇幅较长,后面将用专门的一篇文章来讲解。
hw_board_init 初始化系统时钟
下面是hw_board_init 代码:
void hw_board_init(char *clock_src, int32_t clock_src_freq, int32_t clock_target_freq)
{
extern void rt_hw_systick_init(void);
extern void clk_init(char *clk_source, int source_freq, int target_freq);
#ifdef SCB_EnableICache
/* Enable I-Cache---------------------------------------------------------*/
SCB_EnableICache();
#endif
#ifdef SCB_EnableDCache
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
#endif
/*函数在程序开始时被调用 HAL_Init() function is called at the beginning of the program */
HAL_Init();
/* 启用中断enable interrupt */
__set_PRIMASK(0);
/* System clock initialization */
clk_init(clock_src, clock_src_freq, clock_target_freq);
/*关闭中断 disbale interrupt */
__set_PRIMASK(1);
rt_hw_systick_init();/*SysTick配置*/
/*默认情况下,Pin驱动程序初始化处于打开状态 Pin driver initialization is open by default */
#ifdef RT_USING_PIN
extern int rt_hw_pin_init(void);
rt_hw_pin_init();
#endif
/*默认情况下,USART驱动程序初始化处于打开状态 USART driver initialization is open by default */
#ifdef RT_USING_SERIAL
extern int rt_hw_usart_init(void);
rt_hw_usart_init();
#endif
}
hw_board_init 初始化系统时钟函数说明:
hw_board_init 参数 | 说明 |
---|---|
clock_src | 时钟来源(目前并未使用到这个参数) |
clock_src_freq | 系统时钟初始频率,即外部晶振频率或者让内部晶振起始频率 |
clock_target_freq | 目标时钟频率(当前系统时钟频,率默认72MHz) |
- clock_src、clock_src_freq、clock_target_freq这三个参数最终并没有调用到,因此如果需要修改系统时钟频率需要自己手动配置,建议使用STM32CubeMX软件来配置比较方便和直观。
- HSI 时钟信号由内部16 MHz RC 振荡器生成,可直接用作系统时钟,或者用作PLL 输入。HSI RC 振荡器的优点是成本较低(无需使用外部组件)。此外,其启动速度也要比HSE 晶振块,但即使校准后,其精度也不及外部晶振或陶瓷谐振器
- HSE时钟信号有两个时钟源: HSE 外部晶振/陶瓷谐振器、HSE 外部用户时钟
rt_hw_systick_init函数SysTick配置说明:
/* SysTick configuration */
void rt_hw_systick_init(void)
{
#if defined (SOC_SERIES_STM32H7)
HAL_SYSTICK_Config((HAL_RCCEx_GetD1SysClockFreq()) / RT_TICK_PER_SECOND);
#else
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / RT_TICK_PER_SECOND);
#endif
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
- HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / RT_TICK_PER_SECOND);代码:用于设置SysTick时钟的频率,RT_TICK_PER_SECOND宏定义是:定义时钟节拍,为 100 时表示 100 个 tick 每秒,一个 tick 为 10ms ,
- HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);代码:设置时钟源分频值
- HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);代码:设置SysTick中断的抢占优先级和响应优先级。
rt_hw_pin_init函数和rt_hw_usart_init函数说明:
rt_hw_pin_init函数根据当前芯片型号选择打开对应的端口时钟。
rt_hw_usart_init函数会根据当前启动的串口列表分别初始化对应的串口端口。
定时器初始化
系统启动时需要初始化定时器管理系统。可以通过下面的函数完成:
void rt_system_timer_init(void)
{
int i;
for (i = 0; i < sizeof(rt_timer_list) / sizeof(rt_timer_list[0]); i++)
{
rt_list_init(rt_timer_list + i);
}
}
定时器链表 rt_timer_list。系统新创建并激活的定时器都会按照以超时时间排序的方式插入到 rt_timer_list 链表中。
在 RT-Thread 中通过宏定义 RT_TIMER_SKIP_LIST_LEVEL 来配置跳表的层数,默认为 1,表示采用一级有序链表图的有序链表算法,每增加一,表示在原链表基础上增加一级索引。
用户main线程初始化、定时器线程初始化和空闲线程初始化
- main线程是默认创建的一个动态线程,方便用户使用。
- 定时器线程是软件定时器使用的线程,如果开启了软件定时器则需要对其初始化。初始化包括定时器的链表和线程信息初始化(线程控制块,线程栈)。
- 空闲线程是静态线程,一直处于就绪状态。