Linux 可执行程序的分段结构

说明

  • Linux 可执行程序是以ELF文件格式存储的,ELF格式是分段的;Linux系统采用段式内存管理架构,二进制程序加载进内存后内存分布也是分段的。
  • windows系统也是类似的。

个人理解

  • 分段是由编译器和操作系统实现,编译时编译器将不同类型的元素存储到相应的段,以区分处理,利于管理和加快操作效率。
  • 分段行为不是固定的,不同编译器,不同平台可能有细微差别,虚拟内存和硬盘都是连续的。
  • 程序由数据和处理两部分组成,处理部分(代码)编译后为二进制指令(代码段)比较固定,而数据部分(变量)有多种形式,例如:全局变量,局部变量,static变量,const变量等等,为了实现这些需求和加快操作效率,因此数据段有多种分段类型。

常见段

代码

  • 段名:代码段(text段)
  • 编译后生成给CPU执行的机器指令,一个程序只有一个代码段,只读,防止程序由于意外事故而修改自身指令。
  • 该段也有可能包含一些只读的常量,例如字符串常量,const修饰的变量(各种单片机的编译器会这样存储),由编译器决定。

变量

全局变量(生命周期等于整个程序执行期)

  • 全局变量在编码时已经固定了,占用内存大小和值已确定,因为这些特性,全局变量可以在编译时就准备好,不需要等到运行时再去逐个分配内存设置值。
  • 这里说的全局变量包括 全局变量,static的全局变量,static的局部变量。
  • 全局变量由于初始值的不同,有以下两种情况。
已赋不为0的初始值
  • 段名:已初始化数据段(data段)
  • 初值不为0,编译器简单处理认为数据都不同,需要逐个变量保存到二进制程序文件中,程序运行时直接将整段数据拷贝进内存。
  • 个人理解:编译器可以进一步优化,相同的值可以只存一份。
  • 该段保存了程序中所有赋了初值并且初值不为0的全局变量,包括全局变量,static的全局变量,static的局部变量。
  • 如果该段数据较多,会导致程序二进制文件非常大,如下:
int ar[300000] = {1}; //将全部存储在data段,虽然值都是一样的
static int a = 10; //保存在data段

void test(){
    static int b = 10; 
}
未赋初值或者初值为0
  • 段名:未初始化数据段(bss段)
  • 由于可以将这些变量的初值处理成一样的,都设置为0,二进制程序文件就没必要存储这些值,只需要记录该段的首地址和段长度,程序运行加载进内存时再按这些参数申请和格式化内存就好。
  • 该段保存程序中没有进行初始化或者初始化为0的全局变量;例如:
int a; 
int b = 0;
static int a;
static int a = 0;

void test(){
    static int b;
    static int b = 0;
}

局部变量(生命周期由系统控制)

  • 段名:栈。
  • 由于局部变量不是固定的,无法在编译时进行处理。
  • 增长方向:自顶向下增长;普通的局部变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。
  • 栈变量最大的特征是:栈变量都应该是临时变量,出栈后内存就非常可能被覆盖。

动态变量(生命周期由程序员控制)

  • 段名:堆。
  • 该段的大小并不固定,可动态扩张或缩减。
  • 因为动态变量并不是固定的,无法在编译时进行处理。
  • 动态存储区,是向高地址扩展的数据类型,是自下向上的扩展方式,动态内存分配的内存区域。
  • 堆变量最大的特征是:大内存变量或者永久变量,临时小内存变量不应该放在堆内存中,应该放在栈内存中。

只读变量

  • 段名:只读变量区域(rodata段)
  • 只读的内存区域,const修饰的常量,以及常量字符串保存在该区域。例如:
const int a = 100; 
char *b = "hello world"。 //hello world保存在该段,但是变量b不是。

存储时形态

  • 二进制程序中不包含堆,栈需要动态管理的段,程序加载进内存才会产生。

