RT-Thread线程创建和切换分析

目录

ARM架构

几条汇编指令

创建线程

线程结构体:

动态创建线程:

静态创建线程:

构造栈

RT-Thread的启动流程:

任务切换实现:

 void rt_hw_context_switch_to(rt_uint32_t to)

void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to):

PendSV_Handler

提出问题:什么叫线程?思考这个问题之前,先想想怎么切换线程?怎么保存线程?

想要回答以上的问题,需要了解一些微机原理和ARM架构及汇编的知识

ARM架构

ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computor),它所用的指令比较简单,有如下特点:

  1. 对内存只有读、写指令
  2. 对于数据的运算是在CPU内部实现
  3. 使用RISC指令的CPU复杂度小一点,易于设计

比如,对于a=a+b这样的算式,需要经过下面4个步骤来实现:

细看这个几个步骤,有些疑问:

1、读a,那么a的值读出后保存在CPU里面哪里?

2、读b,那么b的值读出后保存在CPU里面哪里?

3、a+b的结果又保存在哪里?

这需要深入ARM处理器的内部,简单概括如下,我们先忽略各种CPU模式(系统模式、用户模式等) 

CPU运行时,先去取指令,再执行指令:

  1. 把内存a的值读入CPU寄存器R0
  2. 把内存b的值读入CPU寄存器R1
  3. 把R0、R1累加存入R0
  4. 把R0的值写入内存a

CPU内部有r0到r15寄存器,这些寄存器有如下别名:

 

 通用寄存器R0-R12:用于传送和暂存数据,也可参与算术逻辑运算和保存运算结果。

堆栈指针寄存器R13(SP):在ARM处理器中共有两个堆栈指针:主堆栈指针(MSP)和进程堆栈指针(PSP)。若用户用到其中一个,另一个必须用特殊指令(MRS、MSR指令来访问),因此任一时刻只能使用其中的一个。主堆栈指针是复位后默认使用的堆栈指针。

链接寄存器R14(LR):当调用一个子程序时,由R14存储返回地址。不像大多数处理器,ARM为了减少访问内存的次数,把返回地址存进CPU内部寄存器中,这样无须访问堆栈空间,,从而提高子程序的调用效率。

程序计数器寄存器R15(PC):PC是程序计数器(PC),其内容为当前正在执行的指令的地址。如果修改它的值,就能改变程序的执行流程。PC值的变化反应了程序的真实流程。 

特殊功能寄存器:ARM内核中有一组特殊功能寄存器,包括程序状态字寄存器(xPSR)、中断屏蔽寄存器(PRIMASK)和控制寄存器(CONTROL)。

程序状态字寄存器(xPSR):程序状态字寄存器在内部分为APSR、 IPSR和EPSR。

几条汇编指令

需要掌握的汇编指令并不多,只需要掌握几条:

  • 读内存指令:LDR,即Load之意
  • 写内存指令:STR,即Store之意
  • 加减指令:ADD SUB
  • 跳转指令:BL,即Branch And Link
  • 入栈指令:PUSH
  • 出栈指令:POP
加载 / 存储指令 (LDR/STR)
加载指令 LDR LDR r0,[addrA] 意思是将地址 addrA 的内容加载 ( 存放 ) r0 里面
存储指令 STR STR r0,[addrA] 意思是将 r0 中的值存储到地址 addrA
加法运算指令 (ADD)
加法运算指令 (ADD) ADD r0,r1,r2 意思为: r0=r1+r2
减法运算指令 (SUB) SUB r0,r1,r2 意思为: r0=r1-r2
寄存器入栈 / 出栈指令 (PUSH/POP)
寄存器入栈 (PUSH) PUSH {r3 ,lr } 意思是将寄存器 r3 和lr 写入内存栈,本质是写内存STR 指令,高标号寄存器写入高地址的栈里,低标号寄存器写入低地址的栈里
lr即 r14 ,写入地址为 sp - 4 的内存,然后: sp=sp-4
r3,写入地址为 sp - 4 的内存,然后: sp=sp-4
寄存器出栈指令 (POP) POP {r3 pc} 意思是取出内存栈的数据放入 r3 pc
本质是读内存 LDR 指令,高标号寄存器的内容来自高地址的栈,低标号寄存器的内容来自低地址的栈
读地址为 sp 的内存存入 r3 ,然后: sp=sp+4
读地址为 sp 的内存存入 pc ,然后: sp=sp+4

