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内核虚拟内存之高端物理内存与非连续内存分配相关推荐

  1. 【清华大学】操作系统 陈渝 Part3 ——物理内存管理 之 连续内存分配

    [清华大学]操作系统 陈渝 Part3 --物理内存管理 之 连续内存分配 3.1计算机体系结构及内存分层 计算机体系机构 内存体系层次 管理内存方法 3.2地址空间 & 地址生成 地址空间定 ...

  2. 深入理解linux操作系统中的高端内存

    Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型.进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存. 段页式机制如下图.   Linux内核地址空间划分 通常32 ...

  3. linux 神秘的0xC0000000内核逻辑地址内核虚拟地址直接映射高端内存

    0xC0000000个人笔记: 0xC0000000:3GB的起始地址.一个进程分为两个部分:私有和全局.私有部分是指进程自己的代码,而全局部分则是指内核代码.局部是进程私有的,而全局则是所有进程公用 ...

  4. linux高端物理内存,Linux内存管理之高端内存

    看了Linux内核内存管理,参考网上的意见整理了一下. 1.页框管理 Linux采用4KB页框大小作为标准的内存分配单元.内核必须记录每个页框的状态,这种状态信息保存在一个类型为page的页描述符中, ...

  5. Linux内存管理:CMA(连续内存分配)(DMA)

    目录 什么是CMA 数据结构 CMA区域 cma_areas 的创建 dts方式 command line方式 将CMA区域添加到Buddy System CMA分配 <Linux内存管理:什么 ...

  6. linux内核虚拟内存之slub分配器

    上一章主要讲述以页为最小单位进行内存分配的伙伴管理算法,较大程度上避免了内存碎片问题.而实际上对内存的申请却不是每次都申请一个页面的(比如文件节点,任务描述符等结构体内存),通常是远小于一个内存页面的 ...

  7. linux查看虚拟内存占用高,linux查看虚拟内存和cpu占用率

    top free cat /proc/meminfo cat /proc/cpuinfo [root@centerlisdb proc]# dmidecode |grep -A16 "Mem ...

  8. Linux内核:kmalloc()和SLOB、SLAB、SLUB内存分配器

    目录 Looking at kmalloc() and the SLUB Memory Allocator Virtual Memory Principles虚拟内存原理 The SLOB Alloc ...

  9. Linux内核:一文搞懂外设I/O内存资源的静态映射方式

    Linux内核访问外设I/O内存资源的方式有两种:动态映射(ioremap)和静态映射(map_desc). 动态映射(ioremap)方式 动态映射方式是大家使用了比较多的,也比较简单.即直接通过内 ...

  10. Linux内核如何私闯进程地址空间并修改进程内存

    进程地址空间的隔离 是现代操作系统的一个显著特征.这也是区别于 "古代"操作系统 的显著特征. 进程地址空间隔离意味着进程P1无法以随意的方式访问进程P2的内存,除非这块内存被声明 ...

最新文章

  1. JAVA常用知识总结(七)——Spring
  2. 在python中、列表中的元素可以是_在Python中存储一个列表的元素,在另一个列表中 – 通过引用?...
  3. 洛谷 P2695 骑士的工作
  4. 【STM32】keil软件常用使用技巧
  5. HTML+CSS+JS实现 ❤️爱心文字3D旋转动画特效❤️
  6. ***redis linux 命令使用总结
  7. java代码审计文件包含_代码审计--一道简单的文件包含题目的多种利用方式
  8. Effective C# 原则22:用事件定义对外接口(译)
  9. Ubuntu 15.10系统安装后要做的15件事
  10. 2021,Java最全的分布式面试题合集附答案,共2w字!
  11. tableexport 文件格式和扩展名不匹配_让信息检索更有效率!百度有哪些你不知道的隐藏玩法?...
  12. 静态文件html中加入php的Url,YII中URL伪静态加前缀.html的方法
  13. java multipy_PyTorch版YOLOv4更新了,适用于自定义数据集
  14. oracle vm 安装win server 2012 错误0x000000C4
  15. 转自啄木鸟学院-IT行业培训班出来的人真的不行吗?
  16. break在c语言中的应用,c语言中break的用法
  17. c语言 运算符的作用,C语言运算符
  18. 树--先序遍历构建二叉树
  19. Greenplum数据库数据分片策略Hash分布——GUC gp_use_legacy_hashops
  20. 3. adb常用命令

热门文章

  1. Android Studio 如何添加悬浮提示
  2. Asp.Net MVC Web应用程序中的安全向量
  3. 通用权限管理系统基类中数据库的连接
  4. linux gcc下实现简单socket套接字小程序
  5. java 如何将异常_java中的异常处理
  6. python实现卷积神经网络_【455】Python 徒手实现 卷积神经网络 CNN
  7. 什么是.NET应用程序域
  8. 使用短生命周期容器(Ephemeral Containers)构建微服务化的工作流
  9. Centos7网络配置
  10. 区块链+”来了,区块链金融将如何颠覆传统金融