linux进程内存分布与管理

本文详细介绍了Linux进程的内存分布,包括BSS段、数据段、代码段、栈区和堆区。同时,讨论了内存映射段、内核空间与用户空间的划分。在进程内存管理方面,讲解了虚拟地址、虚拟内存技术以及堆内存的brk()和mmap()管理方式。文章还提及了glibc库如何优化内存分配,减少系统调用带来的性能损耗。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、进程内存分布

在这里插入图片描述
在这里插入图片描述
  一个程序的进程本质上都是由bss段、data段、text段三个组成的。可以看到一个进程在存储(没有调入内存)时分为代码段、数据区和未初始化数据区三部分,如下所示:

  1. BSS段(未初始化数据区):通常用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS段属于静态分配,程序结束后静态变量资源由系统自动释放。
  2. 数据(data)段(已初始化数据区):存放程序中已初始化的全局变量的一块内存区域。数据段也属于静态内存分配。
  3. 代码段(text段):存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量。

说明:

  • text段和data段在编译时分配好空间,而bss段并不占用可执行文件的大小,它是由链接器来获取内存的。
  • bss段(未进行初始化的数据)的内容并不存放在磁盘上的程序文件中。其原因是内核在程序开始运行前将它们设置为0。需要存放在程序文件中的只有正文段和初始化数据段。
  • data段(已经初始化的数据)则为数据分配空间,数据保存到目标文件中。
  • data段包含经过初始化的全局变量以及它们的值。BSS段的大小从可执行文件中得到,然后链接器得到这个大小的内存块,紧跟在数据段的后面。当这个内存进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。

进程在调入内存时,也就是在运行时的两个区域:栈区和堆区。

  • 栈区:由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再对自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。栈底对应高地址,栈顶对应低地址,栈由内存高地址向低地址生长。Linux下进程栈的默认大小是10M,可以通过 ulimit -s查看并修改默认栈大小。
  • 堆区:用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。windows下,进程的高位2G留给内核,低位2G留给用户,所以进程堆的大小小于2G。Linux下,进程的高位1G留给内核(内核空间),低位3G留给用户(用户空间),所以进程堆大小小于3G。

在这里插入图片描述

内存映射段

内核将文件的内容直接映射到内存。任何应用程序都可以通过Linux的mmap()系统调用或者Windows的CreateFileMapping()/MapViewOfFile()请求这种映射。内存映射是一种方便高效的文件I/O方式,所以它被用来加载动态库。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据。在Linux中,malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存。“大块”意味着比MMAP_THRESHOLD还大,缺省128KB,可以通过mallocp()调整。

内核空间和用户空间

Linux的虚拟地址空间范围为0~4G(intel x86架构32位),Linux内核将这4G字节的空间分为两部分,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF)供各个进程使用,称为“用户空间”。
每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享,实际看来每个进程拥有4G的虚拟内存空间。
内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是进程的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。 虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000)。另外,使用虚拟地址可以很好的保护内核空间被用户空间破坏,虚拟地址到物理地址转换过程有操作系统和CPU共同完成(操作系统为CPU设置好页表,CPU通过MMU单元进行地址转换)。

二、进程内存管理

1. 虚拟地址

为了充分利用和管理系统内存资源,Linux采用虚拟内存管理技术,利用虚拟内存技术让每个进程都有4GB 互不干涉的虚拟地址空间。进程初始化分配和操作的都是基于这个"虚拟地址",只有当进程需要实际访问内存资源的时候才会建立虚拟地址和物理地址的映射,调入物理内存页。
无论是用户空间还是内核空间,使用的地址都是虚拟地址,当需进程要实际访问内存的时候,会由内核的「请求分页机制」产生「缺页异常」调入物理内存页。
Linux内存管理单元(MMU)管理着地址空间和物理内存的转换,其中的页表(Page table)存储着页(程序地址空间)和页框(物理内存空间)的映射表。
在这里插入图片描述

2. 虚拟内存技术

请求分页存储管理、请求分段存储管理、请求段页式存储管理
在这里插入图片描述

3. 堆内存管理

堆是一块巨大的内存空间,常常占据整个虚拟空间的绝大部分。申请堆的比较好的方法就是程序向操作系统申请一块适当大小的堆空间,然后由程序自己管理这块空间(内存池),避免频繁申请和释放堆空间。

Linux堆空间分配方式:1. brk()系统调用;2. mmap()。两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

brk()/sbrk() // 通过移动Heap堆顶指针brk,达到增加内存目的
mmap()/munmap() // 通过文件影射的方式,把文件映射到mmap区

brk()的作用实际上就是设置进程数据段的结束地址,即它可以扩大或缩小数据段。如果将数据段的结束地址向高地址移动,那么被扩大的部分空间就可以被程序使用。而mmap()的作用是向操作系统申请一段虚拟地址空间。

使用malloc函数进行动态堆内存分配时,对于小于128k的请求,它会在现有的堆空间里,按照堆分配算法为它分配一块空间并返回;对于大于128kb的请求,它会使用mmap()函数为它返回一块匿名空间,然后在匿名空间中分配空间。

glibc库分配内存
为什么需要glibc呢?我们知道,系统调用本身会产生软中断,导致程序从用户态陷入内核态,比较消耗资源。试想,如果频繁分配回收小块内存区,那么将有很大的性能耗费在系统调用中。因此,为了减少系统调用带来的性能损耗,glibc采用了内存池的设计,增加了一个代理层,每次内存分配,都优先从内存池中寻找,如果内存池中无法提供,再向操作系统申请。
在这里插入图片描述

  • 分配内存 < DEFAULT_MMAP_THRESHOLD,走__brk,从内存池获取,失败的话走brk系统调用;
  • 分配内存 > DEFAULT_MMAP_THRESHOLD,走__mmap,直接调用mmap系统调用;

在这里插入图片描述

[1] https://www.cnblogs.com/coolYuan/p/9228739.html
[2] https://blog.youkuaiyun.com/jinking01/article/details/106825769
[3] linux内存管理(详解) https://zhuanlan.zhihu.com/p/149581303
[4] glibc内存管理那些事儿 https://www.jianshu.com/p/2fedeacfa797

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值