linux-kernel-address-space

funciton call
linux_storage.png
linux_addree_map.png
linux_vm_area_struct.png

Linux内核地址空间

内存分为三个区段: 1. 用于DMA的内存,2. 常规内存,3.高端内存

内核空间分配api

kmalloc and kzalloc

Description

kmalloc is the normal method of allocating memory for objects smaller than page size in the kernel.

最常用的内核空间内存分配函数。最终是通过调用get_free_pages来实现.kmalloc()是基于slab/slob/slub分配分配算法上实现的,不少地方将其作为slab/slob/slub分配算法的入口,实际上是略有区别的。

最小分配大小 32/64 Byte 最大 128 KB -16

flags argument

GFP_USER - Allocate memory on behalf of user. May
sleep.
GFP_KERNEL - Allocate normal kernel ram. May sleep.
GFP_ATOMIC - Allocation will not sleep. May use emergency pools. For example, use this inside interrupt handlers.
GFP_HIGHUSER - Allocate pages from high memory.

貌似也能从高端内存分配 并不能,是说可能会从高端内存中分配,具体由平台决定

物理内存只能按页面进行分配,不超过128KB

get_free_page

参数类似于kmalloc.基于buddy机制

alloc_pagesLinux页分配器的核心函数alloc_pages_node的简单宏,主要处理了NUMA

vmalloc

vmalloc Description

分配虚拟地址空间的连续区域,尽管可能在物理地址上不是连续的。

访问每个页面需要单独调用alloc_pages.通过红黑树管理

说明

Linux内存分配的基础。 内存分配使用效率不高,如可能,应该直接对页面进行分配。

以上三个函数获得的内存地址均为虚拟地址。但kmalloc、get_free_pages地址与物理内存只差基于PAGE_OFFSET的偏移。不需要修改页表

kmalloc()分配的内存处于3GB~high_memory之间,而vmalloc()分配的内存在VMALLOC_START~4GB之间,也就是非连续内存区。

一般情况下在驱动程序中都是调用kmalloc()来给数据结构分配内存,而vmalloc()用在为活动的交换区分配数据结构,为某些I/O驱动程序分配缓冲区,或为模块分配空间

vmalloc则不同。且分配得到的地址是不能在处理器之外使用。

正确使用场合: 分配一大堆连续的,只在软件中存在的,用于缓冲的内存区域

且其不能在原子上下文中使用,因为其内部实现调用了kmalloc来获取页表的存储空间,因而可能休眠

ioremap

vmalloc不同的是,ioremap并不实际分配内存。其返回的内存地址不能够被直接访问,需要通过特定的接口来操作。

vmalloc相同的是,ioremap也是面向页的,会新建页表

Linux的内存管理

linux_storage.png

地址类型

用户虚拟空间地址

内核地址空间

  • 内核逻辑地址

      与物理地址只差一个偏移量,如`kmalloc`
    
  • 内核虚拟地址

       不必是线性对应一对一。
    

__pa(),__va()完成相应的转换

高端内存和低端内存

内核无法直接操作没有映射到内核地址空间的内存。

为了获得更多的地址空间,处理器增添了地址扩展特性。

  • 只有内存的低端部分拥有逻辑地址 剩余的部分(高端内存)没有

  • 访问高端内存前,内核必须建立明确的虚拟映射

内存管理结构

页结构struct page

因为高端内存无法使用逻辑地址,且为了保存物理内存信息,对系统中每个物理页,都有一个page结构相对应。

  • atomic_t count;

    这个页的引用数. 当这个 count 掉到 0, 这页被返回给空闲列表.

  • void *virtual;

    这页的内核虚拟地址, 如果它被映射; 否则, NULL. 低内存页一直被映射; 高内存页常常不是. 这个成员不是在所有体系上出现; 它通常只在页的内核虚拟地址无法轻易计算时被编译. 如果你想查看这个成员, 正确的方法是使用 page_address 宏.

  • struct page virt_to_page(void kaddr)

    这个宏, 定义在 <asm/page.h>, 采用一个内核逻辑地址并返回它的被关联的struct page指针. 因为它需要一个逻辑地址, 它不能操作来自 vmalloc 的内存或者高端内存

  • struct page *pfn_to_page(int pfn)

    为给定的页帧号返回 struct page指针. 如果需要, 它在传递给 pfn_to_page之前使用pfn_valid来检查一个页帧号的有效性.

  • void page_address(struct page page)

    返回这个页的内核虚拟地址, 如果这样一个地址存在. 对于高端内存, 那个地址仅当这个页已被映射才存在. 这个函数在<linux/mm.h> 中定义. 大部分情况下, 你应该使用 kmap 而不是 page_address

