当前位置: 首页 > ds >正文

RT-Thread源码阅读(2)——任务启动与调度

线程初始化

线程初始化,相关解释见注释

static rt_err_t _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_list_init(&(thread->tlist));// 初始化线程入口和参数thread->entry = (void *)entry;thread->parameter = parameter;// 初始化栈空间大小thread->stack_addr = stack_start;thread->stack_size = stack_size;// 将栈空间全部初始化为'#',后续可以以此来看栈空间最大被使用了多少rt_memset(thread->stack_addr, '#', thread->stack_size);// 栈初始化,后续解释thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,(rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t)), (void *)_thread_exit);// 优先级初始化thread->current_priority = priority;// 分配可运行的时间片thread->init_tick = tick;thread->remaining_tick = tick;/* error and flags */thread->error = RT_EOK;thread->stat = RT_THREAD_INIT;/* initialize cleanup function and user data */thread->cleanup = 0;thread->user_data = 0;// 线程定时器初始化,后续解释rt_timer_init(&(thread->thread_timer), thread->name, _thread_timeout, thread, 0, RT_TIMER_FLAG_ONE_SHOT);// 线程初始化回调函数RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));return RT_EOK;
}
struct exception_stack_frame
{rt_uint32_t r0;rt_uint32_t r1;rt_uint32_t r2;rt_uint32_t r3;rt_uint32_t r12;rt_uint32_t lr;rt_uint32_t pc;rt_uint32_t psr;
};struct stack_frame
{/* r4 ~ r11 register */rt_uint32_t r4;rt_uint32_t r5;rt_uint32_t r6;rt_uint32_t r7;rt_uint32_t r8;rt_uint32_t r9;rt_uint32_t r10;rt_uint32_t r11;struct exception_stack_frame exception_stack_frame;
};rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit)
{struct stack_frame *stack_frame;rt_uint8_t *stk;unsigned long i;// 计算出SPstk = stack_addr + sizeof(rt_uint32_t);stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);stk -= sizeof(struct stack_frame);stack_frame = (struct stack_frame *)stk;/* init all register */for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i++){((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;}stack_frame->exception_stack_frame.r0 = (unsigned long)parameter; /* r0 : argument */stack_frame->exception_stack_frame.r1 = 0;                        /* r1 */stack_frame->exception_stack_frame.r2 = 0;                        /* r2 */stack_frame->exception_stack_frame.r3 = 0;                        /* r3 */stack_frame->exception_stack_frame.r12 = 0;                       /* r12 */stack_frame->exception_stack_frame.lr = (unsigned long)texit;     /* lr */stack_frame->exception_stack_frame.pc = (unsigned long)tentry;    /* entry point, pc */stack_frame->exception_stack_frame.psr = 0x01000000L;             /* PSR *//* return task's current stack address */return stk;
}

第一眼看这个代码,可能会有一个疑问,调用rt_hw_stack_init函数时stack_addr入参为stack_addr + stack_size - sizeof(rt_ubase_t)

函数开始又 stack_addr + sizeof(rt_uint32_t),为何不直接入参stack_addr + stack_size,加4减4既不是多此一举?

向下增长的栈 - sizeof(rt_ubase_t)是对应着当前栈顶,例如栈空间buff[100],不减的话栈指向buff[100],访问就会溢出

thread.c作为内核文件,为了保持参数含义统一性,向上/下增长的栈入参都为栈顶/底位置,没毛病

对于cpuport.c不同的单片机会有不同的内容,架构可能不一样,主要区别如下:

  • 当SP指针指向的地址空间没有存放有效数据,则称之为空堆栈

  • 当SP指针指向的地址空间存放有有效数据,则称之为满堆栈

因此针对满堆栈,写入数据的流程为先移动SP指针再填写有效数据;而对于空堆栈则是先填写有效数据再移动堆栈指针

由满堆栈、空堆栈与向上增长堆栈、向下增长堆栈,共可组成四种组合:

  • 向上递增满堆栈(满增)
  • 向下递增满堆栈(满减)
  • 向上递增空堆栈(空增)
  • 向下递增空堆栈(空减)
