内核代码阅读(4) - 页面映射的数据结构

页式映射的数据结构

物理内存 - PGD,PMD,PT的数据结构

#if CONFIG_X86_PAE
    typedef struct { unsigned long pte_low, pte_high; } pte_t;
    typedef struct { unsigned long long pmd; } pmd_t;
    typedef struct { unsigned long long pgd; } pgd_t;
    #define pte_val(x)    ((x).pte_low | ((unsigned long long)(x).pte_high << 32))
    #else
    typedef struct { unsigned long pte_low; } pte_t;
    typedef struct { unsigned long pmd; } pmd_t;
    typedef struct { unsigned long pgd; } pgd_t;
    #define pte_val(x)    ((x).pte_low)
    #endif
    #define PTE_MASK    PAGE_MASK
    
    typedef struct { unsigned long pgprot; } pgprot_t;
    
    #define pmd_val(x)    ((x).pmd)
    #define pgd_val(x)    ((x).pgd)
    #define pgprot_val(x)    ((x).pgprot)
内核代码中对pmd_t, pgd_t, pte_t的定义只是用struct包装了一层unsigned long,目的是为了进行类型检查。
pgprot_t:
    由于pte_t指向一个页面的地址,而页面是4K对齐的,所以pte_t的低12位是0. 这12位被用来描述所指向页面的9种属性。
#define _PAGE_PRESENT    0x001
    #define _PAGE_RW    0x002
    #define _PAGE_USER    0x004
    #define _PAGE_PWT    0x008
    #define _PAGE_PCD    0x010
    #define _PAGE_ACCESSED    0x020
    #define _PAGE_DIRTY    0x040
    #define _PAGE_PSE    0x080    /* 4 MB (or 2MB) page, Pentium+, if present.. */
    #define _PAGE_GLOBAL    0x100    /* Global TLB entry PPro+ */
    
    #define _PAGE_PROTNONE    0x080    /* If not present */
实际中pgprot_t的值总是小于0x1000
pte_t的值总是大于0x1000
两者结合起来就是内存中要存储的pte_t的值。
#define __mk_pte(page_nr,pgprot) __pte(((page_nr) << PAGE_SHIFT) | pgprot_val(pgprot))

物理内存 - 全局变量mem_map

一个连续的大数组struct page mem_map[N];每一个page代表一个页面。
而一个pte对于MMU来说是一个页面的高20位地址,而对于kernel来说一个一个页面的编号,正好是mem_map的下标。
#define pte_page(x)    (mem_map+((unsigned long)(((x).pte_low >> PAGE_SHIFT))))
page结构:
typedef struct page {
        struct list_head list;
        struct address_space *mapping;
        unsigned long index;
        struct page *next_hash;
        atomic_t count;
        unsigned long flags;    /* atomic flags, some possibly updated asynchronously */
        struct list_head lru;
        unsigned long age;
        wait_queue_head_t wait;
        struct page **pprev_hash;
        struct buffer_head * buffers;
        void *virtual; /* non-NULL if kmapped */
        struct zone_struct *zone;
    } mem_map_t;
各个变量的顺序是由讲究的,尽力使联系紧密的在CPU的同一个cache line中。

物理内存 - ZONE

系统所有的page数据结构是最低层描述物理内存的结构了,做为内存分配时候的批发仓库。
这些page结构被分成了ZONE。
ZONE_DMA:1) 是磁盘IO所必须的,如果系统把page分配光了,就不能进行IO了。2) DMA的操作不经过CPU,也就是不经过MMU,没有内存映射机制,所以要单独管理。3) DMA的内存需要连续的物理页面。
typedef struct zone_struct {
    spinlock_t        lock;
    unsigned long        offset;
    unsigned long        free_pages;
    unsigned long        inactive_clean_pages;
    unsigned long        inactive_dirty_pages;
    unsigned long        pages_min, pages_low, pages_high;
    struct list_head    inactive_clean_list;
    free_area_t        free_area[MAX_ORDER]; // MAX_ORDER = 10
     
    char            *name;
    unsigned long        size;
     
    struct pglist_data    *zone_pgdat;
    unsigned long        zone_start_paddr;
    unsigned long        zone_start_mapnr;
    struct page        *zone_mem_map;
    } zone_t;
    #define ZONE_DMA        0
    #define ZONE_NORMAL        1
    #define ZONE_HIGHMEM        2
    #define MAX_NR_ZONES        3
free_area_t就是有名的buddy算法的数据结构。
offset 是在mem_map的偏移。表示该zone_t管辖的页面的起始下标。一个页面被一个zone管辖后就不会改变了。
free_area_struct的free_list是一个双向链表的挂钩。而page结构中第一个成员struct list_head list就是通过free_list挂入了一个free_area。
typedef struct free_area_struct {
        struct list_head    free_list;
            unsigned int        *map;
    } free_area_t;

物理内存 - NUMA非均质存储结构

