:Linux 内存管理 重要结构体

系统 1731 0
虚拟内存地址与实际内存地址之间的关系,是如此转换的,逻辑地址-->线性地址-->物理地址。也是从分段单元到分页单元的转换。在 linux中,用户程序所使用的地址与硬件使用的物理地址是不等同的。虚拟内存引入一个间接层,它使得许多操作成为可能。在引入虚拟内存这个概念和方法 后,在系统中运行的程序可以分配比物理内存更多的内存。而linux的地址有分下面几个类型:

用户虚拟地址:用户空间所能看到的常规地址
物 理地址:在处理器和系统内存之间使用
总线地址:在外围总线和内存之间使用
内核逻辑地址:组成内核的常规地址空间,该地址映射了部分或者全 部内存,并经常被视为物理地址。
内核虚拟地址:其与物理地址的映射不必是线性的和一对一的,所有的逻辑地址都是内核虚拟地址,但是有些内核地址不 是逻辑地址。

UMA,一致内存访问体系结构,此结构通常用pg_data_t来引用,系统中每个节点链接到一个以NULL结尾的 pgdat_list链表中,而其中的每个节点利用pg_data_tnode_next字段链接到下一个节点。

对大型机来说,内存会分 成很多簇,每个簇都被认为是一个节点。struct pg_data_t体现此概念,系统中的每个节点链接到一个以NULL结尾的pgdat_list脸表中,而其中的每个节点利用 pg_data_tnode_next连接到下一个节点。内存中,每个节点被分成很多的成为管理区的块,而用于表示内存中的某个范围,一个管理区由 sruct zone_struct描述,并被定义为zone_t,且每个管理区的类型都是ZONE_DMA(低端范围的物理内存,内存首部 16MB),ZONE_NORMAL(由内核直接映射到线性地址空间的较高部分,16MB--896MB)或者ZONE_HIGHMEM(系统中预留的可 用内存空间,不被内核直接映射,896MB--末尾)中的一种。而三者中,ZONE_NORMAL是影响系统性能最重要的管理区。

系统的 内存划分成大小确定的许多块,这些块用struct page结构体来描述,所有的结构都存储在一个全局mem_map数组中,通常存放在ZONE_NORMAL的首部,或者就在小内存系统中装入内核映像而 预留的区域之后。就因为ZONE_NORMAL大小有限,所以linux才会提出高端内存这个概念。
linux中,运用page结构体来保存内核 需要知道的所有物理内存信息,对系统中每个物理页,都有一个page结构体相对应。

内存中的每个节点都由pa_data_t描述,而此则 由struct pglist_data定义而来,在分配一个页面时,linux 采用节点局部分配的策略,从最靠近运行中的cpu的节点分配内存,由于进程往往是在同一个cpu上运行,因此从当前节点得到的内存很有可能被用到,每个管 理区由一个struct zone_t描述,此结构体用于跟踪页面使用情况统计数,空闲区域信息和锁信息等,在<linux/mmzone.h>文件中声明.

以 下这三个结构体互相结合,彼此指向形成了一个关于内存分配的树型结构。

typedef struct pglist_data {
        zone_t node_zones[MAX_NR_ZONES];该节点所在管理区为HIGHMEM、NORMAL、DAM三者之一。
        zonelist_t node_zonelists[NR_GFPINDEX];按照分配时的管理区顺序排列。通过page_alloc.c中的 bulid)zaonelists()建立顺序。
        struct page *node_mem_map;struct page数组中的第一个页面被放置在全局mem_map数组中。
        unsigned long *valid_addr_bitmap;描述内存节点中空洞的位图,因为并没有实际的内存空间存在。
        struct bootmem_data *bdata;指向内存引导程序
        unsigned long node_start_paddr;节点的起始物理地址
        unsigned long node_start_mapnr;
它 指出该节点在全局mem_map中的页面偏移,通过计算mem_map与该节点的局部mem_map中成为lmem_map之间的页面数,得到页面偏移。
        unsigned long node_size;管理区中的页面总数
        int node_id;节点的ID号(NID),从0开始
        struct pglist_data *node_next;指向下一个节点。
} pg_data_t;

