960 likes | 1.55k Views
Linux 内存管理. 内存管理概述 物理内存 vs 虚拟内存 物理地址 vs 虚拟地址 ( 逻辑地址 ) 物理地址空间 vs 虚拟地址空间 内核虚拟地址空间 vs 用户虚拟地址空间 Linux 内存管理数据结构 Page Memory zone Linux 内存分配与回收 以页为单位分配内存空间 以字节为单位分配内存空间 Slab 分配器 Cache Slab 分配与释放内核对象 Linux 进程的地址空间 mm_struct Vm_area_struct 页表. 内存管理主要职责.
E N D
Linux内存管理 • 内存管理概述 • 物理内存 vs 虚拟内存 • 物理地址 vs 虚拟地址 (逻辑地址) • 物理地址空间 vs 虚拟地址空间 • 内核虚拟地址空间 vs 用户虚拟地址空间 • Linux内存管理数据结构 • Page • Memory zone • Linux内存分配与回收 • 以页为单位分配内存空间 • 以字节为单位分配内存空间 • Slab分配器 • Cache • Slab • 分配与释放内核对象 • Linux进程的地址空间 • mm_struct • Vm_area_struct • 页表
内存管理主要职责 • 内存分配(allocate)与回收(deallocate) • 内存扩充 • 虚拟内存 • 地址变换 • 虚拟地址 =》物理地址 • 内存保护 • 内存使用状态跟踪keeping track • 通过数据结构的字段来记录内存的使用情况
物理内存 vs. 虚拟内存 • 物理内存 Physical Memory • RAM • 虚拟内存 Virtual Memory • 在外存上开辟空间来缓存物理内存页面,实现内存扩展 • Linux:在磁盘上开辟的Swap区 • Windows:在磁盘上开辟的VM区 • 内存扩充 • 借用外存空间来扩展物理内存空间,方法是让进程的部分代码或数据进入物理内存,其余驻留在外存,在需要时再通过替换进入物理内存。
虚拟地址 vs. 物理地址 • 虚拟地址(逻辑地址) • 程序编程使用虚拟地址 • 虚拟地址空间:进程能够访问的虚拟地址范围。 • 在32位字长X86机器上,虚拟地址空间大小为4GB。 • 物理地址 • 物理内存上以字节为单位编码的地址。 • 物理地址空间:物理内存的地址范围。
4GB 进程虚拟地址空间 系统中每个进程都有自己的地址空间 0 地址变换 (地址翻译) (1)
地址变换 (地址翻译) (2) • MMU(Memory management Unit)负责把虚拟地址翻译为物理地址,并让一个进程始终运行于系统的物理内存中。“硬件地址翻译” • 无论CPU运行于用户态还是核心态,程序执行时,交给CPU访问的地址是虚拟地址,MMU通过读取控制寄存器CR3得到页目录的指针,然后根据快表TLB(Translation lookaside buffer)和页表(Page Table)将该虚拟地址转换为物理地址。 • 想一想:若将程序的虚拟地址转换为物理地址后,发现不在物理内存中,怎么办?
页面替换 (1) • 虚拟内存通过“页” 进行组织。 • 页是OS在物理内存和磁盘swap区之间移动的基本单位。 • 移入内存page in • 移出page out 操作系统使每个进程都以为自己拥有整个地址空间的独家访问权,这个幻觉是借助“虚拟内存”实现的。
页面替换 (2) • 进程只能操作位于物理内存中的页面,所有进程共享物理内存。 • 在进程运行时,经过地址翻译,当发现引用的页面不在物理内存时,需要从磁盘调入内存。 • MMU会产生一个页错误(page fault)。内核对此事件作出响应,并判断该引用是否有效。 • 若无效,内核向进程发出一个“segmentation violation”信号 • 若有效,内核从磁盘取回该页,换入到内存中。一旦页面进入内存,进程便被解锁,可以重新运行。因此,进程本身并不知道它曾经因为页面换入事件而等待了一会儿。 • Linux 内核常驻物理内存,只有用户进程才被移入移出。
内存保护 • 防止程序越界和越权行为 • 不允许用户进程访问操作系统的存储区域 • 每个进程都在自己的地址空间中运行,互不干扰 • 内存保护的措施 • 界限保护 • 设置界限寄存器,限制进程的活动空间。 • 保护锁 • 为共享内存区设置一个读/写保护锁,在CPU中设置保护锁开关,它表示进程的读/写权限。只有进程的开关代码和内存区的保护锁匹配时方可进行访问。 • 保护模式 • 将CPU的工作模式分为用户态与核心态。核心态下的进程可以访问整个内存地址空间,而用户态下的进程只能访问在界限寄存器所规定范围内的空间。
地址空间 • 操作系统对内存的使用 • OS启动阶段:临时使用内存 • OS正常运行阶段 • 内核虚拟地址空间 • 存放“内核映像”:kernel code and data • 被众多进程和内核共享 • 用户虚拟地址空间 • 用户进程的code、data、stack等 • i386地址空间 • 在32位机器平台上,虚拟地址空间为4GB • 内核虚拟地址空间 1GB • 用户虚拟地址空间 3GB • 思考:QQ程序在运行过程中,占用哪个空间?
空间映射 Some area (128M) of memory is reserved for storing several kernel data structures that store information about the memory map and page tables. ZONE_HIGNMEM ZONE_NORMAL ZONE_DMA
分清几个术语 • 物理内存 vs 虚拟内存 • 物理地址 vs 虚拟地址 (逻辑地址) • 物理地址空间 vs 虚拟地址空间 • 内核虚拟地址空间 vs 用户虚拟地址空间 • 注意: • 不要把“虚拟地址空间”和“物理地址空间”直接对应起来,他们之间映射(Mapping)才能建立关系
Linux内存管理 内存管理概述 Linux内存管理数据结构 Page Memory zone Linux内存分配与回收 Slab分配器 Linux进程的地址空间 页表
Page (1) • 内核以物理页作为内存管理的基本单位。 • Page Size • Most 32-bit architectures have 4KB pages, whereas most 64-bit architectures have 8KB pages. • 页描述符 • 内核用struct page结构体来描述系统中的每个物理页。 • include/linux/mm.h (L202)
Page(2) • unsigned long flag • 每bit代表一个状态,每个page可以有多个不同的状态 • include/linux/page-flags.h (L50)
Page(3) • atomic_t _count; • 该页被引用的次数,可用page_count()函数查看 • 0表示页空闲,可用 • void *virtual; • 页的virtual address • struct list_head lru; • 通过next 和 prev 指向LRU List中的页面 • 注意: • 内核用Page结构体来描述某个物理页的属性,而不是描述包含在其中的数据 • 系统中的每个物理页都要分配一个page结构体
Zone (1) • 由于硬件的限制,内核并不能对所有的页一视同仁。 • 一些设备只能用某些特定的内存范围来执行DMA • 一些体系结构支持物理地址空间大于虚拟地址空间 • 导致一些物理内存永远不能被映射到内核地址空间上 • 内核把内存划分为三个不同的功能区(zone) • include/linux/mmzone.h (L65) • #define ZONE_DMA 0 • used for DMA page frames • #define ZONE_NORMAL 1 • non-DMA pages with virtual mapping • #define ZONE_HIGHMEM 2 • “高端内存” • pages whose addresses are not contained in the virtual address space
Zone (2) • 区(zone)的划分和使用与体系结构密切相关 • 某些体系结构在内存的任何地址上都可以执行DMA • 而在x86上, ISA设备只能访问物理内存的前16MB • 在某些体系结构上,所有内存都可以被直接映射 • 故ZONE_HIGNMEM为空,没有对应的物理内存页 • 区的划分没有任何物理意义,只不过是内核为了管理页而采取的逻辑分组。 • 类比:校园 • 页 vs 平方米 • 区 vs 生活区、教学区、科研区
Zone (3) (include/linux/mmzone.h L101) struct zone { …… …… }
Zone (4) • spinlock_t lock; • spinlock; lock for the zone descriptor itself • unsigned long free_pages; • the number of free pages that are left in the zone • nr_free_pages() • unsigned long pages_min, pages_low, pages_high; • Fields commonly accessed by the page allocator • pages_min 内核保持至少pages_min个页空闲
Zone (5) • spinlock_t lru_lock; • spinlock for the LRU page list • struct list_head active_list; • list of the active pages • struct list_head inactive_list; • a list of pages that can be reclaimed • int all_unreclaimable; • 1 if all pages in the zone are pinned • struct free_area free_area[MAX_ORDER]; • The buddy system uses the free_area bitmap
Zone (6) 相关函数 • include/linux/mmzone.h • for_each_zone() #define for_each_zone(zone) \ for (zone = pgdat_list->node_zones; zone; zone = next_zone(zone)) • is_highmem() static inline int is_highmem(struct zone *zone) { return (zone - zone->zone_pgdat->node_zones == ZONE_HIGHMEM); } • is_normal() static inline int is_normal(struct zone *zone) { return (zone - zone->zone_pgdat->node_zones == ZONE_NORMAL); }
Linux内存管理 内存管理概述 物理内存 vs 虚拟内存 物理地址 vs 虚拟地址 (逻辑地址) 物理地址空间 vs 虚拟地址空间 内核虚拟地址空间 vs 用户虚拟地址空间 Linux内存管理数据结构 Page Memory zone Linux内存分配与回收 以页为单位分配内存空间 以字节为单位分配内存空间 Slab分配器 Cache Slab 分配与释放内核对象 Linux进程的地址空间
分配与释放内存空间 释放内存 • 以页为单位 • free_pages • __free_pages • free_page • 以字节为单位 • kfree • 物理上连续 • vfree() • 逻辑上连续 • 分配内存 • 以页为单位 • alloc_pages() • __get_free_pages () • alloc_page () • __get_free_page () • get_zeroed_page() • 以字节为单位 • kmalloc() • 物理上连续 • vmalloc() • 逻辑上连续
分配页@物理内存 (1) • 以页为单位,内核分配连续的物理页 • struct page * alloc_pages (unsigned int gfp_mask, unsigned int order) • include/linux/gfp.h L97 • 分配2order (即1 << order) 个连续的物理页 • 返回第一个页的page结构体指针,若出错,返回NULL • void * page_address(struct page *page) 通过该函数可以将page结构体指针转换为逻辑地址 • unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order) • 和alloc_pages作用相同, 直接返回第一个页的逻辑地址 • struct page * alloc_page(unsigned int gfp_mask) • unsigned long __get_free_page(unsigned int gfp_mask)
分配页@物理内存 (2) • unsigned long get_zeroed_page(unsigned int gfp_mask) • 分配一页,但所有内容初始化为0 (为了安全考虑) • 返回逻辑地址 • 若一个页回收后再分配给用户空间,则其间数据须清除或填充为0
释放页@物理内存 • 释放页函数 • void __free_pages(struct page *page, unsigned int order) • void free_pages(unsigned long addr, unsigned int order) • void free_page(unsigned long addr) • 注意: • 只能释放分配的属于自己的页(进程A不能释放进程B的页) • 参数错误(struct page指针、order、addr逻辑地址),都将导致内核崩溃
分配与释放页举例 unsigned long addr; addr = __get_free_pages(GFP_KERNEL, 3); if (! addr) { /* insufficient memory: you must handle this error! */ return ENOMEM; } /* 'addr' is now the address of the first of eight contiguous pages ... */ free_pages(addr, 3); /* * our pages are now freed and we should no * longer access the address stored in 'page' */
补充 • __get_dma_pages • #define __get_dma_pages(gfp_mask, order) __get_free_pages((gfp_mask) | GFP_DMA, (order)) • the pages requested be from ZONE_DMA by adding that flag onto the page flag mask.
分配字节内存块@物理内存 • 以字节为单位,分配连续的一段物理内存 • void *kmalloc(size_t size, int flags) • include/linux/slab.h L85 • 分配至少size字节大小的一块连续内存 • 返回内存块的指针,若出错,返回NULL • 举例 struct dog *ptr; ptr = kmalloc(sizeof(struct dog), GFP_KERNEL); if (!ptr) /* handle error ... */
释放字节内存块@物理内存 • void kfree(const void *ptr) • 释放通过kmalloc分配的一块连续内存 • 举例 char *buf; buf = kmalloc(BUF_SIZE, GFP_ATOMIC); if (!buf) /* error allocting memory ! */ /*if no longer need the memory, do not forget to free it*/ kfree(buf);
gfp_mask flags 分配标志 (1) • 标志可分为三类 • 行为修饰符(action modifiers) • 指导内核该如何分配所需的内存 • 区修饰符(zone modifiers) • 指导内核从哪个区分配所需的内存 • 类型修饰符(types) • 组合了前两者,将各种可能用到的组合归纳为不同的类型 • 简化了修饰符的使用,一般只需指定类型修饰符就可以了
gfp_mask flags 分配标志 (2) • 行为修饰符(action modifiers) • 指导内核该如何分配所需的内存(在分配内存的过程中) • ptr = kmalloc(size, __GFP_WAIT | __GFP_IO | __GFP_FS);
gfp_mask flags 分配标志 (3) • 区修饰符(zone modifiers) • 指导内核从哪个区分配所需的内存 • 若不指定,内核缺省从ZONE_NORMAL分配内存
gfp_mask flags 分配标志 (4) • 类型修饰符(types) • 组合了前两者,将各种可能用到的组合归纳为不同的类型 • 简化了修饰符的使用,一般只需指定类型修饰符就可以了
vmalloc() (1) • kmalloc() • 以字节为单位分配内存空间,物理地址连续,虚拟地址连续 • 硬件不理解虚拟地址,故要求物理上连续的块 • vmalloc() • 以字节为单位分配内存空间,物理地址无需连续,但虚拟地址连续。这和用户空间的malloc()相似。 • 软件可以使用虚拟地址连续的内存块,通过“页表”映射,故无需物理上连续 • 两者比较 • 出于性能,很多内核代码都用kmalloc(),而不是vmalloc() • vmalloc()获得的内存块须一个一个通过TLB映射 • vmalloc()一般用在为了获得大块内存时,例如把模块动态插入到内核中
vmalloc() (2) • void * vmalloc(unsigned long size) • 返回size字节大小的内存空间指针,逻辑上连续,物理上可能不连续 • 若出错,返回NULL • void vfree(void *addr) • 举例 char *buf; buf = vmalloc(16 * PAGE_SIZE); /* get 16 pages */ if (!buf) /* error! failed to allocate memory */ / * buf now points to at least a 16*PAGE_SIZE bytes * of virtually contiguous block of memory */ /*after using, make sure to free it*/ vfree(buf);
分配与释放内存空间 释放内存 • 以页为单位 • free_pages • __free_pages • free_page • 以字节为单位 • kfree • 物理上连续 • vfree() • 逻辑上连续 • 分配内存 • 以页为单位 • alloc_pages() • __get_free_pages () • alloc_page () • __get_free_page () • get_zeroed_page() • 以字节为单位 • kmalloc() • 物理上连续 • vmalloc() • 逻辑上连续
Linux内存管理 内存管理概述 Linux内存管理数据结构 Linux内存分配与回收 Slab分配器 Cache Slab 分配与释放内核对象 Linux进程的地址空间
Slab分配器 (1) “内核加速” • 源于观察 • 内核会频繁地创建和销毁内核数据对象(kernel data objects),例如task_struct结构体、inode结构体等。 • 这些都是小数据对象(占用的内存空间小,远远不够一页)。 • 频繁地创建和销毁这些内核数据对象将导致的问题 • 每一次创建一个object,需消耗时间为它们寻找并分配合适的内存空间 • 一个object销毁后,又需要回收内存空间,这将造成很多内存碎片 • Slab的发明人认为 • 能否把内核数据object在使用完后,其占据的地址空间不释放,而先保留起来,并通过链表组织起来,以备后用 • 在下次创建某个object的时候,直接寻找以前object的内存空间 • Object结构体所占内存空间已分配好,某些字段已经完成了初始化 • 这相当于有了一个关于object(及其对应内存空间)的缓存(Cache) • Slab分配器 • Slab allocator最先在SunOS 5.4中实现。Linux引入了其思想和名字。
Slab分配器 (2) • 基本思想 • Slab allocator is an object based allocator. • Slab allocator is to have caches of commonly used objects kept in an initialised state available for use by the kernel. • The slab allocator aims to to cache the freed object so that the basic structure is preserved between uses. • slab分配支持Object与hardware cache line对齐和着色,提高系统性能
Slab分配器 (3) • Slab分配器的构成 • 由多个cache组成,形成一个双向循环链表(cache chain) • cache • 每个cache组织管理一类内核数据对象Object • 例如,struct task_struct、inode、dentry、 mm_struct、 fs_cache等 • 每个cache由多个slab组成 • Slab • 一个slab由一个或多个物理上连续的页组成 • 一个page包含了一些内核数据对象object. • 这些数据对象是slab分配和释放的最小内存单位。 • 这些内存空间上保留了以前为object分配并初始化的内存块 • 为了便于为Object查找和分配内存空间 • 每个cache有三条由slab构成的链表。 • slabs_full //slab满,即没有空闲的object • slabs_partial //slab部分满,即有些object还空闲着 • slabs_empty //slab空, 所有object都是空闲的 • 若需要为某个object分配空间,先查slabs_partial, 再查slabs_empty. • 条件变动后,slab可以在这三条链表之间移动,修改指针就可以了
Slab分配器 (4) -- kmem_cache_t • Cache Descriptor --- kmem_cache_t include/linux/slab.h mm/slab.c mm/slab.c include/linux/slab.h
Slab分配器 (5) ---创建cache • 创建cache kmem_cache_t * kmem_cache_create(const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long), void (*dtor)(void*, kmem_cache_t *, unsigned long)) • 参数 • name为Cache的名字 • size为Cache中每个元素的大小 • align是第一个对象偏移,用于在内存页中对齐。一般标准对齐时,值为0。 • flag用来控制cache的行为。若无特殊行为,设为0. 也可用以下标志 “或”运算来设定 • SLAB_NO_REAP 不要在内存短缺时自动回收object (不建议) • SLAB_HWCACHE_ALIGN 将每个object和物理cache line对齐(对齐可加快性能,但浪费内存) • SLAB_MUST_HWCACHE_ALIGN 每个Object必须和物理Cache Line对齐。(对齐越严格, 浪费的内存就越多) • SLAB_POISON 用(a5a5a5a5)来填充slab内存空间。“中毒”,有利于对未初始化的内存的访问 • SLAB_RED_ZONE 在slab已经分配的内存页周围设置“红色警戒区”,防止缓冲越界。 • SLAB_PANIC 当创建Cache失败时提醒Slab分配器。 • SLAB_CACHE_DMA 在ZONE_DMA空间为slab分配空间。 • ctor cache的构造(constructor)函数。当需要追加新页到Cache时,调用该函数。若无,则设为NULL • dtor cache的析构(destructor)函数。当需要从Cache中删除页时,调用该函数。若无,则设为NULL • 若创建成功,返回Cache的指针;否则,返回NULL
Slab分配器 (6) ---销毁cache • 销毁cache • int kmem_cache_destroy(kmem_cache_t *cachep) • 返回值: • 成功,返回0;否则,返回非0 • 调用该函数销毁Cache的条件: • Cache中所有slab为空。若其中一个slab的某个object正在被使用,则不能销毁
Slab分配器 (7) – slab的创建与释放 • Slab • 为新的slab分配内存空间(连续的内存page) • static void *kmem_getpages(kmem_cache_t *cachep, int flags, int nodeid) • 实质上调用了__get_free_pages() • 只有cache中没有空或者部分满的slab时才会创建新slab,并为其分配内存空间 • 释放slab空间 • kmem_freepages() • 实质上调用了free_pages() • 一般不释放slab的内存页,只有内存变得紧缺或者cache被显式地销毁
Slab分配器 (8) – 获取与释放Object • Cache和Slab创建好以后,就可以通过Slab分配器来获取Object了 • 获取object • void * kmem_cache_alloc(kmem_cache_t *cachep, int flags) • 从cachep指定的cache中获取一个object • 返回值:返回指向object的指针 • 疑问:为什么没有指明要何种object?例如task_struct 或者 inode? • 释放object • void kmem_cache_free(kmem_cache_t *cachep, void *objp) • 从cachep的cache中释放objp指向的object • 疑问:释放后,其内存空间还在不在? • 实质上是将引用该object的指针断开,标记该Object为空闲(free),并让其回到原来slab中的object链表中去