如何进行rt-thread中的压栈与出栈分析

如何进行rt-thread中的压栈与出栈分析,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

rt-thread中的压栈与出栈

1.说明

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

 

2.使用场景

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

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

对于一个人来讲:

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

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

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

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

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

如何进行rt-thread中的压栈与出栈分析

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文件。

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

 

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

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

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

.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指向下线程的地址。此时需要恢复下一个需要切换的线程的寄存器。

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

看完上述内容,你们掌握如何进行rt-thread中的压栈与出栈分析的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读!

原创文章,作者:bd101bd101,如若转载,请注明出处:https://blog.ytso.com/222909.html

(0)
上一篇 2022年1月6日
下一篇 2022年1月6日

相关推荐

发表回复

登录后才能评论