质地相同的内存称为一个node。一个node的内存是独立的,由独立的mem_map数组,分配连续物理页面的时候不跨node。
typedef struct pglist_data {
            zone_t node_zones[MAX_NR_ZONES]; //当前节点的最多3个的‘管理区’。相反,在zone_t中的zone_pgdat指向‘管理区’所属于的node
        zonelist_t node_zonelists[NR_GFPINDEX]; //分配策略
        struct page *node_mem_map; //指向当前节点的page数组
        unsigned long *valid_addr_bitmap;
        struct bootmem_data *bdata;
        unsigned long node_start_paddr;
        unsigned long node_start_mapnr;
        unsigned long node_size;
        int node_id;
        struct pglist_data *node_next;
    } pg_data_t;

分配策略:一个zonelist_t代表一个分配策略,最多有NR_GFPINDEX256个策略,分配内存时候要指定分配的策略。

typedef struct zonelist_struct {
            zone_t * zones [MAX_NR_ZONES+1]; // NULL delimited
        int gfp_mask;
    } zonelist_t;
每个zonelist_t代表着一个分配策略。zone_t指向一个管理区,可以跨node。
分配内存时候根据指定的策略从node_zonelist选出一个策略,然后从zonelist_t中的zones从下标0开始的‘管理区’开始分配。
比如:DMA内存分配。到当前节点找到分配DMA策略的zonelist_t,然后从第0个‘管理区’开始分配,这个管理区有可能是一个指向本地DMA内存的zone。
    如果分配失败,就从第1个‘管理区’分配,这个管理区有可能代表全局的DMA内存的管理结构zone。

虚拟内存 - vma

vma是从进程的角度。
struct vm_area_struct {
    struct mm_struct * vm_mm;    /* VM area parameters */
    unsigned long vm_start;
    unsigned long vm_end;
    /* linked list of VM areas per task, sorted by address */
    struct vm_area_struct *vm_next;
    pgprot_t vm_page_prot;
    unsigned long vm_flags;
    /* AVL tree of VM areas per task, sorted by address */
    short vm_avl_height;
    struct vm_area_struct * vm_avl_left;
    struct vm_area_struct * vm_avl_right;
    /* For areas with an address space and backing store,
     * one of the address_space->i_mmap{,shared} lists,
     * for shm areas, the list of attaches, otherwise unused.
     */
    struct vm_area_struct *vm_next_share;
    struct vm_area_struct **vm_pprev_share;
    struct vm_operations_struct * vm_ops;
    unsigned long vm_pgoff;        /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */
    struct file * vm_file;
    unsigned long vm_raend;
    void * vm_private_data;        /* was vm_pte (shared mem) */
    };
描述一个进程中一段连续的虚拟地址空间,且属性相同。
vm_next以链表形式串起来,按照vm_start升序排列。vm_avl_left, vm_avl_right以平衡树形式串起来。
vm_next_share, vm_pprev_share, vm_file和文件相关。
vm_ops是回调
struct vm_operations_struct {
    void (*open)(struct vm_area_struct * area);
    void (*close)(struct vm_area_struct * area);
    struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int write_access);
    };
vm_mm是这个vma所属于的mm_struct结构:
struct mm_struct {
    struct vm_area_struct * mmap;        /* list of VMAs */
    struct vm_area_struct * mmap_avl;    /* tree of VMAs */
    struct vm_area_struct * mmap_cache;    /* last find_vma result */ 35%的命中率
    pgd_t * pgd;
    atomic_t mm_users;            /* How many users with user space? */
    atomic_t mm_count;            /* How many references to "struct mm_struct" (users count as 1) */
    int map_count;                /* number of VMAs */
    struct semaphore mmap_sem;
    spinlock_t page_table_lock;
    
    struct list_head mmlist;        /* List of all active mm's */
    
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long start_brk, brk, start_stack;
    unsigned long arg_start, arg_end, env_start, env_end;
    unsigned long rss, total_vm, locked_vm;
    unsigned long def_flags;
    unsigned long cpu_vm_mask;
    unsigned long swap_cnt;    /* number of pages to swap on next pass */
    unsigned long swap_address;
    
    /* Architecture-specific MM context */
    mm_context_t context;
    };
一个进程一个mm_struct,描述一个进程所用内存的细节。
vma是按照vm_start从小到大排序。vma的查找插入操作在mmap.c中。比较特殊的是插入操作,用一个指针就搞定了往list中插入节点。
pprev = &mm->mmap; //2级指针,记录要插入位置的前面一个vma中的next的地址,妙!
    while (*pprev && (*pprev)->vm_start <= vmp->vm_start)
        pprev = &(*pprev)->vm_next;
        vmp->vm_next = *pprev;
    *pprev = vmp;
当mmap中的vma个数大于32时启用avl
上一篇:内核代码阅读(25) - 强制调度


下一篇:RabbitMQ DAG启动图解