The stack must also conform to the following constraint at a public interface:
• SP mod 8 = 0. The stack must be double-word aligned.

Cortex-M是满减堆栈,RV32也是满减堆栈,AAPCS中还要求栈作为调用入口时保持8字节对齐

所以不难理解如下内容,当然8字节对齐可能造成4字节空间浪费

stk  = stack_addr + sizeof(rt_uint32_t);
stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
stk -= sizeof(struct stack_frame);

观察后续代码可知,R4-R11在地址小的空间,也会被先出栈,此时返回的栈指针stk指向的就是R4,返回给thread->sp

这里有几个寄存器非常重要

  • r0:第一个参数,即thread->parameter
  • pc:函数入口,即thread->entry
  • lr:线程return后的入口,即_thread_exit,用来回收资源



启动线程——加入调度

rt_err_t rt_thread_startup(rt_thread_t thread)
{thread->number_mask = 1L << thread->current_priority;/* change thread stat */thread->stat = RT_THREAD_SUSPEND;/* then resume it */rt_thread_resume(thread);if (rt_thread_self() != RT_NULL){/* do a scheduling */rt_schedule();}return RT_EOK;
}rt_err_t rt_thread_resume(rt_thread_t thread)
{rt_base_t level;if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_SUSPEND){return -RT_ERROR;}// 如果在suspend列表中则移除rt_list_remove(&(thread->tlist));// 定时器相关,后续介绍rt_timer_stop(&thread->thread_timer);// 加入调度列表rt_schedule_insert_thread(thread);RT_OBJECT_HOOK_CALL(rt_thread_resume_hook, (thread));return RT_EOK;
}void rt_schedule_insert_thread(struct rt_thread *thread)
{rt_base_t level;/* it's current thread, it should be RUNNING thread */if (thread == rt_current_thread){thread->stat = RT_THREAD_RUNNING | (thread->stat & ~RT_THREAD_STAT_MASK);goto __exit;}/* READY thread, insert to ready queue */thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK);// 加入到调度链表/* there is no time slices left(YIELD), inserting thread before ready list*/if((thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0){rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));}/* there are some time slices left, inserting thread after ready list to schedule it firstly at next time*/else{rt_list_insert_after(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));}// 说明对应的优先级有线程rt_thread_ready_priority_group |= thread->number_mask;
}



启动第一个线程

rt_system_scheduler_start

调用rt_hw_context_switch_to启用第一个线程

void rt_system_scheduler_start(void)
{// ......// 此时只注册了main和IDLE线程,获取到的高优先级线程必然是main线程to_thread = _scheduler_get_highest_priority_thread(&highest_ready_priority);rt_current_thread = to_thread;rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);
}
  • MRS 指令: 对状态寄存器CPSR和SPSR进行读操作
  • MSR指令: 对状态寄存器CPSR和SPSR进行写操作

rt_hw_context_switch_to

