主要基于Linux,介绍X86-64和ARM64的用户栈结构。断断续续的学了不少和栈相关的知识,今天打算整理用户栈相关的知识,废话少说,下面进入正题。python
栈有时也称堆栈,是一种受限的线性表,只能在线性表的一端按序进行插入(进栈)和删除(出栈),所以先进栈的数据会后出栈。为了便于描述,咱们习惯将在线性表进行插入和删除的一端称为栈顶,另外一端称为栈底。栈顶会随着插入和删除而发生变化,栈底则保持不变。ide
其实,栈在计算机中就是一块连续的存储区域(至少虚拟地址是连续的),只不过在这块连续的存储区域写入和删除数据按照先进后出的规则进行,在计算机中使用两个指针就能够彻底描述一个栈,bp(base pointer)指向栈底,sp(stack pointer)指向栈顶,以下图所示。函数
上面主要讲了栈的定义,在上面栈的定义中至少有两个地方没有说清楚,一是往栈中增长数据时,栈是往高地址增长仍是往低地址增长;二是栈顶指针SP指向的地方是否存放数据。向高地址增加的栈称为递增栈(Ascendant Stack),向低地址增加的栈称为递减栈(Decendant Stack)。SP指向栈顶元素(即SP指向的地方存放数据)的栈为满栈(Full Stack),SP指向下一个栈顶元素位置(即SP指向的地方不存放数据)的栈为空栈(Empty Stack)。很显然一个栈不能同时为递增栈和递减栈,也不能同时为满栈和空栈。所以,存在4种类型的栈,即空增栈(Empty Ascendant Stack,EA)、空减栈(Empty Descendant Stack,ED)、满增栈(Full Ascendant Stack,FA)和满减栈(Full Descendant Stack,FD)。设计
在对空增栈中压入数据时,先把数据放入SP所指的位置处,而后SP=SP+1。对这种栈的压入操做,至关于C语言的 memory[SP++]=data;
或者至关于ARM64的汇编语言str x1,[SP],#8
。出栈操做至关于C语言的data=memory[--SP];
或者ARM64的汇编语言ldr x1,[SP,#-8]!
。指针
在对空减栈中压入数据时,先把数据放入SP所指的位置处,而后SP=SP-1。对这种栈的压入操做,至关于C语言的 memory[SP--]=data;
或者至关于ARM64的汇编语言str x1,[SP],#-8
。出栈操做至关于C语言的data=memory[++SP];
或者ARM64的汇编语言ldr x1,[SP,#8]!
。code
在对满增栈中压入数据时,先对SP操做腾出位置SP=SP+1,而后数据放入SP指向的位置。对这种栈的压入操做,至关于C语言的 memory[++SP]=data;
或者至关于ARM64的汇编语言str x1,[SP,#8]!
。出栈操做至关于C语言的data=memory[SP--];
或者ARM64的汇编语言ldr x1,[SP],#-8
。生命周期
在对满减栈中压入数据时,先对SP操做腾出位置SP=SP-1,而后数据放入SP指向的位置。对这种栈的压入操做,至关于C语言的 memory[--SP]=data;
或者至关于ARM64的汇编语言str x1,[SP,#-8]!
。出栈操做至关于C语言的data=memory[SP++];
或者ARM64的汇编语言ldr x1,[SP],#8
。或者X86-64的汇编指令push r1
。x86-64的汇编指令push和pop操做栈是按照满减栈的规则进行。默认状况下,ARM64也使用满减栈的规则操做栈。进程
栈的生命周期是和进程的生命周期保持一致的,进程在则栈在,进程亡则栈亡。所以,不妨从进程的生命周期探讨栈的生命周期。一个用户进程从无到开始运行,须要通过几个重要的步骤:内存
结合上面所述以及下图所示,栈的生命周期能够分为4个部分:编译器
_start
函数将 Linux Kernel建立的栈和libc库函数接上头,由体系结构相关的汇编语言编写,核心做用是将栈顶地址赋值给SP,还将Linux设置的栈传递、参数传递以及一些库函数的函数指针传递给C语言编写的函数__libc_start_mian
。_start
函数只是起到一个过渡做用,根据CPU的体系结构将Linux Kernel初始化好的栈传递给后续的C语言编写的函数。__libc_start_mian
函数是一个C语言写的函数,运行到该函数时用户栈的结构已是编译器设计的了,同时因为_start
函数已经设置好了SP的值,各类压栈、出找操做都在不断调整SP的值。该函数的功能主要有,main调用前的初始化工做;调用main;main函数返回后的清尾工做。对于Linux内核而言,将整个内存空间划分为两个部分,Kernel Space 和User Space,前者用于支撑Linux Kenrel自己的运行所需空间,后者就是用于支持用户程序所需的运行空间。用户栈就是位于用户空间,通常位于用户空间的最高部分,向低地址处增加。