LV10-06-内核内存管理-01-基础知识
本文主要是内核内存管理基础知识的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
Linux开发板 | 华清远见 底板: FS4412_DEV_V5 核心板: FS4412 V2 |
u-boot | 2013.01 |
点击查看本文参考资料
参考方向 | 参考原文 |
--- | --- |
点击查看相关文件下载
文件 | 下载链接 |
--- | --- |
一、虚拟地址
通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间,为了充分利用和管理系统内存资源,Linux采用虚拟内存管理技术,利用虚拟内存技术让每个进程都有 4GB 互不干涉的虚拟地址空间。
不管是用户空间还是内核空间,我们使用的地址都是虚拟地址,当需进程要实际访问内存的时候,会由内核的请求分页机制产生缺页异常,然后调入物理内存页。
二、物理地址
linux 内核将物理内存等分成 N 块 4KB ,称之为一页,每页都用一个 struct page 来表示,采用伙伴关系算法维护(暂时没有深究),所有的物理内存会被内核大概分为 3 个管理区域,分别为:
- ZONE_DMA
DMA内存区域,包含0MB~16MB之间的内存页框,可以由老式基于 ISA 的设备通过 DMA 使用,直接映射到内核的地址空间。
- ZONE_NORMAL
普通内存区域,包含16MB~896MB之间的内存页框,常规页框,直接映射到内核的地址空间。
- ZONE_HIGHMEM
高端内存区域,包含896MB以上的内存页框,不进行直接映射,可以通过永久映射和临时映射进行这部分内存页框的访问。
三、映射关系
虚拟地址和物理地址有怎样的映射关系呢?大概如下图所示:
内核空间虚拟地址各部分详情如下:
【说明】图中high_memory处为虚拟地址3GB + 896MB的位置,PAGE_OFFSET处为虚拟地址3GB位置,也就是内核空间开始的位置。
- 低端内存
内核空间前896MB的空间被称为低端内存,也就是虚拟地址的3GB~3GB+896MB的范围,这一部分的虚拟地址可以直接与896MB大小的物理内存进行映射,加上一个偏移量就可以了,即:虚拟地址 = 3G + 物理地址。可被直接映射的这部分物理内存可以细分为ZONE_DMA和ZONE_NORMAL。
低端内存分配方式:
(1)使用 kmalloc() 进行分配,它是一种小内存分配,采用的是 slab 算法,分配的内存虚拟地址连续,物理地址也连续。
(2)使用 get_free_page() 函数进行分配,它是整页分配,每次分配2的n次方页内存空间,n 最大为10,需要申请大片的内存空间的时候可以采用此种方式,它分配的内存虚拟地址连续,物理地址也连续。
- 高端内存
大于3G+896M的内核空间被称为高端内存,也就是虚拟地址的 3GB+896MB ~4GB的范围,可以细分为vmalloc区、持久映射区、固定映射区
高端内存分配方式:
使用 vmalloc() 函数进行分配,使用该函数申请的内存空间的虚拟地址连续,但是物理地址不连续。
四、内核中常用动态分配
1. kmalloc
1.1 kmalloc()
我们使用以下命令查询一下函数所在头文件:
1 | grep kmalloc -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于动态申请内存,kmalloc() 申请的内存位于直接映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。
【函数参数】
- size:size_t类型,表示要申请的内存空间的大小,注意不要超过128KB。
- flags:
gfp_t
类型,表示分配内存的方法。
点击查看 flags 常用取值
flags | 说明 |
---|---|
GFP_ATOMIC | 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断。 |
GFP_KERNEL | 正常分配内存。 |
GFP_DMA | 给 DMA 控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续)。 |
- 参考用法
1 | flags 的参考用法 |
【返回值】void * 类型,返回分配好的内存的首地址,一般都需要进行强制类型转换,转换为我们需要的类型,分配失败返回NULL。
【使用格式】none
【注意事项】 none
1.2 kzalloc()
我们使用以下命令查询一下函数所在头文件:
1 | grep kzalloc -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于动态申请内存,并且将申请的内存区域初始化为0(可以看出其实是通过调用kmalloc完成的分配)。
【函数参数】
- size:size_t类型,表示要申请的内存空间的大小,注意不要超过128KB。
- flags:
gfp_t
类型,表示分配内存的方法,同 kmalloc()。
【返回值】void * 类型,返回分配好的内存的首地址,一般都需要进行强制类型转换,转换为我们需要的类型,分配失败返回NULL。
【使用格式】none
【注意事项】 none
1.3 kfree()
我们使用以下命令查询一下函数所在头文件:
1 | grep kfree -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于释放通过 kmalloc() 或者 kkzalloc() 函数申请的动态内存。
【函数参数】
- objp:void * 类型,表示要释放的内存空间的首地址。
【返回值】none
【使用格式】none
【注意事项】 none
2. vmalloc
2.1 vmalloc()
我们使用以下命令查询一下函数所在头文件:
1 | grep vmalloc -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于动态申请内存,vmalloc() 函数会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。。
【函数参数】
- size:unsigned long类型,表示要申请的内存空间的大小。
【返回值】void * 类型,返回分配好的内存的首地址,一般都需要进行强制类型转换,转换为我们需要的类型,分配失败返回NULL。
【使用格式】none
【注意事项】 vmalloc() 可以睡眠,因此不能从异常上下文调用。
2.2 vfree()
我们使用以下命令查询一下函数所在头文件:
1 | grep vfree -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于释放通过 vmalloc() 函数申请的动态内存。
【函数参数】
- addr:void * 类型,表示要释放的内存空间的首地址。
【返回值】none
【使用格式】none
【注意事项】 vfree() 可以睡眠,因此不能从异常上下文调用。
3. 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 要快;
4. 选择原则
一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。
总的来说:
(1)小内存(< 128k)用kmalloc,大内存用vmalloc或get_free_page;
(2)如果需要比较大的内存,并且要求使用效率较高时用 get_free_page,否则用vmalloc。