函数运行的本质:

如下是一个简单的程序,主函数里调用add_val函数:

int add_val(int *pa,int *pb)
{
	volatile int tmp;
	tmp = *pa;
	tmp = tmp+ *pb;
	*pa = tmp;

}
 
int main( void )
{

	int a =1;
	int b =2;
	
	add_val(&a,&b);
	
	return 0;

}

 对应的汇编代码:
 

0x080023D0 B50C      PUSH     {r2-r3,lr}   //进入main函数,寄存器r3、lr的值都存入内存的栈中(lr保存程序返回地址)
    15:         int a =1;                 //将a的值存入栈中sp+4,局部变量是放在栈里的
0x080023D2 2001      MOVS     r0,#0x01
0x080023D4 9001      STR      r0,[sp,#0x04]
    16:         int b =2; 
    17:          
0x080023D6 2002      MOVS     r0,#0x02    //将b的值存入栈中sp+0,局部变量放在栈里
0x080023D8 9000      STR      r0,[sp,#0x00]
    18:         add_val(&a,&b); 
    19:          
0x080023DA 4669      MOV      r1,sp
0x080023DC A801      ADD      r0,sp,#0x04
0x080023DE F7FEFCC6  BL.W     add_val (0x08000D6E)    //跳转到0x08000D6E处
    20:         return 0; 
    21:  

add_val函数的汇编代码:
0x08000D6E B508      PUSH     {r3,lr} //进入函数,寄存器r3、lr的值都存入内存的栈中
0x08000D70 4602      MOV      r2,r0
     6:         tmp = *pa; 
0x08000D72 6810      LDR      r0,[r2,#0x00]    //将寄存器r2的值存放到r0,其中r2是函数的第一个参数
0x08000D74 9000      STR      r0,[sp,#0x00]    //将寄存器r0的值存储到sp指向的内存
     7:         tmp = tmp+ *pb; 
0x08000D76 6808      LDR      r0,[r1,#0x00]    //将寄存器r1的值存放到r0,其中r1是函数的第二个参数
0x08000D78 9B00      LDR      r3,[sp,#0x00]    //将寄存器sp指向内存的值存放到r3
0x08000D7A 4418      ADD      r0,r0,r3         //将寄存器r0和r3相加,保存到r0
0x08000D7C 9000      STR      r0,[sp,#0x00]    //将寄存器r0的值存储到sp指向的内存
     8:         *pa = tmp; 
     9:  
0x08000D7E 9800      LDR      r0,[sp,#0x00]    //将寄存器sp指向内存的值存放到r0
0x08000D80 6010      STR      r0,[r2,#0x00]    //将寄存器r0的值存放到r2中,其中r2将作为函数的返回值

0x080023E2 2000      MOVS     r0,#0x00
    22: } 
0x080023E4 BD0C      POP      {r2-r3,pc}        //退出函数,获取内存的栈中的数据放入r2 r3 pc

什么叫线程?怎么保存线程?

什么叫线程:运行中的函数,被暂停运行的函数

怎么保存线程:把暂停瞬间的CPU寄存器的值,保存进栈里

创建线程

我个人认为线程主要就3要素:线程栈、线程控制块、线程的入口函数

线程结构体:

struct rt_thread
{
    /* rt object */
    char        name[RT_NAME_MAX];                      /**< 线程名字 */
    rt_uint8_t  type;                                   /**< 对象类型 */
    rt_uint8_t  flags;                                  /**< 对象标志 */

#ifdef RT_USING_MODULE
    void       *module_id;                              /**< id of application module */
#endif

    rt_list_t   list;                                   /**< 对象容器的链表 */
    rt_list_t   tlist;                                  /**< 优先级就绪链表 */

    /* stack point and entry */
    void       *sp;                                     /**< 栈顶指针 */
    void       *entry;                                  /**< 函数指针*/
    void       *parameter;                              /**< 入口函数参数 */
    void       *stack_addr;                             /**< 栈的起始地址 */
    rt_uint32_t stack_size;                             /**< 栈大小 */

    /* error code */
    rt_err_t    error;                                  /**< error code */

    rt_uint8_t  stat;                                   /**< thread status */

    /* priority */
    rt_uint8_t  current_priority;                       /**< 当前优先级*/
    rt_uint8_t  init_priority;                          /**< 初始优先级 */
#if RT_THREAD_PRIORITY_MAX > 32
    rt_uint8_t  number;
    rt_uint8_t  high_mask;
#endif
    rt_uint32_t number_mask;

#if defined(RT_USING_EVENT)
    /* thread event */
    rt_uint32_t event_set;
    rt_uint8_t  event_info;
#endif

#if defined(RT_USING_SIGNALS)
    rt_sigset_t     sig_pending;                        /**< 信号挂起 */
    rt_sigset_t     sig_mask;                           /**< 信号屏蔽 */

    void            *sig_ret;                           /**< the return stack pointer from signal */
    rt_sighandler_t *sig_vectors;                       /**< vectors of signal handler */
    void            *si_list;                           /**< the signal infor list */
#endif

    rt_ubase_t  init_tick;                              /**< 一次能运行多少个tick */
    rt_ubase_t  remaining_tick;                         /**< 当前运行还剩多少个tick */

    struct rt_timer thread_timer;                       /**< built-in thread timer */

    void (*cleanup)(struct rt_thread *tid);             /**< cleanup function when thread exit */

    /* light weight process if present */
#ifdef RT_USING_LWP
    void        *lwp;
#endif

    rt_uint32_t user_data;                             /**< 用户私有数据 */
};

创建线程的过程,就是线程的三要素的创建以及构造栈的过程。

函数接口如下:根据线程栈和线程控制块的内存是动态分配还是静态分配可以分为静态创建线程和动态创建线程两种方式,对应有脱离线程和删除线程两种方式,脱离线程只是从内核对象容器中脱离,内存不会被释放, 删除线程则会从对象容器中删除,内存也会相应的释放

动态创建线程:

rt_thread_t rt_thread_create(const char *name,
                             void (*entry)(void *parameter),
                             void       *parameter,
                             rt_uint32_t stack_size,
                             rt_uint8_t  priority,
                             rt_uint32_t tick)
{
    struct rt_thread *thread;
    void *stack_start;

    /*1、分配对象,分配线程结构体*/
    thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread,
                                                    name);
    if (thread == RT_NULL)
        return RT_NULL;
    /*2、分配栈*/
    stack_start = (void *)RT_KERNEL_MALLOC(stack_size);
    if (stack_start == RT_NULL)
    {
        /* allocate stack failure */
        rt_object_delete((rt_object_t)thread);

        return RT_NULL;
    }
    /*3、初始化栈,即构造栈的内容*/
    _rt_thread_init(thread,
                    name,
                    entry,
                    parameter,
                    stack_start,
                    stack_size,
                    priority,
                    tick);

    return thread;
}

静态创建线程:

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)
{
    /* thread check */
    RT_ASSERT(thread != RT_NULL);
    RT_ASSERT(stack_start != RT_NULL);

    /* 初始化对象 */
    rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name);

    return _rt_thread_init(thread,
                           name,
                           entry,
                           parameter,
                           stack_start,
                           stack_size,
                           priority,
                           tick);
}

从上面两个创建线程的关键来看,区别就是动态创建线程是动态分配内存给线程控制块和线程栈,静态则是用户事先分配好了线程控制块和线程栈,二者关键都有调用内核的函数_rt_thread_init(...)函数,该函数主要是构造栈的内容。

static 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)
{
    /* 把用户设定的参数,进行参数赋值 */
    rt_list_init(&(thread->tlist));

    thread->entry = (void *)entry;
    thread->parameter = parameter;

    /* stack init */
    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,
                                          (void *)((char *)thread->stack_addr + thread->stack_size - 4),
                                          (void *)rt_thread_exit);

    /* priority init */
    RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
    thread->init_priority    = priority;
    thread->current_priority = priority;

    thread->number_mask = 0;
#if RT_THREAD_PRIORITY_MAX > 32
    thread->number = 0;
    thread->high_mask = 0;
#endif

    /* tick init */
    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,
                  rt_thread_timeout,
                  thread,
                  0,
                  RT_TIMER_FLAG_ONE_SHOT);

    /* initialize signal */
#ifdef RT_USING_SIGNALS
    thread->sig_mask    = 0x00;
    thread->sig_pending = 0x00;

    thread->sig_ret     = RT_NULL;
    thread->sig_vectors = RT_NULL;
    thread->si_list     = RT_NULL;
#endif

#ifdef RT_USING_LWP
    thread->lwp = RT_NULL;
#endif

    RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));

    return RT_EOK;
}

