Linux开发心得总结14 - linux情景分析--存储管理

来源:互联网 发布:ios 模仿淘宝商品详情 编辑:程序博客网 时间:2024/05/22 13:01
 

2.1  linux内存管理基本框架

 linux中的分段分页机制分三层,页目录(PGD),中间目录(PMD),页表(PT)。PT中的表项称为页表项(PTE)。注意英文缩写,在linux程序中函数变量的名字等都会和英文缩写相关。

LINUX中的三级映射流程如图:

但是arm结构的MMU在硬件只有2级映射,所以在软件上会跳过PMD表。即:在PGD中直接放的是PT的base address。在linux软件上就是:

[cpp] view plaincopyprint?
  1. #define PMD_SHIFT       21   
  2. #define PGDIR_SHIFT 21             //让PMD_SHIFT 和 PGDIR_SHIFT 相等就可以了。  


 

新的2.6内核和内核源码情景分析上的差别挺大的,在2.6.11版本以后,linux将软件上的3级映射变成了4级映射,在PMD后面增加了一个PUD(page upper directory). 在arm的两级映射中,跳过PMD和PUD

在2.6.39内核arch/arm/include/asm/pgtable.h中有下代码:

[cpp] view plaincopyprint?
  1. /* 
  2.  * Hardware-wise, we have a two level page table structure, where the first 
  3.  * level has 4096 entries, and the second level has 256 entries.  Each entry 
  4.  * is one 32-bit word.  Most of the bits in the second level entry are used 
  5.  * by hardware, and there aren't any "accessed" and "dirty" bits. 
  6.  * 
  7.  * Linux on the other hand has a three level page table structure, which can 
  8.  * be wrapped to fit a two level page table structure easily - using the PGD 
  9.  * and PTE only.  However, Linux also expects one "PTE" table per page, and 
  10.  * at least a "dirty" bit. 
  11.  * 
  12.  * Therefore, we tweak the implementation slightly - we tell Linux that we 
  13.  * have 2048 entries in the first level, each of which is 8 bytes (iow, two 
  14.  * hardware pointers to the second level.)  The second level contains two 
  15.  * hardware PTE tables arranged contiguously, preceded by Linux versions 
  16.  * which contain the state information Linux needs.  We, therefore, end up 
  17.  * with 512 entries in the "PTE" level. 
  18.  * 
  19.  * This leads to the page tables having the following layout: 
  20.  * 
  21.  *    pgd             pte 
  22.  * |        | 
  23.  * +--------+ 
  24.  * |        |       +------------+ +0 
  25.  * +- - - - +       | Linux pt 0 | 
  26.  * |        |       +------------+ +1024 
  27.  * +--------+ +0    | Linux pt 1 | 
  28.  * |        |-----> +------------+ +2048 
  29.  * +- - - - + +4    |  h/w pt 0  | 
  30.  * |        |-----> +------------+ +3072 
  31.  * +--------+ +8    |  h/w pt 1  | 
  32.  * |        |       +------------+ +4096 
  33.  * 
  34.  * See L_PTE_xxx below for definitions of bits in the "Linux pt", and 
  35.  * PTE_xxx for definitions of bits appearing in the "h/w pt". 
  36.  * 
  37.  * PMD_xxx definitions refer to bits in the first level page table. 
  38.  * 
  39.  * The "dirty" bit is emulated by only granting hardware write permission 
  40.  * iff the page is marked "writable" and "dirty" in the Linux PTE.  This 
  41.  * means that a write to a clean page will cause a permission fault, and 
  42.  * the Linux MM layer will mark the page dirty via handle_pte_fault(). 
  43.  * For the hardware to notice the permission change, the TLB entry must 
  44.  * be flushed, and ptep_set_access_flags() does that for us. 
  45.  * 
  46.  * The "accessed" or "young" bit is emulated by a similar method; we only 
  47.  * allow accesses to the page if the "young" bit is set.  Accesses to the 
  48.  * page will cause a fault, and handle_pte_fault() will set the young bit 
  49.  * for us as long as the page is marked present in the corresponding Linux 
  50.  * PTE entry.  Again, ptep_set_access_flags() will ensure that the TLB is 
  51.  * up to date. 
  52.  * 
  53.  * However, when the "young" bit is cleared, we deny access to the page 
  54.  * by clearing the hardware PTE.  Currently Linux does not flush the TLB 
  55.  * for us in this case, which means the TLB will retain the transation 
  56.  * until either the TLB entry is evicted under pressure, or a context 
  57.  * switch which changes the user space mapping occurs. 
  58.  */  
[cpp] view plaincopyprint?
  1. <p>#define PTRS_PER_PTE  512                              //PTE的个数  
  2. #define PTRS_PER_PMD  1   
  3. #define PTRS_PER_PGD  2048</p><p>#define PTE_HWTABLE_PTRS (PTRS_PER_PTE)  
  4. #define PTE_HWTABLE_OFF  (PTE_HWTABLE_PTRS * sizeof(pte_t))  
  5. #define PTE_HWTABLE_SIZE (PTRS_PER_PTE * sizeof(u32))</p><p>/*  
  6.  * PMD_SHIFT determines the size of the area a second-level page table can map  
  7.  * PGDIR_SHIFT determines what a third-level page table entry can map  
  8.  */  
  9. #define PMD_SHIFT  21   
  10. #define PGDIR_SHIFT  21 //另PMD和PDGIR相等,来跳过PMD。</p>  

/*linux将PGD为2k,每项为8个byte。(MMU中取值为前高12bit,为4k,每项4个byte,但linux为什么要这样做呢?) ,另外linux在定义pte时,定义了两个pte,一个供MMU使用,一个供linux使用,来用描述这个页。
 *根据注释中的表图,我看到有  #define PTRS_PER_PTE  512   #define PTRS_PER_PGD  2048 ,  linux将PDG定义为2K,8byte,
每个pte项为512,4byte。 他将 两个pte项进行了一下合并。为什么?为什么?

**/

在进程中,传说是可以看到4G的空间,按照linux的用户空间和内核空间划分。 其实进程可以看到3G的自己进程的空间,3G-4G的空间是内核空间,进程仅能通过系统调用进入。

 

2.2地址映射全过程

将x86的段地址,略。。。

2.3几个重要的数据结构和函数

PGD,PTE的值定义:

[cpp] view plaincopyprint?
  1. /* 
  2.  * These are used to make use of C type-checking.. 
  3.  */  
  4. typedef struct { pteval_t pte; } pte_t;  
  5. typedef struct { unsigned long pmd; } pmd_t;  
  6. typedef struct { unsigned long pgd[2]; } pgd_t; //定义一个[2]数组,这样就和上面的介绍对应起来了,每个PGD是一个8个byte的值(即两个long型数)。  
  7. typedef struct { unsigned long pgprot; } pgprot_t;  

这几个结构体定义PTE等他们的结构, pgprot_t 是page protect的意思,最上面的图可知,在具体映射时候,需要将PTE表中的高20bit的值 + 线性地址的低 12bit的值  才是具体的物理地址。所以PTE只有高20bit是在地址映射映射时有效的,那么他的低12bit用来放一些protect的数据,如writeable,rdonly......下面是pgprot_t的一些值:

[cpp] view plaincopyprint?
  1. #define __PAGE_NONE     __pgprot(_L_PTE_DEFAULT | L_PTE_RDONLY | L_PTE_XN)  
  2. #define __PAGE_SHARED       __pgprot(_L_PTE_DEFAULT | L_PTE_USER | L_PTE_XN)  
  3. #define __PAGE_SHARED_EXEC  __pgprot(_L_PTE_DEFAULT | L_PTE_USER)  
  4. #define __PAGE_COPY     __pgprot(_L_PTE_DEFAULT | L_PTE_USER | L_PTE_RDONLY | L_PTE_XN)  
  5. #define __PAGE_COPY_EXEC    __pgprot(_L_PTE_DEFAULT | L_PTE_USER | L_PTE_RDONLY)  
  6. #define __PAGE_READONLY     __pgprot(_L_PTE_DEFAULT | L_PTE_USER | L_PTE_RDONLY | L_PTE_XN)  
  7. #define __PAGE_READONLY_EXEC    __pgprot(_L_PTE_DEFAULT | L_PTE_USER | L_PTE_RDONLY)  


所以当我们想做一个PTE时(即生成一个page的pte),调用下列函数:

[cpp] view plaincopyprint?
  1. #define mk_pte(page,prot)   pfn_pte(page_to_pfn(page), prot)  

这个函数即是 page_to_pfn(page)得到高20bit的值 + prot的值。 prot即为pgprot_t的结构型值,低12bit。表示页的属性。

 生成这一个pte后,我们要让这个pte生效,就要在将这个值 赋值到对应的地方去。

 set_pte(pteptr,pteval):  但是在2.6.39没有找到这个代码,过程应该差不多了,算了·····

另外,我们可以通过对PTE的低12bit设置,来让mmu判断,是否建立了映射?还是建立了映射但已经被swap出去了? 

还有其他很多的函数,和宏定义,具体可以看《深入linux内核》的内存寻址这章。

 

这里都是说的PTE中的低12bit的, 但是在2.6.39中明显多了一个linux pt, 具体做什么用还不太清楚。但是linux在arch/arm/include/asm/pgtable.h 中由个注释

[cpp] view plaincopyprint?
  1. /* 
  2.  * "Linux" PTE definitions. 
  3.  * 
  4.  * We keep two sets of PTEs - the hardware and the linux version. 
  5.  * This allows greater flexibility in the way we map the Linux bits 
  6.  * onto the hardware tables, and allows us to have YOUNG and DIRTY 
  7.  * bits. 
  8.  * 
  9.  * The PTE table pointer refers to the hardware entries; the "Linux" 
  10.  * entries are stored 1024 bytes below. 
  11.  */  

应该可以看出,linux pt和PTE的低12bit是相呼应的,用来记录page的一些信息。

 

 

linux中,每个物理页都有一个对应的page结构体,组成一个数组,放在mem_map中。

[cpp] view plaincopyprint?
  1. /* 
  2.  * Each physical page in the system has a struct page associated with 
  3.  * it to keep track of whatever it is we are using the page for at the 
  4.  * moment. Note that we have no way to track which tasks are using 
  5.  * a page, though if it is a pagecache page, rmap structures can tell us 
  6.  * who is mapping it. 
  7.  */  
  8. struct page {  
  9.     unsigned long flags;        /* Atomic flags, some possibly 
  10.                      * updated asynchronously */  
  11.     atomic_t _count;        /* Usage count, see below. */  
  12.     union {  
  13.         atomic_t _mapcount; /* Count of ptes mapped in mms, 
  14.                      * to show when page is mapped 
  15.                      * & limit reverse map searches. 
  16.                      */  
  17.         struct {        /* SLUB */  
  18.             u16 inuse;  
  19.             u16 objects;  
  20.         };  
  21.     };  
  22.     union {  
  23.         struct {  
  24.         unsigned long private;      /* Mapping-private opaque data: 
  25.                          * usually used for buffer_heads 
  26.                          * if PagePrivate set; used for 
  27.                          * swp_entry_t if PageSwapCache; 
  28.                          * indicates order in the buddy 
  29.                          * system if PG_buddy is set. 
  30.                          */  
  31.         struct address_space *mapping;  /* If low bit clear, points to 
  32.                          * inode address_space, or NULL. 
  33.                          * If page mapped as anonymous 
  34.                          * memory, low bit is set, and 
  35.                          * it points to anon_vma object: 
  36.                          * see PAGE_MAPPING_ANON below. 
  37.                          */  
  38.         };  
  39. #if USE_SPLIT_PTLOCKS   
  40.         spinlock_t ptl;  
  41. #endif   
  42.         struct kmem_cache *slab;    /* SLUB: Pointer to slab */  
  43.         struct page *first_page;    /* Compound tail pages */  
  44.     };  
  45.     union {  
  46.         pgoff_t index;      /* Our offset within mapping. */  
  47.         void *freelist;     /* SLUB: freelist req. slab lock */  
  48.     };  
  49.     struct list_head lru;       /* Pageout list, eg. active_list 
  50.                      * protected by zone->lru_lock ! 
  51.                      */  
  52.     /* 
  53.      * On machines where all RAM is mapped into kernel address space, 
  54.      * we can simply calculate the virtual address. On machines with 
  55.      * highmem some memory is mapped into kernel virtual memory 
  56.      * dynamically, so we need a place to store that address. 
  57.      * Note that this field could be 16 bits on x86 ... ;) 
  58.      * 
  59.      * Architectures with slow multiplication can define 
  60.      * WANT_PAGE_VIRTUAL in asm/page.h 
  61.      */  
  62. #if defined(WANT_PAGE_VIRTUAL)   
  63.     void *virtual;          /* Kernel virtual address (NULL if 
  64.                        not kmapped, ie. highmem) */  
  65. #endif /* WANT_PAGE_VIRTUAL */  
  66. #ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS   
  67.     unsigned long debug_flags;  /* Use atomic bitops on this */  
  68. #endif   
  69.   
  70. #ifdef CONFIG_KMEMCHECK   
  71.     /* 
  72.      * kmemcheck wants to track the status of each byte in a page; this 
  73.      * is a pointer to such a status block. NULL if not tracked. 
  74.      */  
  75.     void *shadow;  
  76. #endif   
  77. };  

页的page结构放在mem_map中。  要想找到一个物理地址对应的page结构很简单, 就是   mem_map[pfn]即可。    
 

 

由于硬件的关系,会将整个内存分成不同的zone,:ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM. 这样分的原因是 :有些芯片的DMA设置只能设置到固定区域的地址,所以将这些区域作为ZONE_DMA以免其他操作占用。NORMAL就是正常的内存。高位内存内核空间因为只有1G不能全部映射,所以也要做特殊的处理,进行映射,才能使用。

这三个ZONE使用的描述符是:

