Presentation is loading. Please wait.

Presentation is loading. Please wait.

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

Similar presentations


Presentation on theme: "Linux内存管理 内存管理概述 Linux内存管理数据结构 Linux内存分配与回收 Slab分配器 Linux进程的地址空间 页表"— Presentation transcript:

1 Linux内存管理 内存管理概述 Linux内存管理数据结构 Linux内存分配与回收 Slab分配器 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 虚拟内存 Virtual Memory 内存扩充 RAM
在外存上开辟空间来缓存物理内存页面,实现内存扩展 Linux:在磁盘上开辟的Swap区 Windows:在磁盘上开辟的VM区 内存扩充 借用外存空间来扩展物理内存空间,方法是让进程的部分代码或数据进入物理内存,其余驻留在外存,在需要时再通过替换进入物理内存。

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

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

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 地址空间 操作系统对内存的使用 i386地址空间 思考:QQ程序在运行过程中,占用哪个空间? 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 虚拟地址空间
注意: 不要把“虚拟地址空间”和“物理地址空间”直接对应起来,他们之间映射(Mapping)才能建立关系

14 Linux内存管理 内存管理概述 Linux内存管理数据结构 Linux内存分配与回收 Slab分配器 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

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

19 Zone (1) 由于硬件的限制,内核并不能对所有的页一视同仁。 内核把内存划分为三个不同的功能区(zone)
一些设备只能用某些特定的内存范围来执行DMA 一些体系结构支持物理地址空间大于虚拟地址空间 导致一些物理内存永远不能被映射到内核地址空间上 内核把内存划分为三个不同的功能区(zone) include/linux/mmzone.h (L65) #define ZONE_DMA used for DMA page frames #define ZONE_NORMAL non-DMA pages with virtual mapping #define ZONE_HIGHMEM “高端内存” pages whose addresses are not contained in the virtual address space

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

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

22 Zone (4) spinlock_t lock; unsigned long free_pages;
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个页空闲

23 Zone (5) spinlock_t lru_lock; struct list_head active_list;
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

24 Zone (6) 相关函数 include/linux/mmzone.h for_each_zone() is_highmem()
#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);

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

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

27 分配页@物理内存 (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, 和alloc_pages作用相同, 直接返回第一个页的逻辑地址 struct page * alloc_page(unsigned int gfp_mask) unsigned long __get_free_page(unsigned int gfp_mask)

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

29 释放页函数 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逻辑地址),都将导致内核崩溃

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

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

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

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

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

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

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

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

38 gfp_mask flags 分配标志 (5)

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

40 vmalloc() (2) void * vmalloc(unsigned long 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);

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

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

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

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

45 Slab分配器 (3) Slab分配器的构成 cache Slab 为了便于为Object查找和分配内存空间
由多个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可以在这三条链表之间移动,修改指针就可以了

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

47 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

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

49 Slab分配器 (7) – 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被显式地销毁

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

51 Slab分配器 (8) 实例:task_struct kernel/fork.c
创建用来管理task_struct的Cache // fork_init() kmem_cache_t *task_struct_cachep; //声明一个cache task_struct_cachep = kmem_cache_create("task_struct", sizeof(struct task_struct), ARCH_MIN_TASKALIGN, SLAB_PANIC, NULL, NULL); 创建一个task_struct // fork() -> do_fork() -> dup_task_struct() struct task_struct *tsk; tsk = kmem_cache_alloc(task_struct_cachep, GFP_KERNEL); if (!tsk) return NULL; 释放一个task_struct kmem_cache_free(task_struct_cachep, tsk); 销毁管理task_struct的Cache //task_struct是内核大的核心对象,使用非常频繁,一般不会销毁 int err; err = kmem_cache_destroy(task_struct_cachep); if (err) /* error destroying cache *