所有的节点都有一个pgdat_list的链表维护,这些节点 都放在该链表中。

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];空闲区域位图。
        char                        *name;管理区的字符串名字“DMA”“Normal”“HighMem”
        unsigned long                size;该管理区的大小,以页面数计算
        struct pglist_data        *zone_pgdat;指向父pg_data_t
        unsigned long                zone_start_paddr;节点的起始物理地址
        unsigned long                zone_start_mapnr;节点在全局mem_map中的页面偏移
        struct page                *zone_mem_map;设计的管理区在全局mem_map中的第一页
} zone_t;

unsigned long                pages_min, pages_low, pages_high;管理区极值
当空闲页面数达到pages_low时,伙伴分配器就会唤醒kswapd(守护程序)释放页面,默认值是 pages_low的两倍。
当达到pages_min时,分配器会以同步方式启动kswapd。
kswapd被唤醒并开始释放页面后,在 high个页面被释放以前,是不会认为该管理区已经“平衡”的,当到达极值后,kswapd再次睡眠。

struct page {
    unsigned long flags;//page状态的标志信息。
    atomic_t _count;//count: page的访问计数,为0说明page空闲,大于0时,page被一个或多个进程真正使用或者kernel用于在等待I/O。    
    union {
        atomic_t _mapcount;
        struct {        /* SLUB */
            u16 inuse;
            u16 objects;
        };
    };
    union {
        struct {
        unsigned long private;
//private:这个保存了一些和mapping(文件mapping到内存)有关的一些特定的信息。
// 如果page是一个buffer page,则它就保存了一个指向buffer_head的指针。
        struct address_space *mapping;
//当文件或设备需要内存映射,文件或设备的inode对象有一个address_space类 型的成员。如果page属于这个文件或设备,mapping将指向inode中这个成员。如果page不属于任何文件或设备,但是mapping被设置 了,则mapping指向了一个address_space类型的swapper_space对象,则page用于管理交换地址空间 (swap address space)了。
        };
#if NR_CPUS >= CONFIG_SPLIT_PTLOCK_CPUS
        spinlock_t ptl;
#endif
        struct kmem_cache *slab;    /* SLUB: Pointer to slab */
        struct page *first_page;    /* Compound tail pages */
    };
    union {
        pgoff_t index;
//index:这个成员根据page的使用的目的有2种可能的含义。
// 第一种情况:如果page是file mapping的一部分,它指明在文件中的偏移。如果page是交换缓存,则它指明在address_space所声明的对象: swapper_space(交换地址空间)中的偏移。
//第二种情况:如果这个page是一个特殊的进程将要释放的一个page块,则这是一个 将要释放的page块的序列值,这个值在__free_page_ok()函数中设置。
        void *freelist;        /* SLUB: freelist req. slab lock */
    };
    struct list_head lru;//lru: page交换调度策略使用。page可能被调度到active_list或者inactive_list队列里。就是使用lru这个list_head。
#if defined(WANT_PAGE_VIRTUAL)
    void *virtual;//不再用于将high memory的映射到ZONE_NORMAL区域的作用了,除了一些其他的体系结构会用到外。
#endif /* WANT_PAGE_VIRTUAL */
#ifdef CONFIG_CGROUP_MEM_RES_CTLR
    unsigned long page_cgroup;
#endif
};

linux采用了一种与具体体系结构无关代码 的三层页表机制来完成内存管理,即使底层的体系结构并不支持这个概念。每一个进程都有一个指向其自己的PGD指针 (mm_struct->pgd),这就是一个物理页面号,其中包含了一个pgd_t类型的数组,进程页表的载入是通过把这个结构体复制到cr3寄 存器完成。PGD表中每个有效的项都指向一个页面号,此页面号包含一个pmd_t类型的PMD项数组,每一个pmd_t又指向另外的页面号,这些页面号由 很多个pte_t类型的PTE构成,而pte_t最终指向包含真正用户数组的页面。

整体结构是这样的:PGD进程内偏移量PMD页面号内 偏移量PTE页面号内偏移量数据号内偏移量。这些结构各自拥有自己的偏移量(offset)在寻址过程中,不断的通过基地址和偏移量来找到下一个相关结构 体最后寻到带有用户数据的页面号。

