ARM 处理器程序运行的过程
ARM 芯片属于精简指令集计算机(RISC: Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:
- 对内存只有读、写指令
- 对于数据的运算是在 CPU 内部实现
- 使用 RISC 指令的 CPU 复杂度小一点,易于设计
比如对于 a=a+b 这样的算式,需要经过下面 4 个步骤才可以实现:
cpu 内部结构:cotex-a7 处理器模式 以及通用寄存器组
CPU 运行时,先去取得指令,再执行指令:
① 把内存 a 的值读入 CPU 寄存器 R0
② 把内存 b 的值读入 CPU 寄存器 R1
③ 把 R0、 R1 累加,存入 R0
④ 把 R0 的值写入内存 a
以上步骤完成了a+b的计算。
程序被中断时,怎么保存现场
当执行到一半时(比如刚把a、b读到R0、R1),中断发生了(或是调用函数、线程调度等等情况),cpu需要立即执行别的程序(别的程序也会用到 R0、R1啊,到时候数据就丢失了),所以在调度到新的执行单位时 必须将R0、R1保存起来(写到栈里,其实保存的不止R0、R1,还有指令寄存器PC 其它等等)。
栈
栈说白了就是一段内存,用来给应用程序存储数据。
线程(中断)的调度方式
以下时线程调度时的示意图:
在线程 A 执行时,中断B发生,那么在跳转 B之前的瞬间需要把线程 A 的 CPU 寄存器的值,保存到栈里;再去执行线程 B;
线程B恢复线程A时也是同样的道理
在cpu执行程序中 函数调用、或是线程切换、发生中断等等情况下 ,都需要保护当前的现场,以保证能完整的还原。
保护现场: 将cpu 内部寄存器中的数据保存到栈中。
恢复现场: 将栈中的数据恢复到cpu 内部寄存器。
在操作系统中有以下几种情况会使用到栈:
a. 函数调用:
在函数 A 里调用函数 B,实际就是中断函数 A 的执行。
那么需要把函数 A 调用 B 之前瞬间的 CPU 寄存器的值,保存到栈里;
再去执行函数 B;
函数 B 返回之后,就从栈中恢复函数 A 对应的 CPU 寄存器值,继续执行。
b. 中断处理
进程 A 正在执行,这时候发生了中断。
CPU 强制跳到中断异常向量地址去执行,
这时就需要保存进程 A 被中断瞬间的 CPU 寄存器值,
可以保存在进程 A 的内核态栈,也可以保存在进程 A 的内核结构体中。
中断处理完毕,要继续运行进程 A 之前,恢复这些值。
c. 进程切换
在所谓的多任务操作系统中,我们以为多个程序是同时运行的。
如果我们能感知微秒、纳秒级的事件,可以发现操作系统时让这些程序依次执行一小段时间,进程 A 的时间用完了,就切换到进程 B。
怎么切换?
切换过程是发生在内核态里的,跟中断的处理类似。
进程 A 的被切换瞬间的 CPU 寄存器值保存在某个地方;
恢复进程 B 之前保存的 CPU 寄存器值,这样就可以运行进程 B 了。
所以,在中断处理的过程中,伴存着进程的保存现场、恢复现场。
进程的调度也是使用栈来保存、恢复现场:
总结
每个函数都有自己的栈,每个线程都有自己的栈。
在多线程任务模式,栈显得尤为重要。进程是cpu 分配资源的最小单位,线程是cpu 调度的最小单位,一个进程中可以有多个线程,它们虽然共用代码段、数据段(全局变量、静态变量等等)、堆等等,但是它们有属于自己的栈。这就保证了在调度的时候不会发生错误。