[cpp] view plaincopyprint?
  1. struct zone {  
  2.     /* Fields commonly accessed by the page allocator */  
  3.   
  4.     /* zone watermarks, access with *_wmark_pages(zone) macros */  
  5.     unsigned long watermark[NR_WMARK];  
  6.   
  7.     /* 
  8.      * When free pages are below this point, additional steps are taken 
  9.      * when reading the number of free pages to avoid per-cpu counter 
  10.      * drift allowing watermarks to be breached 
  11.      */  
  12.     unsigned long percpu_drift_mark;  
  13.   
  14.     /* 
  15.      * We don't know if the memory that we're going to allocate will be freeable 
  16.      * or/and it will be released eventually, so to avoid totally wasting several 
  17.      * GB of ram we must reserve some of the lower zone memory (otherwise we risk 
  18.      * to run OOM on the lower zones despite there's tons of freeable ram 
  19.      * on the higher zones). This array is recalculated at runtime if the 
  20.      * sysctl_lowmem_reserve_ratio sysctl changes. 
  21.      */  
  22.     unsigned long       lowmem_reserve[MAX_NR_ZONES];  
  23.   
  24. #ifdef CONFIG_NUMA   
  25.     int node;  
  26.     /* 
  27.      * zone reclaim becomes active if more unmapped pages exist. 
  28.      */  
  29.     unsigned long       min_unmapped_pages;  
  30.     unsigned long       min_slab_pages;  
  31. #endif   
  32.     struct per_cpu_pageset __percpu *pageset;  
  33.     /* 
  34.      * free areas of different sizes 
  35.      */  
  36.     spinlock_t      lock;  
  37.     int                     all_unreclaimable; /* All pages pinned */  
  38. #ifdef CONFIG_MEMORY_HOTPLUG   
  39.     /* see spanned/present_pages for more description */  
  40.     seqlock_t       span_seqlock;  
  41. #endif   
  42.     struct free_area    free_area[MAX_ORDER];  
  43.   
  44. #ifndef CONFIG_SPARSEMEM   
  45.     /* 
  46.      * Flags for a pageblock_nr_pages block. See pageblock-flags.h. 
  47.      * In SPARSEMEM, this map is stored in struct mem_section 
  48.      */  
  49.     unsigned long       *pageblock_flags;  
  50. #endif /* CONFIG_SPARSEMEM */   
  51.   
  52. #ifdef CONFIG_COMPACTION   
  53.     /* 
  54.      * On compaction failure, 1<<compact_defer_shift compactions 
  55.      * are skipped before trying again. The number attempted since 
  56.      * last failure is tracked with compact_considered. 
  57.      */  
  58.     unsigned int        compact_considered;  
  59.     unsigned int        compact_defer_shift;  
  60. #endif   
  61.   
  62.     ZONE_PADDING(_pad1_)  
  63.   
  64.     /* Fields commonly accessed by the page reclaim scanner */  
  65.     spinlock_t      lru_lock;     
  66.     struct zone_lru {  
  67.         struct list_head list;  
  68.     } lru[NR_LRU_LISTS];  
  69.   
  70.     struct zone_reclaim_stat reclaim_stat;  
  71.   
  72.     unsigned long       pages_scanned;     /* since last reclaim */  
  73.     unsigned long       flags;         /* zone flags, see below */  
  74.   
  75.     /* Zone statistics */  
  76.     atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];  
  77.   
  78.     /* 
  79.      * The target ratio of ACTIVE_ANON to INACTIVE_ANON pages on 
  80.      * this zone's LRU.  Maintained by the pageout code. 
  81.      */  
  82.     unsigned int inactive_ratio;  
  83.   
  84.   
  85.     ZONE_PADDING(_pad2_)  
  86.     /* Rarely used or read-mostly fields */  
  87.   
  88.     /* 
  89.      * wait_table       -- the array holding the hash table 
  90.      * wait_table_hash_nr_entries   -- the size of the hash table array 
  91.      * wait_table_bits  -- wait_table_size == (1 << wait_table_bits) 
  92.      * 
  93.      * The purpose of all these is to keep track of the people 
  94.      * waiting for a page to become available and make them 
  95.      * runnable again when possible. The trouble is that this 
  96.      * consumes a lot of space, especially when so few things 
  97.      * wait on pages at a given time. So instead of using 
  98.      * per-page waitqueues, we use a waitqueue hash table. 
  99.      * 
  100.      * The bucket discipline is to sleep on the same queue when 
  101.      * colliding and wake all in that wait queue when removing. 
  102.      * When something wakes, it must check to be sure its page is 
  103.      * truly available, a la thundering herd. The cost of a 
  104.      * collision is great, but given the expected load of the 
  105.      * table, they should be so rare as to be outweighed by the 
  106.      * benefits from the saved space. 
  107.      * 
  108.      * __wait_on_page_locked() and unlock_page() in mm/filemap.c, are the 
  109.      * primary users of these fields, and in mm/page_alloc.c 
  110.      * free_area_init_core() performs the initialization of them. 
  111.      */  
  112.     wait_queue_head_t   * wait_table;  
  113.     unsigned long       wait_table_hash_nr_entries;  
  114.     unsigned long       wait_table_bits;  
  115.   
  116.     /* 
  117.      * Discontig memory support fields. 
  118.      */  
  119.     struct pglist_data  *zone_pgdat;  
  120.     /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */  
  121.     unsigned long       zone_start_pfn;  
  122.   
  123.     /* 
  124.      * zone_start_pfn, spanned_pages and present_pages are all 
  125.      * protected by span_seqlock.  It is a seqlock because it has 
  126.      * to be read outside of zone->lock, and it is done in the main 
  127.      * allocator path.  But, it is written quite infrequently. 
  128.      * 
  129.      * The lock is declared along with zone->lock because it is 
  130.      * frequently read in proximity to zone->lock.  It's good to 
  131.      * give them a chance of being in the same cacheline. 
  132.      */  
  133.     unsigned long       spanned_pages;  /* total size, including holes */  
  134.     unsigned long       present_pages;  /* amount of memory (excluding holes) */  
  135.   
  136.     /* 
  137.      * rarely used fields: 
  138.      */  
  139.     const char      *name;  
  140. } ____cacheline_internodealigned_in_smp;  


在这个描述符中,有一个struct free_area free_area[MAX_ORDER];  这样的成员变量,这个数组的每个元素都包含一个page的list,那么,一共有MAX_ORDER个list。他是将 所有的free page按照连续是否进行分组。 有2个连续的page的,有4个连续的page的,有8个连续的page的,....2^MAX_ORDER。  这样将page进行分类。当需要alloc一些page时,可以方便的从这些list中找连续的page。(slub中也会像这样来分类,但是slub中分类是按byte为单位,如2byte,4byte....)

 

另外还有一个 

[cpp] view plaincopyprint?
  1. struct zone_lru {  
  2.         struct list_head list;  
  3.     } lru[NR_LRU_LISTS];  
[cpp] view plaincopyprint?
  1. <p>/* 
  2.  * We do arithmetic on the LRU lists in various places in the code, 
  3.  * so it is important to keep the active lists LRU_ACTIVE higher in 
  4.  * the array than the corresponding inactive lists, and to keep 
  5.  * the *_FILE lists LRU_FILE higher than the corresponding _ANON lists. 
  6.  * 
  7.  * This has to be kept in sync with the statistics in zone_stat_item 
  8.  * above and the descriptions in vmstat_text in mm/vmstat.c 
  9.  */  
  10. #define LRU_BASE 0   
  11. #define LRU_ACTIVE 1   
  12. #define LRU_FILE 2</p><p>enum lru_list {  
  13.  LRU_INACTIVE_ANON = LRU_BASE,  
  14.  LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,  
  15.  LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,  
  16.  LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,  
  17.  LRU_UNEVICTABLE,  
  18.  NR_LRU_LISTS  
  19. };</p>  

