编程-函数栈与进程栈

一. 代码编程极简进化史

从纸带上的机器码,到汇编语言算是比较自然而然的变化。因为汇编语言每一条指令即对应着一条机器指令码。汇编中的jump和branch为代码模块化组织提供了最原始的形式。

从汇编语言到C语言算是又一个比较自然的进一步简化,因为C语言是过程式。而相较于汇编的jump/branch,C语言的函数调用(function call)算是带领代码模块化组织形式走出了石器时代。

后续的面向对象语言,也是基于function call, 只是编译器会自动生成constructor/destructor。但正如函数调用的概念,为面向对象提供了基础一样,基于function call和function generate的面向对象语言,也提供了很新的代码组织形式和新的想象,带领编程走进了现代化。

二. 函数栈

程序 = 算法 + 数据。

算法的承载为一条条指令;数据的承载即是寄存器和内存,为指令操作的直接对象。

相较于汇编语言只使用寄存器即可执行运算,并跳来跳去。C语言并不以寄存器为直接操作对象,而是内存中的一个一个变量。

相较于汇编语言jump/branch时,依赖于LR寄存器记录跳转指令下一条指令的地址,然后才能跳回来,C语言的function call则依赖于编译器根据ABI中规定的参数寄存器和返回值寄存器,以及栈来存储函数临时变量和函数的返回地址;

每个函数占用的栈区间,我们成为函数的栈帧,栈帧的内容为:

1. 函数用到的临时变量;

2. 函数的返回地址;

函数调用一层层进行下去,栈里的内容即为一层层函数调用的栈帧,即为函数栈。

下面举例说明。

下面举例说明。

函数add,基于PowerPC VLE的汇编代码如下:

其中,mflr r0把存有函数返回地址的lr寄存器的值存入r0,然后se_stw r0,36(rsp)把返回地址写入栈;函数返回时,se_lwz r0,36(rsp)从栈中读取函数返回地址,然后se_mtlr r0把返回地址写入lr,然后blr返回到lr指定的地址处执行,即上一层函数调用本函数的下一条指令处执行。

Java里面Exception的printStackTrace()打印出的一层层函数调用栈是函数栈最明显的例子。Linux Kernel panic时也会打印函数调用栈,以及栈帧的内容,可以观察临时变量和返回地址。

三. 进程栈

进程栈存储进程的调用关系吗?不是,进程没有调用关系。

进程栈存储进程的父子关系吗?不是,父子进程之间也没有调用关系。

进程栈存储进程的抢占关系,高优先级进程抢占低优先级进程时,则需要把低优先级进程的Context存储起来,以便后续恢复。另一种情况是,中断打断当前进程时,也需要存储当前进程的Context。

进程上下文存储的内容:

1.进程的函数调用栈;

2.进程的状态,Task Control Block(TCB),包括进程使用的寄存器的当前值;

每个进程都会分配专属的栈空间,以存储该进程的上下文。需要恢复时,即从该进程的上下文中恢复各个寄存器的值,接着被抢占时的位置继续执行。

四. 对比

由此可以看出,进程栈是指进程的栈,而非函数栈那样,存放一层层函数调用的栈帧。多个进程的栈是离散的,分开的,而非像函数栈那样连续存放。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180630G0JSP800?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券