查看原文
其他

[笨叔点滴3] “栈”谁便宜了?

小笨叔 奔跑吧Linux社区 2019-04-24

 有一次和女同学A吃饭,她带着小姑凉一块。在吃饭期间,同学给夹了块豆腐,小姑凉用天真无邪的眼神看着我:笨叔,你除了占我妈便宜,你还占过谁的便宜?  我顿时无语了。。。



上面是网上的一个小段子,生活里经常有人说谁谁占了谁的便宜?那计算机是不是也有占小便宜呢?计算机里还真有不少便宜可以“栈”,只不过不是“占”,而是各种形式的“栈”,不知道大家知不知道计算机里有多少个栈?比如说:

  1.     内核栈

  2.     中断栈

  3.     进程栈

  4.     线程栈

  5.     硬件栈

  6.     软件栈

  7.     堆栈

  8.     还有人占着茅坑


        昨天我们聊了ARM32上或是奇葩或者先进的中断栈,我们今天继续来聊“栈”。

   

01 啥是栈



先看看啥是栈?栈的英文叫做stack。那中文里栈是怎么解释的呢?

1.储存货物或供旅客住宿的房屋:货栈|客栈。

2.养牲畜的竹、木栅栏:马栈。


那在计算机,它是啥呢,其实就是一个存放数据的数据结构类型。一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。看下面这个图应该比较清楚了。


这种数据结构的特点是 后入先出 (LIFO, Last In First Out),数据只能在串列的一端 (称为:栈顶 top) 进行 推入 (push) 和 弹出 (pop) 操作。向栈中存储数据称为PUSH,从栈中取数据称为POP。


大多数的处理器架构,都有实现硬件栈。有专门的栈指针寄存器,以及特定的硬件指令来完成 入栈/出栈 的操作。例如在 ARM 架构上,R13 (SP) 指针是堆栈指针寄存器,而 PUSH 是用于压栈的汇编指令,POP 则是出栈的汇编指令。


我们常常听人说,堆栈,那堆栈是个什么鬼?究竟是堆呢还是栈呢?其实堆栈本身就是栈,只是换了个抽象的名字,换了个马甲,有些人就昏了。


那堆是什么?在数据结构里,堆可以被看成是一棵树,如:堆排序。在操作系统里,堆是操作系统里管理内存的一种方式, 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。而栈,由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。


所以,堆和栈还有堆栈,他们三个是不是容易搞混了。


02 栈有啥子用嘛?

栈主要是有两大作用,一个是函数调用,另外一个是进程调度。

先说说函数调用。我们知道函数调用需要注意哪些东西?大家想到的可能是

  1. 参数怎么传

  2. 函数返回值怎么传


那在不同的计算机体系结构里,有不同的做法,但是他们相同的地方是一定会用到栈。

ARM和ARM64使用的是ATPCS(ARM-Thumb Procedure Call Standard/ARM-Thumb过程调用标准)的函数调用约定。


对于ARM来说:

参数1~参数4 分别保存到 R0~R3 寄存器中 ,剩下的参数从右往左一次入栈,返回值存放在 R0 中。


对于ARM64来说:

参数1~参数8 分别保存到 X0~X7 寄存器中 ,剩下的参数从右往左一次入栈,返回值存放在 X0 中。


总之,会用到栈来保存函数调用的参数。另外还有一个东西需要保存,那就是局部变量。


以ARM32为例,一个函数A调用另外一个函数B的栈的布局图如下。fp寄存器(r11)和sp寄存器两个指向的区域,称为一个栈帧(stack frame),函数调用经常是嵌套的,在同一时刻,栈中会有多个函数的信息。每个未完成运行的函数占用一个独立的连续区域,即栈帧。栈帧存放着函数参数,局部变量及恢复前一栈帧所需要的数据等。

如上图所示,假设当前运行在函数B里面,那么当前的fp和sp寄存器所指示的区域就是当前的栈,栈帧B。当函数B返回到函数A时候,栈帧B里保存的sp和fp寄存器所构造的另一个栈,就是栈帧A了。


所以,栈是链接起来的‘桢’的一个列表,按递减地址次序分配栈的每一块。寄存器 sp 总是指向在最当前桢中最低的使用的地址。ARM上栈有点奇葩。


其他CPU体系结构中说的SP栈指针,都是指向栈顶的,但是ARM的栈是自减栈,栈是向下生长的,也就是栈底处于高地址处,栈顶处于低地址处。有点奇葩和绕口。


栈还有另外一个用途就是进程切换。每个进程都有自己的系统栈空间,这个栈空间说的是内核栈,这个是在fork的时候就分配好的。所以,进程切换的时候,需要把前任的进程的上下文保存到前任进程的内核栈里。在每一个进程的生命周期中,必然会通过到系统调用陷入内核。在执行系统调用陷入内核之后,这些内核代码所使用的栈并不是原先进程用户空间中的栈,而是一个单独内核空间的栈,这个称作进程内核栈。进程跑在用户空间,需要一个栈,进程跑在内核空间也需要一个栈,所以这些栈的定义是不一样。


我们下一次在来和大家聊聊哪些“栈”谁的便宜。


最后一个问题,CPU上电时候第一条指令为什么要用汇编来实现?能不能让CPU第一条指令就执行C语言的代码?


能还是不能?为啥子嘛?



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存