kmap映射

include <linux/highmem.h>

void kmap(struct page page);

void kunmap(struct page *page);

  • kmap 为系统中的任何页返回一个内核虚拟地址. 对于低端内存页, 它只返回页的逻辑地址; 对于高内存, kmap 在内核地址空间的一个专用部分中创建一个特殊的映射. 使用 kmap 创建的映射应当一直使用 kunmap 来释放;

  • 一个有限数目的这样的映射可用, 因此最好不要在它们上停留太长时间. kmap 调用维护一个计数器, 因此如果 2 个或 多个函数都在同一个页上调用 kmap,也是正确的. 还要注意 kmap 可能睡眠当没有映射可用时.

  • 如块驱动中make_request函数需要对bvec中的page进行kmap映射以得到可以操作的内核虚拟地址

页表与VMA

  • 页表
    将虚拟地址转化为物理地址

  • VMA

    虚拟内存区( VMA )用来管理一个进程的地址空间的不同区域的内核数据结构. 一个 VMA 代表一个进程的虚拟内存的一个同类区域: 一个有相同许可标志和被相同对象(如, 一个文件或者交换空间)支持的连续虚拟地址范围. 它松散地对应于一个"段"的概念, 尽管可以更好地描述为"一个有它自己特性的内存对象". 一个进程的内存映射有下列区组成:

    • 给程序的可执行代码(常常称为 text)的一个区.

    • 给数据的多个区, 包括初始化的数据(它有一个明确的被分配的值, 在执行开始), 未初始化数据(BBS),以及程序堆栈.

    • 给每个获得的内存映射的一个区域.

vm_area_struct结构

linux_vm_area_struct.png

  • 当用户空间调用mmap完成地址映射时,系统会同时创建一个vm_area_struct

  • 当驱动设备需要实现mmap接口时,会需要对此结构的函数进行操作

  • <linux/mm.h>中定义

struct mm_struct

  • 每个进程都拥有一个struct mm_struct <linux/sched.h>负责整合其他的数据结构.

  • 其中包含了:虚拟内存区域链表,页表以及相关内存管理信息,还包括自旋锁以及信号量。

  • 通常的方法是使用 current->mm 来获得此结构. 注意内存关联结构可在进程之间共享; Linux 线程的实现以这种方式工作

内存映射

  • 内存映射可被实现来提供用户程序对设备内存的直接存取。意味着关联一些用户空间地址到设备内存

  • mmap限制以PAGE_SIZE为单位进行映射。可以极大提升吞吐量

  • mmapfile_operations的结构一部分。

mmap原型

  • int (*mmap) (struct file *filp, struct vm_area_struct *vma);

  • 为了执行mmap只需要为该地址建立页表,并将vma->vm_ops替换成一系列新操作。

建立页表的方法

使用remap_pfn_range一次性建立

int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);

int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t prot);

负责为一段物理地址建立新的页表。

第一个函数当pfn只想实际物理RAM时使用。后者是当指向I/O内存时使用。

使用nopage_VMA每次建立一个页表

struct page *(*nopage)(struct vm_area_struct *vma,unsigned long address,int *type)

地址映射关系

  • 在进程的task_struct 结构中包含一个指向 mm_struct 结构的指针,mm_strcut 用来描述一个进程的虚拟地址空间。

  • 进程的 mm_struct 则包含装入的可执行映像信息以及进程的页目录指针pgd。该结构还包含有指向 vm_area_struct 结构的几个指针,每个vm_area_struct代表进程的一个虚拟地址区间。

  • vm_area_struct 结构含有指向vm_operations_struct 结构的一个指针,vm_operations_struct 描述了在这个区间的操作。

  • vm_operations 结构中包含的是函数指针;其中,open、close 分别用于虚拟区间的打开、关闭,而nopage 用于当虚存页面不在物理内存而引起的“缺页异常”时所应该调用的函数,当Linux处理这一缺页异常时(请页机制),就可以为新的虚拟内存区分配实际的物理内存。

linux_addree_map.png

参考链接

Author: hjl
Link: https://huqianshan.github.io/Linux/linux-kernel-address-space/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.