美文网首页
Linux中物理内存管理

Linux中物理内存管理

作者: 圣村的希望 | 来源:发表于2020-12-14 00:27 被阅读0次
    1. Linux中内存模型:平坦、非连续和稀疏模型

         Linux中的内存模型说的是站在cpu的角度,物理内存的分布情况。

    • 平坦模型:从任意一个进程的角度看,在其访问物理内存的时候,物理地址空间是连续的,没有空洞的。这种内存模型下,物理内存可以看成是由一个连续的page frame构成的,每一个page frame对应有一个struct page实例,这样物理内存可以看成是由page数组组成的,可以用一个mem_map数组表示。这样page frame number和mem_map数组下标是线性的。
      Linux内存模型-平坦内存模型.jpeg
    • 非连续内存模型:cpu在访问物理内存的时候不是连续,里面是存在空洞的。他可以将每个由空洞隔开的物理内存分为多个节点,每个节点内的物理内存是连续的。这里需要区分非连续内存模型和NUMA的关系,NUMA是Linux内存架构中的一种,他是memory和进程之间的关系。但是NUMA和非连续内存模型在形式上很相像。
      Linux内存模型-非连续内存模型.jpeg
    • 稀疏模型:这个模型自己没有太理解好,给自己后续的成长空间
    2、Linux内存架构中的node、zone和page

          Linux内存架构分为UMA(统一内存访问)和NUMA(非统一内存访问),UMA可以看成是一个节点的NUMA。NUMA是因为物理内存组织是分布式的,每个cpu有自己的本地内存,可以快速访问本地内存,可以通过总线进行访问其它cpu的本地内存。

    UMA和NUMA关系
          node: 在NUMA架构中,是先划分为不同的节点node,然后每个节点又划分为不同的内存域,每个内存域由很多page构成。节点node用struct pglist_data结构表示。
    typedef struct pglist_data {
         int nr_zones;//内存域的个数,一般就是DMA、Normal和Highmem
         struct zone node_zones[MAX_NR_ZONES];//当前节点的内存域
         struct zonelist node_zonelists[MAX_ZONELIST];//备用内存域链表
    
         unsigned long node_size;//当前node中有多少page frame
         struct page *node_mem_map;//当前node中的所有page构成的数组
    
         int node_id;//node id
         unsigned long node_start_paddr;//node的起始物理地址
         struct pglist_data *node_next;//指向下一个node的
    
         spinlock_t lru_lock;
         ...
    } pg_data_t; 
    

          zone:硬件的限制,内核对不同的page frame采用不同的处理方法,将相同属性的page frame归到一个zone中,主要分为DMA、Normal和Highmem,切记zone是对物理地址的划分,不是对虚拟地址的划分。

    • DMA可以直接在内存和外设之间进行数据的读写,不需要cpu的直接参与。由于硬件的限制,DMA并不是可以访问所有的内存,实际只可以访问16M一下的内存。
    • Highmem:适用于要访问的物理内存地址空间大于虚拟地址空间的,不能直接建立直接映射场景的高端内存。
    • normal:除开dma和highmem的物理内存。
    struct zone {
         spinlock_t         lock;
         unsigned long      spanned_pages;//zone含有的page frame数目
         unsigned long      present_pages; //zone中抛开内存空洞page frame的数目
         unsigned long      nr_reserved_highatomic;//保留内存    
         atomic_long_t      managed_pages;//buddy管理的page frame数目
         struct free_area   free_area[MAX_ORDER];//空闲链表组成的,按不同连续page frame规格组成
         unsigned long      _watermark[NR_WMARK];
         long               lowmem_reserve[MAX_NR_ZONES];
         atomic_long_t      vm_stat[NR_VM_ZONE_STAT_ITEMS];
         unsigned long      zone_start_pfn;//起始物理页面号
         struct pglist_data *zone_pgdat;
         struct page        *zone_mem_map;
         ...    
    } 
    

          page:在Linux中物理内存按每页4KB的大小进行划分(也有大页4MB的划分),每个page frame都有一个struct page的结构来表示。每个struct page结构体占用32字节,也是依据体系的。

    struct page {
        unsigned long flags;//表示page frame的状态和属性
        atomic_t count;  //引用计数
        atomic_t _mapcount; //被映射的个数,就是在page table中有多少个entry含有这个page frame
        struct list_head lru;//page frame根据页的活跃程度分别挂在active_list或inactive_list双向链表中,lru就是指向所在链表中前后节点的指针
        struct address_space *mapping;//如果当前page frame属于某个文件的话,则mapping指向文件inode 对应的address space
        unsigned long index;         
        ...  
    } 
    

    申请物理内存:实际物理内存大小 + vm_struct(虚拟地址空间管理结构) + struct page实例。

    3.Linux中的内存分配:buddy和slab分配器

         在Linux中内存分配的基本单位是page frame页,对应的分配器主要是buddy伙伴分配器和slab。在说这两个分配器之前,可以先说下最原始的分配模型:空闲链表和内存池。

    • 空闲链表:就是将内存中的所有空闲内存块用链表的形式链起来free list。在进行内存分配时,扫描free list分配一个空闲的块给当前进程。但是这样是很容易产生外部碎片的。
    • 内存池:将一块大内存分成很多小内存,不同的内存又按照不同的尺寸分成大小相同的块。,同一内存池中的空闲内存块按照free list链接起来。

         buddy伙伴分配器就是基于内存池的思想建立起来,他是在内存释放的时候,会先去检查物理上相邻的左右page frame,如果相邻的page frame也是空闲的就会合成一个更大的空闲内存。
         每个内存域都关联了一个staruct zone实例,其中保存了用于管理伙伴数据的主要数组。

    struct zone {
        /* free areas of different sizes */
        struct free_area    free_area[MAX_ORDER];
        ...
    }
    
    struct free_area {
        struct list_head free_list[MIGRATE_TYPES];//空闲链表,并且按不同的类型进行的区分
        unsigned long nr_free;//当前内存区中空闲内存块的数目
    };
    
    • free_area:这个是当前内存域的空闲页链表,由buddy进行管理。他是按照不同的连续页框的内存规格进行链表相连。MAX_ORDER=11,分别表示一次分配了多少个连续的page frame。
    • nr_free:当前内存区中的空闲块的数目。
    • free_list:为了方便内存的回收,buddy在分配的时候,依据页的移动类型进行细分,方便内存的回收。可移动、可回收和不可移动等类型。


      伙伴系统组织关系
    5.slab内存分配

         buddy内存分配的单位是page frame的,他的大小是4KB。对于小于4KB的小内存分配来说也用buddy的话会产生很多内部碎片,内存分配使用的效率不高。这就诞生了slab分配器的原因。slab分配器是基于对象进行管理的,相同类型的对象归位一类,每当要分配该类型对象的时候直接分配,这样避免了内部碎片。


    slab分配器数据结构
    slab数据结构组织形式
    struct kmem_cache {
        struct array_cache *array[NR_CPUS];//per_cpu数据,记录了本地高速缓存的信息,也是用于跟踪最近释放的对象,每次分配和释放都要直接访问它。
        unsigned int limit;//本地高速缓存中空闲对象的最大数目
        unsigned int buffer_size;/*buffer的大小,就是对象的大小*/
        unsigned int flags;     /* constant flags */
        unsigned int num;       /* # of objs per slab *//*slab中有多少个对象*/
        gfp_t gfpflags;       /*与伙伴系统交互时所提供的分配标识*/  
        struct list_head next;//用于将高速缓存连入cache chain
        //用于组织该高速缓存中的slab
        struct kmem_list3 *nodelists[MAX_NUMNODES];/*最大的内存节点*/
    };
    
    
    struct kmem_list3 {
    /*三个链表中存的是一个高速缓存slab*/
    /*在这三个链表中存放的是cache*/
        struct list_head slabs_partial; //包含空闲对象和已经分配对象的slab描述符
        struct list_head slabs_full;//只包含非空闲的slab描述符
        struct list_head slabs_free;//只包含空闲的slab描述符
        unsigned long free_objects;  /*高速缓存中空闲对象的个数*/
        unsigned int free_limit;   //空闲对象的上限
        unsigned int colour_next;   /* Per-node cache coloring *//*即将要着色的下一个*/
        spinlock_t list_lock;
        struct array_cache *shared; /* shared per node */
        struct array_cache **alien; /* on other nodes */
        unsigned long next_reap;    /* updated without locking *//**/
        int free_touched;       /* updated without locking */
    };
    
    struct slab {
        struct list_head list;   //用于将slab连入keme_list3的链表
        unsigned long colouroff;   //该slab的着色偏移
        void *s_mem;        /* 指向slab中的第一个对象*/
        unsigned int inuse; /* num of objs active in slab */已经分配出去的对象
        kmem_bufctl_t free;       //下一个空闲对象的下标
        unsigned short nodeid;   //节点标识符
    };
    
    struct array_cache {
        unsigned int avail;/*当前cpu上有多少个可用的对象*/
        unsigned int limit;/*per_cpu里面最大的对象的个数,当超过这个值时,将对象返回给伙伴系统*/
        unsigned int batchcount;/*一次转入和转出的对象数量*/
        unsigned int touched;/*标示本地cpu最近是否被使用*/
        spinlock_t lock;/*自旋锁*/
        void *entry[];  /*
                 * Must have this definition in here for the proper
                 * alignment of array_cache. Also simplifies accessing
                 * the entries.
                 */
    };
    

         slab分配过程,每次分配的时候直接从本地cpu告诉缓存中进行交互,这里是软件的高速缓存(kmem_cache中的array_cache),在cpu高速缓存中没有空闲对象的时候,就会从kmem_list中的slabs_partial链表中找一个空闲slab进行分配对象,partial中没有的时候再去slabs_free链表中找,slabs_free中没有的时候slab分配器通过buddy分配器申请page frame去形成一个新的slab进而来分配内存。这里可以看到slab分配器是基于buddy分配器之上的一个抽象。每个类型的对象都有自己的kmem_cache,这样会有很多的cache管理结构。

    相关文章

      网友评论

          本文标题:Linux中物理内存管理

          本文链接:https://www.haomeiwen.com/subject/nwnpgktx.html