浅谈Unix/linux下的内存管理

程序和内存的概念

 

程序是指在硬盘上的可执行文件。当程序被运行时,需要将可执行文件加载到内存,在内存中的可执行文件形成进程,一个进程(文件)可以同时存在多个进程(内存)。

 

内存区域的划分

 

运行程序的时候,需要将可执行文件加载到内存中,形成进程。每个进程占据了一块独立的内存区域,这块内存区域又划分成不同的区域,从低地址到高地址依次为:代码区、只读常量区、全局区/数据区、BSS段、堆区、栈区 。

 

代码区:存放可执行指令(如:函数地址)。

只读常量区:存放字面值常量、具有常属性且被初始化的全局和静态局部变量(如:被const关键字修饰的全局变量和被const关键字修饰的静态局部变量)。

全局区/数据区:存放已初始化的全局变量和静态局部变量。

BBS段:存放未初始化的全局变量和静态局部变量,并把它们的值初始化为0。

堆区:存放动态分配的内存。

栈区:存放非静态局部变量。

 

总结:

(1)按照地址从小到大进行排列,进程中的内存区域依次为:代码区 只读常量区 全局区/数据区   BSS段  堆区   栈区。

(2)其中代码区和只读常量区一般统称为代码区,其中全局区/数据区和BSS段一般统称为全局区/数据区。

(3)栈区和堆区之间并没有严格的分隔线,可以进行微调,并且堆区的分配一般按照地址从小到大进行,而栈区的分配一般按照地址从大到小进行分配。

 

虚拟内存管理技术

一切对虚拟内存的越权访问,都将导致段错误。如:试图访问没有映射到物理内存的虚拟内存或试图以非法方式访问虚拟内存,如对只读内存做写操作等。

 

内存空间与地址空间

每个进程的用户空间地址都是在0~3G-1之间,但它们所对应的物理内存却是各自独立的,系统为每个进程的用户空间维护一张专属于该进程的内存映射表,记录虚拟内存到物理内存的对应关系,因此在不同进程之间交换虚拟内存地址是毫无意义的。用户空间的内存映射表会随着进程的切换而不断发生变化。

用户空间与内核空间

所有进程的内核空间地址都是在3G~4G-1之间,它们所对应的物理内存只有一份,系统为所有进程的内核空间维护一张内存映射表 init_mm.pgd,记录虚拟内存到物理内存的对应关系,因此不同进程通过系统调用所访问的内核代码和数据是同一份。内核空间的内存映射表则无需随着进程的切换而发生变化。

使用malloc函数申请动态内存

(1)使用malloc申请动态内存的注意事项。使用mollc函数申请内存时,除了申请所需要的内存大小之外,肯恩能海水申请额外的12字节,用于存储一些管理内存的相关信息,比如内存的大小等等。

       使用malloc申请的内存,一定要注意不要对所申请的内存空间进行越界访问,避免造成数据结构的破坏。

 

(2)使用malloc申请内存大小的一般原则

       一般来说,使用malloc申请比较小的动态内存时,操作系统会一次性分配33个内存页的大小,最终目的就是为了提高效率。

       使用size可以查看程序的内存分配情况。

 

使用free函数释放动态内存

       一般来说,使用malloc比较大的内存时,系统会分配34个内存页,当所申请的内存超过34个内存页时,系统会再次分配33个内存页(也就是按照33个内存页为基本单位分配)。

       对于使用free释放内存时,则释放多少就减少多少,当使用free释放完毕所有内存时,系统可能会保留33个内存页以备再次申请使用,以此提高效率。

 

内存管理的相关函数

(1)getpagesize函数

函数功能:

       主要用于获取当前系统中内存页的大小,一般为4kb。

 

(2)sbrk函数

       #include<unistd.h>

       void*sbrk(intptr_t increment);

函数功能:主要用于调整内存的大小

       参数increment > 0,则表示申请内存空间

       参数increment < 0,则表示释放内存空间

       参数increment = 0,则表示获取内存空间的当前位置

返回值:成功返回调整内存大小之前的位置,失败返回(void*)-1

 

注意:

       一般来说,使用sbrk申请比较小的内存时,系统默认分配一个内存页的大小,一旦申请的内存超过一个内存页时,则再次分配一个内存页(也就是按照一个内存页为基本单位进行分配),而释放内存时,如果释放之后剩下的内存足够用一个内存页表示,则一次性释放一个内存页。

       使用sbrk申请内存时,不会申请额外的空间存储管理内存的相关信息。

       使用sbrk函数申请内存比释放内存更加简单。

 

(3)brk函数

       intbrk(void* addr);

函数功能:

       主要用于根据参数指定的目标位置调整内存大小。

       如果目标位置 > 之前的目标位置 => 申请内存

       如果目标位置 < 之前的目标位置 => 释放内存

       如果目标位置 = 之前的目标位置 => 内存不变

返回值:

       成功返回0,失败返回-1。

 

注意:

       使用brk函数释放内存比较方便,因此一般情况下都使用sbrk函数和brk函数搭配使用,使用sbrk函数负责申请内存,使用brk函数负责释放内存。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。