上面函数主要是把用户创建线程传入的参数进行赋值,以及最为关键的构造栈的内容。这里问个问题,创建线程之后,系统怎么知道去哪里执行入口函数entry,前面说到的CPU寄存器中的PC寄存器就是反映了程序的执行流程,只要把PC寄存器设为入口函数的地址即可,所以这里必须构造栈的内容。

构造栈

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 */

    /* return task's current stack address */
    return stk;
}

 这里可以看到一些熟悉的CPU寄存器,结构体stack_freame有以下内容组成:r4-r11(8个)、以及另外8个(除去SP,因为SP在线程结构体中定义了,这个函数返回值就是SP)。

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;
};

这里为什么要放分成两个结构体,不直接把16个寄存器放入一个结构体中,而且函数感觉只给(r0、r1、r2一部分寄存器赋值)?通过网上查找资料,在Cortex M3和M4权威指南中有解答,栈的初始化会有两部分,一部分是软件初始化和硬件初始化

这里为什么要放分成两个结构体,不直接把16个寄存器放入一个结构体中,而且函数感觉只给(r0、r1、r2一部分寄存器赋值)?通过网上查找资料,在Cortex M3和M4权威指南中有解答,栈的初始化会有两部分,exception_stack_frame 部分是ARM的硬件压栈部分, stack_frame 的R4-R11则是需要自行实现的压栈部分。

