本帖最后由 dirty 于 2025-1-5 13:24 编辑
本篇梳理阅读笔记学习内存管理章节。
内存管理架构
内存管理架构分为用户空间、内核空间和硬件三个层面。
用户空间
应用程序使用malloc()申请内存,使用free()释放内存。这个是我们很熟悉经常用到的,通常是配对使用。
内核空间
内核空间的基本功能
虚拟内存管理负责从进程的虚拟地址空间分配虚拟页,sys_brk用来扩大或收缩堆,sys_mmap用来在内存映射区域分配虚拟页,sys_munmap用来释放虚拟页。内核使用延迟分配物理内存的策略。页分配器负责分配物理页,当前使用的页分配器是伙伴分配器。内核空间提供了把页划分成小内存块分配的块分配器,提供分配内存的接口kmaloc()和释放内存的接口 kfree(),支持3种块分配器:SLAB分配器、SLUB分配器和SLOP分配器。
内核空间的扩展功能
不连续页分配器提供了分配内存的接口vmalloc和释放内存的接口vfee,在内存碎片化的时候,申请连续物理页的成功率很低,可以申请不连续的物理页,映射到连续的虚页,即虚拟地址连续而物理地址不连续。
硬件层面
处理器包含一个称为内存管理单元(Memory ManagementUnit,MMU)的部件,负责把虚拟地址转换成物理地址。
内存映射
内存映射是在进程的虚拟地址空间中创建一个映射,分为以下两种。通常把文件映射的物理页称为文件页,把匿名映射的物理页称为匿名页。
●文件映射:文件支持的内存映射,把文件的一个区间映射到进程的虚拟地址空间,数据源是存储设备上的文件。
●匿名映射:没有文件支持的内存映射,把物理内存映射到进程的虚拟地址空间,没有数据源。
内存映射的原理如下。
①创建内存映射的时候,在进程的用户虚拟地址空间中分配一个虚拟内存区域。
②Linux内核采用延迟分配物理内存的策略,在进程第一次访问虚拟页的时候,产生缺页异常。如果是文件映射,那么分配物理页,把文件指定区间的数据读到物理页中,然后在页表中把虚拟页映射到物理页;如果是匿名映射,那么分配物理页,然后在页表中把虚拟页映射到物理页。
内存管理子系统提供了以下常用的系统调用。
(1)mmap()用来创建内存映射。
(2)mremap()用来扩大或缩小已经存在的内存映射,可能同时移动。
(3)munmap()用来删除内存映射。
(4)brk()用来设置堆的上界。
(5)remap_file_pages()用来创建非线性的文件映射。
(6)mprotect()用来设置虚拟内存区域的访问权限。
(7)madvise()用来向内核提出内存使用的建议,应用程序告诉内核期望怎样使用指定的虚拟内存区域,以便内核可以选择合适的预读和缓存技术。
在内核空间中可以使用以下两个函数。
(1)remap_pf_range把内存的物理页映射到进程的虚拟地址空间,这个函数的用处是实现进程和内核共享内存。
(2)io_remap_pfrange把外设寄存器的物理地址映射到进程的虚拟地址空间,进程可以直接访问外设寄存器。
书籍这一章讲了差不多三百页,内存管理底层这块讲的也比较详细,面面俱到去梳理学习还是有些浩繁,有需要部分可以作为参考书籍重点查阅。作为Linux应用,了解下用户空间使用以及内核空间、硬件层面的基本原理 会加深对Linux内核的理解,当然,经常用得到的内存管理方面API还是应该熟记、熟用。
应用
下面是mmap、munmap内存映射创建与解除应用代码。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
int main() {
int fd;
struct stat sb;
char *map, *p;
int i;
char c;
// 打开文件
fd = open("try.txt", O_RDWR);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
// 获取文件大小
if (fstat(fd, &sb) == -1) {
perror("fstat");
close(fd);
exit(EXIT_FAILURE);
}
// 将文件映射到内存
map = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
perror("mmap");
close(fd);
exit(EXIT_FAILURE);
}
printf("mmap 将文件映射到内存 OK\r\n");
// 关闭文件描述符,因为文件已经映射到内存
close(fd);
// 处理文件内容(例如,将所有字母转换为大写)
p = map;
for (i = 0; i < sb.st_size; i++) {
c = *p;
if (c >= 'a' && c <= 'z') {
*p = c - 'a' + 'A';
}
p++;
}
printf("将所有字母转换为大写完成\r\n");
// 解除映射
if (munmap(map, sb.st_size) == -1) {
perror("munmap");
exit(EXIT_FAILURE);
}
printf("munmap 解除映射 OK\r\n");
return 0;
}
下面是运行情况,可以看到写入前文本小写字母转为大写字母,内存映射创建与解除均成功。
本篇对内存管理有了初步梳理学习并小试牛刀,主要是学以致用,活学活用,有所取舍,后面继续学习与分享。
|