52 Slab分配器 (9) 总结 cache => slab => object
从cache中分配内核对象空间,将回收的对象还给cache 查看 cat /proc/slabinfo cat /proc/slabinfo | grep task_struct 分类 specialized cache task_struct (进程PCB,sizeof(task_struct)), inode(索引节点) dentry (目录项 ) General cache 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, KB **(DMA) 在ZONE_DMA中

53 Linux内存管理 内存管理概述 Linux内存管理数据结构 Linux内存分配与回收 Slab分配器 进程地址空间 页表
mm_struct Vm_area_struct 页表

54 进程地址空间 (1) 内核的内存管理包含两方面 每个进程都有自己独立的虚拟地址空间 管理内核地址空间(内核进程的地址空间)
进程地址空间 (1) 内核的内存管理包含两方面 管理内核地址空间(内核进程的地址空间) 管理用户地址空间 (用户进程的地址空间) 每个进程都有自己独立的虚拟地址空间 虚拟内存以页为基本单位,大小等同于物理页 进程的地址空间可以大于系统的物理内存 系统中的所有进程共享物理内存 两个不同的进程可以在各自的虚拟地址空间的相同地址上存放不同的数据。但进程之间也可以选择共享地址空间,这样的进程称为“线程” 进程的虚拟地址通过页目录/页表实现与物理地址的映射

55 进程地址空间 (2) 内存区域(memory area) 对一个进程来说,常见的内存区域有 一般一个进程并不会占满所有的虚拟内存空间
进程地址空间 (2) 内存区域(memory area) 一般一个进程并不会占满所有的虚拟内存空间 进程只能访问属于自己的合法有效的虚拟地址范围,这个地址区间被称为简称内存区域(memory area); 在内核中常常称为虚拟内存区域 (virtual memory area,VMA) 每个内存区域可能存在特定的访问属性 只读、只写、可执行 对一个进程来说,常见的内存区域有 Text section Data section Stack Heap

56 include/linux/sched.h
进程地址空间 (3) 内核使用mm_struct来描述进程的地址空间 使用vm_area_struct来描述内存区域 include/linux/sched.h include/linux/mm.h

57 进程地址空间 (4)

58 mm_struct include/linux/sched.h L204

59 mm_struct struct vm_area_struct * mmap; atomic_t mm_users;
正在使用该地址空间的进程数目。 若两线程共享该区域,值为2 atomic_t mm_count; 本mm_struct结构体的引用计数 当没有进程引用时,mm_count为0 内核会检查该计数值,若为0,则mm_struct被销毁 struct list_head mmlist; 其prev和next指针将所有进程的mm_struct连接为一个双向循环链表 init_mm指针指向这个双向循环链表的头,它代表init进程的mm_struct 该双向循环链表操作时,要通过mmlist_lock来防止并发访问

60 分配和释放mm_struct 分配mm_struct 释放mm_struct
在进程的task_struct中,字段mm指向其对应的mm_struct fork()利用copy_mm()复制父进程的mm_struct 而实质上,有了slab分配器以后,子进程的mm_struct是通过kernel/fork.c中的allocate_mm()从mm_cachep中分配得到的。 释放mm_struct 当进程退出时,内核调用exit_mm() 调用mmput()减少mm_struct中的mm_users计数 若mm_users为0,则调用mmdrop()减少mm_count 若mm_count为0,则free_mm通过kmem_cache_free()将mm_struct还给slab分配器的mm_cachep

61 include/linux/sched.h
vm_area_struct 内存区域 内存区域(memory area)或(virtual memory area,VMA) vm_area_struct描述了地址空间连续的一段内存 include/linux/sched.h include/linux/mm.h

62 vm_area_struct 内存区域 Linux内核将每段VMA作为一个单独的内存对象进行管理 每个VMA拥有一致的属性(例如访问权限)

63 VMA Flags include/linux/mm.h L117 想一想:VMA flags与前面学习的GFP flags有什么区别?

