Linux内核剖析 之 内存管理
1. 内存管理区
为什么分成不同的内存管理区?
ISA总线的DMA处理器有严格的限制:仅仅能对物理内存前16M寻址。
内核线性地址空间仅仅有1G,CPU不能直接訪问全部的物理内存。
ZONE_DMA 小于16M内存页框
ZONE_NORMAL 16M~896M内存页框
ZONE_HIGHMEM 大于896M内存页框
ZONE_DMA和ZONE_NORMAL区域包括的页框,通过线性的映射到内核线性地址空间。内核能够直接訪问(对应的内核页表早已经建立)。
ZONE_HIGHMEN 动态映射到内核线性地址空间。
为什么既有线性映射又有动态映射?
线性映射。保持了内核页表的稳定性,降低了TLB miss。动态映射使得内核能够訪问到全部的物理内存。
内核将页框的信息保存在类型为struct page的页描写叙述符中。全部的页描写叙述符存放在数组mem_map(下标即为页框号)中。
struct page {unsigned long flags; //标志,同一时候高位用来指向页框所在的管理区,包括多达32个页框的状态标志//比如:PG_highmem 页框至于ZONE_HIGHMEM管理区// PG_slab 页框包括在slab中atomic_t _count; //页框引用计数atomic_t _mapcount; //页框中的页表项数目unsigned long private; //在不同情况下,意义不同.当页框为空暇时,由伙伴系统使用struct address_space *mapping;unsigned long index; //在不同情况下,意义不同struct list_head lru; //双向链表指针};
每一个管理区都有自己的描写叙述符:
struct zone {unsigned long free_pages; //管理区中空暇页数目unsigned long pages_min //保留页框的数目 (保留页框池)unsigned long pages_low;unsigned long pages_high; //两个watermark,与回收页框,管理区分配器有关struct per_cpu_pageset pageset[]; //(每CPU页框快速缓存)spinklock_t lock; //保护该描写叙述符unsigned long lowmem_reserve[] //在内存不足的临界情况下,管理区必须保留的页框数struct free_area free_area[]; //管理区空暇页框块(伙伴系统)struct page *zone_mem_map; //指向管理区第一个页框unsigned long zone_start_pfn; //第一个页框的下标char *name; //管理区名字(DMA, NORMAL, HighMem)..........}
保留的页框池
有内存分配请求时,假设有足够多的空暇内存可用,请求就被立马满足,否则,必须回收一些内存,堵塞当前执行的内核代码(内核控制路径),直到有内存被释放。
可是。中断或临界区内代码不能被堵塞。原子内存分配请求。
为了降低原子内存分配请求失败,内核保留了一个页框池。
保留池大小 = [sqrt(16*直接映射内存)] (KB)
ZONE_DMA、ZONE_NORMAL按各自大小比例贡献。
struct zone中的unsigned long pages_min
//保留页框的数目 (保留页框池)。
2.分区页框分配器
被称为分区页框分配器的内核子系统,处理对连续页框组的内存分配请求。
管理区分配器:接收动态内存分配和释放的请求。(搜索能满足请求的内存管理区,触发页框回收。
。。)
每CPU页框快速缓存:快速满足本地CPU发出的单一页框请求。
伙伴系统:解决外碎片问题。
2.1 请求和释放页框
struct page * alloc_pages(unsigned int gfp_mask, unsigned int order)gfp_mask:一组标志,指明怎样寻找空暇的页框order:请求分配2order个页框若成功。返回第一个分配页框的页描写叙述符地址
区修饰符:
__GFP_DMA
__GFP_HIGHMEM
行为修饰符:
__GFP_WAIT 同意内核堵塞等待空暇页框的当前内核控制路径
__GFP_HIGH 同意内核訪问保留页框池
__GFP_COLD 所请求页框可能为”冷的“(每CPU快速缓存)
__GFP_REPEAT 内核重试内存分配直到成功为止
__GFP_NOFALL 与 __GFP_REPEAT 同样
__GFP_NORETRY 一次内存分配后失败后不再重试
void __free_pages(struct page *page, unsigned int order)
释放页框函数
2.2 伙伴系统
频繁的请求和释放不同大小的连续页框,必定导致在已分配页框的块内分散了很多小块的空暇页框。
由此产生的问题是:即使有足够的空暇页框能够满足请求。但当要分配一个大块的连续页框时,无法满足请求。
这就是著名的内存管理问题:外碎片问题。
linux採用伙伴系统算法来解决外碎片的问题。
把全部的空暇页框分组为11个块链表。
每一个块链表包括大小为1,2,4,8,16,32,64,128,256,512,1024个连续的页框。每一个块的第一个页框的物理地址必须是该块大小的整数倍。
比如,大小为16个页框的块,其起始地址为16x212的倍数。
算法原理:
通过一个简单的样例说明该算法的工作原理——
假设请求一个256个页框的块,先在256个页框的链表内检查是否有一个空暇的块。
假设没有这种块,算法会查找下一个更大的块,在512个页框的链表中找一个空暇块。假设存在这种块,内核就把512的页框分成两半,一半用作满足请求,还有一半插入256个页框的链表中。假设512个页框的块链表也没有空暇块,就继续找更大的块,1024个页框的块。
假设这种块存在。内核把1024个页框的256个页框用作请求。然后从剩余的768个中拿出512个插入512个页框的链表中。把最后256个插入256个页框的链表中。
页框块的释放过程:
内核试图把大小为b的一对空暇伙伴块合并为一个大小为2b的单独块。
满足下面条件的两个块成为伙伴:
两个块具有同样的大小:b。它们的物理地址连续。
第一个页框的物理地址为2xbx212的倍数。
该算法是迭代的,假设成功合并所释放的块。它会试图合并2b的块。
数据结构:
分配块:
static struct page *__rmqueue(struct zone *zone, unsigned int order){struct free_area * area;unsigned int current_order;struct page *page;unsigned int index;for (current_order = order; current_order < MAX_ORDER; ++current_order) {area = zone->free_area + current_order;if (list_empty(&area->free_list))continue; //从order開始,搜索第一个满足要求的页框块page = list_entry(area->free_list.next, struct page, lru);list_del(&page->lru); //将找到的页框块的第一个页框描写叙述符从链表删除index = page - zone->zone_mem_map;if (current_order != MAX_ORDER-1)MARK_USED(index, current_order, area);zone->free_pages -= 1UL << order; //管理区的内存页框块降低return expand(zone, page, index, order, current_order, area);//假设current_order>order,把剩余未分配的页框块,插入合适的链表}return NULL;}static inline struct page * expand(struct zone *zone, struct page *page, unsigned long index, int low, int high, struct free_area *area){//曾经面提到的样例为例unsigned long size = 1 << high;while (high > low) {area--;high--;size >>= 1;BUG_ON(bad_range(zone, &page[size]));list_add(&page[size].lru, &area->free_list);MARK_USED(index + size, high, area)。}return page;}
释放块:
page—须要释放页框块的首个页框的页描写叙述符
base—管理区首个页框的页描写叙述符
order—页框块页框个数的对数
area—页数为2order的块的链表
static inline void __free_pages_bulk (struct page *page, struct page *base,struct zone *zone, struct free_area *area, unsigned int order){unsigned long page_idx, index, mask;if (order)destroy_compound_page(page, order);mask = (~0UL) << order;page_idx = page - base; //块中第一个页框的索引號,相对于管理区的首个页框if (page_idx & ~mask) //页框块的起始物理地址必须是2orderx212 所以page_idx须要是2order的倍数BUG();index = page_idx >> (1 + order);zone->free_pages += 1 << order;while (order < MAX_ORDER-1) {struct page *buddy1, *buddy2;BUG_ON(area >= zone->free_area + MAX_ORDER);if (!__test_and_change_bit(index, area->map)) //检查伙伴块是否已经被分配/* //?? ? ? ???* the buddy page is still allocated.*/break;/* Move the buddy up one level. */buddy1 = base + (page_idx ^ (1 << order)); //伙伴块的索引號buddy2 = base + page_idx;BUG_ON(bad_range(zone, buddy1));BUG_ON(bad_range(zone, buddy2));list_del(&buddy1->lru);mask <<= 1;order++;area++;index >>= 1;page_idx &= mask; //合并后块中第一个页框的索引}list_add(&(base + page_idx)->lru, &area->free_list);}
2.3 每CPU页框快速缓存
内核常常请求和释放单个页框。
为了提高性能。每一个内存管理区定义了每CPU页框快速缓存。当中包括一些预先分配的页框它们被用于满足本地CPU发出的单一页框请求。
每一个内存管理区为每一个CPU提供了两个快速缓存:热快速缓存和冷快速缓存。
热快速缓存:它存在的页框中所包括的内容可能就在CPU硬件快速缓存中。
假设进程在分配页框后。就向页框中写,那么从热快速缓存中获得页框对性能有利。
假设页框将被DMA操作。则从冷快速缓存中获得页框。对性能有利(节约了热快速缓存)
数据结构:
struct zone {struct per_cpu_pageset pageset[];}
struct per_cpu_pageset {int count; //快速缓存中页框的个数int low; //下界,快速缓存须要补充int high; //上界,快速缓存须要释放到伙伴系统中int batch; //每次加入或释放的个数struct list_head list //快速缓存中包括的页框描写叙述符链表}
struct page *buffered_rmqueue(struct zone *zone, int order, int gfp_flags);
释放页框到每CPU页框快速缓存:
void fastcall free_hot_cold_page(struct page *page, int cold);
2.4 管理区分配器
管理区分配器是内存页框分配器的前端。必须找到一个包括足够多空暇页框的内存区,满足内存请求。
注意:
管理区分配器保护保留的页框池;
当内存不足,且同意堵塞当前内核控制路径时,触发页框回收算法;
保存小而珍贵的ZONE_DMA区域。
管理区分配器的核心:__alloc_pages()
3.高端内存页框的内核映射
到眼下为止所介绍的函数。都是获得一块连续的内存页框,要想訪问物理内存,必需要将页框映射到线性地址空间中。
过程:(1)分配物理页框。(2)得到未映射到不论什么物理页框的线性地址;(3)更新页表,使得线性地址映射到物理页框。
对于ZONE_DMA和ZONE_NORMAL区域,线性映射到内核地址空间前896M。
依据物理地址能够直接得到对应的线性地址。而且此部分的内核页表在系统初始化时已经建立。
__va((unsigned long)(page-mem_pag) << 12)
而且这样的映射是临时的,通过反复使用线性地址,使得整个高端内存可以在不同的时间被訪问。
出于不同的目的。内核使用三种不同的机制将页框映射到高端内存。
*永久内核映射
*暂时内核映射(固定映射)
*非连续内存分配
后128M的线性地址,被分为例如以下三个部分:
3.1 永久内核映射
永久内核映射同意内核建立高端页框到内核线性地址空间的长期映射。其可能堵塞当前进程,不能在中断处理程序等不同意堵塞的代码中调用。
永久内核映射使用内核页表中的一个专门的页表。页表的地址存放在pkmap_page_table变量中。页表的表项数目:(LAST_PKMAP 1024)
该页表映射的线性地址空间PKMAP_BASE 0xff800000UL (3G+1016M) ——>0x3fe页全局文件夹的第873项。
也就是,内核通过永久内核映射。最多同一时候訪问4M高端内存。
页表中每一项都有一个计数器:
static int pkmap_count[LAST_PKMAP];
计数器为0
相应的页表项没有映射到不论什么高端内存页框,而且是可用的。
计数器为1
相应的页表项没有映射到不论什么高端内存页框,但不能使用,由于自从它最后一次使用以来,其相应的TLB表项还未被刷新。
计数器为n
相应的页表项映射到一个高端内存页框。
void *kmap(struct page *page){might_sleep();if (page < highmem_start_page)return page_address(page); //不在高端内存中,直接返回映射的线性地址return kmap_high(page);}
void fastcall *kmap_high(struct page *page){unsigned long vaddr;spin_lock(&kmap_lock);vaddr = (unsigned long)page_address(page);if (!vaddr)vaddr = map_new_virtual(page);pkmap_count[PKMAP_NR(vaddr)]++;if (pkmap_count[PKMAP_NR(vaddr)] < 2)BUG();spin_unlock(&kmap_lock);return (void*) vaddr;}
static inline unsigned long map_new_virtual(struct page *page){unsigned long vaddr;int count;start:count = LAST_PKMAP;/* Find an empty entry */for (;;) {last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;if (!last_pkmap_nr) {flush_all_zero_pkmaps();count = LAST_PKMAP;}if (!pkmap_count[last_pkmap_nr])break; /* Found a usable entry */if (--count)continue;/** Sleep for somebody else to unmap their entries*/{DECLARE_WAITQUEUE(wait, current);__set_current_state(TASK_UNINTERRUPTIBLE);add_wait_queue(&pkmap_map_wait, &wait);spin_unlock(&kmap_lock);schedule();remove_wait_queue(&pkmap_map_wait, &wait);spin_lock(&kmap_lock);/* Somebody else might have mapped it while we slept */if (page_address(page))return (unsigned long)page_address(page);/* Re-start */goto start;}}vaddr = PKMAP_ADDR(last_pkmap_nr);set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));//建立内核页表pkmap_count[last_pkmap_nr] = 1;set_page_address(page, (void *)vaddr);return vaddr;}
3.2 暂时内核映射
FIXADDR_START (3G + 1020M)
FIXADDR_TOP (0xfffff000UL) (除去最后一页)
enum km_type {KM_BOUNCE_READ,KM_SKB_SUNRPC_DATA,KM_SKB_DATA_SOFTIRQ,KM_USER0,KM_USER1,KM_BIO_SRC_IRQ,KM_BIO_DST_IRQ,KM_PTE0,KM_PTE1,KM_IRQ0,KM_IRQ1,KM_SOFTIRQ0,KM_SOFTIRQ1,KM_TYPE_NR};
同一个窗体永不会被两个不同的控制路径同一时候使用
void *kmap_atomic(struct page *page, enum km_type type){enum fixed_addresses idx;unsigned long vaddr;/* even !CONFIG_PREEMPT needs this, for in_atomic in do_page_fault */inc_preempt_count(); //禁止内核抢占if (page < highmem_start_page)return page_address(page);idx = type + KM_TYPE_NR*smp_processor_id();vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx); //获得线性地址set_pte(kmap_pte-idx, mk_pte(page, kmap_prot)); //建立页表__flush_tlb_one(vaddr);return (void*) vaddr;}
3.3 非连续内存区管理
主要特点:通过连续的线性地址訪问非连续的页框。
长处:非连续的页框,避免了外碎片;
缺点:打乱了内核页表。
Linux在为活动的交换区分配数据结构, 为模块分配空间某些I/O驱动程序等等方面都用到了非连续内存区
从high_memory(896M)到PKMAP_BASE(1016M)之间的线性地址空间用于非连续内存区。
high_memory与VMALLOC_START之间有8M(VMALLOC_OFFSET)的安全区。为了捕获对内存的越界訪问。
vmalloc区之间的4KB的安全区也是出于相同的考虑。
数据结构:
每一个非连续内存区都相应一个类型为 vm_struct 的描写叙述符
struct vm_struct {void *addr; //内存区起始线性地址unsigned long size; //内存区的大小加4096(内存区之间安全区的大小)unsigned long flags;struct page **pages; //页描写叙述符指针数组unsigend int nr_pages; //指针数组长度(内存区页的个数)unsigned long phys_addr; //该字段为0struct vm_struct *next; //指向下一个vm_struct}
分配非连续内存区
void *vmalloc(unsigend long size):void *__vmalloc(unsigned long size, int gfp_mask, pgprot_t prot){struct vm_struct *area;struct page **pages;unsigned int nr_pages, array_size, i;size = PAGE_ALIGN(size); //(((addr)+PAGE_SIZE-1)&PAGE_MASK)if (!size || (size >> PAGE_SHIFT) > num_physpages)return NULL;area = get_vm_area(size, VM_ALLOC);//在VMALLOC_START和 VMALLOC_END之间寻找大小为(szie+4096)的空暇连续地址空间if (!area)return NULL;nr_pages = size >> PAGE_SHIFT;array_size = (nr_pages * sizeof(struct page *));area->nr_pages = nr_pages;area->pages = pages = kmalloc(array_size, (gfp_mask & ~__GFP_HIGHMEM));if (!area->pages) {remove_vm_area(area->addr);kfree(area);return NULL;}memset(area->pages, 0, array_size);for (i = 0; i < area->nr_pages; i++) {area->pages[i] = alloc_page(gfp_mask);//vmalloc基于伙伴系统。 每次从伙伴系统分配一页if (unlikely(!area->pages[i])) {/* Successfully allocated i pages, free them in __vunmap() */area->nr_pages = i;goto fail;}}if (map_vm_area(area, prot, &pages)) //建立页表goto fail;return area->addr;fail:vfree(area->addr);return NULL;}
前面讲的几种动态映射都会改动内核页表。
永久内核映射与非连续内存区改动内核页表后,进程页表中内核线性地址空间部分须要与其保持一致。
那么内核怎样确保对内核页表的改动,能传递到各个进程页表中?
===>>>
当内核建立新页表。
内核态的进程訪问内核线性地址。相应的页文件夹项为空。缺页发生。
缺页处理程序检查这个缺页的线性地址是否在主内核页表中。一旦缺页处理程序发现主内核页表对应的页文件夹项不为空。
就把页文件夹项的值复制到进程的对应位置,并恢复进程的运行。
当内核撤销线性地址到物理页框的映射,把页表项设为0;
内核态进程訪问线性地址,对应页表项为0。缺页发生,缺页处理程序觉得这种訪问是一个错误。
Linux内核剖析 之 内存管理的更多相关文章
- linux内核分析之内存管理
1.struct page /* Each physical page in the system has a struct page associated with * it to keep tra ...
- linux内核--用户态内存管理
在上一篇博客“内核内存管理”中,描述的内核内存管理的相关算法和数据结构,在这里简单描述用户态内存管理的数据结构和算法. 一,相关结构体 与进程地址空间相关的全部信息都包含在一个叫做“内存描述符”的数据 ...
- 《PHP内核剖析 - 变量/内存管理》
本文总结自: <PHP7 内核剖析 - 变量的内部实现> 一:变量的实现 - 变量是一个语言实现的基础. - 在PHP中,变量的组成部分为 变量名(zval) 变量值(zend_value ...
- 初探Linux内核中的内存管理
Linux内核设计与实现之内存管理的读书笔记 初探Linux内核管理 内核本身不像用户空间那样奢侈的使用内存; 内核不支持简单快捷的内存分配机制, 用户空间支持? 这种简单快捷的内存分配机制是什么呢? ...
- [转]linux内核分析笔记----内存管理
转自:http://blog.csdn.net/Baiduluckyboy/article/details/9667933 内存管理,不用多说,言简意赅.在内核里分配内存还真不是件容易的事情,根本上是 ...
- <Linux内核源码>内存管理模型
题外语:本人对linux内核的了解尚浅,如果有差池欢迎指正,也欢迎提问交流! 首先要理解一下每一个进程是如何维护自己独立的寻址空间的,我的电脑里呢是8G内存空间.了解过的朋友应该都知道这是虚拟内存技术 ...
- linux内核源码——内存管理:段页式内存及swap
os的内存管理大概可以分成两块:1.段页式管理(虚存)2.swap in 和 swap out 段页式管理 段式管理的图像:运行时重定位 多级页表的管理图像 块表加速 用户(程序员)希望用段,物理内 ...
- Linux内核笔记:内存管理
逻辑地址由16位segment selector和offset组成 根据segment selector到GDT或LDT中去查找segment descriptor 32位base,20位limit, ...
- Linux内核高端内存 转
Linux内核地址映射模型x86 CPU采用了段页式地址映射模型.进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存. 段页式机制如下图. Linux内核地址空间划分 通 ...
随机推荐
- 基于RHCS的web双机热备集群搭建
基于RHCS的web双机热备集群搭建 RHCS集群执行原理及功能介绍 1. 分布式集群管理器(CMAN) Cluster Manager.简称CMAN.是一个分布式集群管理工具.它执行在集群的各个节 ...
- Android:不让 EditText 在 Activity 中自动弹出键盘
通过属性 android:windowSoftInputMode 可以做到隐藏键盘的. android:windowSoftInputMode 属性有: stateUnspecified,stateU ...
- nginx中配置404错误页面的教程
什么是404页面如果网站出了问题,或者用户试图访问一个并不存在的页面时,此时服务器会返回代码为404的错误信息,此时对应页面就是404页面.404页面的默认内容和具体的服务器有关.如果后台用的是NGI ...
- WCF入门学习3-配置文件与部署iis
配置文件设置 --------------------------------------------------- 创建的时候都会有个配置文件,其实有一个WCF配置编辑器,右键就可以点出来设置. 需 ...
- angular学习笔记(七)-迭代2
视图的迭代和它的ng-repeat属性绑定的数据是实时绑定的,一旦数据发生了改变,视图也会立即更新迭代. 还是刚才的那个例子,给它添加一个添加数据按钮和一个删除数据按钮. <!DOCTYPE h ...
- cocos2d-x Schedule详解
原理介绍 Cocos2d-x调度器为游戏提供定时事件和定时调用服务.所有Node对象都知道如何调度和取消调度事件,使用调度器有几个好处: 每当Node不再可见或已从场景中移除时,调度器会停止. Coc ...
- Mock制作假数据
name 为属性名, rule 为规则, value 为值,属性名和生成规则之间用|分隔,生成规则的格式有7种: 字符串 String, 数字 Number, 布尔型 Boolean, 对象 Obje ...
- Datax将本地文件导入Hbase数据库!!!酷酷酷
Hbase Writer的json文件链接: https://github.com/alibaba/DataX/blob/master/hbase11xwriter/doc/hbase11xwrite ...
- emWin教程目录汇总
目录 第一章: 当前主流的小型嵌入式 GUI 第2章 初学 emWin 的准备工作及其快速上手
- 4款基于html5 canvas充满想象力的重力特效
今天给大家分享4个物理和重力实验,用来展示 html canvas 的强大.几年前,所有这些实验都必须使用 Java 或 Flash 才能做.在下面这些惊人的例子中,就个人而言,我比较喜欢仿真布料的那 ...