分段和分页

先看一幅图

linux内存管理机制内核空间(linux内存管理源码分析-页框分配器)(1)

linux内存管理机制内核空间(linux内存管理源码分析-页框分配器)(2)

地址空间
  1. ZONE_DMA:包含0MB~16MB之间的内存页框,可以由老式基于ISA的设备通过DMA使用,直接映射到内核的地址空间。
  2. ZONE_NORMAL:包含16MB~896MB之间的内存页框,常规页框,直接映射到内核的地址空间。
  3. ZONE_HIGHMEM:包含896MB以上的内存页框,不进行直接映射,可以通过永久映射和临时映射进行这部分内存页框的访问。

整个结构如下图

linux内存管理机制内核空间(linux内存管理源码分析-页框分配器)(3)

结点和管理区描述符

为了用于NUMA架构,使用了node用来描述一个地方的内存。对于我们PC来说,一台PC就是一个node。node用struct pglist_data结构表示:

/* 内存结点描述符,所有的结点描述符保存在 struct pglist_data *node_data[MAX_NUMNODES] 中 */ typedef struct pglist_data { /* 管理区描述符的数组 */ struct zone node_zones[MAX_NR_ZONES]; /* 页分配器使用的zonelist数据结构的数组,将所有结点的管理区按一定的关联链接成一个链表,分配内存时会按照此链表的顺序进行分配 */ struct zonelist node_zonelists[MAX_ZONELISTS]; /* 结点中管理区的个数 */ int nr_zones; #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */ /* 结点中页描述符的数组,包含了此结点中所有页框描述符,实际分配是是一个指针数组 */ struct page *node_mem_map; #ifdef CONFIG_MEMCG /* 用于资源限制机制 */ struct page_cgroup *node_page_cgroup; #endif #endif #ifndef CONFIG_NO_BOOTMEM /* 用在内核初始化阶段 */ struct bootmem_data *bdata; #endif #ifdef CONFIG_MEMORY_HOTPLUG /* 自旋锁 */ spinlock_t node_size_lock; #endif /* 结点中第一个页框的下标,在numa系统中,页框会有两个序号,所有页框的一个序号,还有就是在此结点中的一个序号 * 比如结点2中的页框1,它在结点2中的序号是1,但是在所有页框中的序号是1001,这个变量就是保存这个结点首页框的序号1000,用于方便转换 */ unsigned long node_start_pfn; /* 内存结点的大小,不包括洞(以页框为单位) */ unsigned long node_present_pages; /* 结点的大小,包括洞(以页框为单位) */ unsigned long node_spanned_pages; /* 结点标识符 */ int node_id; /* kswaped页换出守护进程使用的等待队列 */ wait_queue_head_t kswapd_wait; wait_queue_head_t pfmemalloc_wait; /* 指针指向kswapd内核线程的进程描述符 */ struct task_struct *kswapd; /* Protected by mem_hotplug_begin/end() */ /* kswapd将要创建的空闲块大小取对数的值 */ int kswapd_max_order; enum zone_type classzone_idx; #ifdef CONFIG_NUMA_BALANCING /* 以下用于NUMA的负载均衡 */ /* Lock serializing the migrate rate limiting window */ spinlock_t numabalancing_migrate_lock; /* Rate limiting time interval */ unsigned long numabalancing_migrate_next_window; /* number of pages migrated during the rate limiting time interval */ unsigned long numabalancing_migrate_nr_pages; #endif } pg_data_t;

我们再看看管理区描述符:

/* 内存管理区描述符 */ struct zone { /* Read-mostly fields */ /* zone watermarks, access with *_wmark_pages(zone) macros */ /* 包括pages_min,pages_low,pages_high * pages_min: 管理区中保留页的数目 * pages_low: 回收页框使用的下界,同时也被管理区分配器作为阀值使用,一般这个数字是pages_min的5/4 * pages_high: 回收页框使用的上界,同时也被管理区分配器作为阀值使用,一般这个数字是pages_min的3/2 */ unsigned long watermark[NR_WMARK]; /* 指明在处理内存不足的临界情况下管理区必须保留的页框数目,同时也用于在中断或临界区发出的原子内存分配请求(就是禁止阻塞的内存分配请求) */ long lowmem_reserve[MAX_NR_ZONES]; #ifdef CONFIG_NUMA int node; #endif /* * The target ratio of ACTIVE_ANON to INACTIVE_ANON pages on * this zone's LRU. Maintained by the pageout code. */ unsigned int inactive_ratio; /* 指向此管理区属于的结点 */ struct pglist_data *zone_pgdat; /* 实现每CPU页框高速缓存,里面包含每个CPU的单页框的链表 */ struct per_cpu_pageset __percpu *pageset; /* * This is a per-zone reserve of pages that should not be * considered dirtyable memory. */ unsigned long dirty_balance_reserve; #ifndef CONFIG_SPARSEMEM /* * Flags for a pageblock_nr_pages block. See pageblock-flags.h. * In SPARSEMEM, this map is stored in struct mem_section */ unsigned long *pageblock_flags; #endif /* CONFIG_SPARSEMEM */ #ifdef CONFIG_NUMA /* * zone reclaim becomes active if more unmapped pages exist. */ unsigned long min_unmapped_pages; unsigned long min_slab_pages; #endif /* CONFIG_NUMA */ /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */ /* 管理区第一个页框下标 */ unsigned long zone_start_pfn; /* 所有正常情况下可用的页,总页数(不包括洞)减去保留的页数 */ unsigned long managed_pages; /* 管理区总大小(页为单位),包括洞 */ unsigned long spanned_pages; /* 管理区总大小(页为单位),不包括洞 */ unsigned long present_pages; /* 指向管理区的传统名称,"DMA" "NORMAL" "HighMem" */ const char *name; /* 对应于伙伴系统中MIGRATE_RESEVE链的页块的数量 */ int nr_migrate_reserve_block; #ifdef CONFIG_MEMORY_ISOLATION /* * Number of isolated pageblock. It is used to solve incorrect * freepage counting problem due to racy retrieving migratetype * of pageblock. Protected by zone->lock. */ /* 在内存隔离中表示隔离的页框块数量 */ unsigned long nr_isolate_pageblock; #endif #ifdef CONFIG_MEMORY_HOTPLUG /* see spanned/present_pages for more description */ seqlock_t span_seqlock; #endif /* 进程等待队列的hash表,这些进程在等待管理区中的某页 */ wait_queue_head_t *wait_table; /* 等待队列散列表的大小 */ unsigned long wait_table_hash_nr_entries; /* 等待队列散列表数组大小 */ unsigned long wait_table_bits; ZONE_PADDING(_pad1_) /* Write-intensive fields used from the page allocator */ /* 保护该描述符的自旋锁 */ spinlock_t lock; /* free areas of different sizes */ /* 标识出管理区中的空闲页框块,用于伙伴系统 */ /* MAX_ORDER为11,分别代表包含大小为1,2,4,8,16,32,64,128,256,512,1024个连续页框的链表 */ struct free_area free_area[MAX_ORDER]; /* zone flags, see below */ /* 管理区标识 */ unsigned long flags; ZONE_PADDING(_pad2_) /* Fields commonly accessed by the page reclaim scanner */ /* 活动及非活动链表使用的自旋锁 */ spinlock_t lru_lock; struct lruvec lruvec; /* Evictions & activations on the inactive file list */ atomic_long_t inactive_age; /* * When free pages are below this point, additional steps are taken * when reading the number of free pages to avoid per-cpu counter * drift allowing watermarks to be breached */ unsigned long percpu_drift_mark; #if defined CONFIG_COMPACTION || defined CONFIG_CMA /* pfn where compaction free scanner should start */ unsigned long compact_cached_free_pfn; /* pfn where async and sync compaction migration scanner should start */ unsigned long compact_cached_migrate_pfn[2]; #endif #ifdef CONFIG_COMPACTION /* * On compaction failure, 1<<compact_defer_shift compactions * are skipped before trying again. The number attempted since * last failure is tracked with compact_considered. */ unsigned int compact_considered; unsigned int compact_defer_shift; int compact_order_failed; #endif #if defined CONFIG_COMPACTION || defined CONFIG_CMA /* Set to true when the PG_migrate_skip bits should be cleared */ bool compact_blockskip_flush; #endif ZONE_PADDING(_pad3_) /* 管理区的一些统计数据 */ atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]; } ____cacheline_internodealigned_in_smp;

管理区页框分配器(管理所有物理内存页框)
  1. 如果要求从DMA区中获取,就只能从ZONE_DMA区中获取。
  2. 如果没有规定从哪个区获取,就按照顺序从 ZONE_NORMAL -> ZONE_DMA 获取。
  3. 如果规定从HIGHMEM区获取,就按照顺序从 ZONE_HIGHMEM -> ZONE_NORMAL -> ZONE_DMA 获取。

linux内存管理机制内核空间(linux内存管理源码分析-页框分配器)(4)

/* 页描述符,描述一个页框,也会用于描述一个SLAB,相当于同时是页描述符,也是SLAB描述符 */ struct page { /* First double word block */ /* 用于页描述符,一组标志(如PG_locked、PG_error),也对页框所在的管理区和node进行编号 */ unsigned long flags; /* Atomic flags, some possibly * updated asynchronously */ union { /* 用于页描述符,当页被插入页高速缓存中时使用,或者当页属于匿名区时使用 */ struct address_space *mapping; /* 用于SLAB描述符,用于执行第一个对象的地址 */ void *s_mem; /* slab first object */ }; /* Second double word */ struct { union { /* 作为不同的含义被几种内核成分使用。例如,它在页磁盘映像或匿名区中标识存放在页框中的数据的位置,或者它存放一个换出页标识符 */ pgoff_t index; /* Our offset within mapping. */ /* 用于SLAB描述符,指向第一个空闲对象地址 */ void *freelist; /* 当管理区页框分配器压力过大时,设置这个标志就确保这个页框专门用于系统释放其他页框时使用 */ bool pfmemalloc; }; union { #if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE) /* SLUB使用 */ unsigned long counters; #else /* SLUB使用 */ unsigned counters; #endif struct { union { /* 页框中的页表项计数,如果没有为-1,如果为PAGE_BUDDY_MAPCOUNT_VALUE(-128),说明此页及其后的一共2的private次方个数页框处于伙伴系统中,正在使用时应该是0 */ atomic_t _mapcount; struct { /* SLUB使用 */ unsigned inuse:16; unsigned objects:15; unsigned frozen:1; }; int units; /* SLOB */ }; /* 页框的引用计数,如果为0,则此页框空闲,并可分配给任一进程或内核;如果大于0,则说明页框被分配给了一个或多个进程,或用于存放内核数据。page_count()返回_count的值,也就是该页的使用者数目 */ atomic_t _count; /* Usage count, see below. */ }; /* 用于SLAB描述符 */ unsigned int active; /* SLAB */ }; }; /* Third double word block */ union { /* 包含到页的最近最少使用(LRU)双向链表的指针,用于插入伙伴系统的空闲链表中,只有块中头页框要被插入 */ struct list_head lru; /* SLAB使用 */ struct { /* slub per cpu partial pages */ struct page *next; /* Next partial slab */ #ifdef CONFIG_64BIT int pages; /* Nr of partial slabs left */ int pobjects; /* Approximate # of objects */ #else short int pages; short int pobjects; #endif }; struct slab *slab_page; /* slab fields */ struct rcu_head rcu_head; #if defined(CONFIG_TRANSPARENT_HUGEPAGE) && USE_SPLIT_PMD_PTLOCKS pgtable_t pmd_huge_pte; /* protected by page->ptl */ #endif }; /* Remainder is not double word aligned */ union { /* 可用于正在使用页的内核成分(例如: 在缓冲页的情况下它是一个缓冲器头指针,如果页是空闲的,则该字段由伙伴系统使用,在给伙伴系统使用时,表明的是块的2的次方数,只有块的第一个页框会使用) */ unsigned long private; #if USE_SPLIT_PTE_PTLOCKS #if ALLOC_SPLIT_PTLOCKS spinlock_t *ptl; #else spinlock_t ptl; #endif #endif /* SLAB描述符使用,指向SLAB的高速缓存 */ struct kmem_cache *slab_cache; /* SL[AU]B: Pointer to slab */ struct page *first_page; /* Compound tail pages */ }; #if defined(WANT_PAGE_VIRTUAL) /* 此页框第一个物理地址对应的线性地址,如果是没有映射的高端内存的页框,则为空 */ void *virtual; #endif /* WANT_PAGE_VIRTUAL */ #ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS unsigned long debug_flags; /* Use atomic bitops on this */ #endif #ifdef CONFIG_KMEMCHECK void *shadow; #endif #ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS int _last_cpupid; #endif }

  1. flags:包含有很多信息,包括此页框属于的node结点号,此页框属于的zone号和此页框的属性。
  2. lru:用于将此页描述符放入相应的链表,比如伙伴系统或者每CPU页框高速缓存。
  3. _count:代表页框的引用计数,0代表此页框空闲,大于0代表此页框分配给了多少个进程使用(共享)。
  1. 不可移动页:在内存中有固定位置,不能移动到其他地方。内核中使用的页大部分是属于这种类型。
  2. 可回收页:不能直接移动,但可以删除,页中的内容可以从某些源中重新生成。例如,页内容是映射到文件数据的页就属于这种类型。对于这种类型,在内存短缺(分配失败)时,会发起内存回收,将这类型页进行回写释放。
  3. 可移动页:可随意移动,用户空间的进程使用的没有映射具体磁盘文件的页就属于这种类型(比如堆、栈、shmem共享内存、匿名mmap共享内存),它们是通过进程页表映射的,把这些页复制到新位置时,只要更新进程页表就可以了。一般这些页是从高端内存管理区获取。
伙伴系统

/* 伙伴系统的一个块,描述1,2,4,8,16,32,64,128,256,512或1024个连续页框的块 */ struct free_area { /* 指向这个块中所有空闲小块的第一个页描述符,这些小块会按照MIGRATE_TYPES类型存放在不同指针里 */ struct list_head free_list[MIGRATE_TYPES]; /* 空闲小块的个数 */ unsigned long nr_free; };

enum { MIGRATE_UNMOVABLE, /* 不可移动页 */ MIGRATE_RECLAIMABLE, /* 可回收页 */ MIGRATE_MOVABLE, /* 可移动页 */ MIGRATE_PCPTYPES, /* 用来表示每CPU页框高速缓存的数据结构中的链表的可移动类型数目 */ MIGRATE_RESERVE = MIGRATE_PCPTYPES, #ifdef CONFIG_CMA MIGRATE_CMA, #endif #ifdef CONFIG_MEMORY_ISOLATION MIGRATE_ISOLATE, /* 不能从这个链表分配页框,因为这个链表专门用于NUMA结点移动物理内存页,将物理内存页移动到使用这个页最频繁的CPU */ #endif MIGRATE_TYPES };

linux内存管理机制内核空间(linux内存管理源码分析-页框分配器)(5)

每CPU页框高速缓存
  1. 因为每个CPU都有自己的硬件高速缓存,当对一个页进行读取写入时,首先会把这个页装入硬件高速缓存,而如果进程对这个处于硬件高速缓存的页进行操作后立即释放掉,这个页有可能还保存在硬件高速缓存中,这样我另一个进程需要请求一个页并立即写入数据的话,分配器将这个处于硬件高速缓存中的页分配给它,系统效率会大大增加。
  2. 减少锁的竞争,假设单页框都是使用free_area来管理,那么多个CPU同时频繁访问时,每次都是只能单CPU获取到页框,其他CPU等待,这会造成大量的锁竞争,导致分配效率降低。

/* 描述一个CPU页框高速缓存 */ struct per_cpu_pageset { /* 高速缓存页框结构 */ struct per_cpu_pages pcp; #ifdef CONFIG_NUMA s8 expire; #endif #ifdef CONFIG_SMP s8 stat_threshold; s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS]; #endif }; struct per_cpu_pages { /* 当前CPU高速缓存中页框个数 */ int count; /* number of pages in the list */ /* 上界,当此CPU高速缓存中页框个数大于high,则会将batch个页框放回伙伴系统 */ int high; /* high watermark, emptying needed */ /* 在高速缓存中将要添加或被删去的页框个数 */ int batch; /* chunk size for buddy add/remove */ /* Lists of pages, one per migrate type stored on the pcp-lists */ /* 页框的链表,如果需要冷高速缓存,从链表尾开始获取页框,如果需要热高速缓存,从链表头开始获取页框 */ struct list_head lists[MIGRATE_PCPTYPES]; };

关于页框回收
  1. 进程映射所占的页面,包括代码段,数据段,堆栈以及动态分配的“存储堆”(malloc分配的)。
  2. 用户空间中通过mmap()把文件内容映射到内存所占的页面。
  3. 匿名页面(没有映射到文件的都是匿名映射,用户空间的堆和栈):进程用户模式下的堆栈以及是使用 mmap 匿名映射的内存区(共享内存区)。注:堆栈所占页面一般不被换出。
  4. 特殊的用于 slab 分配器的缓存,比如用于缓存文件目录结构 dentry 的 cache,以及用于缓存索引节点 inode 的 cache
  5. tmpfs文件系统使用的页。
结尾

linux内存管理机制内核空间(linux内存管理源码分析-页框分配器)(6)

,