3. 内存管理
- RAM 的一部分(SRAM)被静态地划分给了内核,用来存放内核代码和静态数据结构。 RAM 的其余部分(如DRAM、SDRAM、DDR)称为动态内存(dynamic memory),这不仅是运行用户进程所需的宝贵资源,也是内核所需的宝贵资源。事实上,整个系统的性能取决于如何有效地管理动态内存。
3.1 内存管理技术
-
页表(page tables):进程在读取指令和存取数据时都要访问内存。在一个虚拟内存系统中,所有的地址都是虚拟地址而非物理地址。操作系统维护一个虚拟地址和物理地址转换的信息(页表),处理器通过这组信息将虚拟地址转换为物理地址。
-
页(page):虚拟内存和物理内存被分为适当大小的块,叫做页,一般其大小为4KB。
-
虚拟地址到物理地址的转换步骤:
- 处理器要找到虚拟地址的页编号和页内偏移量;
- 处理器根据虚拟地址和物理地址的映射关系将虚拟页编号转换为物理页;
- 根据偏移量访问物理页的确定偏移位置。
-
每个物理页面都有一个
struct page
结构,位于include/linux/mm.h
,该结构体包含了管理物理页面时的所有信息,下面给出该结构体的具体描述:typedef struct page {struct list_head list; //指向链表中的下一页struct address_space *mapping; //用来指定我们正在映射的索引节点(inode)unsigned long index; //在映射表中的偏移struct page *next_hash; //指向页高速缓存哈希表中下一个共享的页atomic_t count; //引用这个页的个数unsigned long flags; //页面各种不同的属性struct list_head lru; //用在 active_list 中wait_queue_head_t wait; //等待这一页的页队列struct page **pprev_hash; //指向页高速缓存哈希表中前一个共享的页struct buffer_head * buffers; //把缓冲区映射到一个磁盘块void *virtual;struct zone_struct *zone; //页所在的内存管理区 } mem_map_t;
-
请求页面调度( Demand Paging )
- 为了节省物理内存,只加载执行程序正在使用的虚拟页,这种进行访问时才加载虚拟页的技术叫做 Demand Paging。Linux 使用 demand paging 技术将可执行映像加载到进程的虚拟内存中,在执行命令时,包含命令的文件被打开,将该文件的内容映射到进程的虚拟内存中。这个过程通过修改描述进程内存映射的数据结构来实现,也叫做内存映射(memory mapping),但实际上只有映像的第一部分真正放在了物理内存中,映像的剩余部分仍然在磁盘上。
- 当一个进程试图访问当前不在内存中的虚拟地址时,处理器无法找到引用的虚拟页对应的页表条目,也就无法将虚拟地址转换为物理地址,这时,处理器通知操作系统发生 page fault。此时区分两种情况:
- 出错的虚拟地址无效,则意味着进程试图访问它不应该访问的虚拟地址。这种情况下,操作系统会中断它,从而保护系统中的其他进程。
- 出错的虚拟地址有效,而只是它所在的页当前不在内存中,操作系统应该从磁盘映像中将对应的页加载到内存中。相对内存存取来讲,磁盘存取需要更长的时间,所以进程一直处于等待状态直到该页被加载到内存中。如果系统当前有其他进程可以运行,操作系统将选择其中一个运行;接着将取到的页写进一个空闲页面,并将一个有效的虚拟页条目加到进程的页表中;然后,这个等待的进程重新执行发生内存出错地方的机器指令。本次虚拟内存存取进行时,处理器能够将虚拟地址转换为物理地址,使得进程能够继续运行。
-
页面置换技术( Swapping)
- 如果进程需要将虚拟页放到物理内存中,而此时已经没有空闲的物理页,操作系统必须废弃物理空间中的另一页,为该页让出空间。
- 如果物理内存中需要废弃的页来自磁盘上的映像或者数据文件,并且该页没有被写过不需要存储,则该页被废弃。如果进程又需要该页,它可以从映像或数据文件中再次加载到内存中。
- 但如果该页已经被改变,操作系统必须保留它的内容以便以后进行访问。这种也叫做 dirty page ,当它从物理内存中废弃时,被存到一种叫做交换文件的特殊文件中。由于访问交换文件与访问处理器、物理内存的速度相比较慢,操作系统必须判断是将数据页写到磁盘上还是将它们保留在内存中以便下次访问。
- 如果判断哪些页将被废弃或者交换的算法效率不高,则会发生颠簸(thrashing),这时页不停地被写到磁盘上,然后又被读回,操作系统频繁地处理此读写任务而无法执行实际的工作。
- Linux 使用 LRU(Least Recently Used,最近最少使用置换算法)的页面技术公平地选择需要从系统中废弃的页面。
- Linux 使用伙伴系统算法解决外碎片问题。把所有的空闲页框分为 11 个块链表,每个块链表分别包含 1、 2、 4、 8、 16、 32、 64、 128、 256、 512 和 1024 个连续的页框。对于 1024个页框的最大请求对应着 4MB 大小的连续 RAM 块。 每个块的第一个页框的物理地址是该块大小的整数倍。
- 非连续内存管理,当对内存区的请求不是很频繁的时候,通过连续的线性地址访问非连续的页框,该方法可以避免外碎片,但是其带来的负面因素打乱了内核表。非连续内存区的大小必须是 4KB的倍数。非连续内存区应用的场合分别有分配数据结构给活动的交换区、分配空间给模块和分配缓冲区给某些 I/O 驱动程序。
- 如果进程需要将虚拟页放到物理内存中,而此时已经没有空闲的物理页,操作系统必须废弃物理空间中的另一页,为该页让出空间。
3.2 内存区管理
-
内存区(memory area)是指具有连续的物理地址和任意长度的内存单元序列。
-
伙伴系统采用页框(一般取值4KB)作为基本内存区,适合于大内存的请求,但对小内存的请求容易造成内碎片。伙伴系统的调用是为了获得存放新内存区所需的额外页框,同时也为了释放不再包含内存区的页框,用一个动态链表来记录每个页框所包含的空闲内存区。
-
为了解决内碎片的问题,将内存区大小按几何分布划分,也就是将内存区划分成 2 的幂的大小。不论请求的大小多大,总能保证内碎片小于内存区的 50%。
-
Linux内核建立了13 个按几何分布的空闲内存区链表,大小从 32B~128KB。
-
物理内存被划分为 3 个区来管理,它们是 ZONE_DMA、 ZONE_NORMAL 和ZONE_HIGHMEM。每个区都用
struct zone_struct
结构表示, 定义于 include/linux/mmzone.h:typedef struct zone_struct {/* Commonly accessed fields:*/spinlock_t lock;unsigned long free_pages;unsigned long pages_min, pages_low, pages_high;int need_balance;/* free areas of different sizes */free_area_t free_area[MAX_ORDER];/* Discontig memory support fields.*/struct pglist_data *zone_pgdat;struct page *zone_mem_map;unsigned long zone_start_paddr;unsigned long zone_start_mapnr;/* rarely used fields: */char *name;unsigned long size; } zone_t;
lock
:用来保证对该结构中其他域的串行访问。free_pages
:在这个区中现有空闲页的个数。pages_min
、pages_low
及pages_high
是对这个区最少、次少及最多页面个数的描述。need_balance
:与 kswapd 合在一起使用。free_area
:在伙伴分配系统中的位图数组和页面链表。zone_pgdat
:该管理区所在的存储结点。zone_mem_map
:该管理区的内存映射表。zone_start_paddr
:该管理区的起始物理地址。zone_start_mapnr
:在 mem_map 中的索引(或下标)。name
:该管理区的名字。
3.3 内核获取内存的方式
- 操作系统的内存管理方案优劣是决定其效率高低的重要因素,时间与空间常常作为内存管理方案优劣的衡量指标。首先,分配/释放内存是一个发生频率很高的操作,所以它要求有一定的实时性,另外,内存又是一种非常宝贵的资源,所以要尽量减少内存碎片的产生。下面介绍内核获取内存的几种方式:
- 伙伴算法分配大片物理内存
alloc_pages(gfp_mask, order)
:获得连续的页框,返回页描述符地址,是其他类型内存分配的基础。_get_free_pages(gfp_mask, order)
:获得连续的页框,并返回页框对应的线性地址。线性地址与物理地址是内核直接映射方式,因此该方法不能用于高端内存(896MB以上)。
- slab缓冲区分配小片物理内存
- 内核提供了后备高速缓存机制,称为“slab 分配器”。 slab 分配器实现的高速缓存具有
kmem_cache_t
类型,可通过调用kmem_cache_create
创建:kmem_cache_create
:建立 slab 的高速缓冲区。kmem_cache_alloc
:试图从本地高速缓存区获得一个空闲的对象。kmalloc(gfp_mask, size)
:获得连续的以字节为单位的物理内存,返回线性地址。
- 内核提供了后备高速缓存机制,称为“slab 分配器”。 slab 分配器实现的高速缓存具有
- 非连续内存区分配
vmalloc(size)
:分配非连续内存区,其线性地址连续,物理地址不连续,减少了外碎片,但是其性能低,因为要打乱内核页表。通常只是分配大内存,例如为活动的交互区分配数据结构、加载内核模块时分配空间、为 I/O 驱动程序分配缓冲区。
- 高端内存映射
kmap(struct page * page)
:用于获得高端内存永久内核映射的线性地址 ;kmap_atomic(struct page * page, enum km_type type)
:用于获得高端内存临时内核映射的线性地址
- 固定线性地址映射
set_fixmap(idx, phys)
:把一个物理地址映射到一个固定的线性地址上。set_fixmap_nocache(idx, phys)
:把一个物理地址映射到一个固定的线性地址上,但禁用该页高速缓存。
- 伙伴算法分配大片物理内存