热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

LinuxKernelDevelopmentEdition3——MemoryManagement

LinuxKernelDevelopmentEdition3——Mem

Linux Kernel Development Edition 3——Memory Management

1. 基本概念

x86 32位CPU架构每个进程都具有4GB的虚拟空间,其中0~3GB属于用户空间,不同进程拥有不同的用户空间,进程私有,进程切换时用户空间也跟着切换。3GB ~ 4GB属于内核空间,由内核负责映射,保持固定不变,所有进程空间共享。因此,每个进程享受4GB的虚拟空间。用户进程最多访问0~3GB物理内存,内核进程可以访问所有的物理内存。

逻辑地址/虚拟地址—(分段)—线性地址—(分页)—物理地址

不开启分页的话,线性地址就是物理地址,只存在一个逻辑地址向物理地址的转换

Linux采用分页模式,也可以说采用分段模式,不过Linux的段都是0~4GB,所以逻辑地址和线性地址一样。

在这里插入图片描述

在虚拟空间中内核占据高地址内存,但是在真实的内存空间中,内存从0x0开始存储。对于内核空间来说,逻辑地址到线性地址(物理地址)只是简单的线性映射,即逻辑地址是x,线性地址就是x-PAGE_OFFSET(0XC000000 3GB)就是宏_pa(x)。用户空间的映射就复杂很多了。

在这里插入图片描述

内核中的代码和数据称为内存映像(kernel image)。系统初始化时,保存在0x000100000(1MB)处。在程序运行时,链接程序会将内核地址换成逻辑地址,也即是使用_val(x)加上PAGE_OFFSET。

2. 高端内存

在这里插入图片描述

为什么需要高端内存呢?内核空间只有1GB,全部映射到物理内存的话只有1GB(0x0~0x40000000),无法访问全部物理内存,这不行。因此内核空间设置了一个高端内存。不是全部映射,如果内核想要访问大于896MB时,就从HIGH_MEM即0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完释放。

在这里插入图片描述

核心:借一段地址,映射到物理内存,用完释放。

在这里插入图片描述

x86内核可以访问的物理空间如下图所示,896MB以下由虚拟空间的逻辑地址直接映射,896MB到4GB结束的空间由HIGHMEM的128MB高端内存映射访问。

在这里插入图片描述

看到虚拟空间的代码一般从0x08048000开始,bomb实验深有体会。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PyKrwCqB-1639535666705)(lkd-mm.assets/image-20211214220513135.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mm07Oloh-1639535666706)(lkd-mm.assets/image-20211214220615739.png)]

3. 页、区、

内存管理单元MMU的基本单位是页。page结构体描述的是物理页而非逻辑页,描述的是内存页的信息而不是页中数据。它的描述是短暂的,仅仅记录当前的使用状况,当然也不会描述其中的数据。struct page包含页的状态,比如是不是脏页、引用次数、映射的虚拟地址是什么void* virtual。

在这里插入图片描述

区:有些页是有特定用途的,把这些合在一起就构成了区。

在这里插入图片描述

DMA:直接存储器访问,不通过CPU,直接硬件和内存进行。

ZONE_DMA该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。ZONE_DMA区中的页用来进行DMA(直接内存访问)时使用。

ZONE_HIGHMEM是高端内存,其中的页不能永久的映射到内核地址空间,也就是说,没有虚拟地址。

剩余的内存就属于ZONE_NORMAL区,叫低端内存。

不是所有体系都定义全部区,有些体系结构,比如x86-64可以映射和处理64位的内存空间,所以它没有ZONE_HIGHMEM区,所有的物理内存都都处于ZONE_DMA和ZONE_NORMAL区。

部分结构体如下:

在这里插入图片描述

4. 获取页

4.1 alloc_pages()

在这里插入图片描述
分配2 o r d e r 2^{order}2order个连续的页,返回第一个物理页的指针,gfp_mask是掩码,一些位决定如何分配页的。
在这里插入图片描述
将物理页转换成起始的页地址。
在这里插入图片描述
和alloc_pages()一样,不过直接返回虚拟地址,相当于alloc_pages+page_address
在这里插入图片描述

只需要一个页的分配函数,都是在上述基础上调用alloc_page()
在这里插入图片描述
得到一个填充为0的页
在这里插入图片描述
一些释放页的函数。释放页的时候一定要小心谨慎,内核中操作不同于在用户态,若是将地址写错,或是order写错,那么都可能会导致系统的崩溃。若是在用户态进行非法操作,内核作为管理者还会阻止并发出警告,而内核是完全信赖自己的,若是在内核态中有非法操作,那么内核可能会挂掉的。

在这里插入图片描述

如何使用上述几个函数

4.2 kmalloc()与vmalloc()

上述4.1以页为单位进行分配,kmalloc和vmalloc以字节为单位进行分配。

不同之处在于,kmalloc分配的是虚拟地址连续,物理地址也连续的一片区域,vmalloc分配的是虚拟地址连续,物理地址不一定连续的一片区域。物理地址不连续,就比较麻烦,需要将逻辑地址一个个的添加页表与物理地址相对应。

这里依然需要特别注意的就是使用释放内存的函数kfree与vfree时一定要注意准确释放,否则会发生不可预测的严重后果。

在这里插入图片描述

5. slab层

上述内存分配与管理是以页为单位的,但很多情况使用的更多的数据结构都不足一页,如进程描述符、文件描述符,一页中可以聚集很多个数据结构。

分配和释放数据结构是所有内核中最普遍的操作之一。为了便于数据的频繁分配和回收,常常会用到一个空间链表。它就相当于对象高速缓存以便快速存储频繁使用的对象类型。在内核中,空闲链表面临的主要问题之一是不能全局控制。当可用内存变得紧张的时候,内核无法通知每个空闲链表,让其收缩缓存的大小以便释放一些内存来。实际上,内核根本不知道有这样的空闲链表。为了弥补这一缺陷,也为了是代码更加稳固,linux内核提供了slab层(也就是所谓的slab分类器),slab分类器扮演了通用数据结构缓存层的角色。

核心思想就是“存储池”的运用。内存片段(小块内存)被看作对象,当被使用完后,并不直接释放而是被缓存到“存储池”里,留做下次使用,这无疑避免了频繁创建与销毁对象所带来的额外负载。

在这里插入图片描述
cache由kmem_cache结构体描述,包括三个链表slabs_full、slabs_partial、slabs_empty。一个slab包含一个或者多个page,通常是一个,用slab结构体描述。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

从cache中分配一个object使用kmem_cache_alloc(),释放通过kmem_cache_free()在这里插入图片描述
slab层将空闲页分解成众多相同长度的小块内存,以供同类型的数据结构使用


推荐阅读
author-avatar
214812031_88fe08
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有