内核代码阅读(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
相关文章
|
存储 算法 Linux
打破常规,Linux内核新的数据结构上场maple tree(下)
打破常规,Linux内核新的数据结构上场maple tree
|
1月前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
51 4
|
3月前
crash —— 获取内核地址布局、页大小、以及栈布局
crash —— 获取内核地址布局、页大小、以及栈布局
|
测试技术 KVM 开发工具
【OS Pintos】Pintos 内核库基本数据结构 | 运行测试用例 alarm-multiple
【OS Pintos】Pintos 内核库基本数据结构 | 运行测试用例 alarm-multiple
171 0
|
7月前
|
存储 算法 Linux
Linux内核代码中常用的数据结构
Linux内核代码中常用的数据结构
124 0
|
存储 算法 网络协议
Linux内核之旅:揭秘关键的数据结构设计
Linux内核之旅:揭秘关键的数据结构设计
|
存储 Linux 编译器
打破常规,Linux内核新的数据结构上场maple tree(上)
打破常规,Linux内核新的数据结构上场maple tree
|
存储 缓存 Java
解析 Java 数据结构:深入了解映射(Map)的特点与应用
在 Java 编程中,映射(Map)是一种非常重要的数据结构,用于存储键值对(Key-Value pairs),其中每个键都唯一对应一个值。映射在实际应用中有着广泛的应用,如数据库索引、缓存管理、配置
|
存储 Java
数据结构之集合和映射
数据结构之集合和映射
|
Linux API C语言
Linux内核基础数据结构-双链表
链表作为一种基本的数据结构,得益于其简单的结构、优良的性能(双向链表的插入和删除复杂度都是O(1)),被广泛的应用于各种程序设计中。链表一般分为单向链表和双向链表。对于单向链表,其删除和插入的一般复杂度都是O(n),所以,工程上一般很少使用,下面介绍的所有链表都是双向链表。
113 0