每个线程都有自己独自的线程栈,其中创建线程最为关键的就是构造栈(关键是PC构造为入口函数参数以便系统知道去哪里执行线程的入口函数以及R0存放入口函数参数),其他寄存器不重要但是也要构造出来。

 以上就是整个创建线程的过程。

RT-Thread的启动流程:

系统是怎么个执行流程。可以分为4个步骤:

1)硬件初始化

2)RTOS系统初始化

3)创建各种线程

4)启动RTOS调度器

创建各种线程有两种方式,一种是每一个线程都创建,最后启动RTOS调度器,后一种是RT-Thread和FreeRTOS默认使用的,先创建一个启动线程,再启动线程里创建其他各种应用线程,最后启动线程把自己删除后调用RTOS的调度器。

其实,可以进行单步运行程序,可以看到,在_main函数里会进入components.c中如下位置:

 这里$Sub$$main和$Super$$main配对使用,是MDK扩展了main函数,在执行main函数之前,会先进入这个函数,如果你想让你的函数也做一些扩展,其实可以只需把main换成function即可,$Sub$$fuction。$Sub$$main函数调用了rtthread_startip()函数:
 

int rtthread_startup(void)
{
    rt_hw_interrupt_disable();

    /* board level initalization
     * NOTE: please initialize heap inside board initialization.
     */
    rt_hw_board_init();

    /* show RT-Thread version */
    rt_show_version();

    /* timer system initialization */
    rt_system_timer_init();

    /* scheduler system initialization */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* signal system initialization */
    rt_system_signal_init();
#endif

    /* create init_thread */
    rt_application_init();

    /* timer thread initialization */
    rt_system_timer_thread_init();

    /* idle thread initialization */
    rt_thread_idle_init();

    /* start scheduler */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}

从上面可以看到这函数包括了硬件初始化rt_hw_board_init()和系统的各种初始化,定时器初始化,调度器Scheduler初始化和启动调度器等,我们最为关心的就是 rt_application_init():创建了main启动线程(优先级为32/3,为10)。