64 VMA Flags VM_READ / VM_WRITE / VM_EXEC VM_SHM VM_IO VM_RESERVED
进程code section对应VMA一般为VM_READ|VM_EXEC 进程data section对应VMA一般为VM_READ|VM_WRITE VM_SHM VMA是否可以在多进程间共享(共享映射 vs. 私有映射) VM_IO VMA被用于设备I/O空间映射 (mmap()进行I/O空间映射用) VM_RESERVED VMA所在物理页不能被换出 (常被设备驱动调用映射) VM_SEQ_READ vs. VM_RAND_READ 根据进程是否需要支持“预取”,调用madvise()可以修改以上两个标志,参数分别为MADV_SEQUENTIAL和MADV_RANDOM

65 VMA Operations vm_area_struct中vm_ops字段指向VMA相关操作函数表
vm_operations_struct (include/linux/mm.h L175) struct vm_operations_struct { void (*open) (struct vm_area_struct *); void (*close) (struct vm_area_struct *); struct page * (*nopage) (struct vm_area_struct *, unsigned long, int); int (*populate) (struct vm_area_struct *, unsigned long, unsigned long, pgprot_t, unsigned long, int); }; open(): 当指定的VMA被加入到一个物理地址空间时被调用 close():当指定的VMA被从一个物理地址空间移除时调用 nopage(): 当要访问的页不在物理内存时,被page fault handler调用 populate(): 当发生缺页中断时,remap_pages()调用populate()进行一个新映射

66 mm_struct与vm_area_struct的组织形式
链表 mm_struct的mmap字段 mmap指向第一个VMA对应的vm_area_struct, 其它VMA通过vm_area_struct的vm_next指针链入,最后一个VMA的vm_next指向NULL 红黑树 (red-black tree) mm_struct的mm_rb字段 mm_rb指向红黑树的root节点,每个VMA通过vm_area_struct中vm_rb字段链接到树中。 Red-black tree是一种二叉树,其所有节点遵从:左孩子节点的值小于右孩子节点。根节点为红色,所有红节点的子节点为黑色。 红黑树的搜索、插入、删除时间复杂度均为O(log(n)) 区别 组织方式不同 链表适用于需要遍历所有VMA,红黑树用于特定VMA操作

67 进程地址空间

68 进程地址空间 Stack Data Section mm_struct Code Section

69 进程地址空间 用户程序被装入内存后,内存区域(memory area) 就有了物理空间 Code section Data section
holds the executable instructions of a program multiple processes can share the same text segment in memory Data section gvar (initialized global variable) BSS (Block Started by Symbol) (non-initialized data) Heap malloc() or new() obtain dynamic memory placed in the heap sys_brk() moves the brk pointer to its new location Stack This contains all the local variables that get allocated. When a function is called, the local variables for that function are pushed onto the stack. As soon as a function ends, the variables associated with the function are popped from the stack.

70 进程地址空间 Stack Data Section gvar mm_struct Code Section local variables
malloc动态分配内存 Data Section non-initialized data gvar mm_struct Code Section null pointer

71 进程地址空间 Example:

72 进程地址空间 The memory map of a process /proc/<pid>/maps inode offset
Libc库 SO: Shared Object Code Segment data Segment major : minor 主设备号:从设备号 inode P:private S:shared offset

73 进程地址空间 也可借助pmap工具,以方便阅读的形式展示

74 搜索VMA: 从red-black tree上搜索
find_vma() (mm/mmap.c L1249) struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr) 搜索vm_end大于addr的第一个VMA 即(1) VMA包含addr 或(2) VMA的起始地址就大于addr find_vma_prev() (mm/mmap.c L1288) struct vm_area_struct * find_vma_prev(struct mm_struct *mm, unsigned long addr, struct vm_area_struct **pprev) it returns the last VMA before addr find_vma_intersection() (include/linux/mm.h L742) struct vm_area_struct * find_vma_intersection(struct mm_struct *mm, unsigned long start_addr, unsigned long end_addr) it returns the first VMA that overlaps a given address interval