这样的成员变量,这也是一个list数组, 其中NR_LRU_LISTS表示在enum 中定义的成员的个数(新的linux中好像有不少这样的用法),这个list数组中,每个list也是很多page结构体。LRU是一中算法,会将长时间不同的page给swap out到flash中。这个数组是LRU算法用的。 其中有 inactive的page, active的page, inactive 的file,等////  在后面对LRU介绍。

 

UMA和NUMA

在linux中,如果CPU访问所有的memory所需的时间都是一样的,那么我们人为这个系统是UMA(uniform memory architecture),但是如果访问memory所需的时间是不一样的(在SMP(多核系统)是很常见的),那么我们人为这个系统是NUMA(Non-uniform memory architecture)。在linux中,系统会将内存分成几个node,每个node中的memory,CPU进入的时间的相等的。这个我们分配内存的时候就会从一个node进行分配。

[cpp] view plaincopyprint?
  1. typedef struct pglist_data {  
  2.     struct zone node_zones[MAX_NR_ZONES];  
  3.     struct zonelist node_zonelists[MAX_ZONELISTS];  
  4.     int nr_zones;  
  5. #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */  
  6.     struct page *node_mem_map;  
  7. #ifdef CONFIG_CGROUP_MEM_RES_CTLR  
  8.     struct page_cgroup *node_page_cgroup;  
  9. #endif   
  10. #endif   
  11. #ifndef CONFIG_NO_BOOTMEM  
  12.     struct bootmem_data *bdata;  
  13. #endif   
  14. #ifdef CONFIG_MEMORY_HOTPLUG   
  15.     /* 
  16.      * Must be held any time you expect node_start_pfn, node_present_pages 
  17.      * or node_spanned_pages stay constant.  Holding this will also 
  18.      * guarantee that any pfn_valid() stays that way. 
  19.      * 
  20.      * Nests above zone->lock and zone->size_seqlock. 
  21.      */  
  22.     spinlock_t node_size_lock;  
  23. #endif   
  24.     unsigned long node_start_pfn;  
  25.     unsigned long node_present_pages; /* total number of physical pages */  
  26.     unsigned long node_spanned_pages; /* total size of physical page 
  27.                          range, including holes */  
  28.     int node_id;  
  29.     wait_queue_head_t kswapd_wait;  
  30.     struct task_struct *kswapd;  
  31.     int kswapd_max_order;  
  32.     enum zone_type classzone_idx;  
  33. } pg_data_t;  

在pglist_data结构中,一个成员变量node_zones[], 他代表了这个node下的所有zone。组成一个数组。

另一个成员变量node_zonelist[]. 这是一个list数组,每一个list中将不同node下的所有的zone都link到一起。数组中不同元素list中,zone的排列顺序不一样。这样当内核需要malloc一些page的时候,可能当前的这个node中并没有足够的page,那么就会按照这个数组list中的顺序依次去申请空间。内核在不同的情况下,需要按照不同的顺序申请空间。所以需要好几个不同的list,这些list就组成了这个数组。

每一个pglist_data结构对应一个node。这样,在每个zone结构上又多了一个node结构。这样内存页管理的结构应该是

node:同过UMA和NUMA将内存分成几个node。(在arm系统中,如果不是smp的一般都是一个node)

zone:在每个node中,再将内存分配成几个zone。

page:在每个zone中,对page进行管理,添加都各种list中。

for example: 可以分析一下 for_each_zone这个宏定义,会发现就是先查找每个node,在每个node下进行zone的扫描。

 

上面的这些都是描述物理空间的,page,zone,node都是物理空间的管理结构,下面的结构体,描述虚拟空间

从物理空间来看,物理空间主要是“供”,他是实实在在存在的,主要目的就是向OS提供空间。 

从虚拟空间来看,虚拟空间是“需”,他是虚拟的,主要是就发送需求。

*****当虚拟空间提出了 “需求”,但物理空间无法满足时候,就会进行swap out操作了。

 

vm_area_struct结构:这个结构描述  进程的的内存空间的情况。

[cpp] view plaincopyprint?
  1. /* 
  2.  * This struct defines a memory VMM memory area. There is one of these 
  3.  * per VM-area/task.  A VM area is any part of the process virtual memory 
  4.  * space that has a special rule for the page-fault handlers (ie a shared 
  5.  * library, the executable area etc). 
  6.  */  
  7. struct vm_area_struct {  
  8.     struct mm_struct * vm_mm;   /* The address space we belong to. */  
  9.     unsigned long vm_start;     /* Our start address within vm_mm. */  
  10.     unsigned long vm_end;       /* The first byte after our end address 
  11.                        within vm_mm. */  
  12.   
  13.     /* linked list of VM areas per task, sorted by address */  
  14.     struct vm_area_struct *vm_next, *vm_prev;  
  15.   
  16.     pgprot_t vm_page_prot;      /* Access permissions of this VMA. */  
  17.     unsigned long vm_flags;     /* Flags, see mm.h. */  
  18.   
  19.     struct rb_node vm_rb;  
  20.   
  21.     /* 
  22.      * For areas with an address space and backing store, 
  23.      * linkage into the address_space->i_mmap prio tree, or 
  24.      * linkage to the list of like vmas hanging off its node, or 
  25.      * linkage of vma in the address_space->i_mmap_nonlinear list. 
  26.      */  
  27.     union {  
  28.         struct {  
  29.             struct list_head list;  
  30.             void *parent;   /* aligns with prio_tree_node parent */  
  31.             struct vm_area_struct *head;  
  32.         } vm_set;  
  33.   
  34.         struct raw_prio_tree_node prio_tree_node;  
  35.     } shared;  
  36.   
  37.     /* 
  38.      * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma 
  39.      * list, after a COW of one of the file pages.  A MAP_SHARED vma 
  40.      * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack 
  41.      * or brk vma (with NULL file) can only be in an anon_vma list. 
  42.      */  
  43.     struct list_head anon_vma_chain; /* Serialized by mmap_sem & 
  44.                       * page_table_lock */  
  45.     struct anon_vma *anon_vma;  /* Serialized by page_table_lock */  
  46.   
  47.     /* Function pointers to deal with this struct. */  
  48.     const struct vm_operations_struct *vm_ops;  
  49.   
  50.     /* Information about our backing store: */  
  51.     unsigned long vm_pgoff;     /* Offset (within vm_file) in PAGE_SIZE 
  52.                        units, *not* PAGE_CACHE_SIZE */  
  53.     struct file * vm_file;      /* File we map to (can be NULL). */  
  54.     void * vm_private_data;     /* was vm_pte (shared mem) */  
  55.     unsigned long vm_truncate_count;/* truncate_count or restart_addr */  
  56.   
  57. #ifndef CONFIG_MMU   
  58.     struct vm_region *vm_region;    /* NOMMU mapping region */  
  59. #endif   
  60. #ifdef CONFIG_NUMA   
  61.     struct mempolicy *vm_policy;    /* NUMA policy for the VMA */  
  62. #endif   
  63. };  

