RT-Thread 流水笔记一 startup ,schedule,thread
这里的startup是在完成__main()后,已经建立完整的C语音运行环境后进行的startup动作
主要方式是在用户的main()函数之前完成系统的初始化,这种做法一般在比较大型的SDK中采用。
把main()作为一个主线程,在有些SDK中可以称为Host主线程。
启动文件完成__main()后 调转到rtthread_startup()
rtthread_startup 开始就关闭中断
1.对硬件初始化
这里最少要对 systickOS 硬件定时器进行初始化,操作系统节拍定时器,并且明确中断服务函数
中断也就是要配置优先级。systick的优先级是比较重要的地方。
用于调试打印的输出端口一般为UART
2.
/* timer system initialization */ rt_system_timer_init(); /* hard timer list */ static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL];
这里说的是对硬件计时器的初始化,所以这个指的是 systick
定时器list指向其本身
3.
/* scheduler system initialization */
rt_system_scheduler_init();
调度器初始化,对thread ready list做了初始化,并且任务对应字设置为0.表示没有任务就绪。
/* create init_thread */
rt_application_init();
初始化一个默认thread main_thread_entry
main_thread_entry就是 main()函数
这里看到RT Thead 的任务创建下个章节写任务创建这里先看完startup
/* timer thread initialization */
rt_system_timer_thread_init();
软件定时器初始化
/* idle thread initialization */
rt_thread_idle_init();空闲任务初始化
/* start scheduler */
rt_system_scheduler_start();
启动调度器
启动调度器的意义是进行一次任务调度,在就绪列表中选取优先级最高的任务。
当前任务为 空的情况下,初始化任务不会进行任务运行。
这里可以看到RTT 使用了Bitmap的技术 快速的找到ready list中优先级最高的任务。
这部分启动代码,大致可以分为四个部分:
(1)初始化与系统相关的硬件;
(2)初始化系统内核对象,例如定时器、调度器、信号;
(3)创建 main 线程,在 main 线程中对各类模块依次进行初始化;
(4)初始化定时器线程、空闲线程,并启动调度器。
任务初始化
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)
{/* parameter check */RT_ASSERT(thread != RT_NULL);RT_ASSERT(stack_start != RT_NULL);/* initialize thread object */rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name);return _thread_init(thread,name,entry,parameter,stack_start,stack_size,priority,tick);
}
使用指针的强制转换把,thread 控制块类型转换为 rt_object_t
/* initialize thread object */rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name);
rt_object 的目的是用同意的数据结构来定义操作系统内部对象
struct rt_object
{char name[RT_NAME_MAX]; /**< name of kernel object */rt_uint8_t type; /**< type of kernel object */rt_uint8_t flag; /**< flag of kernel object */#ifdef RT_USING_MODULEvoid *module_id; /**< id of application module */
#endifrt_list_t list; /**< list node of kernel object */
};
rt object是主资源链表里面的主要对象,通过lis节点加入到容器中。
list 对应的是thread 控制块中的 obj list 代表thread 该对象在容器中的位置。
通过list 本身的地址反推 object的地址,从而获取实体对象。(使用 Linux container of()函数)
rt_object_init() 的目的是把创建出的thread 对象插入到 _object_container 结构中。
_thread_init()
作用是把thread 控制块中的变量初始化。
list thread 用于线程在状态链表中的节点。
初始化堆栈 所有空间填充 “#”
输出化堆栈部分:
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;stk = 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 */#if USE_FPUstack_frame->flag = 0;
#endif /* USE_FPU *//* return task's current stack address */return stk;
}
在移植层cpuport.c中 (ARM Cortex M4 为例)
RT thread 定义了数据结构 栈帧,其意义是一次上下文转换需要操作的数据,按照顺序定义在stack_frame中
相对于Freertos而言,RT thread 这里的可读性会跟强。
在没有FPU的情况下如下所示
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;
};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;
}
并且模拟一次入栈。这部分和Freertos一致。
thread->stat = RT_THREAD_INIT;
线程状态 为初始化状态
初始化部分这边完成了。thread并没有加入到就绪列表。
需要额外的手动激活
rt_thread_startup(tid);
/*** @brief This function will start a thread and put it to system ready queue.** @param thread is the thread to be started.** @return Return the operation status. If the return value is RT_EOK, the function is successfully executed.* If the return value is any other values, it means this operation failed.*/
rt_err_t rt_thread_startup(rt_thread_t thread)
{/* parameter check */RT_ASSERT(thread != RT_NULL);RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);/* calculate priority attribute */
#if RT_THREAD_PRIORITY_MAX > 32thread->number = thread->current_priority >> 3; /* 5bit */thread->number_mask = 1L << thread->number;thread->high_mask = 1L << (thread->current_priority & 0x07); /* 3bit */
#elsethread->number_mask = 1L << thread->current_priority;
#endif /* RT_THREAD_PRIORITY_MAX > 32 */RT_DEBUG_LOG(RT_DEBUG_THREAD, ("startup a thread:%s with priority:%d\n",thread->name, 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;
}
其主要作用是把thread加入到就绪链表
/* change thread stat */
thread->stat = RT_THREAD_SUSPEND;
thread 状态设定为SUSPEND
插入到就绪链表后为RT_THREAD_READY
第一次任务切换是如果没有发生,则不会主动发起任务调度。
if (rt_thread_self() != RT_NULL){/* do a scheduling */rt_schedule();}
到这里任务已经就绪,栈中已经包含入口地址
rt_schedule() 主动唤起一次调度请求
/*** This function will perform one schedule. It will select one thread* with the highest priority level, then switch to it.*/
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){register rt_ubase_t highest_ready_priority;#if RT_THREAD_PRIORITY_MAX <= 32highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#elseregister 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;
#endif/* get switch to thread */to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,struct rt_thread,tlist);/* if the destination thread is not the same as current thread */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));/* switch to new thread */RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,("[%d]switch to priority#%d ""thread:%.*s(sp:0x%p), ""from thread:%.*s(sp: 0x%p)\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);
#endifif (rt_interrupt_nest == 0){rt_hw_context_switch((rt_ubase_t)&from_thread->sp,(rt_ubase_t)&to_thread->sp);/* enable interrupt */rt_hw_interrupt_enable(level);return ;}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);}}}/* enable interrupt */rt_hw_interrupt_enable(level);
}
主动唤起任务切换的目的是readlist 已经更新,需要用rt_schedule 刷新看看有没有更高优先级的任务需要执行。
这里有一个有趣的问题
rt_schedule 开头的部分关闭中断,然后去计算最高优先级的任务,然后进行一次切换,再去恢复中断。
禁止中断的时间是操作系统非常重要的性能参数。
Freertos的做法是一直打开中断,在进入PendSV关中断 中去判断最高的优先级,理论上这里的 关闭中断时间会稍微短一些。但是会略微增加上下文切换的耗时。
https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/porting/porting
官方文件中有很详细的上下文切换解释。
回顾一下freertos
任务初始化的模拟入栈
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{/* Simulate the stack frame as it would be created by a context switchinterrupt. */pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */*pxTopOfStack = portINITIAL_XPSR; /* xPSR */pxTopOfStack--;*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */pxTopOfStack--;*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */pxTopOfStack -= 5; /* R12, R3, R2 and R1. */*pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */return pxTopOfStack;
}
__asm void vPortSVCHandler( void )
{PRESERVE8ldr r3, =pxCurrentTCB /* Restore the context. */ldr r1, [r3] /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */ldmia r0!, {r4-r11} /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */msr psp, r0 /* Restore the task stack pointer. */isbmov r0, #0msr basepri, r0orr r14, #0xdbx r14
}
/*-----------------------------------------------------------*/__asm void prvStartFirstTask( void )
{PRESERVE8/* Use the NVIC offset register to locate the stack. */ldr r0, =0xE000ED08ldr r0, [r0]ldr r0, [r0]/* Set the msp back to the start of the stack. */msr msp, r0/* Globally enable interrupts. */cpsie icpsie fdsbisb/* Call SVC to start the first task. */svc 0nopnop
}
/*-----------------------------------------------------------*/
static void prvPortStartFirstTask( void )
{
__asm volatile(
" ldr r0, =0xE000ED08 \n" /向量表偏移寄存器地址 CotexM3/
" ldr r0, [r0] \n" /取向量表地址/
" ldr r0, [r0] \n" /取 MSP 初始值/
/重置msp指针 宣示 系统接管/
" msr msp, r0 \n"
" cpsie i \n" /开中断/
" cpsie f \n" /开异常/
/流水线相关/
" dsb \n" /数据同步隔离/
" isb \n" /指令同步隔离/
/触发异常 启动第一个任务/
" svc 0 \n"
" nop \n"
);
}
第一个程序运行的时候主栈复位。然后跳转到SVC处理第一次上下文切换
void vPortSVCHandler( void )
{
__asm volatile (
/取 pxCurrentTCB 的地址/
“ldr r3, pxCurrentTCBConst2 \n”
/取出 pxCurrentTCB 的值 : TCB 地址/
“ldr r1, [r3] \n”
/*取出 TCB 第一项 : 任务的栈顶 */
“ldr r0, [r1] \n”
/恢复寄存器数据/
“ldmia r0!, {r4-r11} \n”
/设置线程指针: 任务的栈指针/
“msr psp, r0 \n”
/流水线清洗/
“isb \n”
“mov r0, #0 \n”
“msr basepri, r0 \n”
/设置返回后进入线程模式/
“orr r14, #0xd \n”
“bx r14 \n”
" \n"
“.align 4 \n”
“pxCurrentTCBConst2: .word pxCurrentTCB \n”
);
首先发生SVC后,ARM处理器会转为 主栈,自动保存R0 ~ PSR
进入SVC后恢复R4~R11
然后清洗流水线返回线程模式。这个时候线程栈中的R0~PSR(任务初始化是填入的值)就会恢复到CPU,从而完成切换。
正常情况下的上下文切换
PUSH 指令和 POP 指令默认使用 SP。
stmdb用于将寄存器压栈,ldmia用于将寄存器弹出栈
1.进入PSV后,CPU自动切换为主栈 也就是当前sp为异常向量表开头设置的主栈
2.mrs r0, psp 获取当前 任务栈的位置
3.保存当前任务控制块
4.把R4~R11 保存到当前 任务栈中,应为r4-r11不会自动入栈。
5.然后保存当前r0也就是当前任务的栈位置
6.执行一次 C函数,保存当前 r3 r14
7.执行选取目标任务
8.恢复r4~r11 这部分是需要手动保存
9.这个时候需要更新到新任务堆栈应为返回的时候,会把psp恢复到CPU
10.线程模式返回,PSP恢复到CPU
void xPortPendSVHandler( void )
{/* This is a naked function. */__asm volatile(/*取出当前任务的栈顶指针 也就是 psp -> R0*/" mrs r0, psp \n"" isb \n"" \n"/*取出当前任务控制块指针 -> R2*/" ldr r3, pxCurrentTCBConst \n"" ldr r2, [r3] \n"" \n"/*R4-R11 这些系统不会自动入栈,需要手动推到当前任务的堆栈*/" stmdb r0!, {r4-r11} \n"/*最后,保存当前的栈顶指针 R0 保存当前任务栈顶地址[R2] 是 TCB 首地址,也就是 pxTopOfStack下次,任务激活可以重新取出恢复栈顶,并取出其他数据*/" str r0, [r2] \n"" \n"/*保护现场,调用函数更新下一个准备运行的新任务*/" stmdb sp!, {r3, r14} \n"/*设置优先级 第一个参数,即:configMAX_SYSCALL_INTERRUPT_PRIORITY进入临界区*/" mov r0, %0 \n"" msr basepri, r0 \n"" bl vTaskSwitchContext \n"" mov r0, #0 \n"" msr basepri, r0 \n"" ldmia sp!, {r3, r14} \n"" \n"/*函数返回 退出临界区pxCurrentTCB 指向新任务取出新的 pxCurrentTCB 保存到 R1*/" ldr r1, [r3] \n"/*取出新任务的栈顶*/" ldr r0, [r1] \n"/*恢复手动保存的寄存器*/" ldmia r0!, {r4-r11} \n"/*设置线程指针 psp 指向新任务栈顶*/" msr psp, r0 \n"" isb \n"/*返回, 硬件执行现场恢复开始执行任务*/" bx r14 \n"" \n"" .align 4 \n""pxCurrentTCBConst: .word pxCurrentTCB \n");
}
这部分基本上和RTT一样
void rt_tick_increase(void)
{struct rt_thread *thread;/* increase the global tick */++ rt_tick;/* check time slice */thread = rt_thread_self();-- thread->remaining_tick;if (thread->remaining_tick == 0){/* change to initialized tick */thread->remaining_tick = thread->init_tick;/* yield */rt_thread_yield();}/* check timer */rt_timer_check();
}```c
/*** This function will let current thread sleep for some ticks.** @param tick the sleep ticks** @return RT_EOK*/
rt_err_t rt_thread_sleep(rt_tick_t tick)
{register rt_base_t temp;struct rt_thread *thread;/* disable interrupt */temp = rt_hw_interrupt_disable();/* set to current thread */thread = rt_current_thread;RT_ASSERT(thread != RT_NULL);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);/* suspend thread */rt_thread_suspend(thread);/* reset the timeout of thread timer and start it */rt_timer_control(&(thread->thread_timer), RT_TIMER_CTRL_SET_TIME, &tick);rt_timer_start(&(thread->thread_timer));/* enable interrupt */rt_hw_interrupt_enable(temp);rt_schedule();/* clear error number of this thread to RT_EOK */if (thread->error == -RT_ETIMEOUT)thread->error = RT_EOK;return RT_EOK;
}
rt_thread_sleep
通过开启Thread 本身的定时器来计算延时。
内置定时器在任务初始化的时候指定了超时函数,用于唤醒正在休眠的线程。
/*** This function is the timeout function for thread, normally which is invoked* when thread is timeout to wait some resource.** @param parameter the parameter of thread timeout function*/
void rt_thread_timeout(void *parameter)
{struct rt_thread *thread;thread = (struct rt_thread *)parameter;/* thread check */RT_ASSERT(thread != RT_NULL);RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND);RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);/* set error number */thread->error = -RT_ETIMEOUT;/* remove from suspend list */rt_list_remove(&(thread->tlist));/* insert to schedule ready list */rt_schedule_insert_thread(thread);/* do schedule */rt_schedule();
}
RT-Thread 流水笔记一 startup ,schedule,thread相关推荐
- java方法中 thread,Java中的線程Thread方法之---join()
上一篇我們說到了Thread中的stop方法,這一篇我們再來看一下方法join的使用,那么方法Join是干啥用的? 簡單回答,同步,如何同步? 怎么實現的? 下面將逐個回答. join方法從字面上的意 ...
- java中thread实例_Java多线程2:Thread中的实例方法
Thread类中的方法调用方式: 学习Thread类中的方法是学习多线程的第一步.在学习多线程之前特别提出一点,调用Thread中的方法的时候,在线程类中,有两种方式,一定要理解这两种方式的区别: 1 ...
- java thread类_java多线程之Thread类
Class Thread java.lang.Object java.lang.Thread 实现接口:Runnable 直接被继承的子类:ForkJoinWorkerThread public cl ...
- php5.6non thread safe 区别,PHP版本Non Thread Safe和Thread Safe如何选择?区别是什么?
PHP版本分为Non Thread Safe和Thread Safe,Non Thread Safe是指非线程安全,Thread Safe是指线程安全,区别是什么?如何选择? Non Thread S ...
- java 怎样 thread dump_怎样分析 JAVA 的 Thread Dumps
展开全部 当有障碍,或者是一个基于 JAVA 的 WEB 应用运行的比预期32313133353236313431303231363533e58685e5aeb931333337623537慢的时候, ...
- QObject::moveToThread: Current thread is not the object`s thread. Cannot move to target thread
报错:Opencv无法显示图像,报错QObject::moveToThread: Current thread is not the object's thread . Cannot move to ...
- rpmdb: BDB0113 Thread/process 18616/139854252218432 failed: BDB1507 Thread died in Berkeley DB libra
报错 :error: rpmdb: BDB0113 Thread/process 18616/139854252218432 failed: BDB1507 Thread died in Berkel ...
- 故障排除: rpmdb: BDB0113 Thread/process 21869/140531746672 failed: BDB1507 Thread died
故障排除: rpmdb: BDB0113 Thread/process 21869/140531746672 failed: BDB1507 Thread died 1. 故障现象 2. 解决过程 1 ...
- C++11学习笔记-----线程库std::thread
在以前,要想在C++程序中使用线程,需要调用操作系统提供的线程库,比如linux下的<pthread.h>.但毕竟是底层的C函数库,没有什么抽象封装可言,仅仅透露着一种简单,暴力美 C++ ...
最新文章
- Caffe框架GPU与MLU计算结果不一致请问如何调试?
- 清华计算机学院新成立,清华AI更进一步:清华大学成立人工智能国际治理研究院...
- File Operations In Java
- 框架:spring总结
- 神策数据荣获 36 氪 「2020 中国新经济之王」之「最具影响力企业」和「最具竞争力企业」双奖 !...
- 《C champion》C语言发展
- 留个HelpAssistant用户后门,呵呵。
- 编写DLL所学所思(1)——导出函数
- 您是否真的要加快Maven的编译/打包速度? 那么takari生命周期插件就是答案。
- Android Input子系统-含实例源码
- 阿里云EDAS 3.0重磅发布,无侵入构建云原生应用
- Dubbo(八)使用配置类方式实现服务提供者消费者dubbo配置
- SkyEye仿真ZYNQ芯片,轻松运行国产操作系统ReWorks
- Windows下第三方库安装Nuget与Vcpkg
- 批量解决win10图标上有两个蓝色箭头的方法
- 计算机换汉语快捷键,电脑常用快捷键
- 关于beginPath()和closePath()的关系canvas的beginPath和closePath分析总结,包括多段弧的情况...
- 其实,前面倒腾那么多,只是为了想玩SPRING BOOT
- 第一章 时间序列基础知识
- linux 安装Python3 并安装Python Blog Wagtail
热门文章
- 《三国演义》人物出场统计
- 欧几里德算法及其扩展算法
- DEMOS和LDMOS的区别
- 高中二年级计算机会考吉大附中,长春小学、初中、高中期末考试时间大曝光
- php字幕文件怎么打开,字幕文件 WebVTT 与 srt 之间的互相转化
- Ubuntu18.04 系统下ROS Melodic安装
- 暴风雨已至!网易云音乐暂停IPO,上市受阻的背后!
- 表面粗糙度等级对照表
- MyEclipse2015Stable2.0安装破解、遇到的问题和简单使用
- Python 实验题目:字符串格式化输出内容为:姓名:张三,学号:1101,张三的平均分为90.65分。(平均分的原始值为:90.6497)