75 创建VMA (1) do_mmap() (include/linux/mm.h L691)
unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long offset) 创建一个新的VMA 文件映射(file-backed mapping): file中offset开始len字节 匿名映射(anonymous mapping): 当file为NULL,offset为0 addr 指定搜索空闲区域的起始位置 功能:创建一个新的VMA,成功返回VMA的起始地址,否则,返回一个负值 创建新VMA,若属性与当前VMA相同且邻接,则与当前VMA合并 (扩充) 否则,则借助slab分配器从vm_area_cachep中分配一个vm_area_struct结构体 使用vma_link()使新的VMA结构体连入到链表和红-黑树中 更新mm_struct中的total_vm字段

76 创建VMA (2) prot :VMA中页的保护标志 (include/asm-i386/mman.h L4)
flag :VMA的映射标志 (include/asm-i386/mman.h L12)

77 创建VMA (3) mmap() 系统调用 (用户空间) mmap() => mmap2() => do_mmap()
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 字节偏移量 void * mmap2(void *start, size_t length, int prot, int flags, int fd, off_t pgoff) 页面偏移量

78 删除VMA do_munmap() (include/linux/mm.h L704) munmap() 系统调用
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len) 删除从start开始,len字节的VMA 成功,返回0;否则,返回负值 munmap() 系统调用 munmap() => sys_munmap() => do_munmap() int munmap(void *start, size_t length)

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

80 页表(Page Tables) (1) 程序操作的是映射到物理内存上的虚拟内存,而CPU直接操作的是物理内存(The processor operates on physical addresses) 当程序访问一个虚拟地址时,首先必须将虚拟地址转换为物理地址,然后CPU才能处理 Page Table virtual address → physical address Linux的页面大小为4KB,则4GB的线性地址空间可划分为1M个页面 若用一个线性页表描述,页表包含1M个表项 为了简化查找,一般都采取了多级页表机制

81 页表(Page Tables) (2) Linux的三级分页机制 在多数体系结构中,搜索页表的工作是由硬件完成的 PGD 页全局目录
Page Global Directory pgd_t 数组 PMD 页中间目录 Page Middle Directory pmd_t 数组 PTE 页表 Page Table pte_t 在多数体系结构中,搜索页表的工作是由硬件完成的 内核须组织好页表,支持和方便硬件查询 页表对应依赖具体的体系结构 i386架构 include/asm-i386/page.h

82 i386二级分页 (1) i386平台的二级分页 虚拟地址 页目录 页表 硬件只支持二级页表(即段页表)
为了适应i386硬件体系结构,Linux的中间页目录(PMD)不起作用。 因此,在i386平台上的Linux系统采用二级分页机制 虚拟地址 1 K 1 K 4 K 页目录 最多有1K个页表 每一项 【页目录中的索引号,页表的首地址】 页表 每一项 【页表中的索引号,页的内存首地址】

83 i386二级分页 (2) 地址映射 CR3

84 页表(Page Tables) (3) 每个进程都有自己的页表 TLB(translation lookaside buffer)
属于同一进程的多个线程共享页表 操作和检索页表时必须使用page_table_lock TLB(translation lookaside buffer) 为了加快查表速度,多数体系结构都提供了TLB TLB是一个将虚拟地址翻译为物理地址的硬件缓存 当访问一个虚拟地址时,CPU先检查TLB 若命中,则直接返回物理地址 否则,通过多级页表查询并返回物理地址 Linux2.6对页表的改进 从ZONE_HIGNMEM分配页表空间 父进程fork()子进程后,父子共享页表,只有父或子进程修改特定页表项时,才拷贝并创建新页表


Download ppt "Linux内存管理 内存管理概述 Linux内存管理数据结构 Linux内存分配与回收 Slab分配器 Linux进程的地址空间 页表"

Similar presentations


Ads by Google