漫谈操作系统6 -- 内存管理

操作系统的内存管理是操作系统对用户态程序访问物理内存进行抽象的一个范例,其中包括内存管理的方方面面,在操作系统的内核态其一般包括为每个用户态进程分配线性地址空间的线性地址空间管理, 实际分配物理内存的页分配器,伙伴系统, 为操作系统的内核数据结构分配内存的slab/slub分配器等。而对于用户态程序来说,POSIX接口兼容的操作系统统一对上提供sbrk系统调用接口,以及mmap等内存映射的接口。 


1. glibc/libc 中的 malloc/free

malloc/free是我们在编写用户态程序中最常用到的内存分配和释放接口(高级语言调用的内存管理函数很多是对此接口之上的封装), 其本质是为用户态的进程申请堆内存。首先当堆空间不足时,malloc会调用sbrk接口从内核申请虚拟内存空间(这里只是虚拟空间,不一定立即分配了内存), 然后malloc在得到的空间内部使用其内建的内存管理算法对其空间进行管理,当前比较常见的算法有比如facebook的jemalloc, 内存分配届的常青树dlmalloc等,其作用都是在一片大的内存之上将其按照malloc的大小分割成小块,当free的时候将这些小块释放并尝试合并。 一个简单的内存管理算法如下图

漫谈操作系统6 -- 内存管理_第1张图片

而复杂的内存管理算法如jemalloc,会根据线程划分成arena,然后其组织管理内存块的思路类似slab分配器,如下图所示

漫谈操作系统6 -- 内存管理_第2张图片


2. 内核中的线性地址空间管理

当用户态进程从内核申请内存时,或者是新的进程创建时,其进程的线性内存空间中的内存没有完全被映射到物理内存之上,比如当新的进程创建是,其大部分的内存地址由于写时复制的原则实际上指向的是和父进程相同的物理内存,而当其数据段发生写操作时才会产生写时复制的page fault并由内核申请物理内存页,然后建立新的从线性地址向物理地址的映射关系,然后进程完成写操作。 所以当我们打开文件,或者申请内存之后,操作系统并不是理解就用相应的物理内存作为对应,而是先把内存虚拟地址空间划出来,然后通过缺页的方式实际分配内存,然后供进程完成读写的操作。例如Linux对应的线性地址空间管理如下图

漫谈操作系统6 -- 内存管理_第3张图片

我们可以看到,在Linux中的进程描述结构体中有一个 mm成员,其标定了当前进程的各个内存段的虚拟地址范围,而其虚拟地址对应的可以是文件,库,可执行代码等,当缺页产生的时候如果是文件则会通过文件系统和bio系统将所需的内容从磁盘读入内存,然后完成对文件的读操作。 此类操作有可能会阻塞当前进程,结合前面讲过的调度的部分,当此中情况发生时就会将执行的线程suspend,并阻塞在相应的资源之上,当资源被满足后会重新唤醒此阻塞的线程。


4. 内核中的内存管理

内核中的核心数据结构都是不会产生缺页中断的,此类内存叫做穿线内存或者叫wired memory,它们不会被换出会常住在系统的物理内存中,如slub/slab分配的 task_struct/proc 这类核心数据结构。当系统通过slab分配器申请task_struct数据结构时,其首先会向task_struct对应的slab分配器查看有没有空余的task_struct结构体,如果没有那么会通过page alloctor分配整页,然后初始化为需要的bucket,再从相应的bucket中拿到一个新的task_struct结构体,而free的过程是将此结构体标记为空闲,供下次分配直接使用。

而另外的内存分配器主要指分配连续大块内存的分配器其一般直接按照页来分配内存,对于Linux来说最终会通过伙伴系统分配相应的物理内存,而对于FreeBSD来说其page allocator直接后端是zone allocator来分配内存。


此处内核的内存管理有一个很重要的内容就是对于UMA架构的处理器来说,CPU到内存的访问速度相等,所以没有必要将内存划分为区域, 而对于NUMA架构的处理器,由于其特殊性,内核的内存分配器会根据CPU访问内存区域速度的不同将内存划分为不同的zone,然后在内存分配时根据就近的zone来分配内存, 相应的其调度器也遵循同样的规则,尽量不会将进程跨跃zone来进行调度。


漫谈操作系统6 -- 内存管理_第4张图片

上图为一个4 NODE的NUMA架构计算机,跨NODE访问内存会通过crossbar总线进行,其效率会极度下降,而UMA相当于其中的一个NODE。普通的多核处理器大多是UMA架构, 主板上有多个socket CPU的一般为NUMA架构。






你可能感兴趣的:(操作系统)