没志青年
发布于 2025-06-14 / 11 阅读
0

11 Linux 内核内存

Linux 内存组织:

函数使用参考:

申请内存

对应释放内存

转为虚拟地址

选择场景

  1. 0~3G 用户空间 :每个应用程序只能使用自己的这份虚拟地址空间

  2. 3G~4G 内核空间:内核使用的虚拟地址空间,应用程序不能直接使用这份地址空间,但可以通过一些系统调用函数与其中的某些空间进行数据通信

实际内存操作时,需要将虚拟地址映射到实际内存的物理地址,然后才进行实际的内存读写

虚拟内存划分:

alloc_pages
alloca_page
__free_pages
page_address
kmap
kmap_atomic
kunmap




连续页分配

(1)申请

1、申请多页内存:

struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
  • order:分配 2^{order}页,order 为 0 就是 1 页

  • gfp_mask:分配标志

    • __GFP_HIGHMEM

为了更方便使用:

  • GFP_USER:

  • GFP_KERNEL:

  • GFP_DMA:

返回值:

  • 成功:指向第一个页描述符 (struct page) 的指针

  • 失败:NULL

2、申请一页内存

struct page *alloc_page(gfp_t gfp_mask)

(2)释放申请的内存

void __free_pages(struct page *page, unsigned int order)

(3)虚拟地址的获取

(4)组合函数

__get_free_pages
_get_free_page
get_zerod_page
free_pages
free_page

这些函数将前面的先获取 page 结构指针再转换为虚拟地址,包装了一下,合二为一。

  • __get_free_pages = alloc_pages +

  • _get_free_page =

  • get_zerod_page=

非连续页分配

vmallov
vzalloc
vfree

slab 分配器

kmem_cache_create
kmem_cache_alloc
kmem_cache_free
kmem_cache_destroy

kmalloc
kzalloc
kfree

I/O 内存

SOC 中的外设的寄存器,这种需要访问硬件地址操作的,Linux 的虚拟内存中无法直接使用,需要将虚拟地址映射到物理地址才行。

#include <linux/io.h>

1、申请/释放 IO 内存区域

struct resource *request_mem_region(resource_size_t start, resource_size_t n, const char *name)
void release_mem_region(resource_size_t start, resource_size_t n)

2、IO 内存映射/取消映射

3、访问映射后的 IO 内存

unsigned readb(void *addr);//1字节   或ioread8(void *addr)
unsigned readw(void *addr);//2字节   或ioread16(void *addr)
unsigned readl(void *addr);//4字节   或ioread32(void *addr)
/*
功能:读取寄存器的值
参数:addr  地址
返回值:读到的数据
*/
​
void writeb(unsigned value, void *addr);//1字节   或iowrite8(u8 value, void *addr)
void writew(unsigned value, void *addr);//2字节  或iowrite16(u16 value, void *addr)
void writel(unsigned value, void *addr);//4字节  或iowrite32(u32 value, void *addr)
/*
 功能:向指定的寄存器中,写入数据。
 参数:value:待写入寄存器中的数据
      Address:寄存器的虚拟地址
*/

IO内存访问接口:

static inline void __iomem *ioremap(unsigned long offset, unsigned long size)
/*
功能:实现IO管脚的映射
参数:offset:该管脚的偏移地址
     Size:该管脚映射空间的大小,几个字节
返回值:成功返回映射的虚拟地址,失败NULL
*/
​
static inline void iounmap(volatile void __iomem *addr)
/*
功能:解除io管脚的映射
参数:addr:io管脚映射的地址
*/
​

1 ioremap 函数的使用: 函数原型:

void __iomem *ioremap(resource_size_t res_cookie, size_t size)

使用时,要包含头文件:

#include <asm/io.h>

作用:

把物理地址 phys_addr 开始的一段空间(大小为 size),映射为虚拟地址;返回值是该段虚拟地址的首地址。

virt_addr = ioremap(phys_addr, size);

实际上,它是按页(4096 字节)进行映射的,是整页整页地映射的。

假设 phys_addr = 0x10002,size=4,ioremap 的内部实现是:

a) phys_addr 按页取整,得到地址 0x10000

b) size 按页取整,得到 4096

c) 把起始地址 0x10000,大小为 4096 的这一块物理地址空间,映射到虚拟地址空间,假设得到的虚拟空间起始地址为 0xf0010000

d) 那么 phys_addr = 0x10002 对应的 virt_addr = 0xf0010002

不再使用该段虚拟地址时,要 iounmap(virt_addr):

void iounmap(volatile void __iomem *cookie)

物理地址映射成虚拟地址

void __iomem *ioremap(phys_addr_t addr, size_t size);
参数:
@addr  物理地址
@size  物理地址大小
返回值:
成功返回虚拟地址,失败返回NULL


DMA


kmalloc

函数原型:

void *kmalloc(size_t size, gfp_t flags);

kmalloc() 申请的内存位于直接映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。    较常用的 flags(分配内存的方法):

  • GFP_ATOMIC —— 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断,分配不到直接返回错误;

  • GFP_KERNEL —— 正常分配内存,分配不到阻塞等待;

  • GFP_DMA —— 给 DMA 控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续)。

flags 的参考用法:

使用场景

flags

进程上下文,可以睡眠

GFP_KERNEL

异常上下文(中断处理程序、软中断、Tasklet),不可以睡眠

GFP_ATOMIC

用于DMA的内存,可以睡眠

GFP_DMA | GFP_KERNEL

用于DMA的内存,不可以睡眠

GFP_DMA | GFP_ATOMIC

kzalloc 是强制清零的 kmalloc 操作,用法和 kmalloc 一样

void *kzalloc(size_t size, gfp_t flags)

对应的内存释放函数为:

void kfree(const void *objp);

vmalloc

void *vmalloc(unsigned long size);

  vmalloc() 函数则会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。

对应的内存释放函数为:

void vfree(const void *addr);

注意:vmalloc() 和 vfree() 可以睡眠,因此不能从异常上下文调用。

kmalloc & vmalloc 的比较

kmalloc()、kzalloc()、vmalloc() 的共同特点是:

  1. 用于申请内核空间的内存;

  2. 内存以字节为单位进行分配;

  3. 所分配的内存虚拟地址上连续;

kmalloc()、kzalloc()、vmalloc() 的区别是:

  1. kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc)

  2. kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;

  3. kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;

  4. kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞;

  5. kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快;

一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。

使用原则:

  1. 小内存(< 128k)用kmalloc,大内存用vmalloc或get_free_page

  2. 如果需要比较大的内存,并且要求使用效率较高时用get_free_page,否则用vmalloc