由于所有在vmlinuz中的普通内核代码都编译成以PAGE_OFFSET+1MB为起始地址,实际 上系统将内核装载到以第一个1MB(0x00100000)为起始地址,实际上系统内核装载到以第一个1MB为起始地址的物理空间中,第一个1MB的地址 常在以些设备用作和BISO进行通讯的地方自行跳过。该文件中的引导初始化代码总是把虚拟地址减去__PAGE_OFFSET,从而获得以1M为起始地址 的物理地址。在开启换页单元以前,必须首先建立相应的页表映射,从而将8MB的物理空间转换为虚拟地址PAGE_OFFSET.

而物理空 间和struct page之间的映射概念也很重要,系统将内核映像装载到1MB物理地址起始位置,这个物理地址就是虚拟地址 PAGE_OFFSET+0x00100000.物理内存为内核映像预留了8MB的虚拟空间,耗个空间可以被两个PGD所访问到,linux为 ZONE_DMA预留了16MB的内存空间,所以真正被内核分配使用的内存起始位置应在0xc1000000,这个位置久违全局量mem_map所在的位 置。通过把物理地址作为mem_map里的一个下标,从而将其转换成对应的struct page。通过把物理地址位右移PAGE_SHIFT位,从而将右移后的物理地址作为物理地址0开始的页面号PFN,同样也是mem_map数组的一个下 标。  
在linux中,为了可以更快的运行程序,从内存中装入数据,还设置了高速缓存管理。其实在,基本上都存在一级缓存和二级缓存,后者更大一点,但是速度要 慢的一些。而地址映射高级缓存行的方式因为体系结构的不同可能会有差别,但是基本会是如下三个方法:
1直接映射,每个内存块只与唯一一个可能的高 速缓存相映射
2关联映射,任意的内存块可以与任意的高速缓存行相对应
3关联集映射,任意的内存块可以与任意的高速缓存行相映射,但是只能 在一个可用行的子集里面映射

虚拟内存的好处之一就是可以让进程都有属于自己的虚拟地址空间,这种虚拟地址空间可以通过操作系统映射到物理 内存。对于进程,它通过一个页表项指针指向一个只读的全局全零页面,以实现在进程的线性地址空间中保留空间。一旦进程对该页面进行写操作,就会发生缺页中 断,这时系统会分配一个新的全零页面,并由一个页表指定,且标记为可写,新的全零页面看起来和原来的全局全零页面完全一样。

地址空间由 sruct mm_struct结构体来管理,而它到struct page之间,还需要有几个步骤,它们之间的关系是通过如下结构体相连,并且这些结构体构成了一个关于文件的各个方面的描述:

struct mm_struct,struct vm_area_struct,struct _file,struct vm_operations_struct,struct dentry,struct inode,struct address_space,struct address_space_operations,struct page.

描述进程地址 空间,一个进程只有一个mm_struct结构,且该结构在进程用户空间中由多个线程共享,线程正是通过任务链表里的任务是否指向同一个 mm_struct来判定的。主要字段:
struct mm_struct{
    struct vm_area_struct * mmap;地址空间中所有VMA的链表首部
    struct vm_area_struct * mmap_avl;
    rb_root_t mm_rb;
    struct vm_area_struct * mmap_cache;最后一次通过find_vma()找到的VMA存放处
    pgd_t * pgd;全局目录表的起始地址
    atomic_t mm_users;访问用户空间部分的用户计数值
    atomic_t mm_count;匿名用户计数值
    int map_count;正在被使用中的vma数量
    struct semaphore mmap_sem;读写保护锁,长期有效
    spinlock_t page_table_lock;用于保护mm_struct中大部分字段
    struct list_head mmlist;所有的mm_struct结构通过它链接在一起
    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; (resident set -->rss 某一时刻,一般一个进程虚存空间不会完全在内存中,一般驻留在内存中的为其虚存空间的子集,rss描述有多少页驻留内存中)
驻留集的大小是该进程常驻内存的页面数,不包括全局零页面,进程 中所有vma区域的内存空间总和,内存中被锁住的常驻页面数。
    unsigned long def_flags;VM_LOCKED用于指定在默认情况下将来所有的映射是上锁还是未锁。
    unsigned long cpu_vm_mask;
    unsigned long swap_cnt;
    unsigned long swap_address;当换出整个进程时,页换出进程记录最后一次被换出的地址
    mm_context_t context;
}