/** void rt_hw_context_switch_to(rt_uint32 to);* R0 --> to*/.global rt_hw_context_switch_to.type rt_hw_context_switch_to, %function
rt_hw_context_switch_to:/* rt_interrupt_to_thread = (rt_ubase_t)&to_thread->sp */LDR     R1, =rt_interrupt_to_thread			;  R1 = &rt_interrupt_to_threadSTR     R0, [R1]						   ;  *R1 = R0 /* rt_interrupt_from_thread = 0 */LDR     R1, =rt_interrupt_from_thread	    ;  R1 = &rt_interrupt_to_thread MOV     R0, #0							  ;  R0 = 0 STR     R0, [R1]						  ;  *R1 = R0 /* rt_thread_switch_interrupt_flag = 1 */LDR     R1, =rt_thread_switch_interrupt_flagMOV     R0, #1STR     R0, [R1]/* SHPR3[23:16]是控制PendSV的优先级, SHPR3[31:24]是控制SysTick的优先级 *//* 这里将其全置为1 */LDR     R0, =SHPR3			   ; R0 = 0xE000ED20 LDR     R1, =PENDSV_PRI_LOWEST	; R1 = 0xFFFF0000LDR.W   R2, [R0,#0]             ; R2 = *R0 ORR     R1, R1, R2              ; R1 = R1 | R2 STR     R1, [R0]                ; *R0 = R1 /* ICSR的bit28 写 1 以悬起 PendSV */LDR     R0, =ICSR               ; R0 = 0xE000ED04 LDR     R1, =PENDSVSET_BIT	    ; R1 = 0x10000000 STR     R1, [R0]			   ; *R0 = R1 /* restore MSP */LDR     r0, =SCB_VTOR		  ; SCB_VTOR存储向量表起始地址 LDR     r0, [r0]               ; r0 = *SCB_VTOR  获取向量表的位置LDR     r0, [r0]			  ; r0 = MSP  向量表第一个字为初始MSP值NOPMSR     msp, r0/* 开中断后应该跳转到PendSV_Handler */CPSIE   F					; Enable 异常 CPSIE   I					; Enable 中断 /* 内存屏障 保证寄存器写入生效 */DSBISB

PendSV_Handler

线程的切换在这里进行

    .global PendSV_Handler.type PendSV_Handler, %function
PendSV_Handler:/* 保存中断状态到R2,然后关闭中断 */MRS     R2, PRIMASKCPSID   I/* 如果 rt_thread_switch_interrupt_flag == 0 则跳转到pendsv_exit */LDR     R0, =rt_thread_switch_interrupt_flagLDR     R1, [R0]CBZ     R1, pendsv_exit /* clear rt_thread_switch_interrupt_flag to 0 */MOV     R1, #0STR     R1, [R0]/* rt_interrupt_from_thread 可能为0 直接转到switch_to_thread */LDR     R0, =rt_interrupt_from_threadLDR     R1, [R0]CBZ     R1, switch_to_thread MRS     R1, PSP                 ; R1 = PSPSTMFD   R1!, {R4 - R11}         ; 将 R4-R11 压入堆栈(保存寄存器)LDR     R0, [R0]			   ; R0 = rt_interrupt_from_threadSTR     R1, [R0]                ; *rt_interrupt_from_thread = R1,即入栈后的SPswitch_to_thread:LDR     R1, =rt_interrupt_to_thread	; R1 = &rt_interrupt_to_threadLDR     R1, [R1]				   ; R1 = rt_interrupt_to_thread = (rt_ubase_t)&to_thread->spLDR     R1, [R1]                    ;  R1 = to_thread->spLDMFD   R1!, {R4 - R11}         	; 从堆栈弹出 R4-R11(恢复寄存器)MSR     PSP, R1                 	; PSP = R1pendsv_exit:/* 恢复中断状态 */MSR     PRIMASK, R2; 在进入异常服务程序后,LR的值被自动更新为特殊的EXC_RETURN,所以只需要在异常中将LR的bit2置1就可以切换PSP了ORR     LR, LR, #0x04BX      LR; 返回会系统会从PSP自动弹出PC等寄存器,这些寄存器在创建线程时赋值 或者 进入中断之前自动保存了



线程调度

