前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >rt-thread中的压栈与出栈分析

rt-thread中的压栈与出栈分析

作者头像
bigmagic
发布2020-03-17 14:58:33
1.3K0
发布2020-03-17 14:58:33
举报
文章被收录于专栏:嵌入式iot

rt-thread中的压栈与出栈

1.说明

本文主要想分析一下rt-thread中线程的压栈与入栈的相关操作。从而更好的掌握线程切换与线程恢复的相关知识。

2.使用场景

首先需要明白的是什么情况下需要进行压栈与出栈的操作?对于这个问题可以做这样的设想,当程序一直做一件事的时候,是顺序执行的,不会有任何干扰。但是此时来了一个中断,那么程序逻辑肯定会优先去处理中断。那么这时需要做哪些事情?

也许这个例子有点脱离实际,讲的通俗明白一些,就是一个人在专注的完成一件事,此时应该是很顺利的进行。但是当事情还没有做完,但是又有一个更加紧急的事情需要你去处理,这时应该怎么做?

对于一个人来讲:

(1)将手里没有做完的事情保留起来,保留当前的进度

(2)将大脑清空,全力完成更加重要的事情

(3)恢复到没有做完的事情的现场,去接着完成没有做完的事情

人的大脑是这样工作的,其实芯片的逻辑也需要这样执行,我们知道芯片如何知道当前程序的状态,无外乎几个重要的寄存器,sp(程序指针寄存器),通用寄存器Rx,以及LR链接寄存器等等。有了这些信息,就可以知道程序当前运行的状态了,这个就是程序的现场。

对于armv7来说,寄存器可以分为以下几种:

armasm_pge1464343210583

在rt-thread操作系统中,涉及到压栈与出栈操作的有两个地方,第一个是中断的进入与中断处理完成后的退出,第二个是线程的切换。

3.简单分析一下rt-thread线程栈的初始化

对于/bsp/qemu-vexpress-a9来说,系统上电后执行rtt的第一行代码在/libcpu/arm/cortex-a/start_gcc.S文件。

然后执行_reset函数,这个函数是汇编函数写的,因为前期没有栈空间,所以代码需要采用汇编指令完成。

然后分配栈空间等等。执行到rtt的其他部分逻辑。这里就不赘述了。这里主要分析的是线程的初始化。

每一个线程在初始化的时候,需要分配栈空间

rt_thread_create/rt_thread_init --> _rt_thread_init --> rt_hw_stack_init

最后调用到了/libcpu/arm/cortex-a/stack.c文件。

代码语言:javascript
复制
rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter,
                             rt_uint8_t *stack_addr, void *texit)
{
    rt_uint32_t *stk;

    stack_addr += sizeof(rt_uint32_t);
    stack_addr  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stack_addr, 8);
    stk      = (rt_uint32_t *)stack_addr;
    *(--stk) = (rt_uint32_t)tentry;         /* entry point */
    *(--stk) = (rt_uint32_t)texit;          /* lr */
    *(--stk) = 0xdeadbeef;                  /* r12 */
    *(--stk) = 0xdeadbeef;                  /* r11 */
    *(--stk) = 0xdeadbeef;                  /* r10 */
    *(--stk) = 0xdeadbeef;                  /* r9 */
    *(--stk) = 0xdeadbeef;                  /* r8 */
    *(--stk) = 0xdeadbeef;                  /* r7 */
    *(--stk) = 0xdeadbeef;                  /* r6 */
    *(--stk) = 0xdeadbeef;                  /* r5 */
    *(--stk) = 0xdeadbeef;                  /* r4 */
    *(--stk) = 0xdeadbeef;                  /* r3 */
    *(--stk) = 0xdeadbeef;                  /* r2 */
    *(--stk) = 0xdeadbeef;                  /* r1 */
    *(--stk) = (rt_uint32_t)parameter;      /* r0 : argument */
    /* cpsr */
    if ((rt_uint32_t)tentry & 0x01)
        *(--stk) = SVCMODE | 0x20;          /* thumb mode */
    else
        *(--stk) = SVCMODE;                 /* arm mode   */

#ifdef RT_USING_LWP
    *(--stk) = 0;       /* user lr */
    *(--stk) = 0;       /* user sp*/
#endif
#ifdef RT_USING_FPU
    *(--stk) = 0;       /* not use fpu*/
#endif

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

初始化线程的时候,每个线程都是有一个栈空间的,这个栈空间不仅仅保存一下参数变量,还在栈地址的首地址处保存了线程执行需要的现场。而且每个线程都有一个独立的栈内存,这个内存就是在栈的入口处。

当线程发生切换的时候,需要取出这些寄存器

代码语言:javascript
复制
.globl rt_thread_switch_interrupt_flag
.globl rt_interrupt_from_thread
.globl rt_interrupt_to_thread
.globl rt_hw_context_switch_interrupt
rt_hw_context_switch_interrupt:
#ifdef RT_USING_SMP
    /* r0 :svc_mod context
     * r1 :addr of from_thread's sp
     * r2 :addr of to_thread's sp
     * r3 :to_thread's tcb
     */

    str     r0, [r1]

    ldr     sp, [r2]
    mov     r0, r3
    bl      rt_cpus_lock_status_restore

    b       rt_hw_context_switch_exit

执行到rt_hw_context_switch_exit函数

代码语言:javascript
复制
.global rt_hw_context_switch_exit
rt_hw_context_switch_exit:

#ifdef RT_USING_SMP
#ifdef RT_USING_SIGNALS
    mov     r0, sp
    cps #Mode_IRQ
    bl      rt_signal_check
    cps #Mode_SVC
    mov     sp, r0
#endif
#endif
#ifdef RT_USING_FPU
/* fpu context */
    ldmfd sp!, {r6}
    vmsr fpexc, r6
    tst  r6, #(1<<30)
    beq 1f
    ldmfd sp!, {r5}
    vmsr fpscr, r5
    vldmia sp!, {d16-d31}
    vldmia sp!, {d0-d15}
1:
#endif

#ifdef RT_USING_LWP
    ldmfd   sp, {r13, r14}^ /* usr_sp, usr_lr */
    add     sp, #8
#endif
    ldmfd   sp!, {r1}
    msr     spsr_cxsf, r1        /* original mode */
    ldmfd   sp!, {r0-r12,lr,pc}^ /* irq return */

该函数可能看起来有些费劲,我来解释一下大概的内容:

当线程间要从上一个线程切换到下一个线程的时候,首先会将切换之前现场保存起来,也就是将这些寄存器的知保存到内存中,然后将sp指向下线程的地址。此时需要恢复下一个需要切换的线程的寄存器。

4.总结

如果需要理清楚rt-thread的栈空间的压栈与入栈,其实最根本的问题就是如何去处理现场状态的问题。也就是每个线程都需要有一个独立的栈空间,然后这些栈空间除了保存数据,还需要保存寄存器。当进行任务切换的时候,当前线程的寄存器需要保存该线程的栈内存中,而下个线程的栈空间则会从自己的栈空间的起始地址处恢复。这个就是rt-thread栈运作的实现逻辑。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嵌入式IoT 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • rt-thread中的压栈与出栈
    • 1.说明
      • 2.使用场景
        • 3.简单分析一下rt-thread线程栈的初始化
          • 4.总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档