> 文章列表 > 任务切换理解

任务切换理解

任务切换理解

文章目录

      • 堆栈的理解
      • 程序寄存器
      • 函数调用
        • 简单描述
        • 保护现场
        • 参数传递与返回
        • 函数调用在栈上的体现
      • 任务切换

堆栈的理解

栈区:编译器自动分配的,用来保存程序中的申请的局部变量、参数。

堆区:用户申请的内存,在c中通过malloc函数来申请。

全局区(静态区)(static):全局变量和静态变量的存储是放在一块的。

程序寄存器

SP:堆栈指针寄存器,始终指向栈顶,相当于X86中的ESP

LR:保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入LR中;子程序通过把LR复制到PC来实现返回。例如:MOV PC, LR 或者BX LR

PC:在ARM中,一条汇编指令的运行有三个步骤,取指、译码、执行,当第一条汇编指令取指完成后,紧接着就是第二条指令的取指,然后第三条。PC总是指向“正在取指”的指令,而不是指向“正在执行”的指令或正在“译码”的指令。一般来说,人们习惯性约定将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第三条指令。当ARM状态时,每条指令为4字节长,所以PC始终指向该指令地址加8字节的地址,即:PC值=当前程序执行位置+8。

FP:R11寄存器,frame pointer,相当于X86的EBP,它指向栈的基地址(栈底)。这里引入栈帧的概念。每个进程都有一个栈,栈帧就是这个栈的一部分。每次调用函数都会产生一个栈帧,它保存被调函数的参数、局部变量等信息。由FP和SP一起确定这个栈帧在栈中的范围。

函数调用

简单描述

程序的调用通过BL来实现,BL 跳转时,会在寄存器 LR(R14)中保存 pc值(ARM中实际上保存的是PC+4),所以返回时只要 MOV pc,lr就好了。

子程序调用也可以这样写

stmfd sp!, {lr}
……
ldmfd sp!, {pc}

它将LR寄存器压入栈中,这样的话,LR寄存器就释放出来了,就可以在子程序中再调用程序了。

补充:https://blog.csdn.net/qq_37205350/article/details/105731547

保护现场

当函数需要进行跳转的时候,可能在其他的函数中会用到通用寄存器,这样在函数返回的时候通用寄存器的值已经不是原先的值了,所以我们要对其进行备份。在跳转之前,将这些寄存器的值压入栈中保存起来,当跳转回来后,再从栈中取出,就可以还原之前的状态了。

参数传递与返回

ATPCS建议函数的形参不超过4个,如果形参个数少于或等于4,则形参由R0,R1,R2,R3四个寄存器进行传递;若形参个数大于4,大于4的部分必须通过堆栈进行传递。

函数调用在栈上的体现

在没有操作系统的裸机程序中,就相当于一个大的进程。一个进程一个栈。

一个程序的入口是main函数,其实main之前还有一些启动函数和初始化函数,这些都先忽略,假定main函数就是第一个函数。这时开始在栈上建立一个栈帧(就叫栈帧M)。FP中保存的就是栈帧M的基地址或者叫栈底,SP保存的就是栈顶的指针。而这两个指针就确定了一个帧的初始范围。而初始化的时候FP和SP所指向的地址可能并不是相邻的,它们之间有一个预开辟空间,这段空间在初始化后用来保存函数中的局部变量。

在执行main函数的时候FP始终是指向栈底的,因为在一个栈帧中它是不变的,所以可以根据它去寻址。而栈顶则随着数据入栈出栈而移动。在main函数中需要调用了一个函数A。则A就需要创建一个栈帧。首先栈帧M会将要传递的参数压入栈中,之后将栈底也就是当前的FP指针也压入栈中。将当前SP的指针的值赋给FP,这时的FP就是A的栈底了再重新给SP分配一个地址,这样现在的FP和SP就规划出了函数A的栈帧(栈帧A)。函数的在调用的时候会根据FP的地址去寻址参数。函数A调用完成后会返回到main函数中,它会释放栈帧A,然后返回到栈帧M。因为现在栈帧A的栈底其实就是原先栈帧M的栈顶,所以将FP的值赋给SP。由于栈帧M的栈底是最后的一个压入栈的,所以会通过SP得到原来的栈顶。这样就恢复了原来栈帧M的状态。之前压入的传递参数已经没有用了,也可以通过操作SP指针来释放。

任务切换

PendSV:可悬挂异常。如果当前没有更高优先级的异常或者中断需要执行,那么就执行pendsv的异常服务函数。 可以看出它有个特点,就是 缓期执行。cpu先执行比它高的服务函数,然后再执行它,不过,PendSV的优先级需要调到最低。为什么它可以达到缓期执行呢?举个例子,当你收到了一个串口中断,进入了中断服务函数,你需要尽快的读出串口数据,但是,这个时候来了系统时钟中断,由于它的优先级比串口高,所以需要先去执行它。如果你任务切换的代码在系统时钟中断服务函数里面,它就会去检查,然后切换任务,它这一系列工作的时间是不可控的,可能你串口中断就等不急它返回了。这时我们可以先在系统时钟中断里挂起PendSV异常,将切换任务的代码放在pendsv的异常服务函数中。系统时钟中断只是挂起一个标志位,然后很快会返回。由于串口中断的优先级高于PendSV异常,所以先去执行串口中断函数,再执行PendSV异常服务函数。这就是缓期执行。