运行时形态

  • 每个进程都独自拥有4GB的虚拟内存空间,使用时再由系统转换成物理内存地址,该转换对用户是透明不可见的,因此时常讲的内存空间都是虚拟内存空间。
  • 程序的代码段和一些全局变量是固定的,不会改变的,编译时会将代码段和这些变量的虚拟内存地址确定,因此每次运行都是固定的。
  • Linux下C程序的内存分布图如下:
  • C程序将内存分为以下5部分,地址从低到高排列:
    在这里插入图片描述

查看段信息

size 命令

  • size命令的作用是:显示一个ELF格式文件(可执行文件或者链接库文件)非运行状态下的各个段的大小(字节占用)。
  • 例如:
xxx@$ size sample_ipc
   text	   data	    bss	    dec	    hex	filename
 148613	 639392	 139276	 927281	  e2631	sample_ipc
 * text 代码段的大小是148613	字节
 * data 段的大小是 639392	字节
 * bss 段的大小是139276字节
 * dec 是 text + data + bss 的总大小的10机制表示,hex是其16进制表示
  • 注意:size 查看的ELF文件结构中的各段字节占用,所以没有堆和栈大小。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
 前言   第一章绪论   Linux与其他类Unix内核的比较   硬件的依赖性   Linux版本   操作系统基本概念   Unix文件系统概述   Unix内核概述   第二章内存寻址   内存地址   硬件中的分段   Linux中的分段   硬件中的分页   Linux中的分页   第三章进程   进程、轻量级进程和线程   进程描述符   进程切换   创建进程   撤消进程   第四章中断和异常   中断信号的作用   中断和异常   中断和异常处理程序的嵌套执行   初始化中断描述符表   异常处理   中断处理   软中断及tasklet   工作队列   从中断和异常返回   第五章内核同步   内核如何为不同的请求提供服务   同步原语   对内核数据结构的同步访问   避免竞争条件的实例   第六章定时测量   时钟和定时器电路   Linux计时体系结构   更新时间和日期   更新系统统计数   软定时器和延迟函数   与定时测量相关的系统调用   第七章进程调度   调度策略   调度算法   调度程序所使用的数据结构   调度程序所使用的函数   多处理器系统中运行队列的平衡   与调度相关的系统调用   第八章内存管理   页框管理   内存区管理   非连续内存区管理   第九章进程地址空间   进程的地址空间   内存描述符   线性区   缺页异常处理程序   创建和删除进程的地址空间   堆的管理   第十章系统调用   POSIXAPI和系统调用   系统调用处理程序及服务例程   进入和退出系统调用   参数传递   内核封装例程   第十一章信号   信号的作用   产生信号   传递信号   与信号处理相关的系统调用   第十二章虚拟文件系统   虚拟文件系统(VFS)的作用   VFS的数据结构   文件系统类型   文件系统处理   路径名查找   VFS系统调用的实现   文件加锁   第十三章I/O体系结构和设备驱动程序   I/O体系结构   设备驱动程序模型   设备文件   设备驱动程序   字符设备驱动程序   第十四章块设备驱动程序   块设备的处理   通用块层   I/O调度程序   块设备驱动程序   打开块设备文件   第十五章页高速缓存   页高速缓存   把块存放在页高速缓存中   把脏页入磁盘   sync()、fsync()和fdatasync()系统调用   第十六章访问文件   读文件   内存映射   直接I/O传送   异步I/O   第十七章回收页框   页框回收算法   反向映射   PFRA实现   交换   第十八章Ext2和Ext3文件系统   Ext2的一般特征   Ext2磁盘数据结构   Ext2的内存数据结构   创建Ext2文件系统   Ext2的方法   管理Ext2磁盘空间   Ext3文件系统   第十九章进程通信   管道   FIFO   SystemVIPC   POSIX消息队列   第二十章程序的执行   可执行文件   可执行格式   执行域   exec函数   附录一系统启动   附录二模块   参考文献

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值