这个数据结构在程序的变量名字常常是vma。

这个结构是成员变量,  vm_start 和vm_end表示一段连续的虚拟区间。  但是并不是一个连续的虚拟区间就可以用一个vm_area_struct结构来表示。而是要 虚拟地址连续,并且这段空间的属性/访问权限等也相同才可以。

所以这段空间的属于和权限用

[cpp] view plaincopyprint?
  1. pgprot_t vm_page_prot;  /* Access permissions of this VMA. */  
  2.  unsigned long vm_flags;  /* Flags, see mm.h. */   

这两个成员变量来表示。看到这两个成员变量可算看到亲人了。 还记得那个大明湖畔的容嬷嬷妈?-------pgprot_t vm_page_prot;

每一个进程的所有vm_erea_struct结构通过

[cpp] view plaincopyprint?
  1. /* linked list of VM areas per task, sorted by address */  
  2.     struct vm_area_struct *vm_next, *vm_prev;  

这个指针link起来。

当然,有上面这种链表的链接方式,查找起来是很麻烦的,而且一个进程中可能会有好多个这样的结构,所以 除了上面的链表,还要有一中更有效的查找方式:

[cpp] view plaincopyprint?
  1. <span style="font-size:13px;">    /* 
  2.      * For areas with an address space and backing store, 
  3.      * linkage into the address_space->i_mmap prio tree, or 
  4.      * linkage to the list of like vmas hanging off its node, or 
  5.      * linkage of vma in the address_space->i_mmap_nonlinear list. 
  6.      */  
  7.     union {  
  8.         struct {  
  9.             struct list_head list;  
  10.             void *parent;   /* aligns with prio_tree_node parent */  
  11.             struct vm_area_struct *head;  
  12.         } vm_set;  
  13.   
  14.         struct raw_prio_tree_node prio_tree_node;  
  15.     } shared;  
  16. </span>  


 

有两种情况虚拟空间会与磁盘flash发生关系。

1. 当内存分配失败,没有足够的内存时候,会发生swap。

2. linux进行mmap系统时候,会将磁盘上的内存map到用户空间,像访问memory一样,直接访问文件内容。 

 

为了迎合1. vm_area_struct 的mapping,vm_next_share,vm_pprev_share,vm_file等。但是在2.6.39中没找到这些变量。仅有:(在page结构体中,也有swap相关的信息)

[cpp] view plaincopyprint?
  1. /* Information about our backing store: */  
  2.     unsigned long vm_pgoff;     /* Offset (within vm_file) in PAGE_SIZE 
  3.                        units, *not* PAGE_CACHE_SIZE */  
  4.     struct file * vm_file;      /* File we map to (can be NULL). */  
  5.     void * vm_private_data;     /* was vm_pte (shared mem) */  
  6.     unsigned long vm_truncate_count;/* truncate_count or restart_addr */  

在后面再分析这些结构。

为了迎合2. vm_area_struct结构提供了

[cpp] view plaincopyprint?
  1. /* Function pointers to deal with this struct. */  
  2.     const struct vm_operations_struct *vm_ops;  

这样的变量,进行操作。

 

在vm_area_struct结构中,有个mm_struct结构,注释说他属于这个结构,由此看来,mm_struct结构应该是vm_area_struct结构的上层。

[cpp] view plaincopyprint?
  1. struct mm_struct {  
  2.     struct vm_area_struct * mmap;       /* list of VMAs */  
  3.     struct rb_root mm_rb;  
  4.     struct vm_area_struct * mmap_cache; /* last find_vma result */  
  5. #ifdef CONFIG_MMU   
  6.     unsigned long (*get_unmapped_area) (struct file *filp,  
  7.                 unsigned long addr, unsigned long len,  
  8.                 unsigned long pgoff, unsigned long flags);  
  9.     void (*unmap_area) (struct mm_struct *mm, unsigned long addr);  
  10. #endif   
  11.     unsigned long mmap_base;        /* base of mmap area */  
  12.     unsigned long task_size;        /* size of task vm space */  
  13.     unsigned long cached_hole_size;     /* if non-zero, the largest hole below free_area_cache */  
  14.     unsigned long free_area_cache;      /* first hole of size cached_hole_size or larger */  
  15.     pgd_t * pgd;  
  16.     atomic_t mm_users;          /* How many users with user space? */  
  17.     atomic_t mm_count;          /* How many references to "struct mm_struct" (users count as 1) */  
  18.     int map_count;              /* number of VMAs */  
  19.   
  20.     spinlock_t page_table_lock;     /* Protects page tables and some counters */  
  21.     struct rw_semaphore mmap_sem;  
  22.   
  23.     struct list_head mmlist;        /* List of maybe swapped mm's.  These are globally strung 
  24.                          * together off init_mm.mmlist, and are protected 
  25.                          * by mmlist_lock 
  26.                          */  
  27.   
  28.   
  29.     unsigned long hiwater_rss;  /* High-watermark of RSS usage */  
  30.     unsigned long hiwater_vm;   /* High-water virtual memory usage */  
  31.   
  32.     unsigned long total_vm, locked_vm, shared_vm, exec_vm;  
  33.     unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;  
  34.     unsigned long start_code, end_code, start_data, end_data;  
  35.     unsigned long start_brk, brk, start_stack;  
  36.     unsigned long arg_start, arg_end, env_start, env_end;  
  37.   
  38.     unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */  
  39.   
  40.     /* 
  41.      * Special counters, in some configurations protected by the 
  42.      * page_table_lock, in other configurations by being atomic. 
  43.      */  
  44.     struct mm_rss_stat rss_stat;  
  45.   
  46.     struct linux_binfmt *binfmt;  
  47.   
  48.     cpumask_t cpu_vm_mask;  
  49.   
  50.     /* Architecture-specific MM context */  
  51.     mm_context_t context;  
  52.   
  53.     /* Swap token stuff */  
  54.     /* 
  55.      * Last value of global fault stamp as seen by this process. 
  56.      * In other words, this value gives an indication of how long 
  57.      * it has been since this task got the token. 
  58.      * Look at mm/thrash.c 
  59.      */  
  60.     unsigned int faultstamp;  
  61.     unsigned int token_priority;  
  62.     unsigned int last_interval;  
  63.   
  64.     /* How many tasks sharing this mm are OOM_DISABLE */  
  65.     atomic_t oom_disable_count;  
  66.   
  67.     unsigned long flags; /* Must use atomic bitops to access the bits */  
  68.   
  69.     struct core_state *core_state; /* coredumping support */  
  70. #ifdef CONFIG_AIO   
  71.     spinlock_t      ioctx_lock;  
  72.     struct hlist_head   ioctx_list;  
  73. #endif   
  74. #ifdef CONFIG_MM_OWNER   
  75.     /* 
  76.      * "owner" points to a task that is regarded as the canonical 
  77.      * user/owner of this mm. All of the following must be true in 
  78.      * order for it to be changed: 
  79.      * 
  80.      * current == mm->owner 
  81.      * current->mm != mm 
  82.      * new_owner->mm == mm 
  83.      * new_owner->alloc_lock is held 
  84.      */  
  85.     struct task_struct __rcu *owner;  
  86. #endif   
  87.   
  88. #ifdef CONFIG_PROC_FS   
  89.     /* store ref to file /proc/<pid>/exe symlink points to */  
  90.     struct file *exe_file;  
  91.     unsigned long num_exe_file_vmas;  
  92. #endif   
  93. #ifdef CONFIG_MMU_NOTIFIER  
  94.     struct mmu_notifier_mm *mmu_notifier_mm;  
  95. #endif   
  96. #ifdef CONFIG_TRANSPARENT_HUGEPAGE   
  97.     pgtable_t pmd_huge_pte; /* protected by page_table_lock */  
  98. #endif   
  99. };  