void rt_application_init(void)
{
    rt_thread_t tid;

#ifdef RT_USING_HEAP
    tid = rt_thread_create("main", main_thread_entry, RT_NULL,
                           RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(tid != RT_NULL);
#else
    rt_err_t result;

    tid = &main_thread;
    result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
                            main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
    RT_ASSERT(result == RT_EOK);
	
    /* if not define RT_USING_HEAP, using to eliminate the warning */
    (void)result;
#endif

    rt_thread_startup(tid);
}

上面这些分析是针对以往的main里创建线程,用的例程是在tshell里创建线程,涉及到RT-Thread的自动初始化机制。

线程切换流程:核心就是恢复和保存现场(上下文)。

线程切换主要就是保存当前线程的上下文以及恢复要切换线程的上下文。

创建完线程之后,都会启动线程:把线程添加到相应的就绪列表中,开启调度。

rt_err_t rt_thread_startup(rt_thread_t thread)
/* 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();
    }
 /* insert to schedule ready list */
    rt_schedule_insert_thread(thread);

任务切换实现:

1、void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to);
2、void rt_hw_context_switch_to(rt_uint32_t to);
3、void PendSV_Handler(void);

 void rt_hw_context_switch_to(rt_uint32_t to)

函数原型如下:该函数用于第一次线程启动,在rt_system_scheduler_start函数中被调用

;/*
; * void rt_hw_context_switch_to(rt_uint32 to);
; * r0 --> to
; * this fucntion is used to perform the first thread switch
; */
rt_hw_context_switch_to    PROC

; 导出rt_hw_context_switch_to,让其具有全局属性,可以在C文件中调用
    EXPORT rt_hw_context_switch_to

    ; 设置rt_interrupt_to_thread的值
    LDR     r1, =rt_interrupt_to_thread    ;将rt_interrupt_to_thread的地址加载到r1
    STR     r0, [r1]                       ;将r0的值存储到rt_interrupt_to_thread

    ; 设置rt_interrupt_from_thread的值为0,表示启动第一次线程切换
    LDR     r1, =rt_interrupt_from_thread    ;将rt_interrupt_from_thread的地址加载到r1
    MOV     r0, #0x0                         ;配置r0等于0
    STR     r0, [r1]                         ;将r0的值存储到rt_interrupt_from_thread

    ; 设置中断标志位rt_thread_switch_interrupt_flag的值为1
    LDR     r1, =rt_thread_switch_interrupt_flag  ;将rt_thread_switch_interrupt_flag的地址加载到r1
    MOV     r0, #1    ;配置r0等于1
    STR     r0, [r1]  ;将r0的值存储到rt_thread_switch_interrupt_flag

    ; 设置PendSV异常的优先级
    LDR     r0, =NVIC_SYSPRI2    
    LDR     r1, =NVIC_PENDSV_PRI
    LDR.W   r2, [r0,#0x00]       ; 读
    ORR     r1,r1,r2             ; 改
    STR     r1, [r0]             ; 写

    ; 触发PendSV异常(产生上下文切换)
    LDR     r0, =NVIC_INT_CTRL
    LDR     r1, =NVIC_PENDSVSET
    STR     r1, [r0]

    ; restore MSP
    LDR     r0, =SCB_VTOR
    LDR     r0, [r0]
    LDR     r0, [r0]
    MSR     msp, r0

    ; 开中断
    CPSIE   F
    CPSIE   I

    ; 永远不会到达这里
    ENDP


void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to):

函数原型如下

;/*
; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
; * r0 --> from
; * r1 --> to
; */
rt_hw_context_switch_interrupt
    EXPORT rt_hw_context_switch_interrupt
rt_hw_context_switch    PROC
    EXPORT rt_hw_context_switch

    ; 设置中断标志位 rt_thread_switch_interrupt_flag 为1
    LDR     r2, =rt_thread_switch_interrupt_flag ;加载rt_thread_switch_interrupt_flag的地址到r2
    LDR     r3, [r2]    ;加载rt_thread_switch_interrupt_flag的值到r3
    CMP     r3, #1      ;r3与1比较,相等执行BEQ指令,否则不执行
    BEQ     _reswitch
    MOV     r3, #1      ; 设置r3的值为1
    STR     r3, [r2]    ; 将r3的值存储到rt_thread_switch_interrupt_flag,即置1
    
    ;设置rt_interrupt_from_thread的值
    LDR     r2, =rt_interrupt_from_thread   ; 加载rt_interrupt_from_thread的地址到r2
    STR     r0, [r2]                        ; 存储r0的值到rt_interrupt_from_thread,即上一个线程栈指针SP的指针

