Linux
内存管理(下)
物理内存管理(页管理)
Linux
内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数
4k(
在
i386
体系结构中
)
大小页,从而分配和回收内存的基本单位便是内存页了。利用分页管理有助于灵活分配内存地址,因为分配
时不必要求必须有大块的连续内存
,系统可以东一页、西一页的凑出所需要的内存供进程使用。虽然如
此,但是实际上系统使用内存还是倾向于分配连续的内存块,因为分配连续内存时,页表不需要更改,因此能降低
TLB
的刷新率(频繁刷新会很大增加访问速度)。
鉴于上述需求,内核分配物理页为了尽量减少不连续情况,采用了“伙
伴”关系来管理空闲页框。伙伴关系分配算法大家不应陌生——几乎所有操作系统书都会提到
,
我们
不去详细说它了,如果不明白可以参看有关资料。这里只需要大家明白
Linux
中
空闲页面的组织和管理利用了伙伴关系,因此空闲页面分配时也需要遵循伙伴关系,最小单位只能是
2
的幂
倍页面大小。内核中分配空闲页框的基本函数是
get_free_page/get_free_pages
,它们或是分配单页或是分配指定的页框(
2
、
4
、
8…512
页)。
注意:
get_free_page
是在内核中分配内存,不同于
malloc
在用户空间中分配,
malloc
利用堆动态分配,实际上是调用
brk()
系统调用,该调用的作用是扩大或缩小进程堆空
间(它会修改进程的
brk
域)。如果现有的内存区域不够容纳堆空间,则会以页面大小的倍数位单位,扩张或收缩对应的内存区域,但
brk
值并非以页面大小为倍数修改,而是按实际请求修改。因此
Malloc
在用户空间分配内存可以以字节为单位分配
,
但内核在内部仍然会是以页为单
位分配的。
另外需要提及的是,物理页在系统中由页框结构
struct paga
描述,系统
中所有的页框存储在数组
mem_map[]
中,可以通过该数组找到系统中的每一页(空闲或非空闲)。而其中的空闲页框则可由上述提到的以伙伴
关系组织的空闲页链表(
free_area[MAX_ORDER]
)
索引。
内核内存使用
Slab
所谓尺有所长,寸有所短。以页为最小单位分配内存对于内核管理系统物理内存来说的确比较方便,但内
核自身最常使用的内存却往往是很小(远远小于一页)的内存块——比如存放文件描述符、进程描述符、虚拟内存区域描述符等行为所需的内存都不足一页。这些用
来存放描述符的内存相比页面而言,就好比是面包屑与面包。一个整页中可以聚集多个这种这些小块内存;而且这些小块内存块也和面包屑一样频繁地生成
/
销毁。
为了满足内核对这种小内存块的需要,
Linux
系统采用了一种被称为
slab
分
配器的技术。
Slab
分配器的实现相当复杂,但原理不难,其核心思想就是“存储池
”
的运用。内存片段(小块内存)被看作对象,当被使用完后,并不直接释放而是被缓存到“存储池”里,
留做下次使用,这无疑避免了频繁创建与销毁对象所带来的额外负载。
Slab
技术不但避免了内存内部分片(下文将解释)带来的不便(引入
Slab
分配器的主要目的是为了减少对伙伴系统分配算法的调用次数——
频繁分配和回收必然会导致内存碎片——难以找到大块连续的可用内存)
,而且可以很好利用硬件缓存提高访问速度。
Slab
并非是脱离伙伴关系而独立存在的一种内存分配方式,
slab
仍然是建立在页面基础之上,换句话说,
Slab
将页面(来自于伙伴关系管理的空闲页框链)撕
碎成众多小内存块以供分配,
slab
中的对象分配和销毁使用
kmem_cache_alloc
与
kmem_cache_free
。
Kmalloc
Slab
分
配器不仅仅只用来存放内核专用的结构体,它还被用来处理内核对小块内存的请求。当然鉴于
Slab
分
配器的特点,一般来说内核程序中对小于一页的小块内存的求情才通过
Slab
分
配器提供的接口
Kmalloc
来完成(虽然它可分配
32
到
131072
字节的内存)。从内核内存分配角度讲
kmalloc
可被看成是
get_free_page
(
s
)的一个有效补充,内存分配粒度更灵活了。
有兴趣的话可以到
/proc/slabinfo
中找到内核执行现场使用的各种
slab
信息统计,其中你会看到系统中所有
slab
的使用信息。从信息中可以看到系统中除了专用结构体使用的
slab
外,还存在大量为
Kmalloc
而准备的
Slab
(其中有些为
dma
准备的)。
内核非连续内存分配(
Vmalloc
)
伙伴关系也好、
slab
技术也好,从内存管理理论角度而言目的基本是一致的,它们都是为了防止“分片”,不过分片又分为外部
分片和内部分片之说,所谓内部分片是说系统为了满足一小段内存区(连续)的需要,不得不分配了一大区域连续内存给它,从而造成了空间浪费;外部分片是指系
统虽有足够的内存,但却是分散的碎片,无法满足对大块“连续内存”的需求。无论何种分片都是系统有效利用内存的障碍。
slab
分配器使得含与一个页面内众多小块内存可独立被分配使用,避免了内部分片,节约了空闲内存。伙伴关系
把内存块按大小分组管理,一定程度上减轻了外部分片的危害,因为页框分配不在盲目,而是按照大小依次有序进行,不过伙伴关系只是减轻了外部分片,但并未彻
底消除。你自己笔画一下多次分配页框后,空闲内存的剩余情况吧。
所以避免外部分片的最终思路还是落到了如何利用不连续的内存块组合
成“看起来很大的内存块”——这里的情况很类似于用户空间分配虚拟内存,内存逻辑上连续,其实影射到并不一定连续的物理内存上。
Linux
内核借用了这个技术,允许内核程序在内核地址空间中分配虚拟地址,同样也利用页表(内核页表)将虚拟
地址影射到分散的内存页上。以此完美地解决了内核内存使用中的外部分片问题。内核提供
vmalloc
函数分配内核虚拟内存,该函数不同于
kmalloc
,它可以分配较
Kmalloc
大得多的内存空间(可远大于
128K
,
但必须是页大小的倍数),但相比
Kmalloc
来说
Vmalloc
需要对内核虚拟地址
进行重影射,必须更新内核页表,因此分配效率上要低一些(用空间换时间)
与用户进程相似内核也有一个名为
init_mm
的
mm_strcut
结构来描述内核地址空间,其中页表项
pdg=swapper_pg_dir
包含了系统内核空间(
3G
-4G
)的映射关系。因此
vmalloc
分配内核虚拟地址必须更新内核页表,而
kmalloc
或
get_free_page
由于分配的连续内存,所以不需要更新内核页表。
malloc
、
fork
、
excute
、
mmap
|
vmalloc
分
配的内核虚拟内存与
kmalloc/get_free_page
分配的内核虚拟内存位于不同的区间,不会重叠。因为内核虚拟空间被分区管理,各司其职。
进程空间地址分布从0到3G
(
其实是到
PAGE_OFFSET,
在
0x86
中它等于
0xC0000000)
,从
3G
到
vmalloc_start
这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页框表
mem_map
等等)比如我使用的系统内存是
64M
(
可以用
free
看到
)
,那么
(3G
——
3G
+64M)
这片内存就应该映射物理内存,而
vmalloc_start
位置应在
3G
+64M
附近(说附近因为是在物理内存映射区与
vmalloc_start
期间还回存在一个
8M
大小的
gap
来防止跃界)
,vmalloc_end
的位置接近
4G
(
说接近是因为最后位置系统会保留一片
128k
大小的区域用于专用页面映射,还由可能会由高端内存映射区,这些都是细节,这里我们不做纠缠
)
。
上图是内存分布的模糊轮廓
由
get_free_page
或
Kmalloc
函数所分配的连续内存都陷于物理映射区域,所以它们返回的内核虚拟地址和实际物理地址仅仅是相差一个
偏移量(
PAGE_OFFSET
),你可以很方便的将其转化为物理内存地址,同时内核也提供了
virt_to_phys
()函数将内核虚拟空间中的物理影射区地址转化为物理地址。要知道,物理内存映射区中的地址与内核
页表是有序对应,系统中的每个物理页框都可以找到它对应的内核虚拟地址(在物理内存映射区中的)。
而
vmalloc
分配的地址则限于
vmalloc_start
与
vmalloc_end
之间。每一块
vmalloc
分配的内核虚拟内存都对应一个
vm_struct
结构体(可别和
vm_area_struct
搞混,那可是进程虚拟内存区域的结构),不同的内核虚拟地址被
4k
打大小空闲区的间隔,以防止越界——见下图)。与进程虚拟地址的特性一样,这些虚拟地址可与物理内
存没有简单的位移关系,必须通过内核页表才可转换为物理地址或物理页。它们有可能尚未被映射,在发生缺页时才真正分配物理页框。
这里给出一个小程序帮助大家认请上面几种分配函数所对应的区域。
#include<linux/module.h>
#include<linux/slab.h>
#include<linux/vmalloc.h>
unsigned char
*pagemem;
unsigned char
*kmallocmem;
unsigned char
*vmallocmem;
int
init_module(void)
{
pagemem
= get_free_page(0);
printk("<1>pagemem=%s",pagemem);
kmallocmem
= kmalloc(100,0);
printk("<1>kmallocmem=%s",kmallocmem);
vmallocmem
= vmalloc(1000000);
printk("<1>vmallocmem=%s",vmallocmem);
}
void
cleanup_module(void)
{
free_page(pagemem);
kfree(kmallocmem);
vfree(vmallocmem);
}
内存管理实例
代码功能介绍
我们希望能通过访问用户空间的内存达到读取内核数据的目的,这样便
可进行内核空间到用户空间的大规模信息传输。
具体的讲,我们要利用内存映射功能,将系统内核中的一部分虚拟内存
映射到用户空间,从而使得用户空间地址等同与被映射的内核内存地址。
代码结构体系介绍
内核空间内存分配介绍
因此我们将试图写一个虚拟字符设备驱动程序,通过它将系统内核空间映射到用户空间
——将内核虚拟内存映射到用户虚拟地址。当然映射地址时少不了定位内核空间对应的物理地
址,并且还要建立新的用户页表项,以便用户进程寻址时能找到对应的物理内存。
从中应该看出,需要我完成既定目标,我们需要获得:被映射内核空间物理地址
和
建立对应的用户进程页表
。
在内核空间中主要存在
kmalloc
分配的物理连续空间和
vmalloc
分配的非物理连续空间。
kmalloc
分配的空间往往被称为内
核逻辑地址
,由于它是连续分配(直接处理物理页框),而且分配首地址一定,所以其分配的内核虚拟地址对应的实际物理地址很容
易获得:内核虚拟地址—
PAGE_OFFSET
(
0xC0000000
)(内核有对应例程
virt_to_phys
)即等于物理地址,而且其对应的页表属于内核页表(
swapper_pg_dir
)
——在系统初始
化时就以建立,因此省去了建立页表的工作。
而
vmalloc
分配的空间被称为内核虚拟地址
,它的问题相对
要复杂些,这是因为其分配的内核虚拟内存空间并非直接操作页框,而是分配的是
vm_struct
结构。该结构逻辑上连续但对应的物理内存并非连续,也就是说它
vamlloc
分配的内核空间地址所对应的物理地址并非可通过简单线性运算获得。从这个意义上讲,它的物理地址在
分配前是不确定的,因此虽然
vmalloc
分配的空间与
kmalloc
一样都是由内核页表来映射的,但
vmalloc
分配内核虚拟地址时必须更新
内核页表。
注释:
vmalloc
分配的内核虚拟内存与
kmalloc/get_free_page
分配的内核逻辑内存位于不同的
区间,不会重叠。因为内核空间被分区管理,各司其职。
进程空间地址分布从0到3G
(
其实是到
PAGE_OFFSET,
在
0x86
中它等于
0xC0000000)
,从
3G
到
vmalloc_start
这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页框表
mem_map
等等)比如我使用的系统内存是
64M
(
可以用
free
看到
)
,那么
(3G
——
3G
+64M)
这片内存就应该映射物理内存,而
vmalloc_start
位置应在
3G
+64M
附近(说附近因为是在物理内存映射区与
vmalloc_start
期间还回存在一个
8M
大小的
gap
来防止跃界)
,vmalloc_end
的位置接近
4G
(
说接近是因为最后位置系统会保留一片
128k
大小的区域用于专用页面映射,还由可能会由高端内存映射区,这些都是细节,这里我们不做纠缠
)
。
另一个需要澄清的是,
vmalloc
分配的内核空间,其
结构是
vm_area
,可千万别与用户空间
malloc
分配的
vm_area_struct
结构混淆。前者由内核页表映射,而后者则由用户页表映射。
上图是内存分布的模
糊轮廓
实例蓝图
为了近可能丰富我们的例子程序的场景,我们选择映射
vmalloc
分配的内核虚拟空间
(
下面我
们简称为
vk
地址
)
到用户空间。
要知道用户进程操作的是虚拟内存区域
vm_area_struct
,我们此刻需要将用户
vma
区间利用用户页表映射到
vk
对应的物理内存上去(如下图所示)。这里主要工作便是建立用户也表项完成映射工作,而这个工作完全
落在了
vma->nopage
操作上,该方法会帮助我们在发生“缺页”时,动态构造映射所需物理
内存的页表项。
我们需要实现
nopage
方法,动态建立对应页表,而在该方法中核心任务是寻找到
vk
地址
对应的内核逻辑地址
。这必然需要我们做以下工作:
a)
找到
vmalloc
虚拟内存对应的内核页表,并寻找到对应的内核页表项。
b)
获取内核页表项对应的物
理页框指针。
c)
通过页框得到对应的内核逻辑地址。
基本函数
我们实例将利用一个虚拟字符驱动程序,驱动负责将一定长的内核虚拟
地址
(vmalloc
分配的
)
映射到设备文件上,以便可以通
过访问文件内容来达到访问内存的目的。这样做的最大好处是提高了内存访问速度,并且可以利用文件系统的接口编程(设备在
Linux
中作为特殊文件处理)访问内存,降低了开发难度。
Map_driver.c
就是我们的虚拟字符驱动程序,不用说它要实现文件操作表(
file_operations
——
字符驱动程序主
要做的工作便是实现该结构
)
中的
,
为了要完成内存映射
,
除了常规的
open/release
操作外
,
必须自己实现
mmap
操作
,
该函数将给定的文件映射到指定的地址空间上,也就是说它将负责把
vmalloc
分配的内核地址映射到我们的设备文件上。
我们下面就谈谈
mmap
操
作的实现细节:
文件操作的
mmap
操
作是在用户进行系统调用
mmap
时被执行的,而且在调用前内核已经给用户找到并分配了合适的虚拟内存区域
vm_area_struct
,这个区域将代表文件内容,所以剩下要做的便是如何把虚拟区域和物理内存挂接到一起了,即构造页
表。由于我门前面所说的原因,我们系统中页表需要动态分配,因此不可使用
remap_page_range
函数一次分配完成,而必须使用
虚拟内存区域
自带的
nopage
方法,在现场构造页表。这样以来,文件操作的
mmap
的方法只要完成“为它得到的虚拟内存区域
绑定对应的操作表
vm_operations”
即可。于是主要的构造工作就落在了
vm_operations
中的
nopage
方法上了。
Nopage
方法中核心内容上面已经提到了是“
寻找到
vk
地址对应的内核逻辑地址”,这
个解析内核页表的工作是需要自己编写辅助函数
vaddr_to_kaddr来完成的,它所作的工作概括来讲就是上文提到的a/b/c三条。
有关整个任务执行路径请看下图。
STEP BY STEP
编译
map_driver.c
为
map_driver.o
模块
,
具体参数见
Makefile
加载模块
:
insmod map_driver.o
生成对应的设备文件
1
在
/proc/devices
下找到
map_driver
对应的设备命和设备号:
grep mapdrv /proc/devices
2
建立设备文件
mknod
mapfile c 254 0
(在我系统里设备
号为
254
)
利用
maptest
读取
mapfile
文件,将取自内核的信息(
”ok”
——
我们在内核中在
vmalloc
分配的空间中填放的信息)打印到用户屏幕。
全部程序下载
mmap.tar
(感谢
Martin Frey
,该程序
主体出自他的灵感)
系统调用mmap
原形是void *mmap2(void *start,
size_t length, int prot, int flags, int fd, off_t pgoff)
。
分享到:
相关推荐
linux内存管理 linux内存管理 linux内存管理
天大 操作系统 课程 linux 内存管理 实验报告
linux 内存管理学习总结 学习心得 内存管理纲要
整个内存管理系统可以分为2部分来看待: 第一部分是对物理内存的管理, 第二部分是对虚拟内存的管理. 物理内存管理的对象是板载的物理内存(DDRAM), 它把物理内存按页划分, 并把这些页放到一个池子里面. 物理内存管理...
linux内存管理源代码导读 pdf格式
linux内存管理工具
详细介绍linux内核内存管理,从内核思想详细介绍伙伴算法、slab内存管理
Linux内存管理 vs. Windows 2000内存管理
Linux虚拟内存管理.pdf
个人多年学习并总结的有关Linux内存管理的笔记,希望对Linux内核爱好者有所帮助!
Linux内存管理编程技术.doc
Linux内存管理实验.pdf
很清晰的描述了linux内存虚拟及物理内存的划分,包括: node介绍 zone介绍 3G~4G内核空间布局 内核整体mm管理结构
cgroup-linux内存资源管理.pdf cgroup-linux内存资源管理.pdf cgroup-linux内存资源管理.pdf cgroup-linux内存资源管理.pdf cgroup-linux内存资源管理.pdf cgroup-linux内存资源管理.pdf cgroup-linux内存资源管理....
linux 内存管理源码,网上下载的,没用过不知道好不好~
Linux操作系统内存管理.pdf 学习资料 复习资料 教学资源
linux内存管理(重要)
介绍Linux的内存管理系统,例如虚拟地址空间及地址映射。
linux内核内存管理图解
linux开发者介绍linux内存管理的机制,图文并茂,细致入里