这个结构在代码中,经常是mm的名字。

他比vm_area_struct结构更上层,每个进程只有一个mm_struct结构在task_struct中由指针。因此mm_struct结构会更具总结型。

所以虚拟空间的联系图为:

 

 

总结一下:

讲解了,

1.PGD,PTE,

2.node--->zone---->page

3. mm_struct------>vm_area_struct

 

虚拟地址和物理地址 通过 PGD,PTE联系起来。

 

2.4越界访问

linux中的虚拟地址通过PGD,PTE等映射到物理地址。但当这个映射过程无法正常映射时候,就会报错,产生page fault exception。那么什么时候会无法正常呢?

  • 编程错误。程序使用了不存在的地址
  • 不是编程错误,linux的请求调页机制。即:当进程运行时,linux并不将全部的资源分配给进程,而是仅分配当前需要的这一部分,当进程需要另外的资源的时候(这时候就会产生缺页异常),linux再分配这部分。

 编程错误linux肯定不会手软的,直接弄死进程。请求调页机制,linux会申请页的。

 

当MMU对不存在的虚拟地址进行映射的时候,会产生异常,__dabt_usr: __dabt_svc。 他们都会调用do_DataAbort。

[cpp] view plaincopyprint?
  1. __dabt_usr:  
  2.     usr_entry  
  3.     kuser_cmpxchg_check  
  4.   
  5.     @  
  6.     @ Call the processor-specific abort handler:  
  7.     @  
  8.     @  r2 - aborted context pc  
  9.     @  r3 - aborted context cpsr  
  10.     @  
  11.     @ The abort handler must return the aborted address in r0, and  
  12.     @ the fault status register in r1.                           //说明了调用do_DataAbort时候传递给他的参数。  r0,r1,  
  13.     @  
  14. #ifdef MULTI_DABORT   
  15.     ldr r4, .LCprocfns  
  16.     mov lr, pc  
  17.     ldr pc, [r4, #PROCESSOR_DABT_FUNC]  
  18. #else   
  19.     bl  CPU_DABORT_HANDLER  
  20. #endif   
  21.   
  22.     @  
  23.     @ IRQs on, then call the main handler  
  24.     @  
  25.     enable_irq  
  26.     mov r2, sp                                           //参数r2  
  27.     adr lr, ret_from_exception  
  28.     b   do_DataAbort                   //调用do_DataAbort  
  29. ENDPROC(__dabt_usr)  


do_DataAbor根据川进来的参数调用 inf->fn ,在这里就是 do_page_fault

[cpp] view plaincopyprint?
  1. asmlinkage void __exception  
  2. do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)  
  3. {  
  4.     const struct fsr_info *inf = fsr_info + (fsr & 15) + ((fsr & (1 << 10)) >> 6);  
  5.     struct siginfo info;  
  6.   
  7.     if (!inf->fn(addr, fsr, regs))  
  8.         return;  
  9.   
  10.     printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n",  
  11.         inf->name, fsr, addr);  
  12.   
  13.     info.si_signo = inf->sig;  
  14.     info.si_errno = 0;  
  15.     info.si_code  = inf->code;  
  16.     info.si_addr  = (void __user *)addr;  
  17.     arm_notify_die("", regs, &info, fsr, 0);  
  18. }  


 

do_page_fault函数是页异常的重要函数:

ps:因为在,__dabt_usr: __dabt_svc这两种情况下都会调用do_DataAbort函数,然后调用do_page_fault,所以调用do_page_fault可能是在内核空间,也可能是在用户空间。在内核空间可能会是 进程通过系统调用,中断等进入的,也可能进程本来是内核线程。所以在do_page_fault中要行进判断的。到底是从哪里发生的异常。

一下函数的分析大部分参考了《深入linux内核》书中的 第9章。

[cpp] view plaincopyprint?
  1. static int __kprobes                               //__kprobes标志应该是调试的一种东西,等以后再专门研究一下。  
  2. do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)  
  3. {  
  4.     struct task_struct *tsk;  
  5.     struct mm_struct *mm;  
  6.     int fault, sig, code;  
  7.   
  8.     if (notify_page_fault(regs, fsr))       //这个函数是专门和上面的__kprobes对应的,调试的东西  
  9.         return 0;  
  10.   
  11.     tsk = current;  
  12.     mm  = tsk->mm;                          //将出现页异常的进程的  的进程描述符 赋给tsk,内存描述符赋给mm  
  13.    
  14.     /* 
  15.      * If we're in an interrupt or have no user 
  16.      * context, we must not take the fault.. 
  17.      */  
  18.     if (in_atomic() || !mm)                //判断发生发生异常的,in_atomic判断是否是在 原子操作中:中断程序,可延迟函数,禁用内核抢占的临界区。   !mm  判断进程是否是内核线程。参照博客线程调度的文章。  
  19.         goto no_context;              //如果缺页是发生在这些情况下,那么就要特殊处理,因为这些程序都是没有用户空间的,要特殊处理。  
  20.                              
  21.     /* 
  22.      * As per x86, we may deadlock here.  However, since the kernel only 
  23.      * validly references user space from well defined areas of the code, 
  24.      * we can bug out early if this is from code which shouldn't. 
  25.      */  
  26.     if (!down_read_trylock(&mm->mmap_sem)) {  
  27.         if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc))  
  28.             goto no_context;  
  29.         down_read(&mm->mmap_sem);  
  30.     }                                           //down sem,没啥说的。  
  31.   
  32.     fault = __do_page_fault(mm, addr, fsr, tsk);          //下面分析。  
  33.     up_read(&mm->mmap_sem);  
  34.   
  35.     /* 
  36.      * Handle the "normal" case first - VM_FAULT_MAJOR / VM_FAULT_MINOR 
  37.      */  
  38.     if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP | VM_FAULT_BADACCESS))))  
  39.         return 0;                                                          //如果返回值不是上面的值。那么就是MAJOR  MINOR,说明问题解决了,return,如果是,那么还要go on  
  40.   
  41.     /* 
  42.      * If we are in kernel mode at this point, we 
  43.      * have no context to handle this fault with. 
  44.      */  
  45.     if (!user_mode(regs))  
  46.         goto no_context; //如果是内核空间出现了 页异常,并且通过__do_page_fault没有没有解决,那么到on_context  
  47.   
  48.     if (fault & VM_FAULT_OOM) {  
  49.         /* 
  50.          * We ran out of memory, or some other thing 
  51.          * happened to us that made us unable to handle 
  52.          * the page fault gracefully. 
  53.          */  
  54.         printk("VM: killing process %s\n", tsk->comm);  
  55.         do_group_exit(SIGKILL);  
  56.         return 0;  
  57.     }  
  58.     if (fault & VM_FAULT_SIGBUS) {  
  59.         /* 
  60.          * We had some memory, but were unable to 
  61.          * successfully fix up this page fault. 
  62.          */  
  63.         sig = SIGBUS;  
  64.         code = BUS_ADRERR;  
  65.     } else {  
  66.         /* 
  67.          * Something tried to access memory that 
  68.          * isn't in our memory map.. 
  69.          */  
  70.         sig = SIGSEGV;  
  71.         code = fault == VM_FAULT_BADACCESS ?  
  72.             SEGV_ACCERR : SEGV_MAPERR;  
  73.     }                       //这上面的英文描述很清楚了  
  74.   
  75.     __do_user_fault(tsk, addr, fsr, sig, code, regs);    //用户态错误,这个函数什么都不做,就是发个新号,弄死进程  
  76.     return 0;  
  77.   
  78. no_context:  
  79.     __do_kernel_fault(mm, addr, fsr, regs);             //内核错误,这个函数什么都不干,发送OOP:::啊啊啊 啊啊啊,这个警告曾经弄死多少好汉  
  80.     return 0;  
  81. }  

 

 