_reswitch
    ;设置rt_interrupt_to_thread的值
    LDR     r2, =rt_interrupt_to_thread     ; 加载rt_interrupt_from_thread的地址到r2
    STR     r1, [r2]                        ; 存储r0的值到rt_interrupt_from_thread,即上一个线程栈指针SP的指针

    LDR     r0, =NVIC_INT_CTRL              ; 触发PendSV异常,实现上下文切换
    LDR     r1, =NVIC_PENDSVSET
    STR     r1, [r0]
    BX      LR                              ; 子程序返回
    ENDP

PendSV_Handler

函数原型如下:


; r0 --> switch from thread stack
; r1 --> switch to thread stack
; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
PendSV_Handler   PROC
    EXPORT PendSV_Handler

    ; 失能中断,为了保护上下文切换不被中断
    MRS     r2, PRIMASK
    CPSID   I

    ; 获取中断标志位,看看是否为0
    LDR     r0, =rt_thread_switch_interrupt_flag    ; 加载rt_thread_switch_interrupt_flag的地址到r0
    LDR     r1, [r0]                ; 加载rt_thread_switch_interrupt_flag的值到r1
    CBZ     r1, pendsv_exit         ; 判断r1是否为0,为0则跳转到pendsv_exit
    ; r1不为0则清零
    MOV     r1, #0x00
    STR     r1, [r0]
    ; 判断rt_interrupt_from_thread的值是否为0
    LDR     r0, =rt_interrupt_from_thread    ;加载rt_interrupt_from_thread的地址到r0
    LDR     r1, [r0]                         ; 加载rt_interrupt_from_thread的值到r1
    CBZ     r1, switch_to_thread    ; 判断r1是否为0,为0则跳转到switch_to_thread
                  ; 第一次线程切换rt_interrupt_from_thread肯定为0,则跳转到switch_to_thread

; ========================== 上文保存 ==============================
; 当进入PendSVC Handler时,上一个线程运行的环境即:
; xPSR,PC(线程入口地址),LR,R12,R3,R2,R1,R0(线程的形参)
; 这些CPU寄存器的值会自动保存到线程的栈中,剩下的r4~r11需要手动保存

    MRS     r1, psp                 ; 获取线程栈指针到r1
    STMFD   r1!, {r4 - r11}         ; 将CPU寄存器r4-r11的值存储到r1指向的地址(每操作一次地址将递减1)
    LDR     r0, [r0]                ; 加载r0指向值到r0,即r0=rt_interrupt_from_thread
    STR     r1, [r0]                ;将r1的值存储到r0,即更新线程栈sp

; ========================== 下文切换 ==============================
switch_to_thread
    LDR     r1, =rt_interrupt_to_thread    ;加载rt_interrupt_to_thread的地址到r1
    LDR     r1, [r1]                       ;加载rt_interrupt_to_thread的值到r1,即sp
    LDR     r1, [r1]                ; 加载rt_interrupt_to_thread的值到r1,即sp

    LDMFD   r1!, {r4 - r11}         ; 将线程栈指针r1(操作之前先递减)指向的内容加载到CPU寄存器r4-r11
    MSR     psp, r1                 ; 将线程栈指针更新到psp

pendsv_exit
    ;恢复中断
    MSR     PRIMASK, r2    
    
    ORR     lr, lr, #0x04  ;确保异常返回使用的堆栈指针是PSP,即LR寄存器的位2要为1
    BX      lr             ;异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
                           ; 同时PSP的值也将更新,即指向任务堆栈的栈顶。在ARMC3中,堆是由高地址向低地址生长的。
    ENDP

本文分析了线程创建和启动流程以及线程切换的大致过程,其中的代码细节还有诸多值得分析推敲的地方,比如:创建任务伪造现场的过程中,传参时stack_addr减去4,后面在函数内部又加回4?

回答参考:RT-Thread-关于栈地址的转换不能理解RT-Thread问答社区 - RT-Thread

thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
                                          (void *)((char *)thread->stack_addr + thread->stack_size - 4),
                                          (void *)rt_thread_exit);
    stk  = stack_addr + sizeof(rt_uint32_t);
    stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
    stk -= sizeof(struct stack_frame);

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值