1虚拟地址空间与堆栈调用

发布时间 2024-01-11 22:51:16作者: 二氧化硅21

进程的虚拟地址空间内存划分和布局

编程语言->产生指令和数据

  • 程序生成exe可执行文件,加载到内存后(不是一步直接加载到物理内存中)如何存放。
  • x86 32位linux下,linux会给进程分配一块2的32次方大小的一块空间(4G),这块空间是一块虚拟内存空间,虚拟内存空间本质上是系列数据结构。
  • 这一块虚拟地址空间分为两块,3G的用户空间,1G的内核空间。
  • 地址起始的一段内容是不可访问的,不可读写。
  • .text为代码段,.rodata为read only只读数据段(常量区,只读不写)
  • .data数据段,.bss也是数据段:前者存储初始化且不为0的数据,后者存储未初始化或初始化为0的。如一个全局变量默认初始化为0,是内核启动阶段操作系统默认对bss中的未初始化变量赋值0。
  • .heap堆内存栈自上而下增长,即低地址->高地址。
  • 加载的共享库即动态库。
  • 函数运行,或产生线程时,都记录在栈stack上,栈自下而上增长,即高地址->低地址。
  • 最后一段是命令行参数和环境变量。
  • 内核空间。

Alt text

图中gdata1-gdata6编译后产生数据于符号表,其中gdata1,gdata4存放于.data段;gdata2,gdata3,gdata5,gdata6存放于.bss段;a,b,c属于栈中的局部变量,链接器对此类变量不感兴趣,符号表不会记录,相反,这三行会产生三条指令。e存放于.data段;f,g存放于.bss段。打印c会显示栈上存放的无效值,打印g则输出0,因为g存储在bss段,自动初始化为0。

红色框存放于代码段.text,蓝色与棕色狂产生变量符号,存放于数据段。

a-c汇编后生成指令存放于代码段,代码执行期间,系统为main函数开辟栈,执行指令将abc的值存放到对应存储空间

每一个进程的用户空间是私有的,内核空间是共享的,进程间可以通过向内核空间中读写数据实现共享。

进程的通信方式:匿名管道通信

函数的调用堆栈详细过程

Alt text
Alt text

  • 第一句:初始化a,符号表中不产生a变量,仅在代码段中保存此句汇编代码,即mov语句,mov语句把10赋值给栈指针
  • 第二局:同第一句
  • 第三局:将实参的值传给形参,调用sum实现相加函数。
    • 首先,sum函数的参数a,b存为寄存器变量并压栈(push,注意压栈是将变量压到栈顶esp),其中形参压栈顺序是从右向左。然后call调用的sum函数,然后立即将call的下一条语句在.text区中的内存地址压栈,该地址标记sum返回后下一步运行的位置。
  • 进入sum后,执行代码前,要先压入ebp栈底地址(标记main),然后开新栈帧:mov ebp, esp,然后esp移动。
  • 执行sum内部的几条语句
  • 将返回结果保存至寄存器变量中
  • 栈帧回退:mov esp, ebp,即将栈顶指针指回栈底。这个过程中,栈被释放,但内容没有被清理,如果一个函数func1返回一个指针,在main中访问该指针指向的值,依然可以访问到,但不安全;如果后续有func2调用,从而建立新的栈帧,那么这块内存可能会被修改。
  • pop ebp弹栈:即将之前压入的ebp栈底地址赋值回给ebp,这意味着函数执行返回了main
  • ret把再次出栈的内容放入CPU的PC寄存器中,继续执行call sum的下一行语句,并继续把两个形参变量所占用的栈空间交还系统,栈顶地址-8