Unix系统的内存管理

Unix系统的内存管理

1、什么是内存?

内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大。内存(Memory)也被称为内存储器,其作用是用于暂时存放CPU中的运算数据,以及与硬盘外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。 内存是由内存芯片、电路板、金手指等部分组成的。

 

2、什么是内存管理?

内存管理是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。

 

3、一个进程的内存空间划分

3.1 进程的定义

有些人会将进程、线程、程序这几个概念混淆。

程序是代码编译链接的产物,是存储在硬盘上的文件,程序是静止的,没有运行。

运行起来的程序就是进程,是在内存中运行的程序,也就是把程序加载到了内存中,是运行的。即程序是在硬盘上的,进程是在内存里的。

一个程序可以运行出多个进程,一个进程也可以用多个程序。内存中的每个进程,都在/proc目录下建立一个目录,目录名就是进程ID(PID),进程结束,目录消失。这个目录就是进程的文件方式。cat /proc/进程ID/maps 查看进程的内存分配情况。getpid()函数获取当前进程的ID。

线程这里只做简单的介绍,形象一点,进程是老板,线程是老板的员工。

3.2 进程的内存空间划分

一个进程的内存空间可划分为以下部分:

1、代码区 :用于存放代码(函数)的区域,是只读区。函数指针就是指向代码区的地址。

2、全局区 :用于存放全局变量的区域和静态(static)变量。

3 、BSS段 :用于存放未初始化的全局变量的区域。BSS段在主函数执行之前会清零。

4、栈区(堆栈stack) :用于局部变量的区域,包括函数的参数和非static的局部变量。系统自动管理栈区。

5、堆区(heap):也叫自由区,程序员唯一可以控制的区域,通常内存分配、回收都是在堆区。malloc,free都是在堆区。堆区的内存系统完全不管,程序员全权管理。(进程结束时,所有内存会自动释放)。

6、只读常量区:存放字符串字面值“asd”和const修饰的全局变量。这个区域也是只读区,很多书把只读常量区并入代码区。

地址从小到大排列顺序:

1、代码区

2、只读常量区

3、全局区

4、BSS段

5、堆区

6、栈区

4、虚拟内存地址空间的机制

Unix/Linux使用虚拟内存地址的方式管理内存,每个进程先天都有0-(4G-1)的虚拟内存地址空间,本质就是整数(编号)。虚拟内存地址本身不能储存任何数据,必须映射到物理内存或者硬盘上的文件后 才能存储数据,否则引发段数据,目前程序员接触的都是虚拟内存地址。

虚拟内存的地址分为用户层和内核层,用户层0-3G,3G-4G是内核层(可以设置),用户层不能直接访问内核层,通过系统函数可以。

内存的基本单位是字节,内存地址是逐字节的,但是内存映射的基本单位不是一个字节,是4096字节(4k),叫一个内存页。函数getpagesize()可以查看内存页的大小。

如果地址弄错了,则会引发段错误,常见的段错误原因有两点:

1、使用没有映射的虚拟内存地址存储数据/获取数据。

2、对没有权限的区域进行操作,比如修改只读区。

5、内存管理的相关函数

1、malloc()函数和free()函数

void* malloc(size_t size)

size是要分配的字节数,返回分配内存的首地址。

内存分配的函数要做两件事情:

1、分配虚拟内存地址

2、映射物理内存/硬盘文件--第一次映射,后面用完了再映射。malloc申请小块内存时,一次映射33个内存页,用完后再继续映射,申请大块内存时,(超过31个内存页),会映射比申请稍微多一点的内存页。

malloc()函数除了分配正常的内存空间,还需要额外开辟一些空间,用于

存储一些附加信息,比如分配内存的大小。

free()一定会释放虚拟内存地址,以便虚拟内存地址可以循环利用,不一定

解除映射,超过33个内存页的部分会释放,最后33个内存页不释放,直到

进程结束时释放。

 

2、sbrk()函数和brk()函数

sbrk()和brk()系统的底层会维护一个位置,通过位置的移动完成内存的分配与回收。映射内存时,以一个内存页作为基本单位。

void *sbrk(int increment)

参数increment是增量,增量为正数时,分配内存,增量为负数时,回收内存

执行成功返回之前的地址,失败返回-1.增量为0,取当前位返回移动之前的位置(可用内存的首地址),这个返回值对于增量为负数的情况没有意义。sbrk()函数与malloc()的实现原理完全不同。

sbrk()在分配内存时很方便,但是回收内存时很麻烦。一般用brk()回收内存。

int brk(void* addr),成功返回0,失败返回-1。

brk()的使用方式就是直接传递一个地址过来,做新位置。brk()必须和sbrk()结合使用,获得第一个位置。

 

3、mmap()和munmap() ---Unix的系统函数,更贴近底层

void* mmap(void* addr,size_t length,int prot,int flags,int fd,off_t offset)

参数addr可以指定映射的首地址,一般为0,交给内核指定。length是分配内存的大小,映射时以页为单位。prot是分配内存的权限,一般用PROT_READ|PORT_WRITE。flags时标识,通常包括以下三个:

MAP_SHARED MAP_PRIVATE :二选一,指明映射的内存是否共享,MAP_SHARED只对映射文件有效。

MAP_ANONYMOUS:映射物理内存,默认映射文件。

fd是文件描述符,在映射文件时有用

offset是文件的偏移量,指定映射文件从哪里开始。

映射物理内存时,fd和offset给0即可

返回成功返回首地址,失败返回MAP_FAILED==(void*)-1

 

int munmap(void* addr,size_t length);

取消映射。

需要说明的是,这三对函数都能完成内存的分配与回收,到底应该用哪一个需要根据实际情况来决定。

系统调用

用户层的程序不能直接访问内核层,系统的核心功能必须通过内核层控制。因此,系统提供了一系列的函数,允许用户层的程序通过函数进入内核层,从而完成功能。这些函数,统称为系统调用(System call)。

猜你喜欢

转载自blog.csdn.net/qq_39545674/article/details/86517276