线程调度的核心就是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();// rt_enter_critical和rt_exit_critical会增减rt_scheduler_lock_nestif (rt_scheduler_lock_nest == 0){rt_ubase_t highest_ready_priority;// rt_thread_ready_priority_group为0表示没有就绪线程if (rt_thread_ready_priority_group != 0){/* need_insert_from_thread: need to insert from_thread to ready queue */int need_insert_from_thread = 0;// 获取当前就绪线程中优先级最高的线程to_thread = _scheduler_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;}// 优先级相同需要判断YIELD标志,在线程tick减到0或者主动YIELD时才会置位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{need_insert_from_thread = 1;}rt_current_thread->stat &= ~RT_THREAD_STAT_YIELD_MASK;}if (to_thread != rt_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);}// 将目标线程从就绪列表中删除rt_schedule_remove_thread(to_thread);to_thread->stat = RT_THREAD_RUNNING | (to_thread->stat & ~RT_THREAD_STAT_MASK);// rt_interrupt_nest为0表示当前线程不在中断上下文中if (rt_interrupt_nest == 0){// 在Cortex-M3中rt_hw_context_switch和rt_hw_context_switch_interrupt一样rt_hw_context_switch((rt_ubase_t)&from_thread->sp, (rt_ubase_t)&to_thread->sp);/* enable interrupt */rt_hw_interrupt_enable(level);goto __exit;}else{RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));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->stat = RT_THREAD_RUNNING | (rt_current_thread->stat & ~RT_THREAD_STAT_MASK);}}}/* enable interrupt */rt_hw_interrupt_enable(level);__exit:return;
}
/** void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);* R0 --> from* R1 --> to*/.global rt_hw_context_switch_interrupt.type rt_hw_context_switch_interrupt, %function.global rt_hw_context_switch.type rt_hw_context_switch, %function
rt_hw_context_switch_interrupt:
rt_hw_context_switch:/* set rt_thread_switch_interrupt_flag to 1 */LDR     R2, =rt_thread_switch_interrupt_flagLDR     R3, [R2]CMP     R3, #1		; 若R3==1则Z=0,Z 标志位通常由 CMP、SUBS、ADDS 等指令设置BEQ     _reswitch	; 若 Z == 1跳转MOV     R3, #1STR     R3, [R2]    ; rt_thread_switch_interrupt_flag = 1LDR     R2, =rt_interrupt_from_thread   /* set rt_interrupt_from_thread */STR     R0, [R2]_reswitch:LDR     R2, =rt_interrupt_to_thread     /* set rt_interrupt_to_thread */STR     R1, [R2]LDR     R0, =ICSR           		; 使能PendSV中断LDR     R1, =PENDSVSET_BITSTR     R1, [R0]BX      LR
http://www.xdnf.cn/news/8299.html

相关文章:

  • ArkTs中的尾随闭包
  • 如何重新设置网络ip地址?全面解析多种方法
  • 第八天 搭建车辆状态监控平台(Docker+Kubernetes) OTA升级服务开发(差分升级、回滚机制)
  • eNSP防火墙实现GRE over IPSec
  • 文件操作和IO-3 文件内容的读写
  • 【Java高阶面经:数据库篇】16、分库分表主键:如何设计一个高性能唯一ID
  • transformer网络
  • 云曦25年春季期中考核复现
  • 【会议推荐|权威出版】2025年电力工程与电气技术国际会议(PEET 2025)
  • Python 训练 day31
  • ssh登录设备总提示密码错误解决方法
  • 使用 Navicat 17 for PostgreSQL 时,请问哪个版本支持 PostgreSQL 的 20150623 版本?还是每个版本都支持?
  • Skia如何在窗口上绘图
  • 突破免疫研究瓶颈!Elabscience IL - 4 抗体 [11B11](APC 偶联)靶向识别小鼠细胞因子
  • 纯JS前端转图片成tiff格式
  • 选择第三方软件检测机构做软件测试的三大原因
  • 从零开始学习QT——第二步
  • Rabbit MQ
  • CSS:vertical-align用法以及布局小案例(较难)
  • Spring AI Alibaba 调用文生语音模型(CosyVoice)
  • 基于labview的声音采集与存储分析系统
  • 深入浅出DDD:从理论到落地的关键
  • 海南藏族自治州政府门户网站集约化建设实践与动易解决方案应用
  • Java集合框架入门指南:从小白到基础掌握
  • 聚水潭ERP(奇门)集成用友ERP(用友U8、U9、NC、BIP、畅捷通T+、好业财)
  • 位图算法——判断唯一字符
  • 百度智能云千帆AppBuilder RAG流程技术文档
  • 佰力博科技与您探讨半导体电阻测试常用的一些方法
  • Qt 布局管理器的层级关系
  • 【I2C】高效实现I2C寄存器读取函数