与 内存区域描述器相关的函 数:mm_init(),allocate_mm(),mm_alloc(),exit_mmap(),copy_mm(),free_mm().

系 统中第一个mm_struct通过init_mm()初始化,以后的mm_struct都会通过复制它来进行设置,所以第一个要手动静态设置,这是一个模 板:
mm_rb:RB_ROOT,
pgd:swapper_pg_dir,
mm_users:ATOMIC_INIT(2),
mm_count:ATOMIC_INIT(1),
mmap_sem:__RWSEM_INITIALLZER(name,mmap_sem),
page_table_lock:SPIN_LOCK_UNLOCKED,
mmlist:LIST_HEAD_INIT(name.mmlist),

而 系统用于分配mm_struct结构的函数有两个:Allocate_mm()是一个预处理宏,从slab allocator中分配mm,而mm_alloc()从slab中分配,然后init.

源代码主要语句:

static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
函数为 给定的进程复制一份mm,仅在创建一个新进程后且需要它自己的mm时由do_fork调用。
{
    struct mm_struct * mm, *oldmm;
    int retval;
    tsk->min_flt = tsk->maj_flt = 0;初始化与内存管理相关的task_struct字段,tsk是一个进程控制块。
    tsk->nvcsw = tsk->nivcsw = 0;
    tsk->mm = NULL;
    tsk->active_mm = NULL;
    oldmm = current->mm;借用当前运行进程的mm来复制。
    if (clone_flags & CLONE_VM) {如果设置clone_vm,则子进程将与父进程共享mm
        atomic_inc(&oldmm->mm_users);用户数量加1,以便于不会过早销毁
        mm = oldmm;
        goto good_mm;
    }
    mm = dup_mm(tsk);//链接过程,以及文件信息的设置。
good_mm:
    mm->token_priority = 0;
    mm->last_interval = 0;
    tsk->mm = mm;
    tsk->active_mm = mm;
    return 0;
}

