关于线程中保存现场的理解
可以把线程理解成任务,假如a和b任务差不多同时发生,先运行A任务,之后保存A的现场
再去运行B的任务,之后运行后就保存B的现场
函数本身不需要保存,但函数执行到哪需要被保存,全局变量没必要保存,因为它本身就在内核上,局部变量本身是保存在自己栈上面的,如果局部变量b在A函数里,假如A函数和B函数里的b对应的栈不冲突的话 ,是不需要保存局部变量的。我们需要保存的是中间变量,什么是中间变量呢,可以理解成原本不存在的新值,例如原来初始化了b,c两个变量(int b/c后)之后又令c=b+2,这个b+2就是中间变量,就是它原本是不存在栈中的,所以需要保存这个中间结构。
那么什么是线程呢?要回答这个问题得先了解ARM架构【详见我的上一篇文章】
我们需要注意的是如果变量是存于栈中的,那我们不用考虑对其保存现场,因为栈本身就有保存的作用,我们在执行完另一个任务后要回到这个任务时,是可以直接从栈中读取这些变量值的。所以我们把目光放向那些不在栈中的值,如寄存器的值(因为我们大多汇编指令都是对寄存器和栈进行操作,所以程序中止时往往会有许多寄存器的值没有被存入栈中),所以我们可以得出结论:保存现场其实是保存寄存器的值(SP也属于寄存器,是R13)。
保存现场的具体操作是令SP=SP-16×4,16×4是因为我们一共有16个寄存器[R0-R15(R13除外),外加一个程序状态寄存器PSR],栈之间地址相差4个比特,所以一共就是16×4。之后往里面存放我们寄存器的值。【实际下图中寄存器的顺序并不是如图所示,只是为了方便理解先排成这样的顺序】
之后就可以去执行其他任务了
创建线程的理论分析
明确一下线程三要素:
①入口函数(函数入口)
②栈 (用于保存现场,如上所述)
③线程控制块(即一个线程结构体,在任务A中止时保护现场时需要将寄存器的值存放于A之间的栈中,这个栈的地址其实是存放在线程结构体里的,当执行完B任务返回时会从线程结构体中读取A栈的地址之后恢复寄存器的值)
基于以上 我们可以去创建一个线程:首先要分配线程控制块,分配栈,建立一个入口函数(创建进程在分配时有静态分配和动态分配,而动态分配在某些安全性非常高的系统里用不了),其他参数没有三要素核心,不再赘述
[注意,句柄即我们刚刚提到的线程控制块结构体]
那么有人可能好奇我们要怎么启动一个线程?我们刚刚有提到说保存地址时要把之后要传给寄存器R0-R15的值保存起来,我们需要注意R15的值的保存很重要,因为R15是程序计数器PC,用来保存中止处将要执行的指令的地址,其实就是我们三要素里的函数入口,所以每当恢复这些寄存器值到CPU时,我们CPU会直接从R15的值即入口函数的地址处进入需要操作的部分。
而我们在启动一个线程的时候会假装暂停在第一条指令之前,目的是为了创建栈并把寄存器的值保存在栈中(所以为了完成此步CPU会分配一个属于任务自己的栈)并恢复到CPU中,之后会CPU会执行PC处也就是入口函数处的程序,这一步的目的简单地说就是让PC寄存器的值等于入口函数处的地址(PC寄存器的值为入口函数的地址),步骤是假装暂停入口函数,构造栈的内容并写入CPU。
先了解一下rt_thread的一些成员
图中的rt_hw_stack_init()的作用是为了
①虚构栈的内容,即用来存放各寄存器的值[如图中stk-=sizeof(struck stack_frame)],我们在这要注意一下就是我们刚刚提及的三要素是有被赋值的,而我们观察可以发现除此之外其他值都被赋零了
②调整SP
【图中的stack_frame其实指的是栈里放寄存器的那块区域】