[cpp] view plaincopyprint?
  1. static int  
  2. __do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,  
  3.         struct task_struct *tsk)  
  4. {  
  5.     struct vm_area_struct *vma;  
  6.     int fault, mask;  
  7.   
  8.     vma = find_vma(mm, addr);    //find_vma函数是从mm结构中找到一个vm_area_struct结构,这个结构的vm_end值 > 参数addr, 但是vm_start可能大于也可能小于,但fing_vma会尽可能找到小于的。即:addr在vma这个区间中。  
  9.     fault = VM_FAULT_BADMAP;  
  10.     if (!vma)                    //如果vma为0,那么想当于所有vma的vm_end地址都 < 参数addr,这个产生异常错误的地址  肯定是无效地址了。 因为根据进程的结构,如上图,所有vm中vm_end的最大值是3G。  
  11.         goto out;  
  12.     if (vma->vm_start > addr)    //如果vm_start>addr,说明这个异常的地址是 图中  的那个 空洞中,那么则可能是在用户态的栈中出错的。则跳去 check_stack  
  13.         goto check_stack;  
  14.   
  15.     /* 
  16.      * Ok, we have a good vm_area for this 
  17.      * memory access, so we can handle it. 
  18.      *///如果上面条件都不是,那么得到的vma结构就包含 addr这个地址。有可能是malloc后出错的。 用户态下的malloc时候,其实并不分配物理空间,只是返回一个虚拟地址,并产生一个vm_area_struct结构,当真正要用到malloc的空间的时候,才会产生异常,在这里得到真正的物理空间。 当然也有可能是其他原因,比如在read only的时候进行write了。  
  19. good_area:  
  20.     if (fsr & (1 << 11)) /* write? */       //fsr从第一段的汇编中得出意义,他是r1.状态。 这里看地址异常时候 是写还是其他。并复制到mask中  
  21.         mask = VM_WRITE;  
  22.     else  
  23.         mask = VM_READ|VM_EXEC|VM_WRITE;  
  24.   
  25.     fault = VM_FAULT_BADACCESS;  
  26.     if (!(vma->vm_flags & mask))  
  27.         goto out;                       //如果是写,但是vma->vm_flag写没有set 1,说明的确是权限的问题,那么就set fault的值,退出。  
  28.   
  29.     /* 
  30.      * If for any reason at all we couldn't handle 
  31.      * the fault, make sure we exit gracefully rather 
  32.      * than endlessly redo the fault. 
  33.      */  
  34. survive:                                          //上面原因都不是,那么就给进程分配一个新的页框。成功返回VM_FAULT_MAJOR(在handle_mm_fault中得到一个页框时候出现了阻塞,进行了睡眠)/VM_FAULT_MINOR(没有睡眠)  
  35.     fault = handle_mm_fault(mm, vma, addr & PAGE_MASK, fsr & (1 << 11));  
  36.     if (unlikely(fault & VM_FAULT_ERROR)) {  
  37.         if (fault & VM_FAULT_OOM)          //返回OOM,没有足够的内存。 到out_of_memory中,sleep一会,retry。  
  38.             goto out_of_memory;  
  39.         else if (fault & VM_FAULT_SIGBUS)  
  40.             return fault;  
  41.         BUG();  
  42.     }  
  43.     if (fault & VM_FAULT_MAJOR)  
  44.         tsk->maj_flt++;  
  45.     else  
  46.         tsk->min_flt++;  
  47.     return fault;  
  48.   
  49. out_of_memory:  
  50.     if (!is_global_init(tsk))  
  51.         goto out;  
  52.   
  53.     /* 
  54.      * If we are out of memory for pid1, sleep for a while and retry 
  55.      */  
  56.     up_read(&mm->mmap_sem);  
  57.     yield();  
  58.     down_read(&mm->mmap_sem);  
  59.     goto survive;  
  60.   
  61. check_stack:  
  62.     if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr))          //检查堆栈,进行expand,扩展原来的栈的vma,然后goto good_area,去得到一个实际的物理地址。  
  63.         goto good_area;  
  64. out:  
  65.     return fault;  
  66. }  


 

进程的用户空间结构:

图上面的堆栈空间个人感觉不对,堆是堆,栈是栈,  准确的说应该是栈吧、、。。堆会在brk()函数中设置的。

这样分析之后基本上2.4和2.5节的内容已经全包含了,下面总结扩展并补充一下下:

  • 当发生页面异常时候,会产生中断,当在用户态时候,会产生,__dabt_usr: 在内核态时__dabt_svc。但他们都会调用到do_DataAbort函数,do_DataAbort会根据中断的寄存器调用相应的处理函数。这里产生页面中断时候,会调用do_page_fault函数。
  • 在do_page_fault函数中,首先会检查中断发生时,是不是在临界区,或者中断中,或者内核线程中。 如果是的话,那么就产生个OOPs。 因为这些地方是不允许异常的。如果异常会产生阻塞,阻塞就会死锁。死锁程序员就会被弄,被弄了程序员就发过来弄linux,所以linux就先弄了程序员,发出OOPs错误。
  • 如果不在临界区中,那么调用__do_page_fault函数。这个函数会先检查 产生异常的地址,是不是进程的已有的线性空间中,即检查vm_area_struct的list中。
  • 如果在的话,检查是不是因为 权限的问题产生的异常,如果是,那么说明应用程序是有问题的,直接弄死他。 如果不是,有可能是写时复制等一些linux机制。调用handle_mm_fault函数,进行分页等。
  • 如果不在vm_area_struc的list中,如果大于所有的vm_area_struct的vm_end,那么说明是错误的地址,也是直接弄死。 如果有小于<vm_end,也小于vm_start,那么说明是在空洞中,应该是栈的问题,去申请更多的栈空间。(为什么<vm_end&&<vm_start就是在栈中,因为malloc等都是事先分配个vm_area_struct结构,当异常时,会找到相应的vm_area_struct结构的。如果找不到,那就是在栈里面了溢出了)。

