这里的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相关推荐

  1. java方法中 thread,Java中的線程Thread方法之---join()

    上一篇我們說到了Thread中的stop方法,這一篇我們再來看一下方法join的使用,那么方法Join是干啥用的? 簡單回答,同步,如何同步? 怎么實現的? 下面將逐個回答. join方法從字面上的意 ...

  2. java中thread实例_Java多线程2:Thread中的实例方法

    Thread类中的方法调用方式: 学习Thread类中的方法是学习多线程的第一步.在学习多线程之前特别提出一点,调用Thread中的方法的时候,在线程类中,有两种方式,一定要理解这两种方式的区别: 1 ...

  3. java thread类_java多线程之Thread类

    Class Thread java.lang.Object java.lang.Thread 实现接口:Runnable 直接被继承的子类:ForkJoinWorkerThread public cl ...

  4. php5.6non thread safe 区别,PHP版本Non Thread Safe和Thread Safe如何选择?区别是什么?

    PHP版本分为Non Thread Safe和Thread Safe,Non Thread Safe是指非线程安全,Thread Safe是指线程安全,区别是什么?如何选择? Non Thread S ...

  5. java 怎样 thread dump_怎样分析 JAVA 的 Thread Dumps

    展开全部 当有障碍,或者是一个基于 JAVA 的 WEB 应用运行的比预期32313133353236313431303231363533e58685e5aeb931333337623537慢的时候, ...

  6. 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 ...

  7. 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 ...

  8. 故障排除: rpmdb: BDB0113 Thread/process 21869/140531746672 failed: BDB1507 Thread died

    故障排除: rpmdb: BDB0113 Thread/process 21869/140531746672 failed: BDB1507 Thread died 1. 故障现象 2. 解决过程 1 ...

  9. C++11学习笔记-----线程库std::thread

    在以前,要想在C++程序中使用线程,需要调用操作系统提供的线程库,比如linux下的<pthread.h>.但毕竟是底层的C函数库,没有什么抽象封装可言,仅仅透露着一种简单,暴力美 C++ ...

最新文章

  1. Caffe框架GPU与MLU计算结果不一致请问如何调试?
  2. 清华计算机学院新成立,清华AI更进一步:清华大学成立人工智能国际治理研究院...
  3. File Operations In Java
  4. 框架:spring总结
  5. 神策数据荣获 36 氪 「2020 中国新经济之王」之「最具影响力企业」和「最具竞争力企业」双奖 !...
  6. 《C champion》C语言发展
  7. 留个HelpAssistant用户后门,呵呵。
  8. 编写DLL所学所思(1)——导出函数
  9. 您是否真的要加快Maven的编译/打包速度? 那么takari生命周期插件就是答案。
  10. Android Input子系统-含实例源码
  11. 阿里云EDAS 3.0重磅发布,无侵入构建云原生应用
  12. Dubbo(八)使用配置类方式实现服务提供者消费者dubbo配置
  13. SkyEye仿真ZYNQ芯片,轻松运行国产操作系统ReWorks
  14. Windows下第三方库安装Nuget与Vcpkg
  15. 批量解决win10图标上有两个蓝色箭头的方法
  16. 计算机换汉语快捷键,电脑常用快捷键
  17. 关于beginPath()和closePath()的关系canvas的beginPath和closePath分析总结,包括多段弧的情况...
  18. 其实,前面倒腾那么多,只是为了想玩SPRING BOOT
  19. 第一章 时间序列基础知识
  20. linux 安装Python3 并安装Python Blog Wagtail

热门文章

  1. 《三国演义》人物出场统计
  2. 欧几里德算法及其扩展算法
  3. DEMOS和LDMOS的区别
  4. 高中二年级计算机会考吉大附中,长春小学、初中、高中期末考试时间大曝光
  5. php字幕文件怎么打开,字幕文件 WebVTT 与 srt 之间的互相转化
  6. Ubuntu18.04 系统下ROS Melodic安装
  7. 暴风雨已至!网易云音乐暂停IPO,上市受阻的背后!
  8. 表面粗糙度等级对照表
  9. MyEclipse2015Stable2.0安装破解、遇到的问题和简单使用
  10. Python 实验题目:字符串格式化输出内容为:姓名:张三,学号:1101,张三的平均分为90.65分。(平均分的原始值为:90.6497)