Linux内核中的内存管理浅谈 .

    xiaoxiao2025-02-06  27

    1。基本框架(此处主要谈页式内存管理)

    4G是一个比较敏感的字眼,早些日子,大多数机器(或者说操作系统)支持的内存上限都是这个数字。为什么呢?

    之所以说是早些日子,因为现在64位的计算机已经很多了,而对于32位的计算机而言,页式管理是这么进行的,逻辑地址格式如下:

    11位:页内偏移OFFSET

    1221位:页面表偏移PT

    2231位:页面目录偏移PGD

    寻址过程如下:

        1)操作系统从寄存器CR3获得当前页面目录指针(基地址);

        2)基地址+页面目录偏移->页面表指针(基地址);

        3)页面表指针+页面表偏移->内存页基址;

        4)内存页基址+页内偏移->具体物理内存单元。

    显然,12位的页内偏移可以寻址4K,所以一张内存页为4K;而总共可寻内存为4G2^10 * 2^10 * 2^12;因此在32位机器上内存上限一般为4G

     

    而操作系统是需要支持不同的平台的,比如32位,比如64位等。所以,linux统一使用页式三层映射:PGDPMDPTOFFSET

    PAE是地址扩充功能(Physical Address Extension)的缩写,如果将内存管理设置为PAE模式,这时候就需要三层映射了。

     

    三层映射架构是如何实现双层映射的?linux在暗地里“弄虚作假”了一番,有点类似领导让linux给三层映射一个重要位置,但是在32位计算机的地盘里就“阳奉阴违”了,只给三层映射一个有名无权的虚职。那么这个虚职是怎么实现的呢?

    首先,开启了PAE模式的计算机是真切需要三层映射的,所以它不会给三层映射虚职,而是需要三层映射机制去做实事的;而32位计算机如果没有开启PAE模式,那么它是不需要三层映射的,双层映射是它更喜欢的。所以,首先是判断什么情况下给三层映射虚职——

    [cpp]  view plain copy print ? 109/*  110 * The Linux x86 paging architecture is ‘compile-time dual-mode’, it  111 * implements both the traditional 2-level x86 page tables and the  112 * newer 3-level PAE-mode page tables.  113 */   114#ifndef __ASSEMBLY__   115#if CONFIG_X86_PAE   116# include <asm/pgtable-3level.h>   117   118/*  119 * Need to initialise the X86 PAE caches  120 */   121extern void pgtable_cache_init(void);   122   123#else   124# include <asm/pgtable-2level.h>  

    [cpp]  view plain  copy  print ? 109/*  110 * The Linux x86 paging architecture is ‘compile-time dual-mode’, it  111 * implements both the traditional 2-level x86 page tables and the  112 * newer 3-level PAE-mode page tables.  113 */   114#ifndef __ASSEMBLY__   115#if CONFIG_X86_PAE   116# include <asm/pgtable-3level.h>   117   118/*  119 * Need to initialise the X86 PAE caches  120 */   121extern void pgtable_cache_init(void);   122   123#else   124# include <asm/pgtable-2level.h>  

    从第一段的注释说明我们可以知道Linux x86 的页式映射机制在编译时可以选择使用传统的双层映射和新的PAE 模式下的三层映射。而从接下来的代码可以知道,如果对 CONFIG_X86_PAE进行了预处理,即开启了PAE 模式,那么就使用 pgtable-3level.h ,并且对 X86 PAE caches 进行初始化,而如果没有,则包含pgtable-2level.h ,即使用双层映射。

     

    pgtable-2level.h实现的双层映射:

    [cpp]  view plain copy print ? 4/*  5 * traditional i386 two-level paging structure:  6 */   7   8#define PGDIR_SHIFT 22   9#define PTRS_PER_PGD 1024   10   11/*  12 * the i386 is two-level, so we don’t really have any  13 * PMD directory physically.  14 */   15#define PMD_SHIFT 22   16#define PTRS_PER_PMD 1  

    [cpp]  view plain  copy  print ? 4/*  5 * traditional i386 two-level paging structure:  6 */   7   8#define PGDIR_SHIFT 22   9#define PTRS_PER_PGD 1024   10   11/*  12 * the i386 is two-level, so we don’t really have any  13 * PMD directory physically.  14 */   15#define PMD_SHIFT 22   16#define PTRS_PER_PMD 1  

    11 行到14 行的注释我们可以知道这里并没有让PMD 实际存在。 PGDIR_SHIFT  PGD 的偏移量——这里的偏移量是指位于 32 位中的几位,显然是 22 位,即第 23 位。而

    PTRS_PER_PGD pointers per PGD,即每个 PGD 位段能表示的指针。这里是 1024 ,显然需要 10 位,那么 PGD 就是从位 22 到位 31 ,即第 23 位到第 32 位。

    于是很显然我们可以了解到PMD 在这里是虚设的,挂了个虚职。因为 PTRS_PER_PMD  1 ,那么占用的是 0位,因为 2^0 = 1 

    到这里,我们知道什么人的地盘上给三层映射挂虚职,怎么设置这个虚职的。而三层映射如果真干起了实事,本质其实和双层映射差不多,只不过多了几个位而已。

     

    ————————————–cut-line

     

    1.数据结构和函数

    众所周知,linux 下有许多与 ANSI C 不同的数据类型,比如 pid_t ;这些类型实际上是通过一层或者若干层的 typedef 定义而实现的,这样做的一个主要原因是为了可移植性的实现,而这样做的影响是看类型即可以很直观地知道用于何处,比如 pid_t 显然是一个进程 id 的类型;另外一个影响便是,编译内核需要使用相应的 gcc 编译器。

     

    那么,在内存管理(1) 中提到的 PGD  PMD  PT 等是什么呢?在 include/asm-i386/page.h 中有如下代码:

    [cpp]  view plain copy print ? 36/*  37 * These are used to make use of C type-checking..  38 */   39#if CONFIG_X86_PAE   40typedef struct { unsigned long pte_low, pte_high; } pte_t;   41typedef struct { unsigned long long pmd; } pmd_t;   42typedef struct { unsigned long long pgd; } pgd_t;   43#define pte_val(x) ((x).pte_low | ((unsigned long long)(x).pte_high << 32))  

    [cpp]  view plain  copy  print ? 36/*  37 * These are used to make use of C type-checking..  38 */   39#if CONFIG_X86_PAE   40typedef struct { unsigned long pte_low, pte_high; } pte_t;   41typedef struct { unsigned long long pmd; } pmd_t;   42typedef struct { unsigned long long pgd; } pgd_t;   43#define pte_val(x) ((x).pte_low | ((unsigned long long)(x).pte_high << 32))  

    在开启了PAE 模式的情况下, pgd_t  pmd_t 都是长整形变量,而 pte_t 分为 pte_low  pte_high 两个部分。 PTE 是指 page table entry ,即某个具体的页表项,指向一张具体的内存页。但是一个内存页并不需要 32位全部使用,因为每张内存页大小都为 4KB ,所以从地址 0 开始,每间隔 4KB 为一张内存页。所以,内存页的首地址的低 12 位都为 0 ,我们只需要高 20 位来指向一个内存页基址,低 12 位用来设置页面状态和权限。另外,还有一个宏用来读取 pte_t 类型的成员。

    而没有开启PAE 模式的情况如下:

    [cpp]  view plain copy print ? 44#else   45typedef struct { unsigned long pte_low; } pte_t;   46typedef struct { unsigned long pmd; } pmd_t;   47typedef struct { unsigned long pgd; } pgd_t;   48#define pte_val(x) ((x).pte_low)   49#endif  

    [cpp]  view plain  copy  print ? 44#else   45typedef struct { unsigned long pte_low; } pte_t;   46typedef struct { unsigned long pmd; } pmd_t;   47typedef struct { unsigned long pgd; } pgd_t;   48#define pte_val(x) ((x).pte_low)   49#endif  

     

    有了PMD 等结构后就有地方存储地址信息了,那么如何获取这些信息呢?见如下几个宏:

    [cpp]  view plain copy print ? 54#define pmd_val(x) ((x).pmd)   55#define pgd_val(x) ((x).pgd)   56#define pgprot_val(x) ((x).pgprot)   57   58#define __pte(x) ((pte_t) { (x) } )   59#define __pmd(x) ((pmd_t) { (x) } )   60#define __pgd(x) ((pgd_t) { (x) } )   61#define __pgprot(x) ((pgprot_t) { (x) } )  

    [cpp]  view plain  copy  print ? 54#define pmd_val(x) ((x).pmd)   55#define pgd_val(x) ((x).pgd)   56#define pgprot_val(x) ((x).pgprot)   57   58#define __pte(x) ((pte_t) { (x) } )   59#define __pmd(x) ((pmd_t) { (x) } )   60#define __pgd(x) ((pgd_t) { (x) } )   61#define __pgprot(x) ((pgprot_t) { (x) } )  

    54 行到 56 行是读取成员变量的宏,而 58 行到 61 行则是进行类型转换。这里出现了一个 pgprot ,展开为page protection ,页面保护。 pgprot 对应着上文提到的页面状态和权限,从而实现页面的保护机制:

    [cpp]  view plain copy print ? 52typedef struct { unsigned long pgprot; } pgprot_t;  

    [cpp]  view plain  copy  print ? 52typedef struct { unsigned long pgprot; } pgprot_t;  

     

    具体的pgprot_t  /include/asm-i386/pgtable.h 中定义:

    [cpp]  view plain copy print ? 187#define _PAGE_PRESENT 0×001   188#define _PAGE_RW 0×002   189#define _PAGE_USER 0×004   190#define _PAGE_PWT 0×008   191#define _PAGE_PCD 0×010   192#define _PAGE_ACCESSED 0×020   193#define _PAGE_DIRTY 0×040   194#define _PAGE_PSE 0×080 /* 4 MB (or 2MB) page, Pentium+, if present.. */   195#define _PAGE_GLOBAL 0×100 /* Global TLB entry PPro+ */   196   197#define _PAGE_PROTNONE 0×080 /* If not present */  

    [cpp]  view plain  copy  print ? 187#define _PAGE_PRESENT 0×001   188#define _PAGE_RW 0×002   189#define _PAGE_USER 0×004   190#define _PAGE_PWT 0×008   191#define _PAGE_PCD 0×010   192#define _PAGE_ACCESSED 0×020   193#define _PAGE_DIRTY 0×040   194#define _PAGE_PSE 0×080 /* 4 MB (or 2MB) page, Pentium+, if present.. */   195#define _PAGE_GLOBAL 0×100 /* Global TLB entry PPro+ */   196   197#define _PAGE_PROTNONE 0×080 /* If not present */  

    显然,pgprot_t 的位设置都是在低 12 位,而 PTE 的指针部分是高 20 位,共同构成了 32 位。那么,二者是如何构成 32 位的页面表表项呢?我们自然而然想到了 20 位左移 12 位再与 pgprot_t 的低 12 位相或,在 pgtable.h 中是由宏 mk_pte 来完成的:

    [cpp]  view plain copy print ? 309#define mk_pte(page, pgprot) __mk_pte((page) - mem_map, (pgprot))  

    [cpp]  view plain  copy  print ? 309#define mk_pte(page, pgprot) __mk_pte((page) - mem_map, (pgprot))  

    而我们自然又遇到了__mk_pte 。那么 __mk_pte 是什么呢?在 /include/asm-i386/pgtable-2level.h中它一个宏:

    [cpp]  view plain copy print ? 63#define __mk_pte(page_nr,pgprot) __pte(((page_nr) << PAGE_SHIFT) | pgprot_val(pgprot))  

    [cpp]  view plain  copy  print ? 63#define __mk_pte(page_nr,pgprot) __pte(((page_nr) << PAGE_SHIFT) | pgprot_val(pgprot))  

    以上为63 行单行。而在/include/asm-i386/page.h 中对 PAGE_SHIFT 进行了宏定义:

    [cpp]  view plain copy print ? 5#define PAGE_SHIFT 12   

    [cpp]  view plain  copy  print ? 5#define PAGE_SHIFT 12   

    所以实现的是将内存页面编号左移12 位再与保护字段pgprot 相或得到了 pte 页面表项。另外在上述中出现了 __pte() ,它的原型为: 58#define __pte(x) ((pte_t) { (x) } ),即进行类型转换。而pgprot_val(pgprot) 的原型为: 56#define pgprot_val(x) ((x).pgprot),与 52typedef struct { unsigned long pgprot; } pgprot_t;相对应则易知是获得某个 pgprot_t 类型变量的成员变量 pgprot 

    最后就剩下一个mem_map 了。我们先来了解一下 /include/linux/mm.h 中的 page 结构。

    首先,先看一段前置说明:

    [cpp]  view plain copy print ? 139/*  140 * Each physical page in the system has a struct page associated with  141 * it to keep track of whatever it is we are using the page for at the  142 * moment. Note that we have no way to track which tasks are using  143 * a page.  144 *  145 * Try to keep the most commonly accessed fields in single cache lines  146 * here (16 bytes or greater). This ordering should be particularly  147 * beneficial on 32-bit processors.  148 *  149 * The first line is data used in page cache lookup, the second line  150 * is used for linear searches (eg. clock algorithm scans).  151 *  152 * TODO: make this structure smaller, it could be as small as 32 bytes.  153 */   

    [cpp]  view plain  copy  print ? 139/*  140 * Each physical page in the system has a struct page associated with  141 * it to keep track of whatever it is we are using the page for at the  142 * moment. Note that we have no way to track which tasks are using  143 * a page.  144 *  145 * Try to keep the most commonly accessed fields in single cache lines  146 * here (16 bytes or greater). This ordering should be particularly  147 * beneficial on 32-bit processors.  148 *  149 * The first line is data used in page cache lookup, the second line  150 * is used for linear searches (eg. clock algorithm scans).  151 *  152 * TODO: make this structure smaller, it could be as small as 32 bytes.  153 */   

    简略说下,就是page 结构是与物理内存页相联系的,从而进行状态跟踪;其次,最经常访问的结构体内的成员字段应该保持在 16 位或者更大的单条缓冲线上——显然,这样有利于高速访问。接着来看page 结构体的定义:

    [cpp]  view plain copy print ? 154typedef struct page {   155 struct list_head list; /* ->mapping has some page lists. */   156 struct address_space *mapping; /* The inode (or …) we belong to. */   157 unsigned long index; /* Our offset within mapping. */   158 struct page *next_hash; /* Next page sharing our hash bucket in  159 the pagecache hash table. */   160 atomic_t count; /* Usage count, see below. */   161 unsigned long flags; /* atomic flags, some possibly  162 updated asynchronously */   163 struct list_head lru; /* Pageout list, eg. active_list;  164 protected by pagemap_lru_lock !! */   165 struct page **pprev_hash; /* Complement to *next_hash. */   166 struct buffer_head * buffers; /* Buffer maps us to a disk block. */   167   168 /*  169 * On machines where all RAM is mapped into kernel address space,  170 * we can simply calculate the virtual address. On machines with  171 * highmem some memory is mapped into kernel virtual memory  172 * dynamically, so we need a place to store that address.  173 * Note that this field could be 16 bits on x86 … ;)  174 *  175 * Architectures with slow multiplication can define  176 * WANT_PAGE_VIRTUAL in asm/page.h  177 */   178#if defined(CONFIG_HIGHMEM) || defined(WANT_PAGE_VIRTUAL)   179 void *virtual/* Kernel virtual address (NULL if  180 not kmapped, ie. highmem) */   181#endif /* CONFIG_HIGMEM || WANT_PAGE_VIRTUAL */   182} mem_map_t;   

    [cpp]  view plain  copy  print ? 154typedef struct page {   155 struct list_head list; /* ->mapping has some page lists. */   156 struct address_space *mapping; /* The inode (or …) we belong to. */   157 unsigned long index; /* Our offset within mapping. */   158 struct page *next_hash; /* Next page sharing our hash bucket in  159 the pagecache hash table. */   160 atomic_t count; /* Usage count, see below. */   161 unsigned long flags; /* atomic flags, some possibly  162 updated asynchronously */   163 struct list_head lru; /* Pageout list, eg. active_list;  164 protected by pagemap_lru_lock !! */   165 struct page **pprev_hash; /* Complement to *next_hash. */   166 struct buffer_head * buffers; /* Buffer maps us to a disk block. */   167   168 /*  169 * On machines where all RAM is mapped into kernel address space,  170 * we can simply calculate the virtual address. On machines with  171 * highmem some memory is mapped into kernel virtual memory  172 * dynamically, so we need a place to store that address.  173 * Note that this field could be 16 bits on x86 … ;)  174 *  175 * Architectures with slow multiplication can define  176 * WANT_PAGE_VIRTUAL in asm/page.h  177 */   178#if defined(CONFIG_HIGHMEM) || defined(WANT_PAGE_VIRTUAL)   179 void *virtual/* Kernel virtual address (NULL if  180 not kmapped, ie. highmem) */   181#endif /* CONFIG_HIGMEM || WANT_PAGE_VIRTUAL */   182} mem_map_t;   

    当我们看到最后一行(182 行)的时候会有种恍然大悟的感觉—— mem_map_t 。于是我们就会联想mem_map 是这么一个类型的变量。

    实际上,mem_map 是一个全局变量(目前为止是),而且是一个指向 page 结构数组的指针;系统在初始化时根据物理内存的大小创建该数组。每一个数组元素都对应一张物理内存页。从软件方面来讲,页面表项的高 20 位是物理页面的编号,即 mem_map 数组的索引下标,通过该下标可以访问到与物理页面对应的page结构。而从硬件方面来讲,页面表项的高 20 位再与 12  0 结合则构成了 32 位,即每张物理页面的基址。

     

    mem_map 映射着全部的物理内存页,而其本身则分为不同的区,比如 ZONE_DMA ZONE_NORMALZONE_HIGHMEM等。其中 ZONE_DMA 是供 DMA 使用的; ZONE_HIGHMEM 是用于处理物理地址超过 1G 的存储空间。

    事实上,三个管理区是这么分配的:~ 16MB 分配给 ZONE_DMA  16 896MB 分配给 ZONE_NORMAL ,最后, 896MB 以上的分配给 ZONE_HIGHMEM 。那么,为什么要这么分配呢?这是由于某些硬件只能特定地访问~ 16MB来执行 DMA 模式;有些机器的配置使得物理内存页面无法总是保持被内核地址映射,这时需要使用ZONE_HIGHMEM 进行动态映射;而其余的就是可以被正常映射的。

    那么,为什么这里是896MB 呢,而不是上文提的 1GB ?这是由于内核不仅为 highmem 预留了空间,也为fixmap  vmalloc 预留了虚存空间。

    OK ,那内核中的虚拟地址是什么?虚拟地址其实就是逻辑地址——与物理地址相对应。

    我们不妨来看看物理地址和内核中虚拟地址在内核空间的关系:

    [cpp]  view plain copy print ? 128#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)   …   132#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)   133#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))   

    [cpp]  view plain  copy  print ? 128#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)   …   132#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)   133#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))   

    pa 表示 physical address ,即物理地址,而 va 表示虚拟地址 virtual address 。这里,我们不得不去看看 __PAGE_OFFSET 

    [cpp]  view plain copy print ? 68/*  69 * This handles the memory map.. We could make this a config  70 * option, but too many people screw it up, and too few need  71 * it.  72 *  73 * A __PAGE_OFFSET of 0xC0000000 means that the kernel has  74 * a virtual address space of one gigabyte, which limits the  75 * amount of physical memory you can use to about 950MB.  76 *  77 * If you want more physical memory than this then see the CONFIG_HIGHMEM4G  78 * and CONFIG_HIGHMEM64G options in the kernel configuration.  79 */   80   81#define __PAGE_OFFSET (0xC0000000)   

    [cpp]  view plain  copy  print ? 68/*  69 * This handles the memory map.. We could make this a config  70 * option, but too many people screw it up, and too few need  71 * it.  72 *  73 * A __PAGE_OFFSET of 0xC0000000 means that the kernel has  74 * a virtual address space of one gigabyte, which limits the  75 * amount of physical memory you can use to about 950MB.  76 *  77 * If you want more physical memory than this then see the CONFIG_HIGHMEM4G  78 * and CONFIG_HIGHMEM64G options in the kernel configuration.  79 */   80   81#define __PAGE_OFFSET (0xC0000000)   

    前置注释有一堆,而宏定义只有一行。在32 位机器上,通过linux 内核的页式映射可以实现 4GB 的逻辑地址(虚拟地址)。而在 4G 字节中, 0xC0000000  0xFFFFFFFF 的这 1G 最高的逻辑地址用于内核本身,称之为“内核空间”;而较低的 3G 字节空间为用户空间。注意,这里的是虚的、逻辑地址。

    于是我们知道了__PAGE_OFFSET 是用户空间和内核空间在虚地址上的分界。然而,物理地址始终是从0×00000000开始的;所以对于内核空间来说, pa  va 就相差了一个 PAGE_OFFSET 。而同时,PAGE_OFFSET 也代表着用户空间的上限。

    到这里,我们了解了内核空间只能“线性映射”1GB“ 的物理地址,如果没有 ZONE_HIGHMEM 来管理高于1GB 的物理地址,那么这些内存就会浪费掉了。于是系统初始化时预留了 128MB的虚存来用于将来可能的映射。以上是对于 x86 体系结构而言,对于其它体系,物理内存可以全部被映射, ZONE_HIGHMEM 为空。

     

    现在回到内存管理区。/include/linux/mmzone.h 中有如下数据结构用于管理区:

    (代码有点长,分段来看)

    [cpp]  view plain copy print ? 39/*  40 * On machines where it is needed (eg PCs) we divide physical memory  41 * into multiple physical zones. On a PC we have 3 zones:  42 *  43 * ZONE_DMA < 16 MB ISA DMA capable memory  44 * ZONE_NORMAL 16-896 MB direct mapped by the kernel  45 * ZONE_HIGHMEM > 896 MB only page cache and user processes  46 */   

    [cpp]  view plain  copy  print ? 39/*  40 * On machines where it is needed (eg PCs) we divide physical memory  41 * into multiple physical zones. On a PC we have 3 zones:  42 *  43 * ZONE_DMA < 16 MB ISA DMA capable memory  44 * ZONE_NORMAL 16-896 MB direct mapped by the kernel  45 * ZONE_HIGHMEM > 896 MB only page cache and user processes  46 */   

    这里的前置注释说明了三个管理区的分布。

    [cpp]  view plain copy print ? 47typedef struct zone_struct {   48 /*  49 * Commonly accessed fields:  50 */   51 spinlock_t lock;   52 unsigned long free_pages;   这里是经常访问的字段。这里遇到了spinlock_t这个数据类型,在/include/asm-i386/spinlock.h中有定义:   22/*  23 * Your basic SMP spinlocks, allowing only a single CPU anywhere  24 */   25   26typedef struct {   27 volatile unsigned int lock;   28#if SPINLOCK_DEBUG   29 unsigned magic;   30#endif   31} spinlock_t;   

    [cpp]  view plain  copy  print ? 47typedef struct zone_struct {   48 /*  49 * Commonly accessed fields:  50 */   51 spinlock_t lock;   52 unsigned long free_pages;   这里是经常访问的字段。这里遇到了spinlock_t这个数据类型,在/include/asm-i386/spinlock.h中有定义:   22/*  23 * Your basic SMP spinlocks, allowing only a single CPU anywhere  24 */   25   26typedef struct {   27 volatile unsigned int lock;   28#if SPINLOCK_DEBUG   29 unsigned magic;   30#endif   31} spinlock_t;   

    由注释我们可以知道这是用来控制SMP 使用的,仅允许单 CPU 工作。

    free_pages 表示着该区目前拥有的空闲页数。

    [cpp]  view plain copy print ? 53 /*  54 * We don’t know if the memory that we’re going to allocate will be freeable  55 * or/and it will be released eventually, so to avoid totally wasting several  56 * GB of ram we must reserve some of the lower zone memory (otherwise we risk  57 * to run OOM on the lower zones despite there’s tons of freeable ram  58 * on the higher zones).  59 */   60 zone_watermarks_t watermarks[MAX_NR_ZONES];   

    [cpp]  view plain  copy  print ? 53 /*  54 * We don’t know if the memory that we’re going to allocate will be freeable  55 * or/and it will be released eventually, so to avoid totally wasting several  56 * GB of ram we must reserve some of the lower zone memory (otherwise we risk  57 * to run OOM on the lower zones despite there’s tons of freeable ram  58 * on the higher zones).  59 */   60 zone_watermarks_t watermarks[MAX_NR_ZONES];   

    由前置注释可知这是为了保留一些低端内存。我们在这里又遇到了一个新的数据类型:

    [cpp]  view plain copy print ? 34typedef struct zone_watermarks_s {   35 unsigned long min, low, high;   36} zone_watermarks_t;   62 /*  63 * The below fields are protected by different locks (or by  64 * no lock at all like need_balance), so they’re longs to  65 * provide an atomic granularity against each other on  66 * all architectures.  67 */   68 unsigned long need_balance;   69 /* protected by the pagemap_lru_lock */   70 unsigned long nr_active_pages, nr_inactive_pages;   71 /* protected by the pagecache_lock */   72 unsigned long nr_cache_pages;   75 /*  76 * free areas of different sizes  77 */   78 free_area_t free_area[MAX_ORDER];   引入free_area_t:   27typedef struct free_area_struct {   28 struct list_head free_list;   29 unsigned long *map;   30} free_area_t;  

    [cpp]  view plain  copy  print ? 34typedef struct zone_watermarks_s {   35 unsigned long min, low, high;   36} zone_watermarks_t;   62 /*  63 * The below fields are protected by different locks (or by  64 * no lock at all like need_balance), so they’re longs to  65 * provide an atomic granularity against each other on  66 * all architectures.  67 */   68 unsigned long need_balance;   69 /* protected by the pagemap_lru_lock */   70 unsigned long nr_active_pages, nr_inactive_pages;   71 /* protected by the pagecache_lock */   72 unsigned long nr_cache_pages;   75 /*  76 * free areas of different sizes  77 */   78 free_area_t free_area[MAX_ORDER];   引入free_area_t:   27typedef struct free_area_struct {   28 struct list_head free_list;   29 unsigned long *map;   30} free_area_t;  

    这里free_area[MAX_ORDER] 是一组队列,用于分配不连续的内存块。队列的实现是通过 free_area_t类型中的成员 struct list_head free_list ,可参加 list.h 

    [cpp]  view plain copy print ? 80 /*  81 * wait_table — the array holding the hash table  82 * wait_table_size — the size of the hash table array  83 * wait_table_shift — wait_table_size  84 * == BITS_PER_LONG (1 << wait_table_bits)  85 *  86 * The purpose of all these is to keep track of the people  87 * waiting for a page to become available and make them  88 * runnable again when possible. The trouble is that this  89 * consumes a lot of space, especially when so few things  90 * wait on pages at a given time. So instead of using  91 * per-page waitqueues, we use a waitqueue hash table.  92 *  93 * The bucket discipline is to sleep on the same queue when  94 * colliding and wake all in that wait queue when removing.  95 * When something wakes, it must check to be sure its page is  96 * truly available, a la thundering herd. The cost of a  97 * collision is great, but given the expected load of the  98 * table, they should be so rare as to be outweighed by the  99 * benefits from the saved space.  100 *  101 * __wait_on_page() and unlock_page() in mm/filemap.c, are the  102 * primary users of these fields, and in mm/page_alloc.c  103 * free_area_init_core() performs the initialization of them.  104 */   105 wait_queue_head_t * wait_table;   106 unsigned long wait_table_size;   107 unsigned long wait_table_shift;   

    [cpp]  view plain  copy  print ? 80 /*  81 * wait_table — the array holding the hash table  82 * wait_table_size — the size of the hash table array  83 * wait_table_shift — wait_table_size  84 * == BITS_PER_LONG (1 << wait_table_bits)  85 *  86 * The purpose of all these is to keep track of the people  87 * waiting for a page to become available and make them  88 * runnable again when possible. The trouble is that this  89 * consumes a lot of space, especially when so few things  90 * wait on pages at a given time. So instead of using  91 * per-page waitqueues, we use a waitqueue hash table.  92 *  93 * The bucket discipline is to sleep on the same queue when  94 * colliding and wake all in that wait queue when removing.  95 * When something wakes, it must check to be sure its page is  96 * truly available, a la thundering herd. The cost of a  97 * collision is great, but given the expected load of the  98 * table, they should be so rare as to be outweighed by the  99 * benefits from the saved space.  100 *  101 * __wait_on_page() and unlock_page() in mm/filemap.c, are the  102 * primary users of these fields, and in mm/page_alloc.c  103 * free_area_init_core() performs the initialization of them.  104 */   105 wait_queue_head_t * wait_table;   106 unsigned long wait_table_size;   107 unsigned long wait_table_shift;   

    一些管理区信息如下:

    [cpp]  view plain copy print ? 109 /*  110 * Discontig memory support fields.  111 */   112 struct pglist_data *zone_pgdat;   113 struct page *zone_mem_map;   114 unsigned long zone_start_paddr;   115 unsigned long zone_start_mapnr;   

    [cpp]  view plain  copy  print ? 109 /*  110 * Discontig memory support fields.  111 */   112 struct pglist_data *zone_pgdat;   113 struct page *zone_mem_map;   114 unsigned long zone_start_paddr;   115 unsigned long zone_start_mapnr;   

    112 表示的是该管理区所在的存储节点; 113 显然是一张内存映射表; 114 是该管理区的物理起始地址,而115 表示的是在 mem_map 中的起始下标。显然这些都可以直接从变量名看出来。

    [cpp]  view plain copy print ? 117 /*  118 * rarely used fields:  119 */   120 char *name;   121 unsigned long size;   122 unsigned long realsize;   123} zone_t;   

    [cpp]  view plain  copy  print ? 117 /*  118 * rarely used fields:  119 */   120 char *name;   121 unsigned long size;   122 unsigned long realsize;   123} zone_t;   

    120 表示的是管理区的名字, 121 表示的是管理区的大小, 122 表示的是管理区实用大小。

     

    当多CPU 引入之后, NUMA(Non-Uniform Memory Architecture)结构体系出现了,即非匀质存储结构。于是,每个 CPU 都有自己的物理地址,并且有一个公共的物存模块。这样有时候会出现CPU 请求的内存块无法在自己管辖的物理地址模块获得,也不能手伸太长去其它 CPU管理的模块,那么就需要到公共模块请求。同时,新的物理页面管理机制也进行了修正。

    NUMA 下,我们称 CPU 请求的一片连续物理内存页为 node (节点)。而且,此时的 mem_map 不再是全局变量,而是从属于具体节点;管理区也不再高高在上,也是被节点所拥有,每个存储节点至少有两个管理区。从而在 zone_struct 上便有了 pglist_data 数据结构,在 /include/linux/mmzone.h 定义:

    [cpp]  view plain copy print ? 142/*  143 * The pg_data_t structure is used in machines with CONFIG_DISCONTIGMEM  144 * (mostly NUMA machines?) to denote a higher-level memory zone than the  145 * zone_struct denotes.  146 *  147 * On NUMA machines, each NUMA node would have a pg_data_t to describe  148 * it’s memory layout.  149 *  150 * XXX: we need to move the global memory statistics (active_list, …)  151 * into the pg_data_t to properly support NUMA.  152 */   153struct bootmem_data;   154typedef struct pglist_data {   155 zone_t node_zones[MAX_NR_ZONES];   156 zonelist_t node_zonelists[GFP_ZONEMASK+1];   157 int nr_zones;   158 struct page *node_mem_map;   159 unsigned long *valid_addr_bitmap;   160 struct bootmem_data *bdata;   161 unsigned long node_start_paddr;   162 unsigned long node_start_mapnr;   163 unsigned long node_size;   164 int node_id;   165 struct pglist_data *node_next;   166} pg_data_t;   

    [cpp]  view plain  copy  print ? 142/*  143 * The pg_data_t structure is used in machines with CONFIG_DISCONTIGMEM  144 * (mostly NUMA machines?) to denote a higher-level memory zone than the  145 * zone_struct denotes.  146 *  147 * On NUMA machines, each NUMA node would have a pg_data_t to describe  148 * it’s memory layout.  149 *  150 * XXX: we need to move the global memory statistics (active_list, …)  151 * into the pg_data_t to properly support NUMA.  152 */   153struct bootmem_data;   154typedef struct pglist_data {   155 zone_t node_zones[MAX_NR_ZONES];   156 zonelist_t node_zonelists[GFP_ZONEMASK+1];   157 int nr_zones;   158 struct page *node_mem_map;   159 unsigned long *valid_addr_bitmap;   160 struct bootmem_data *bdata;   161 unsigned long node_start_paddr;   162 unsigned long node_start_mapnr;   163 unsigned long node_size;   164 int node_id;   165 struct pglist_data *node_next;   166} pg_data_t;   

    首先看看158  struct page *node_mem_map ,由于每个节点有一片的内存页,这里的 node_mem_map 便是用来映射表示它们的( page 结构数组);接着看首行, 155  zone_t node_zones[MAX_NR_ZONES]是该节点所拥有的管理区,同时在 zone_struct 也有一行 struct pglist_data *zone_pgdat ,指向所属节点pglist_data 数据结构。

     

    ————————————–cut-line –以上数据结构用于物理内存页面管理 –2009-04-20 

     

    ————————————–cut-line

     

    (续)数据结构和函数

    现在开始接触的是用于虚存管理的数据结构和函数。

    通常,一个进程所需要使用的虚存空间是离散的各个区间,而区间的数据结构是/include/linux/mm.h中定义的:

    [cpp]  view plain copy print ? 38/*  39 * This struct defines a memory VMM memory area. There is one of these  40 * per VM-area/task. A VM area is any part of the process virtual memory  41 * space that has a special rule for the page-fault handlers (ie a shared  42 * library, the executable area etc).  43 */   44struct vm_area_struct {   45 struct mm_struct * vm_mm; /* The address space we belong to. */   46 unsigned long vm_start; /* Our start address within vm_mm. */   47 unsigned long vm_end; /* The first byte after our end address  48 within vm_mm. */   49   50 /* linked list of VM areas per task, sorted by address */   51 struct vm_area_struct *vm_next;   52   53 pgprot_t vm_page_prot; /* Access permissions of this VMA. */   54 unsigned long vm_flags; /* Flags, listed below. */   55   56 rb_node_t vm_rb;   57   58 /*  59 * For areas with an address space and backing store,  60 * one of the address_space->i_mmap{,shared} lists,  61 * for shm areas, the list of attaches, otherwise unused.  62 */   63 struct vm_area_struct *vm_next_share;   64 struct vm_area_struct **vm_pprev_share;   65   66 /* Function pointers to deal with this struct. */   67 struct vm_operations_struct * vm_ops;   68   69 /* Information about our backing store: */   70 unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE  71 units, *not* PAGE_CACHE_SIZE */   72 struct file * vm_file; /* File we map to (can be NULL). */   73 unsigned long vm_raend; /* XXX: put full readahead info here. */   74 void * vm_private_data; /* was vm_pte (shared mem) */   75};   

    [cpp]  view plain  copy  print ? 38/*  39 * This struct defines a memory VMM memory area. There is one of these  40 * per VM-area/task. A VM area is any part of the process virtual memory  41 * space that has a special rule for the page-fault handlers (ie a shared  42 * library, the executable area etc).  43 */   44struct vm_area_struct {   45 struct mm_struct * vm_mm; /* The address space we belong to. */   46 unsigned long vm_start; /* Our start address within vm_mm. */   47 unsigned long vm_end; /* The first byte after our end address  48 within vm_mm. */   49   50 /* linked list of VM areas per task, sorted by address */   51 struct vm_area_struct *vm_next;   52   53 pgprot_t vm_page_prot; /* Access permissions of this VMA. */   54 unsigned long vm_flags; /* Flags, listed below. */   55   56 rb_node_t vm_rb;   57   58 /*  59 * For areas with an address space and backing store,  60 * one of the address_space->i_mmap{,shared} lists,  61 * for shm areas, the list of attaches, otherwise unused.  62 */   63 struct vm_area_struct *vm_next_share;   64 struct vm_area_struct **vm_pprev_share;   65   66 /* Function pointers to deal with this struct. */   67 struct vm_operations_struct * vm_ops;   68   69 /* Information about our backing store: */   70 unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE  71 units, *not* PAGE_CACHE_SIZE */   72 struct file * vm_file; /* File we map to (can be NULL). */   73 unsigned long vm_raend; /* XXX: put full readahead info here. */   74 void * vm_private_data; /* was vm_pte (shared mem) */   75};   

    45 行是定义了一个指向 mm_struct 结构体的指针,该结构体稍后了解。 vm_start  vm_end 是这一段vm_area 的开始和结束位置,然而 vm_end 是该 vm_area 之后的第一个地址,不属于本 vm_area 

    51 行定义了一个指向 vm_area_struct 结构体的指针 vm_next 。这是由于进程使用的区间是离散的,所以各个区间需要形成链表来保持联系,这里的 vm_next 便是指向下一片 vm_area 的;该链表是按地址排序的。

    53 行的 pgprot_t vm_page_prot 显然是本 vm_area 的保护信息, pgprot_t 在之前有谈过。

    54 行的 vm_flags 是本 vm_area 的标志,如下:

    [cpp]  view plain copy print ? 77/*  78 * vm_flags..  79 */   80#define VM_READ 0×00000001 /* currently active flags */   81#define VM_WRITE 0×00000002   82#define VM_EXEC 0×00000004   83#define VM_SHARED 0×00000008   84   85#define VM_MAYREAD 0×00000010 /* limits for mprotect() etc */   86#define VM_MAYWRITE 0×00000020   87#define VM_MAYEXEC 0×00000040   88#define VM_MAYSHARE 0×00000080   89   90#define VM_GROWSDOWN 0×00000100 /* general info on the segment */   91#define VM_GROWSUP 0×00000200   92#define VM_SHM 0×00000400 /* shared memory area, don’t swap out */   93#define VM_DENYWRITE 0×00000800 /* ETXTBSY on write attempts.. */   94   95#define VM_EXECUTABLE 0×00001000   96#define VM_LOCKED 0×00002000   97#define VM_IO 0×00004000 /* Memory mapped I/O or similar */   98   99 /* Used by sys_madvise() */   100#define VM_SEQ_READ 0×00008000 /* App will access data sequentially */   101#define VM_RAND_READ 0×00010000 /* App will not benefit from clustered reads */   102   103#define VM_DONTCOPY 0×00020000 /* Do not copy this vma on fork */   104#define VM_DONTEXPAND 0×00040000 /* Cannot expand with mremap() */   105#define VM_RESERVED 0×00080000 /* Don’t unmap it from swap_out */   106   107#ifndef VM_STACK_FLAGS   108#define VM_STACK_FLAGS 0×00000177   109#endif   

    [cpp]  view plain  copy  print ? 77/*  78 * vm_flags..  79 */   80#define VM_READ 0×00000001 /* currently active flags */   81#define VM_WRITE 0×00000002   82#define VM_EXEC 0×00000004   83#define VM_SHARED 0×00000008   84   85#define VM_MAYREAD 0×00000010 /* limits for mprotect() etc */   86#define VM_MAYWRITE 0×00000020   87#define VM_MAYEXEC 0×00000040   88#define VM_MAYSHARE 0×00000080   89   90#define VM_GROWSDOWN 0×00000100 /* general info on the segment */   91#define VM_GROWSUP 0×00000200   92#define VM_SHM 0×00000400 /* shared memory area, don’t swap out */   93#define VM_DENYWRITE 0×00000800 /* ETXTBSY on write attempts.. */   94   95#define VM_EXECUTABLE 0×00001000   96#define VM_LOCKED 0×00002000   97#define VM_IO 0×00004000 /* Memory mapped I/O or similar */   98   99 /* Used by sys_madvise() */   100#define VM_SEQ_READ 0×00008000 /* App will access data sequentially */   101#define VM_RAND_READ 0×00010000 /* App will not benefit from clustered reads */   102   103#define VM_DONTCOPY 0×00020000 /* Do not copy this vma on fork */   104#define VM_DONTEXPAND 0×00040000 /* Cannot expand with mremap() */   105#define VM_RESERVED 0×00080000 /* Don’t unmap it from swap_out */   106   107#ifndef VM_STACK_FLAGS   108#define VM_STACK_FLAGS 0×00000177   109#endif   

    80 83 行分别表示页是否可以被读、写、执行和共享。

    85 88 行表示可以对 80 83 行的标志进行设置。

    95 行表示该页含可执行代码。

    96 行表示该页被锁。

    其它标志均有注释。

    在这里一般会有个疑惑,一个vm_area可能包含很多个内存页,为什么只有一个 vm_page_prot vm_flags呢?这是因为同一片 vm_area 的所有页面都必须保持相同的保护信息和状态标志。

    现在回到vm_area_struct 

    56 行是 rb_node_t vm_rb; rb_node_t 是红黑树 (red-black tree) 节点类型。红黑树的结构如下:

    [cpp]  view plain copy print ? 100typedef struct rb_node_s   101{   102 struct rb_node_s * rb_parent;   103 int rb_color;   104#define RB_RED 0   105#define RB_BLACK 1   106 struct rb_node_s * rb_right;   107 struct rb_node_s * rb_left;   108}   109rb_node_t;   

    [cpp]  view plain  copy  print ? 100typedef struct rb_node_s   101{   102 struct rb_node_s * rb_parent;   103 int rb_color;   104#define RB_RED 0   105#define RB_BLACK 1   106 struct rb_node_s * rb_right;   107 struct rb_node_s * rb_left;   108}   109rb_node_t;   

    之所以使用红黑树是因为使用链表搜索的话每次都要从头开始,会影响效率。

    63 64 行为共享内存中的前后区间:

    [cpp]  view plain copy print ? 58 /*  59 * For areas with an address space and backing store,  60 * one of the address_space->i_mmap{,shared} lists,  61 * for shm areas, the list of attaches, otherwise unused.  62 */   63 struct vm_area_struct *vm_next_share;   64 struct vm_area_struct **vm_pprev_share;   67行定义了一个vm_ops,指向的是一个vm_oprations_struct结构体,该结构体在/include/linux/mm.h有定义:   128/*  129 * These are the virtual MM functions - opening of an area, closing and  130 * unmapping it (needed to keep files on disk up-to-date etc), pointer  131 * to the functions called when a no-page or a wp-page exception occurs.  132 */   133struct vm_operations_struct {   134 void (*open)(struct vm_area_struct * area);   135 void (*close)(struct vm_area_struct * area);   136 struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int unused);   137};   

    [cpp]  view plain  copy  print ? 58 /*  59 * For areas with an address space and backing store,  60 * one of the address_space->i_mmap{,shared} lists,  61 * for shm areas, the list of attaches, otherwise unused.  62 */   63 struct vm_area_struct *vm_next_share;   64 struct vm_area_struct **vm_pprev_share;   67行定义了一个vm_ops,指向的是一个vm_oprations_struct结构体,该结构体在/include/linux/mm.h有定义:   128/*  129 * These are the virtual MM functions - opening of an area, closing and  130 * unmapping it (needed to keep files on disk up-to-date etc), pointer  131 * to the functions called when a no-page or a wp-page exception occurs.  132 */   133struct vm_operations_struct {   134 void (*open)(struct vm_area_struct * area);   135 void (*close)(struct vm_area_struct * area);   136 struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int unused);   137};   

    显然可见vm_ops 是一个指针,可以执行操作函数作用在该 vm_area 上。其中 open  close 用于打开、关闭虚存空间。而当请求页面不在内存中调用 nopage

    vm_area_struct后面的成员都有注释。

     

    ————————————–cut-line

     

    在了解vm_area_struct 的开始,我们提到了 mm_struct 

    [cpp]  view plain copy print ? 206struct mm_struct {   207 struct vm_area_struct * mmap; /* list of VMAs */   208 rb_root_t mm_rb;   209 struct vm_area_struct * mmap_cache; /* last find_vma result */   210 pgd_t * pgd;   211 atomic_t mm_users; /* How many users with user space? */   212 atomic_t mm_count; /* How many references to “struct mm_struct” (users count as 1) */   213 int map_count; /* number of VMAs */   214 struct rw_semaphore mmap_sem;   215 spinlock_t page_table_lock; /* Protects task page tables and mm->rss */   216   217 struct list_head mmlist; /* List of all active mm’s. These are globally strung  218 * together off init_mm.mmlist, and are protected  219 * by mmlist_lock  220 */   221   222 unsigned long start_code, end_code, start_data, end_data;   223 unsigned long start_brk, brk, start_stack;   224 unsigned long arg_start, arg_end, env_start, env_end;   225 unsigned long rss, total_vm, locked_vm;   226 unsigned long def_flags;   227 unsigned long cpu_vm_mask;   228 unsigned long swap_address;   229   230 unsigned dumpable:1;   231   232 /* Architecture-specific MM context */   233 mm_context_t context;   234};   

    [cpp]  view plain  copy  print ? 206struct mm_struct {   207 struct vm_area_struct * mmap; /* list of VMAs */   208 rb_root_t mm_rb;   209 struct vm_area_struct * mmap_cache; /* last find_vma result */   210 pgd_t * pgd;   211 atomic_t mm_users; /* How many users with user space? */   212 atomic_t mm_count; /* How many references to “struct mm_struct” (users count as 1) */   213 int map_count; /* number of VMAs */   214 struct rw_semaphore mmap_sem;   215 spinlock_t page_table_lock; /* Protects task page tables and mm->rss */   216   217 struct list_head mmlist; /* List of all active mm’s. These are globally strung  218 * together off init_mm.mmlist, and are protected  219 * by mmlist_lock  220 */   221   222 unsigned long start_code, end_code, start_data, end_data;   223 unsigned long start_brk, brk, start_stack;   224 unsigned long arg_start, arg_end, env_start, env_end;   225 unsigned long rss, total_vm, locked_vm;   226 unsigned long def_flags;   227 unsigned long cpu_vm_mask;   228 unsigned long swap_address;   229   230 unsigned dumpable:1;   231   232 /* Architecture-specific MM context */   233 mm_context_t context;   234};   

    207 行的 mmap 指向虚存区间链表。

    208 行是指向红黑树。

    209 行的 mmap_cache 指向最后一次使用的虚存区间,因为虚存区间有若干个内存页,下一次请求的内存页很可能还在该区间。

    210 行的 pgd 显然是进程的页面目录,当内核调度一个进程运行时,将该指针转换为物理地址并写入控制寄存器 CR3 

    211 行的 mm_users 表示用户空间中有多少用户。而 212 行的 mm_count 表示该 mm_count 结构的被引用数。

    213  map_count 表示 vm_area 的个数。

    214  215 是一些状态控制,进行诸如锁定等状态控制。

    217 行是 mm_struct 链表。

    余下部分用途较显然。

     

    mm_users  mm_count 我们可以知道一个 mm_struct 允许被多个进程引用,但是一个进程只能使用一个mm_struct结构。

    至此,我们了解到以下几点。

    1 。虚存方面是由 vm_area_struct  mm_struct 进行处理的。 32 位的计算机可以形成 4G 的虚存空间,其中 3  4G 的虚存空间用作内核空间,其余用作用户空间。 mm_struct 是用户空间抽象,位于虚存管理的高层。而 vm_area_struct则是从属于 mm_struct 。一个进程允许有多个 vma ,这些虚存区间构成链表以及红黑树,在 vma 个数较少的时候使用链表操作,个数多的时候使用红黑树操作。mm_struct 中的 mmap 指向vma 链表,而 map_count 则指示有多少个 vma 。当一个进程进入运行时,进程所对应的 mm_struct 中的pgd (页面目录)被写入控制寄存器 CR3 ,于是页式映射机制的源头 CR3 就有内容了。

    2 。在 CR3 被设置以后,便可以进行页式映射了。负责将虚拟地址映射为物理地址的内存管理单元从 CR3 读出数据,然后结合 pgd 等内容完成映射。

     

    此外,如果要通过进程的虚拟地址找到所属区间以及相应的vma 结构可以使用 find_vma 

    [cpp]  view plain copy print ? 666/* Look up the first VMA which satisfies addr < vm_end, NULL if none. */   667struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)   668{   669 struct vm_area_struct *vma = NULL;   670   671 if (mm) {   672 /* Check the cache first. */   673 /* (Cache hit rate is typically around 35%.) */   674 vma = mm->mmap_cache;   675 if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {   676 rb_node_t * rb_node;   677   678 rb_node = mm->mm_rb.rb_node;   679 vma = NULL;   680   681 while (rb_node) {   682 struct vm_area_struct * vma_tmp;   683   684 vma_tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb);   685   686 if (vma_tmp->vm_end > addr) {   687 vma = vma_tmp;   688 if (vma_tmp->vm_start <= addr)   689 break;   690 rb_node = rb_node->rb_left;   691 } else   692 rb_node = rb_node->rb_right;   693 }   694 if (vma)   695 mm->mmap_cache = vma;   696 }   697 }   698 return vma;   699}   

    [cpp]  view plain  copy  print ? 666/* Look up the first VMA which satisfies addr < vm_end, NULL if none. */   667struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)   668{   669 struct vm_area_struct *vma = NULL;   670   671 if (mm) {   672 /* Check the cache first. */   673 /* (Cache hit rate is typically around 35%.) */   674 vma = mm->mmap_cache;   675 if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {   676 rb_node_t * rb_node;   677   678 rb_node = mm->mm_rb.rb_node;   679 vma = NULL;   680   681 while (rb_node) {   682 struct vm_area_struct * vma_tmp;   683   684 vma_tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb);   685   686 if (vma_tmp->vm_end > addr) {   687 vma = vma_tmp;   688 if (vma_tmp->vm_start <= addr)   689 break;   690 rb_node = rb_node->rb_left;   691 } else   692 rb_node = rb_node->rb_right;   693 }   694 if (vma)   695 mm->mmap_cache = vma;   696 }   697 }   698 return vma;   699}   

    首先通过查找mmap_cache ,如果不是,则在链表中或者红黑树中搜索。如果返回 0 ,表示还没有创建 vma,这时候就需要创建一个新的虚存区间结构。

     

    ————————————–cut-line

     

    1。越界访问

    页式映射将虚拟地址转换成物理地址,并不是每次映射都是成功的,以下是几种失败的情况:

    1 )映射过程中遇到 pgd 或者 pte 等项为空,映射没有建立

    2 )物理页面不在内存中

    3 )权限不符

    于是就有相应的错误处理程序/arch/i386/mm/fault.c 中的 do_page_fault() 

    [cpp]  view plain copy print ? 130/*  131 * This routine handles page faults. It determines the address,  132 * and the problem, and then passes it off to one of the appropriate  133 * routines.  134 *  135 * error_code:  136 * bit 0 == 0 means no page found, 1 means protection fault  137 * bit 1 == 0 means read, 1 means write  138 * bit 2 == 0 means kernel, 1 means user-mode  139 */   140asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)   141{   

    [cpp]  view plain  copy  print ? 130/*  131 * This routine handles page faults. It determines the address,  132 * and the problem, and then passes it off to one of the appropriate  133 * routines.  134 *  135 * error_code:  136 * bit 0 == 0 means no page found, 1 means protection fault  137 * bit 1 == 0 means read, 1 means write  138 * bit 2 == 0 means kernel, 1 means user-mode  139 */   140asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)   141{   

    由前置注释可知,错误码第0 位为0 表示页面不存在,1 表示权限不符;第1 位为0 表示为读访问引起的错误,1 表示写访问引起错误;第2 位为0 表示错误发生在内核态,1 表示在用户态。

    该页面错误处理机制需要两个参数,一个是regs 指向错误前现场, error_code 如上。

    [cpp]  view plain copy print ? 151 /* get the address */   152 __asm__(”movl %%cr2,%0″:”=r” (address));   

    [cpp]  view plain  copy  print ? 151 /* get the address */   152 __asm__(”movl %%cr2,%0″:”=r” (address));   

    这两行是获得导致映射失败的线性地址,它存储在CR2 中,由汇编语言实现。

    接着首先是处理在内核空间发生的非权限不符错误:

    [cpp]  view plain copy print ? 160 /*  161 * We fault-in kernel-space virtual memory on-demand. The  162 * ‘reference’ page table is init_mm.pgd.  163 *  164 * NOTE! We MUST NOT take any locks for this case. We may  165 * be in an interrupt or a critical region, and should  166 * only copy the information from the master page table,  167 * nothing more.  168 *  169 * This verifies that the fault happens in kernel space  170 * (error_code & 4) == 0, and that the fault was not a  171 * protection error (error_code & 1) == 0.  172 */   173 if (address >= TASK_SIZE && !(error_code & 5))   174 goto vmalloc_fault;   175   176 mm = tsk->mm;   177 info.si_code = SEGV_MAPERR;   

    [cpp]  view plain  copy  print ? 160 /*  161 * We fault-in kernel-space virtual memory on-demand. The  162 * ‘reference’ page table is init_mm.pgd.  163 *  164 * NOTE! We MUST NOT take any locks for this case. We may  165 * be in an interrupt or a critical region, and should  166 * only copy the information from the master page table,  167 * nothing more.  168 *  169 * This verifies that the fault happens in kernel space  170 * (error_code & 4) == 0, and that the fault was not a  171 * protection error (error_code & 1) == 0.  172 */   173 if (address >= TASK_SIZE && !(error_code & 5))   174 goto vmalloc_fault;   175   176 mm = tsk->mm;   177 info.si_code = SEGV_MAPERR;   

    由前置注释可知if 条件的判断保证了错误发生在内核空间,而且不是权限不符错误。这种错误转向vmalloc_fault 处理,该处理机制也在内部定义。

    接着处理的是中断或者进程映射未建立的情况:

    [cpp]  view plain copy print ? 179 /*  180 * If we’re in an interrupt or have no user  181 * context, we must not take the fault..  182 */   183 if (in_interrupt() || !mm)   184 goto no_context;   

    [cpp]  view plain  copy  print ? 179 /*  180 * If we’re in an interrupt or have no user  181 * context, we must not take the fault..  182 */   183 if (in_interrupt() || !mm)   184 goto no_context;   

    在这段代码之下是一段有关于堆栈越界的处理。当用尽了本进程的堆栈空间后,如果再执行进栈操作,由于堆栈是从上往下延伸的,所以一般情况下会把数据写到(%esp-4) 位置,如果是 32 字节操作则是 (%esp-32)了。

    [cpp]  view plain copy print ? 188 vma = find_vma(mm, address);   

    [cpp]  view plain  copy  print ? 188 vma = find_vma(mm, address);   

    查找虚存区间。

    如果没有找到:

    [cpp]  view plain copy print ? 189 if (!vma)   190 goto bad_area;   

    [cpp]  view plain  copy  print ? 189 if (!vma)   190 goto bad_area;   

    转向bad_area 处理。

    如果找到,且地址大于vma 起始地址(非堆栈)则转向:

    [cpp]  view plain copy print ? 191 if (vma->vm_start <= address)   192 goto good_area;   

    [cpp]  view plain  copy  print ? 191 if (vma->vm_start <= address)   192 goto good_area;   

    而如果是堆栈,那么VM_GROWSDOWN 标记为 1 ,当向下越界时,如果超过 %esp-32 那么就转向 bad_area 否则扩充堆栈,调用 expand_stack() 

    [cpp]  view plain copy print ? 193 if (!(vma->vm_flags & VM_GROWSDOWN))   194 goto bad_area;   195 if (error_code & 4) {   196 /*  197 * accessing the stack below %esp is always a bug.  198 * The “+ 32″ is there due to some instructions (like  199 * pusha) doing post-decrement on the stack and that  200 * doesn’t show up until later..  201 */   202 if (address + 32 < regs->esp)   203 goto bad_area;   204 }   205 if (expand_stack(vma, address))   206 goto bad_area;   

    [cpp]  view plain  copy  print ? 193 if (!(vma->vm_flags & VM_GROWSDOWN))   194 goto bad_area;   195 if (error_code & 4) {   196 /*  197 * accessing the stack below %esp is always a bug.  198 * The “+ 32″ is there due to some instructions (like  199 * pusha) doing post-decrement on the stack and that  200 * doesn’t show up until later..  201 */   202 if (address + 32 < regs->esp)   203 goto bad_area;   204 }   205 if (expand_stack(vma, address))   206 goto bad_area;   

    但是并不是无限制地扩充堆栈的,每个进程都有限制,如果超过就跳转到bad_area 。如果允许扩充,转向good_area 继续完成新增页面对物理内存的映射。

    具体的处理机制见/arch/i386/mm/fault.c 

    转载请注明原文地址: https://ju.6miu.com/read-1296143.html
    最新回复(0)