下面再说handle_mm_fault函数:

  • 他会首先 检查是否已经存在了PTE等映射,如果不在alloc 所有的 PUD,PMD,PTE等,建立映射。然后调用handle_pte_fault函数。注意:由于有些在刚建立的pte,所有pte里面全是0,有些是以前是建立好的,所以里面pte里面不是0,可能有其他值。所以下面还会做判断。
  • handle_pte_fault函数会判断具体缺页的类型,具体分为3类, 会根据这三类调用不同的函数。具体调用的函数,以后再说吧。。。。。
  •                  1.这个页从来没有被访问过,也就是这个pte中全是0,pte_none这个宏返回1
  •                   2.以前访问过这个页,但这个页是非线性磁盘文件的映射,即:dirty位 置1, pte_file返回1
  •                   3. 以前访问过这个页,但内容已经被保存在磁盘上了,即:dirty位 0

 

下图是 understand linux kernel书中的一个图,中文图在中文书中的P378

 

 

 

 

2.6 物理页面的使用和周转

2.8 页面的定期换出

2.9 页面的换入

这三节都是讲的页框的回收和释放内容。

具体代码太难太难,简要介绍原理,尽可能是解释代码吧。

(参考书籍:《深入理解linux内核》17章  《Professional Linux Kernel Architecture》Chapter 18)

 深入理解linux内核》讲的2.6内核,但有点老,有些函数对不上。 《professional linux kernel architecture》是新版(2.6.24)的linux介绍。但是于2.6.39相比也有点对不上。哭

 

 页框回收算法(page frame reclaiming algorithm,PFRA)

 页框回收算法,是回收一些物理内存页。 在两种情况下会触发这个算法,

1, 当进行alloc_page时等申请内存时,但内存中空闲的page已经不够了。就会触发PFRA去回收一些页。得到更多的free的page。

2, 如果仅仅在第1个条件才触发PFRA,那alloc_page函数就会等待很长时间。所以,linux会创建一个kswapd内核线程。会定期的被唤醒去调用PFRA算法,尽量去保证linux有较多的物理内存空间。不会低于high_wmark_pages。

 

那么去回收那些页呢?PFRA会将“很久”不用的页reclaim到硬盘/flash上,然后释放这个页。对于页被reclaim到硬盘/flash的哪个位置?PFRA进行了分类:不可回收页,可交换页,可同步页,可丢弃页。

  1. 不可回收页,包括:本来就是空闲的页,内核空间使用的页,临时锁定的页。内核空间的页linux认为会比较频繁,也比较重要,不会reclaim。
  2. 可同步页。如一些打开的文件,过mmap映射到内存的占用的页。这样的页本来在硬盘中就有备份。所以系统只需要查看这些页是否被修改过,如果被修改过,就将其写到硬盘上,然后reclaim,如果没有被修改过,那么直接reclaim。
  3. 可交换页。这些页是对应于这2种页的。包括:用户进行的malloc的页,或者和其他进程共享的页等。这些页在硬盘上没有对应的区域,所以要想将其写到硬盘上,就要有一个 交换区(swap区),内核会将其写到 这个交换区。然后再reclaim。
  4. 可丢弃页。如:slab机制保留的未使用的页,文件系统目录项等的一些缓存的页。这些页可以直接丢弃。

那么如何判断页是否“很久”没有用呢?有个LRU()算法。

 

 在zone这个结构体中,会有一些list:

[cpp] view plaincopyprint?
  1. /* Fields commonly accessed by the page reclaim scanner */  
  2.     spinlock_t      lru_lock;     
  3.     struct zone_lru {  
  4.         struct list_head list;  
  5.     } lru[NR_LRU_LISTS];  


 用来连接这个zone中的各个不同状态的页。

[cpp] view plaincopyprint?
  1. enum lru_list {  
  2.     LRU_INACTIVE_ANON = LRU_BASE,  
  3.     LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,  
  4.     LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,  
  5.     LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,  
  6.     LRU_UNEVICTABLE,  
  7.     NR_LRU_LISTS  
  8. };  


LRU_INACTIVE_ANON  和LRU_ACTIVE_ANON对应于匿名页,就是上面说的可交换的页。 LRU_INACTIVE_FILE 和 LRU_ACTIVE_FILE对应于可同步页。LRU_UNEVICTABLE应该指不可回收的页。

 

讲解一下上图中相关的一些知识:

首先两个方框表示两个list,一个active,一个inactive。从了在zone的这个list中可以找到active的页,也可以从page这个结构体中检查。当page结构体中的 flag这个成员变量,flag&PG_active ==1时,page对应的页是active的,如flag&PG_active ==0,则是inactive的。

在每个方框中,也有两个部分,Ref0和Ref1。他代表页是否刚被访问过。如:当一个进程访问一个页时候,就会调用mark_page_accessed函数,这个函数将这个页的page结构的flag变量 flag |= PG_reference;这样标志页刚刚被访问过。但linux的kswap进程会扫描所有页,将 flag &= ~PG_reference;标志页有一段时间没有被访问过了。这样如果在Ref0中停留过长时间的话,就会调用shrink_active_list函数将这个页移动到inactive list中。

 

下面介绍回收的流程:

上图就是介绍的两个shrink memory的方法。一个是当alloc_page内存不够时,直接direct page reclaim。 一种是swap daemons。

当然他们shrink的强度是不一样的,direct page reclaim是紧急情况的,必须要回收到的。而swap daemons是尽量回收,保留更多的内存空间。所以在try_to_free_pages在调用shrink_zones函数中会传入个priority,这个priority代表shrink的强度,priority会不断减一,强度越来越大。直到释放了足够的alloc_page的空间。如果priority减到0了,还不能shrink足够的空间。那么就是调用out_of_memory函数,来kill 进程,释放空间。

 

在进行shrink_page_list中,会根据相关的页进行不同的设置。如:

如当shrink  LRU_INACTIVE_ANON  里的page的时候,就会将所有对应这个页的页表(PTE)的present位和dirty位请0.但是其他位不为0. 其他位标志了这个页所在的swap区的位置(此时pte里的值已经不是页表信息了,而是表示这个页保存在磁盘的地址。具体意义可看《深入理解linux内核》的710页,换出页标识符)。

当shrink  LRU_INACTIVE_FILE  里的page的时候,就会将所有对应这个页的页表(PTE)的present位请0,但dirty位置一.由于这些页在磁盘上本来就是有对应的文件的,所以不需要记录他的具体问题,linux可以找到。

所以就有了上面讨论缺页异常时候,读页的那三个函数。和判断。。。。。。吐舌头

 

在上面提到过,将所有对应于该页的pte,由于对应于一个页,可能会有不止一个的PTE映射在这个页上,那么如果通过这个page结构体来找到对应的所有的pte呢?这个机制叫反响映射,在《深入理解linux内核》的回收页框的  674页,反向映射。

 

原创粉丝点击