Linux中物理内存管理

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管理结构。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容

  • 虚拟内存1.1. 为什么要使用虚拟内存技术1.2. 理论前提1.3. 虚拟内存实现1.4. 页机制1.5. MMU...
    墨染书阅读 11,408评论 3 33
  • 1. 用户空间 通常 32 位 Linux 虚拟地址空间划分, 0-3GB为用户空间,3GB-4GB为内核空间。每...
    Fly_Li阅读 1,678评论 0 5
  • linux性能四大相关子系统,cpu, 内存,磁盘io,网络。内存的使用贯彻系统,任何活的东西都是活在内存中。 内...
    心远气自静阅读 295评论 0 1
  • Linux 内存管理 1 页的概念 linux 内核中把物理页作为内存分配的最小单位,32位CPU 页的大小通常为...
    赤兔欢阅读 3,261评论 0 5
  • 本文以32位机器为准,串讲一些内存管理的知识点。 1. 虚拟地址、物理地址、逻辑地址、线性地址 虚拟地址又叫线性地...
    linux服务器开发阅读 2,037评论 0 0