1
完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
基本信息
[tr]名称描述说明[/tr]
前面的讲解都是线程的基础知识,下面以线程调度器为中心进行线程管理的分析。 线程调度器启动前执行的相关流程 系统启动的最后一步就是启动线程调度器。放到最后启动调度器是因为要把前面需要使用的线程初始化之后才可以进行线程调度(切换)和执行。下面是系统的启动流程(rtthread_startup函数),下面会对这个流程中的关键点进行分析: 说明:
在 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%02xn",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); } 返回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))) [tr]参数说明[/tr]
线程调度器启动流程 下图是线程的初始化时***入到就绪链表(rt_thread_priority_table)的过程: 启动线程调度器会启动一个优先级最高的线程,但是普通的线程的启动后(执行rt_thread_startup()函数后)还要到下一次进入SysTick_Handler中断函数中处理才能正式启动。 线程调度过程 根据线程切换的方式分为线程到线程切换和中断到线程切换。将切换的主动和被动进行区分,可以得出以下表格的内容 线程到线程切换: [tr]情况描述主动或被动放弃CPU[/tr]
[tr]情况描述主动或被动放弃CPU[/tr]
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(); } 上面的代码执行的步骤是:
[tr]线程状态定义描述16进制值2进制值[/tr]
执行完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 interruptn")); //将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线程为例子,进行程序执行情况的描述。 test线程优先级为4,tick值为10,main线程优先级为10,tick值20. 执行步骤如下:
上述的内容只是将线程调度器在进行线程调度时候的逻辑进行了分析,与线程调度器相关的还有线程优先级的计算方法,由于篇幅原因,放到后面分析。 知识点 获取结构体首地址的宏 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))) 这个宏的作用是通过结构体成员的地址,返回结构体的地址 [tr]参数说明[/tr]
例如结构体 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. */ [tr]元素说明[/tr]
将前后引用域都指向同一个地址 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 } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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); /*关闭中断 di***ale 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 } [tr]hw_board_init 参数说明[/tr]
/* 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); }
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线程初始化、定时器线程初始化和空闲线程初始化
|
|||
|
|||
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1780 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1621 浏览 1 评论
1081 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
728 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1679 浏览 2 评论
1938浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
731浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
570浏览 3评论
596浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
556浏览 3评论
小黑屋| 手机版| Archiver| 德赢Vwin官网 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 16:32 , Processed in 0.888424 second(s), Total 74, Slave 58 queries .
Powered by 德赢Vwin官网 网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
德赢Vwin官网 观察
版权所有 © 湖南华秋数字科技有限公司
德赢Vwin官网 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号