1 / 84

Linux 内存管理

Linux 内存管理. 内存管理概述 物理内存 vs 虚拟内存  物理地址 vs 虚拟地址 ( 逻辑地址 ) 物理地址空间 vs 虚拟地址空间 内核虚拟地址空间 vs 用户虚拟地址空间 Linux 内存管理数据结构 Page Memory zone Linux 内存分配与回收 以页为单位分配内存空间 以字节为单位分配内存空间 Slab 分配器 Cache Slab 分配与释放内核对象 Linux 进程的地址空间 mm_struct Vm_area_struct 页表. 内存管理主要职责.

beyla
Télécharger la présentation

Linux 内存管理

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Linux内存管理 • 内存管理概述 • 物理内存 vs 虚拟内存  • 物理地址 vs 虚拟地址 (逻辑地址) • 物理地址空间 vs 虚拟地址空间 • 内核虚拟地址空间 vs 用户虚拟地址空间 • Linux内存管理数据结构 • Page • Memory zone • Linux内存分配与回收 • 以页为单位分配内存空间 • 以字节为单位分配内存空间 • Slab分配器 • Cache • Slab • 分配与释放内核对象 • Linux进程的地址空间 • mm_struct • Vm_area_struct • 页表

  2. 内存管理主要职责 • 内存分配(allocate)与回收(deallocate) • 内存扩充 • 虚拟内存 • 地址变换 • 虚拟地址 =》物理地址 • 内存保护 • 内存使用状态跟踪keeping track • 通过数据结构的字段来记录内存的使用情况

  3. 存储系统结构 “金字塔”

  4. 物理内存 vs. 虚拟内存 • 物理内存 Physical Memory • RAM • 虚拟内存 Virtual Memory • 在外存上开辟空间来缓存物理内存页面,实现内存扩展 • Linux:在磁盘上开辟的Swap区 • Windows:在磁盘上开辟的VM区 • 内存扩充 • 借用外存空间来扩展物理内存空间,方法是让进程的部分代码或数据进入物理内存,其余驻留在外存,在需要时再通过替换进入物理内存。

  5. 虚拟地址 vs. 物理地址 • 虚拟地址(逻辑地址) • 程序编程使用虚拟地址 • 虚拟地址空间:进程能够访问的虚拟地址范围。 • 在32位字长X86机器上,虚拟地址空间大小为4GB。 • 物理地址 • 物理内存上以字节为单位编码的地址。 • 物理地址空间:物理内存的地址范围。

  6. 4GB 进程虚拟地址空间 系统中每个进程都有自己的地址空间 0 地址变换 (地址翻译) (1)

  7. 地址变换 (地址翻译) (2) • MMU(Memory management Unit)负责把虚拟地址翻译为物理地址,并让一个进程始终运行于系统的物理内存中。“硬件地址翻译” • 无论CPU运行于用户态还是核心态,程序执行时,交给CPU访问的地址是虚拟地址,MMU通过读取控制寄存器CR3得到页目录的指针,然后根据快表TLB(Translation lookaside buffer)和页表(Page Table)将该虚拟地址转换为物理地址。 • 想一想:若将程序的虚拟地址转换为物理地址后,发现不在物理内存中,怎么办?

  8. 页面替换 (1) • 虚拟内存通过“页” 进行组织。 • 页是OS在物理内存和磁盘swap区之间移动的基本单位。 • 移入内存page in • 移出page out 操作系统使每个进程都以为自己拥有整个地址空间的独家访问权,这个幻觉是借助“虚拟内存”实现的。

  9. 页面替换 (2) • 进程只能操作位于物理内存中的页面,所有进程共享物理内存。 • 在进程运行时,经过地址翻译,当发现引用的页面不在物理内存时,需要从磁盘调入内存。 • MMU会产生一个页错误(page fault)。内核对此事件作出响应,并判断该引用是否有效。 • 若无效,内核向进程发出一个“segmentation violation”信号 • 若有效,内核从磁盘取回该页,换入到内存中。一旦页面进入内存,进程便被解锁,可以重新运行。因此,进程本身并不知道它曾经因为页面换入事件而等待了一会儿。 • Linux 内核常驻物理内存,只有用户进程才被移入移出。

  10. 内存保护 • 防止程序越界和越权行为 • 不允许用户进程访问操作系统的存储区域 • 每个进程都在自己的地址空间中运行,互不干扰 • 内存保护的措施 • 界限保护 • 设置界限寄存器,限制进程的活动空间。 • 保护锁 • 为共享内存区设置一个读/写保护锁,在CPU中设置保护锁开关,它表示进程的读/写权限。只有进程的开关代码和内存区的保护锁匹配时方可进行访问。 • 保护模式 • 将CPU的工作模式分为用户态与核心态。核心态下的进程可以访问整个内存地址空间,而用户态下的进程只能访问在界限寄存器所规定范围内的空间。

  11. 地址空间 • 操作系统对内存的使用 • OS启动阶段:临时使用内存 • OS正常运行阶段 • 内核虚拟地址空间 • 存放“内核映像”:kernel code and data • 被众多进程和内核共享 • 用户虚拟地址空间 • 用户进程的code、data、stack等 • i386地址空间 • 在32位机器平台上,虚拟地址空间为4GB • 内核虚拟地址空间 1GB • 用户虚拟地址空间 3GB • 思考:QQ程序在运行过程中,占用哪个空间?

  12. 空间映射 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

  13. 分清几个术语 • 物理内存 vs 虚拟内存  • 物理地址 vs 虚拟地址 (逻辑地址) • 物理地址空间 vs 虚拟地址空间 • 内核虚拟地址空间 vs 用户虚拟地址空间 • 注意: • 不要把“虚拟地址空间”和“物理地址空间”直接对应起来,他们之间映射(Mapping)才能建立关系

  14. Linux内存管理 内存管理概述 Linux内存管理数据结构 Page Memory zone Linux内存分配与回收 Slab分配器 Linux进程的地址空间 页表

  15. 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)

  16. Page(2) • unsigned long flag • 每bit代表一个状态,每个page可以有多个不同的状态 • include/linux/page-flags.h (L50)

  17. Page(3) • atomic_t _count; • 该页被引用的次数,可用page_count()函数查看 • 0表示页空闲,可用 • void *virtual; • 页的virtual address • struct list_head lru; • 通过next 和 prev 指向LRU List中的页面 • 注意: • 内核用Page结构体来描述某个物理页的属性,而不是描述包含在其中的数据 • 系统中的每个物理页都要分配一个page结构体

  18. 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

  19. Zone (2) • 区(zone)的划分和使用与体系结构密切相关 • 某些体系结构在内存的任何地址上都可以执行DMA • 而在x86上, ISA设备只能访问物理内存的前16MB • 在某些体系结构上,所有内存都可以被直接映射 • 故ZONE_HIGNMEM为空,没有对应的物理内存页 • 区的划分没有任何物理意义,只不过是内核为了管理页而采取的逻辑分组。 • 类比:校园 • 页 vs 平方米 • 区 vs 生活区、教学区、科研区

  20. Zone (3) (include/linux/mmzone.h L101) struct zone { …… …… }

  21. 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个页空闲

  22. 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

  23. 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); }

  24. Linux内存管理 内存管理概述 物理内存 vs 虚拟内存  物理地址 vs 虚拟地址 (逻辑地址) 物理地址空间 vs 虚拟地址空间 内核虚拟地址空间 vs 用户虚拟地址空间 Linux内存管理数据结构 Page Memory zone Linux内存分配与回收 以页为单位分配内存空间 以字节为单位分配内存空间 Slab分配器 Cache Slab 分配与释放内核对象 Linux进程的地址空间

  25. 分配与释放内存空间 释放内存 • 以页为单位 • free_pages • __free_pages • free_page • 以字节为单位 • kfree • 物理上连续 • vfree() • 逻辑上连续 • 分配内存 • 以页为单位 • alloc_pages() • __get_free_pages () • alloc_page () • __get_free_page () • get_zeroed_page() • 以字节为单位 • kmalloc() • 物理上连续 • vmalloc() • 逻辑上连续

  26. 分配页@物理内存 (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)

  27. 分配页@物理内存 (2) • unsigned long get_zeroed_page(unsigned int gfp_mask) • 分配一页,但所有内容初始化为0 (为了安全考虑) • 返回逻辑地址 • 若一个页回收后再分配给用户空间,则其间数据须清除或填充为0

  28. 释放页@物理内存 • 释放页函数 • 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逻辑地址),都将导致内核崩溃

  29. 分配与释放页举例 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' */

  30. 补充 • __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.

  31. 分配字节内存块@物理内存 • 以字节为单位,分配连续的一段物理内存 • 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 ... */

  32. 释放字节内存块@物理内存 • 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);

  33. gfp_mask flags 分配标志 (1) • 标志可分为三类 • 行为修饰符(action modifiers) • 指导内核该如何分配所需的内存 • 区修饰符(zone modifiers) • 指导内核从哪个区分配所需的内存 • 类型修饰符(types) • 组合了前两者,将各种可能用到的组合归纳为不同的类型 • 简化了修饰符的使用,一般只需指定类型修饰符就可以了

  34. gfp_mask flags 分配标志 (2) • 行为修饰符(action modifiers) • 指导内核该如何分配所需的内存(在分配内存的过程中) • ptr = kmalloc(size, __GFP_WAIT | __GFP_IO | __GFP_FS);

  35. gfp_mask flags 分配标志 (3) • 区修饰符(zone modifiers) • 指导内核从哪个区分配所需的内存 • 若不指定,内核缺省从ZONE_NORMAL分配内存

  36. gfp_mask flags 分配标志 (4) • 类型修饰符(types) • 组合了前两者,将各种可能用到的组合归纳为不同的类型 • 简化了修饰符的使用,一般只需指定类型修饰符就可以了

  37. gfp_mask flags 分配标志 (5)

  38. vmalloc() (1) • kmalloc() • 以字节为单位分配内存空间,物理地址连续,虚拟地址连续 • 硬件不理解虚拟地址,故要求物理上连续的块 • vmalloc() • 以字节为单位分配内存空间,物理地址无需连续,但虚拟地址连续。这和用户空间的malloc()相似。 • 软件可以使用虚拟地址连续的内存块,通过“页表”映射,故无需物理上连续 • 两者比较 • 出于性能,很多内核代码都用kmalloc(),而不是vmalloc() • vmalloc()获得的内存块须一个一个通过TLB映射 • vmalloc()一般用在为了获得大块内存时,例如把模块动态插入到内核中

  39. 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);

  40. 分配与释放内存空间 释放内存 • 以页为单位 • free_pages • __free_pages • free_page • 以字节为单位 • kfree • 物理上连续 • vfree() • 逻辑上连续 • 分配内存 • 以页为单位 • alloc_pages() • __get_free_pages () • alloc_page () • __get_free_page () • get_zeroed_page() • 以字节为单位 • kmalloc() • 物理上连续 • vmalloc() • 逻辑上连续

  41. Linux内存管理 内存管理概述 Linux内存管理数据结构 Linux内存分配与回收 Slab分配器 Cache Slab 分配与释放内核对象 Linux进程的地址空间

  42. Slab分配器 (1) “内核加速” • 源于观察 • 内核会频繁地创建和销毁内核数据对象(kernel data objects),例如task_struct结构体、inode结构体等。 • 这些都是小数据对象(占用的内存空间小,远远不够一页)。 • 频繁地创建和销毁这些内核数据对象将导致的问题 • 每一次创建一个object,需消耗时间为它们寻找并分配合适的内存空间 • 一个object销毁后,又需要回收内存空间,这将造成很多内存碎片 • Slab的发明人认为 • 能否把内核数据object在使用完后,其占据的地址空间不释放,而先保留起来,并通过链表组织起来,以备后用 • 在下次创建某个object的时候,直接寻找以前object的内存空间 • Object结构体所占内存空间已分配好,某些字段已经完成了初始化 • 这相当于有了一个关于object(及其对应内存空间)的缓存(Cache) • Slab分配器 • Slab allocator最先在SunOS 5.4中实现。Linux引入了其思想和名字。

  43. 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对齐和着色,提高系统性能

  44. 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可以在这三条链表之间移动,修改指针就可以了

  45. Slab分配器 (4) -- kmem_cache_t • Cache Descriptor --- kmem_cache_t include/linux/slab.h mm/slab.c mm/slab.c include/linux/slab.h

  46. 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

  47. Slab分配器 (6) ---销毁cache • 销毁cache • int kmem_cache_destroy(kmem_cache_t *cachep) • 返回值: • 成功,返回0;否则,返回非0 • 调用该函数销毁Cache的条件: • Cache中所有slab为空。若其中一个slab的某个object正在被使用,则不能销毁

  48. 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被显式地销毁

  49. 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链表中去

More Related