深入解析Linux内存管理.ppt
《深入解析Linux内存管理.ppt》由会员分享,可在线阅读,更多相关《深入解析Linux内存管理.ppt(95页珍藏版)》请在三一办公上搜索。
1、深入解析Linux内存管理,季丹,目录,预备知识页表管理内核页表物理内存高端内存地址映射虚拟内存地址空间高速缓存页框回收交换机制缺页异常共享内存文件映射程序执行,预备知识,微机原理内存芯片AT&T汇编保护模式脚本链接内核架构,页表管理,1.逻辑地址转线性地址2.线性地址转物理地址,逻辑地址转线性地址,机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被访问到。我们写个最简单的hello world程序,用gccs编译,再反编译后会看到以下指令:mov 0 x80495b0,%eax这里的内存地址0 x80495b0 就是一
2、个逻辑地址,必须加上隐含的DS 数据段的基地址,才能构成线性地址。也就是说 0 x80495b0 是当前任务的DS数据段内的偏移。在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节)。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index)。Linux中逻辑地址等于线性地址。为什么这么说呢?因为Linux所有的段(用户代码段、用户数据段、内核代码段、内核数据段)的线性地址都是从 0 x00000000 开始,长度4G,这样 线性地址=逻辑地址+0 x0
3、0000000,也就是说逻辑地址等于线性地址了。,逻辑地址转线性地址,这样的情况下Linux只用到了GDT,不论是用户任务还是内核任务,都没有用到LDT。GDT的第12和13项段描述符是 _KERNEL_CS 和_KERNEL_DS,第14和15项段描述符是 _USER_CS 和_USER_DS。内核任务使用_KERNEL_CS 和_KERNEL_DS,所有的用户任务共用_USER_CS 和_USER_DS,也就是说不需要给每个任务再单独分配段描述符。内核段描述符和用户段描述符虽然起始线性地址和长度都一样,但DPL(描述符特权级)是不一样的。_KERNEL_CS 和_KERNEL_DS 的DP
4、L值为0(最高特权),_USER_CS 和_USER_DS的DPL值为3。用gdb调试程序的时候,用info reg 显示当前寄存器的值:cs 0 x73 115ss 0 x7b 123ds 0 x7b 123es 0 x7b 123可以看到ds值为0 x7b,转换成二进制为 00000000 01111011,TI字段值为0,表示使用GDT,GDT索引值为 01111,即十进制15,对应的就是GDT内的_USER_DATA 用户数据段描述符。从上面可以看到,Linux在x86的分段机制上运行,却通过一个巧妙的方式绕开了分段。Linux主要以分页的方式实现内存管理。,线性地址转物理地址,前面说
5、了Linux中逻辑地址等于线性地址,那么线性地址怎么对应到物理地址呢?这个大家都知道,那就是通过分页机制,具体的说,就是通过页表查找来对应物理地址。准确的说分页是CPU提供的一种机制,Linux只是根据这种机制的规则,利用它实现了内存管理。在保护模式下,控制寄存器CR0的最高位PG位控制着分页管理机制是否生效,如果PG=1,分页机制生效,需通过页表查找才能把线性地址转换物理地址。如果PG=0,则分页机制无效,线性地址就直接做为物理地址。分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页(page),每页包含4k字节的地址空间(为简化分析,我们不考虑扩展分页的情况)。这样每一页的起
6、始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表(page table)。注意,为了实现每个任务的平坦的虚拟内存,每个任务都有自己的页目录表和页表。为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。,线性地址转物理地址,为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。32位的线性地址被分成3个部分:最高10位 Directory 页目录表偏移量,中间10位 Table是页表偏移量,最低12位Offset是物理页内的字节偏移量。页目录表的大小为4k(刚好是一个
7、页的大小),包含1024项,每个项4字节(32位),项目里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。页表的大小也是4k,同样包含1024项,每个项4字节,内容为最终物理页的物理内存起始地址。每个活动的任务,必须要先分配给它一个页目录表,并把页目录表的物理地址存入cr3寄存器。页表可以提前分配好,也可以在用到的时候再分配。还是以 mov 0 x80495b0,%eax 中的地址为例分析一下线性地址转物理地址的过程。前面说到Linux中逻辑地址等于线性地址,那么我们要转换的线性地址就是0 x80495b0。转换的过程是由CPU自动完成的,Linux所要做的就是准备
8、好转换所需的页目录表和页表(假设已经准备好,给页目录表和页表分配物理内存的过程很复杂,后面再分析)。,线性地址转物理地址,内核先将当前任务的页目录表的物理地址填入cr3寄存器。线性地址 0 x80495b0 转换成二进制后是 0000 1000 0000 0100 1001 0101 1011 0000,最高10位0000 1000 00的十进制是32,CPU查看页目录表第32项,里面存放的是页表的物理地址。线性地址中间10位00 0100 1001 的十进制是73,页表的第73项存储的是最终物理页的物理起始地址。物理页基地址加上线性地址中最低12位的偏移量,CPU就找到了线性地址最终对应的物
9、理内存单元。我们知道Linux中用户进程线性地址能寻址的范围是0 3G,那么是不是需要提前先把这3G虚拟内存的页表都建立好呢?一般情况下,物理内存是远远小于3G的,加上同时有很多进程都在运行,根本无法给每个进程提前建立3G的线性地址页表。Linux利用CPU的一个机制解决了这个问题。进程创建后我们可以给页目录表的表项值都填0,CPU在查找页表时,如果表项的内容为0,则会引发一个缺页异常,进程暂停执行,Linux内核这时候可以通过一系列复杂的算法给分配一个物理页,并把物理页的地址填入表项中,进程再恢复执行。当然进程在这个过程中是被蒙蔽的,它自己的感觉还是正常访问到了物理内存。,线性地址转物理地址
10、,内核页表,临时内核页表最终内核页表,临时内核页表,最终内核页表,物理内存,1.内核页表2.内存描述3.物理探测4.引导内存5.Pre-Cpu Cache6.伙伴机制7.Slab机制,1.内核页表,struct page unsigned long flags;atomic_t _count;union atomic_t _mapcount;unsigned int inuse;union struct unsigned long private;struct address_space*mapping;#if NR_CPUS=CONFIG_SPLIT_PTLOCK_CPUS spinlock
11、_t ptl;#endif struct kmem_cache*slab;/*SLUB:Pointer to slab*/struct page*first_page;/*Compound tail pages*/;union pgoff_t index;/*Our offset within mapping.*/void*freelist;/*SLUB:freelist req.slab lock*/;struct list_head lru;#if defined(WANT_PAGE_VIRTUAL)void*virtual;#endif/*WANT_PAGE_VIRTUAL*/#ifde
12、f CONFIG_CGROUP_MEM_RES_CTLRunsigned long page_cgroup;#endif;,2.内存描述-pglist_data,typedef struct pglist_data struct zone node_zonesMAX_NR_ZONES;struct zonelist node_zonelistsMAX_ZONELISTS;int nr_zones;#ifdef CONFIG_FLAT_NODE_MEM_MAPstruct page*node_mem_map;#endifstruct bootmem_data*bdata;#ifdef CONFI
13、G_MEMORY_HOTPLUGspinlock_t node_size_lock;#endifunsigned long node_start_pfn;unsigned long node_present_pages;/*total number of physical pages*/unsigned long node_spanned_pages;/*total size of physical page range,including holes*/int node_id;wait_queue_head_t kswapd_wait;struct task_struct*kswapd;in
14、t kswapd_max_order;pg_data_t;,2.内存描述-zone,struct zone/*Fields commonly accessed by the page allocator*/unsigned longpages_min,pages_low,pages_high;unsigned longlowmem_reserveMAX_NR_ZONES;#ifdef CONFIG_NUMAint node;unsigned longmin_unmapped_pages;unsigned longmin_slab_pages;struct per_cpu_pageset*pag
15、esetNR_CPUS;#elsestruct per_cpu_pagesetpagesetNR_CPUS;#endifspinlock_tlock;#ifdef CONFIG_MEMORY_HOTPLUG/*see spanned/present_pages for more description*/seqlock_tspan_seqlock;#endifstruct free_areafree_areaMAX_ORDER;#ifndef CONFIG_SPARSEMEMunsigned long*pageblock_flags;#endif/*CONFIG_SPARSEMEM*/ZONE
16、_PADDING(_pad1_)/*Fields commonly accessed by the page reclaim scanner*/spinlock_tlru_lock;struct list_headactive_list;struct list_headinactive_list;,2.内存描述-zone,unsigned longnr_scan_inactive;unsigned longpages_scanned;/*since last reclaim*/unsigned longflags;/*zone flags,see below*/*Zone statistics
17、*/atomic_long_tvm_statNR_VM_ZONE_STAT_ITEMS;int prev_priority;ZONE_PADDING(_pad2_)/*Rarely used or read-mostly fields*/wait_queue_head_t*wait_table;unsigned longwait_table_hash_nr_entries;unsigned longwait_table_bits;struct pglist_data*zone_pgdat;/*zone_start_pfn=zone_start_paddr PAGE_SHIFT*/unsigne
18、d longzone_start_pfn;unsigned longspanned_pages;/*total size,including holes*/unsigned longpresent_pages;/*amount of memory(excluding holes)*/*rarely used fields:*/const char*name;_cacheline_internodealigned_in_smp;,物理探测,在系统boot的时候,kernel通过0 x15中断获得机器内存容量。有三种参数88H(只能探测最大64MB的内存),E801H(得到大小),E820H(获得
19、memory map)。这个memory map称为E820图,在kernel的初始化代码中会将这个memory map复制到一个kernel中的数据结构e820map里,kernel需要通过这个结构来计算可用的内存容量。调用print_memory_map打印出各个内存段的范围和类型,我的内存是2G的,打印结果如下:0.000000BIOS-providedphysicalRAMmap:0.000000BIOS-e820:0000000000000000-000000000009f000(usable)0.000000BIOS-e820:000000000009f000-0000000000
20、0a0000(reserved)0.000000BIOS-e820:00000000000f0000-0000000000100000(reserved)0.000000BIOS-e820:0000000000100000-0000000001e00000(usable)0.000000BIOS-e820:0000000001e00000-0000000001e80040(reserved)0.000000BIOS-e820:0000000001e80040-000000007bed0000(usable)0.000000BIOS-e820:000000007bed0000-000000007
21、bed3000(ACPINVS)0.000000BIOS-e820:000000007bed3000-000000007bee0000(ACPIdata)0.000000BIOS-e820:000000007bee0000-000000007bf00000(reserved)0.000000BIOS-e820:000000007c000000-0000000080000000(reserved)0.000000BIOS-e820:00000000f0000000-00000000f4000000(reserved)0.000000BIOS-e820:00000000fec00000-00000
22、00100000000(reserved),引导内存,Pre-Cpu Cache,管理区分配器,伙伴机制,整体结构,伙伴机制,Linux内核通过伙伴算法来管理物理内存。伙伴系统(Buddy System)在理论上是非常简单的内存分配算法。它的用途主要是尽可能减少外部碎片,同时允许快速分配与回收物理页面。为了减少外部碎片,连续的空闲页面,根据空闲块(由连续的空闲页面组成)大小,组织成不同的链表(或者orders)。这样所有的2个页面大小的空闲块在一个链表中,4个页面大小的空闲块在另外一个链表中,以此类推。,伙伴机制,注意,不同大小的块在空间上,不会有重叠。当一个需求为4个连续页面时,检查是否有4
23、个页面的空闲块而快速满足请求。若该链表上(每个结点都是大小为4页面的块)有空闲的块,则分配给用户,否则向下一个级别(order)的链表中查找。若存在(8页面的)空闲块(现处于另外一个级别的链表上),则将该页面块分裂为两个4页面的块,一块分配给请求者,另外一块加入到4页面的块链表中。这样可以避免分裂大的空闲块,而此时有可以满足需求的小页面块,从而减少外面碎片。,Slab机制,Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法。Jeff 的分配器是围绕对象缓存进行的。在内核中,会为有限的对象集(例如文件描述符和其他常见结构)分配大量
24、内存。Jeff 发现对内核中普通对象进行初始化所需的时间超过了对其进行分配和释放所需的时间。因此他的结论是不应该将内存释放回一个全局的内存池,而是将内存保持为针对特定目而初始化的状态。例如,如果内存被分配给了一个互斥锁,那么只需在为互斥锁首次分配内存时执行一次互斥锁初始化函数(mutex_init)即可。后续的内存分配不需要执行这个初始化函数,因为从上次释放和调用析构之后,它已经处于所需的状态中了。Linux slab 分配器使用了这种思想和其他一些思想来构建一个在空间和时间上都具有高效性的内存分配器。与传统的内存管理模式相比,slab 缓存分配器提供了很多优点。首先,内核通常依赖于对小对象的
25、分配,它们会在系统生命周期内进行无数次分配。slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题。slab 分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象重复进行初始化。最后,slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能。,slab对象管理器,slab对象管理器,slab着色基本原理,CPU访问内存时使用哪个cache line是通过低地址的若干位确定的,比如cache line大小为32,那么是从bit5开始的若干位。因此相距很远的内存地址,如果这些位的地址相同,还
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 深入 解析 Linux 内存 管理

链接地址:https://www.31ppt.com/p-6128629.html