linux内核虚拟内存之高端物理内存与非连续内存分配
1、高端物理内存
在第一章已经讲过,在usr/kernel为3:1的情况下,在一台32位的体系结构上最多只有896MB的内存可以直接访问,而超过部分就只能通过映射后进行访问。而64位机器有足够多的虚拟地址空间,就不会存在这个问题。
结合第一章线性空间布局,内核空间映射情况如下(以本系统为例):
直接内存映射区:PAGE_OFFSET ~ PAGE_OFFSET + highmem(最大0~896M): 0xc0000000- 0xcfa00000。
动态映射区:VMALLOC_START ~ VMALLOC_END : 0xd0000000 -0xff000000
永久映射区:PAGE_OFFSET-2M ~ PAGE_OFFSET:0xbfe00000 -0xc0000000
固定映射区:FIXADDR_START ~ FIXADDR_TOP:0xfff00000 -0xfffe0000
(1)动态映射区
通过vmalloc/vfree实现,见下一节。
(2)永久映射区
用于将高端内存长久映射到内存虚拟地址。通过一下函数实现:
void *kmap(struct page *page)
将一个给定页映射到内核地址空间,一般用于高端内存也可以用于低端内存。如果page对应的是低端内存中的一页,函数只会单纯的返回该页的虚拟地址。如果页位于高端内存,则会建立永久映射,再返回地址。该函数可以睡眠只用于进程上下文中。
该区域只有2M数量有限,当不再需要高端内存时,应该通过下面函数解除映射:
void kunmap(struct page *page)
(3)固定映射区
主要解决持久映射不能用于中断处理程序而增加的临时内核映射。通过下面函数实现:
void *kmap_atomic(struct page *page)
void __kunmap_atomic(void *kvaddr)
2、非连续内存分配
伙伴算法解决外部碎片问题,slab算法解决内部碎片问题,但是在使用过程中还是会产生碎片,从而导致申请大块连续内存将可能持续失败。因此,引入了不连续页面管理算法即vmalloc机制。
vmalloc和kmalloc工作方式类似,只是前者虚拟地址连续而物理地址不一定连续,后者虚拟地址和物理地址都连续。vmalloc为了把物理上不连续的页转化成虚拟地址空间连续的页,必须专门建立页表项进行一个一个映射,直接导致比直接映射大的多的TLB抖动,影响性能。内核一般使用kmalloc,系统/proc/vmallocinfo可以查看当前通过vmalloc申请的映射区域:
…
0xbf214000-0xbf222000 57344 module_alloc_update_bounds+0xc/0x5cpages=13 vmalloc
0xbf225000-0xbf228000 12288 module_alloc_update_bounds+0xc/0x5cpages=2 vmalloc
0xd000a000-0xd004b000 266240 atomic_pool_init+0x0/0x108phys=4f500000 user
0xd004b000-0xd0057000 49152 cramfs_uncompress_init+0x30/0x64pages=11 vmalloc
…
从上面可以看出,加载模块时也使用vmalloc机制,但是映射的虚拟地址不在VMALLOC_START ~ VMALLOC_END,而是专门的模块地址:MODULE_VADDR~ PKMAP_BASE(PAGE_OFFSET -16M ~ PAGE_OFFSET-2M)共4M大小,其他申请的虚拟地址都在VMALLOC_START ~ VMALLOC_END。
(1)数据结构描述
非连续内存区通过vm_struct结构体描述:
struct vm_struct {
struct vm_struct *next; //指向下一个vm_struct结构体
void *addr; //非连续内存区的虚拟地址空间起始地址
unsigned long size; //申请的内存区大小+page_size(page_size保留安全间隙)
unsigned long flags; //映射的内存类型VM_ALLOC(vmalloc)or VM_IOREMAP(ioremap)
struct page **pages; //直线nr_pages数组指针,该数组由指向页描述符指针组成即每个不连续的物理页
unsigned int nr_pages; //页个数
phys_addr_t phys_addr; //映射硬件设备时I/O共享内存,否则一般都为0
const void *caller;
};
非连续内存区虚拟空间管理通过vmap_area结构体描述:
struct vmap_area {
unsigned long va_start; //虚拟空间起始地址
unsigned long va_end; //虚拟空间结束地址
unsigned long flags; //映射的内存类型
struct rb_node rb_node; //红黑树管理虚拟地址,根据地址排序
struct list_head list; //链表方式管理虚拟地址,根据地址排序
struct list_head purge_list; /*"lazy purge" list */
struct vm_struct *vm; //指向非连续内存区描述符
struct rcu_head rcu_head;
};
(2)初始化
在start_kernal的mm_init通过vmalloc_init实现初始化,mm/vmalloc.c中定义如下:
void __init vmalloc_init(void)
{
struct vmap_area *va;
struct vm_struct *tmp;
int i;
for_each_possible_cpu(i) {
struct vmap_block_queue *vbq;
struct vfree_deferred *p;
vbq = &per_cpu(vmap_block_queue, i);
spin_lock_init(&vbq->lock);
INIT_LIST_HEAD(&vbq->free);
p = &per_cpu(vfree_deferred, i);
init_llist_head(&p->list);
INIT_WORK(&p->wq, free_work);
}
/* Import existing vmlist entries. */
for (tmp = vmlist; tmp; tmp = tmp->next) {
va = kzalloc(sizeof(struct vmap_area), GFP_NOWAIT);
va->flags = VM_VM_AREA;
va->va_start = (unsigned long)tmp->addr;
va->va_end = va->va_start + tmp->size;
va->vm = tmp;
__insert_vmap_area(va);
}
vmap_area_pcpu_hole = VMALLOC_END;
vmap_initialized = true;
}
该函数先是遍历每CPU的vmap_block_queue和vfree_deferred变量及初始化。其中vmap_block_queue是非连续内存块队列管理结构,主要是队列以及对应的保护锁;vfree_deferred是vmalloc的内存延迟释放管理,除了队列初始外,还创建了一个free_work()工作队列用于异步释放内存。接着,将已经存在于vmlist链表的各项非连续区通过__insert_vmap_area插入到非连续内存块的管理中。
(3)vmalloc解析
vmalloc用于申请非连续区域,当有高端内存时先从高端内存申请,没有高端内存再从低端内存中申请。
void *vmalloc(unsigned long size)
{
return __vmalloc_node_flags(size, NUMA_NO_NODE,
GFP_KERNEL |__GFP_HIGHMEM);
}
__vmalloc_node_flags -> __vmalloc_node-> __vmalloc_node_range是最终的实现,其源码如下:
void *__vmalloc_node_range(unsigned longsize, unsigned long align,
unsigned long start, unsigned long end, gfp_t gfp_mask,
pgprot_t prot, int node, const void *caller)
{
struct vm_struct *area;
void *addr;
unsigned long real_size = size;
size = PAGE_ALIGN(size);
if (!size || (size >> PAGE_SHIFT) > totalram_pages)
goto fail;
/*申请虚拟地址空间,并添加到地址排序的红黑树中进行管理*/
area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNLIST,
start, end, node, gfp_mask,caller);
if (!area)
goto fail;
/*申请对应的物理内存:首先计算需要的物理页数nr_pages及存储等量页面的页描述符数组空间大小pages;接着,根据页面数循环申请物理页面空间;最后对申请的页面进行页表更新 */
addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller);
if (!addr)
return NULL;
clear_vm_unlist(area);
//内存泄露监测
kmemleak_alloc(addr, real_size, 3, gfp_mask);
return addr;
fail:
return NULL;
}
需要注意的一点:所有进程都共享内核空间,因此这里更新的是内核的主页表即init_mm所在的swap_pg_dir页表,进程则是拷贝它。
(4)vfree
vfree用于释放有vmalloc申请的非连续空间,是vmalloc的倒序操作。其实现如下:
void vfree(const void *addr)
{
BUG_ON(in_nmi());
kmemleak_free(addr);
if (!addr)
return;
if (unlikely(in_interrupt())) {
struct vfree_deferred *p = &__get_cpu_var(vfree_deferred);
llist_add((struct llist_node *)addr, &p->list);
schedule_work(&p->wq);
}else
__vunmap(addr, 1);
}
先撤销内存泄漏监测;如果当前释放操作在中断中,那么将释放的内存空间加入到当前的CPU的vfree_deferred管理列表中,继而通过schedule_work唤醒free_work工作队列,对内存进行异步释放操作;如果不在中断,直接通过__vunmap()进行内存释放:删除红黑树对应的虚拟地址空间管理区,释放物理页面,释放管理对象页面。
linux内核虚拟内存之高端物理内存与非连续内存分配相关推荐
- 【清华大学】操作系统 陈渝 Part3 ——物理内存管理 之 连续内存分配
[清华大学]操作系统 陈渝 Part3 --物理内存管理 之 连续内存分配 3.1计算机体系结构及内存分层 计算机体系机构 内存体系层次 管理内存方法 3.2地址空间 & 地址生成 地址空间定 ...
- 深入理解linux操作系统中的高端内存
Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型.进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存. 段页式机制如下图. Linux内核地址空间划分 通常32 ...
- linux 神秘的0xC0000000内核逻辑地址内核虚拟地址直接映射高端内存
0xC0000000个人笔记: 0xC0000000:3GB的起始地址.一个进程分为两个部分:私有和全局.私有部分是指进程自己的代码,而全局部分则是指内核代码.局部是进程私有的,而全局则是所有进程公用 ...
- linux高端物理内存,Linux内存管理之高端内存
看了Linux内核内存管理,参考网上的意见整理了一下. 1.页框管理 Linux采用4KB页框大小作为标准的内存分配单元.内核必须记录每个页框的状态,这种状态信息保存在一个类型为page的页描述符中, ...
- Linux内存管理:CMA(连续内存分配)(DMA)
目录 什么是CMA 数据结构 CMA区域 cma_areas 的创建 dts方式 command line方式 将CMA区域添加到Buddy System CMA分配 <Linux内存管理:什么 ...
- linux内核虚拟内存之slub分配器
上一章主要讲述以页为最小单位进行内存分配的伙伴管理算法,较大程度上避免了内存碎片问题.而实际上对内存的申请却不是每次都申请一个页面的(比如文件节点,任务描述符等结构体内存),通常是远小于一个内存页面的 ...
- linux查看虚拟内存占用高,linux查看虚拟内存和cpu占用率
top free cat /proc/meminfo cat /proc/cpuinfo [root@centerlisdb proc]# dmidecode |grep -A16 "Mem ...
- Linux内核:kmalloc()和SLOB、SLAB、SLUB内存分配器
目录 Looking at kmalloc() and the SLUB Memory Allocator Virtual Memory Principles虚拟内存原理 The SLOB Alloc ...
- Linux内核:一文搞懂外设I/O内存资源的静态映射方式
Linux内核访问外设I/O内存资源的方式有两种:动态映射(ioremap)和静态映射(map_desc). 动态映射(ioremap)方式 动态映射方式是大家使用了比较多的,也比较简单.即直接通过内 ...
- Linux内核如何私闯进程地址空间并修改进程内存
进程地址空间的隔离 是现代操作系统的一个显著特征.这也是区别于 "古代"操作系统 的显著特征. 进程地址空间隔离意味着进程P1无法以随意的方式访问进程P2的内存,除非这块内存被声明 ...
最新文章
- JAVA常用知识总结(七)——Spring
- 在python中、列表中的元素可以是_在Python中存储一个列表的元素,在另一个列表中 – 通过引用?...
- 洛谷 P2695 骑士的工作
- 【STM32】keil软件常用使用技巧
- HTML+CSS+JS实现 ❤️爱心文字3D旋转动画特效❤️
- ***redis linux 命令使用总结
- java代码审计文件包含_代码审计--一道简单的文件包含题目的多种利用方式
- Effective C# 原则22:用事件定义对外接口(译)
- Ubuntu 15.10系统安装后要做的15件事
- 2021,Java最全的分布式面试题合集附答案,共2w字!
- tableexport 文件格式和扩展名不匹配_让信息检索更有效率!百度有哪些你不知道的隐藏玩法?...
- 静态文件html中加入php的Url,YII中URL伪静态加前缀.html的方法
- java multipy_PyTorch版YOLOv4更新了,适用于自定义数据集
- oracle vm 安装win server 2012 错误0x000000C4
- 转自啄木鸟学院-IT行业培训班出来的人真的不行吗?
- break在c语言中的应用,c语言中break的用法
- c语言 运算符的作用,C语言运算符
- 树--先序遍历构建二叉树
- Greenplum数据库数据分片策略Hash分布——GUC gp_use_legacy_hashops
- 3. adb常用命令