static struct mm_struct * mm_init(struct mm_struct * mm, struct task_struct *p)
{
    atomic_set(&mm->mm_users, 1);用户数为1
    atomic_set(&mm->mm_count, 1);mm引用数为1
    init_rwsem(&mm->mmap_sem);初始化保护vma链表的信号量
    INIT_LIST_HEAD(&mm->mmlist);初始化mm链表
    mm->flags = (current->mm) ? current->mm->flags : MMF_DUMP_FILTER_DEFAULT;设置标识位
    mm->core_waiters = 0;
    mm->nr_ptes = 0;
    set_mm_counter(mm, file_rss, 0);
    set_mm_counter(mm, anon_rss, 0);
    spin_lock_init(&mm->page_table_lock);
    rwlock_init(&mm->ioctx_list_lock);
    mm->ioctx_list = NULL;
    mm->free_area_cache = TASK_UNMAPPED_BASE;
    mm->cached_hole_size = ~0UL;
    mm_init_owner(mm, p);//void mm_init_owner(struct mm_struct *mm, struct task_struct *p){mm->owner = p;}
    if (likely(!mm_alloc_pgd(mm))) {
        mm->def_flags = 0;
        return mm;
    }
    free_mm(mm);
    return NULL;

#define allocate_mm()    (kmem_cache_alloc(mm_cachep, GFP_KERNEL))
#define free_mm(mm)    (kmem_cache_free(mm_cachep, (mm)))

struct mm_struct * mm_alloc(void)
{
    struct mm_struct * mm;
    mm = allocate_mm();//kmem_cache_alloc(mm_cachep, GFP_KERNEL)从slab分配器分配一个mm_struct
    if (mm) {
        memset(mm, 0, sizeof(*mm));字段归零
        mm = mm_init(mm, current);//初始化
    }
    return mm;
}

进程的地址空间很少用满,而一般仅仅用到其中一些分离的区域,这个区域由结构体 vm_area_struct来表示,区域之间是不会交叉的,各自代表一个有着相同属性和用途的地址集合。一个进程所有被映射的区域都可以在/proc /PID/maps里面看到。

当一个文件被映射到内存,则可以通过vm_file字段得到struct_file.而这个字段又指向 struct inode,索引节点用于找到struct address_space,而在后者中,包含与文件有关的所有信息,包括一系列指向与文件系统相关操作函数的指针。

struct vm_area_struct {
    struct mm_struct * vm_mm;所述的mm_struct
    unsigned long  vm_start;这个区域的起始地址
    unsigned long vm_end;这个区域的结束地址
    struct vm_area_struct * vm_next;一个地址空间中的所有vma都按地址空间次序通过该字段简单的链接在一起。
    pgrot t_vm_page_prot;对应的每个pte里的保护标志位
    unsigned long vm_flags;这个vma的保护标志位和属性标志位
    short vm_avl_height;
    rb_node_ vm_rb;所有的vma都存储在一个红黑树上以加快查找速度
    struct vm_area_struct * vm_avl_left;
    struct vm_area_struct * vm_avl_rigth;  
    struct vm_area_struct * vm_next_share;把文件映射而来的vma共享区域链接在一起
    struct vm_area_struct ** vm_pprev_share;vm_next_share的辅助指针
    struct vm_operations_struct * vm_ops;包含指向与磁盘同步操作时所需要函数的指针。此字段包含有指向open(),close(),nopage()的函数指针
    unsigned long vm_pgoff;在已被映射文件里对齐页面的偏移
    struct file * vm_file;指向被映射的文件的指针
    unsigned long vm_raend;预读窗口的结束地址,在发生错误时,一些额外的页面将被收回,这个值决定了这些额外页面的个数。
    void * vm_private_data;一些设备驱动私有数据的存储,与内存管理无关。
}

pgrot t_vm_page_prot;对应的每个pte里的保护标志位:
_PAGE_PRESENT页面常驻内存,不进行换出操作
_PAGE_PROTNONE 页面常驻内存,但不可访问
_PAGE_RW页面可能被写入时设置该位
_PAGE_USER页面可以被用户空间访问时设置该位
_PAGE_DIRTY 页面被写入时设置该位
_PAGE_ACCESSED页面被访问时设置该位

unsigned long vm_flags;这个vma的保护标志位和属性标志位

保护标志位
VM_READ页面可能被读取
VM_WRITE页面可 能被写入
VM_EXEC页面可能被执行
VM_SHARED页面可能被共享
VM_DONTCOPY vma不能在fork时被复制
VM_DONTEXPAND 防止一个区域被重新设置大小。标志位没有被使用过

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);


struct address_space {定义的文件信息
    struct inode        *host;
    struct radix_tree_root    page_tree;
    rwlock_t        tree_lock;
    unsigned int        i_mmap_writable;
    struct prio_tree_root    i_mmap;
    struct list_head    i_mmap_nonlinear;
    spinlock_t        i_mmap_lock;
    unsigned int        truncate_count;
    unsigned long        nrpages;在地址空间中正被使用且常驻内存的页面数
    pgoff_t            writeback_index;
    const struct address_space_operations *a_ops;操纵文件系统的函数结构。每一个文件系统都提供其自身的operations.
    unsigned long        flags;
    struct backing_dev_info *backing_dev_info;
    spinlock_t        private_lock;
    struct list_head    private_list;
    struct address_space    *assoc_mapping;    


struct address_space_operations {定义的函数方法
    int (*writepage)(struct page *page, struct writeback_control *wbc);
    int (*readpage)(struct file *, struct page *);
    void (*sync_page)(struct page *);
    int (*writepages)(struct address_space *, struct writeback_control *);
    int (*set_page_dirty)(struct page *page);
    int (*readpages)(struct file *filp, struct address_space *mapping,struct list_head *pages, unsigned nr_pages);
    int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
    int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
    int (*write_begin)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned flags,struct page **pagep, void **fsdata);
    int (*write_end)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned copied,struct page *page, void *fsdata);
    sector_t (*bmap)(struct address_space *, sector_t);
    void (*invalidatepage) (struct page *, unsigned long);
    int (*releasepage) (struct page *, gfp_t);
    ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,loff_t offset, unsigned long nr_segs);
    int (*get_xip_mem)(struct address_space *, pgoff_t, int,void **, unsigned long *);
    int (*migratepage) (struct address_space *,struct page *, struct page *);
    int (*launder_page) (struct page *);
};

:Linux 内存管理 重要结构体


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论