Linux的内存管理

Linux采用请求分页存储管理方法。

系统为每个进程提供4GB的虚拟内存空间。各虚拟内存空间各自独立。

 

 一 硬件基础

还是逻辑地址,线性地址,物理地址,分段机制和分页机制依次转换。

 

其中涉及到GDT,LDT,段寄存器,段描述符,

逻辑地址到线性地址的转换

 

 linux的分段模型

Linux使用如下段描述符

内核代码段,内核数据段,用户代码段,用户数据段,TSS段,默认LDT段

段基地址为0,段界限4GB,偏移量=线性地址

Linux必须分别为内核和用户程序创建代码段和数据段

虚拟地址等同于线性地址

分页机制

 

 

二 虚拟内存的管理

linux中,每个用户都可以访问4GB的线性虚拟地址空间。

分为用户空间和内核空间。

用户空间:0到3GB-1,可以直接访问

内核空间:3GB到4GB-1,存放供操作系统和内核访问的代码和数据,用户进程不能访问

 

注意!!!!!

  所有的进程的3GB到4GB-1的虚拟空间都是一样的,

linux以此方式让内核态进程共享代码段和数据段。

注意!!!!!

一个进程通过系统调用之后,就进入内核态了。,,一开始还想task_struct中有没有什么标志位神马的。

 

附上神图

 

 好图+1

 

 

 

 mm_struct详解

前面也已经提到过了这个,task_struct的mm项指向这个,描述进程的虚拟空间。

现在给出具体代码

struct mm_struct 
{  struct vm_area_struct * mmap;    
                                          /*指向VMA链表表头的指针*/ 
    rb_root_t mm_rb;             /*指向进程红黑树的根*/ 
    struct vm_area_struct * mmap_cache; 
                                              /*指向最后使用的VMA*/ 
    pgd_t * pgd;             /*指向进程页目录表的指针*/
    atomic_t mm_users;                 /*用户空间数*/
    atomic_t mm_count;
                                    /* 访问mm_struct结构的计数*/
    int map_count;                           /* VMA的数量 */ 
    struct rw_semaphore mmap_sem;   /*读写信号量*/ 
    spinlock_t page_table_lock;  
                                          /*保护任务页表和mm->rss*/
    struct list_head mmlist;       /*所有活动mm的列表*/    
    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; 
        /*驻留内存页框总数,VMA总数及被锁 VMA总数*/
    unsigned long def_flags;          
    unsigned long cpu_vm_mask;  
    unsigned long swap_address;
    unsigned dumpable:1;
    mm_context_t context; 
                            /*和具体硬件结构有关的MM上下文*/
};

vm_area_struct详解

虚拟内存区域名为vma,是进程一段连续的区域

用vm_area_struct描述

进程的mm_struct的mmap指向这个链表的首地址

 

 注意vma和其代表vm_area_struct按地址排序

 

 

vma数量大的时候启用AVL树排序

 

代码

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;  
     /*指向下一个vm_area_struct结构体,链表的首地址由*/ /*mm_struct中成员项mmap指出*/
   pgprot_t  vm_page_prot;      /*该VMA的访问权限*/
   unsigned short vm_flags;  /*指出虚存区域的操作特性*/ 
   struct rb_node vm_rb;       /*红黑树*/
   struct list_head shared;
struct vm_operations_struct * vm_ops; 
            /*该结构体中包含着指向各种操作的函数的指针*/

    /* 后援存储器的信息*/
   unsigned long vm_pgoff;  /*PAGE_SIZE单元中的偏移量*/   
   unsigned long vm_offset; /*该区域的内容相对于文件起始位置的偏移量,或相对于共享内存首址的偏移量*/
   
   struct file * vm_file;/* 若虚存区域映射的是磁盘文件或设备文件的内容,则vm_file指向这个文件,否则为NULL*/
   void * vm_private_data; /*共享内存页表vm_pte */ 
};

 

 

 

内核态地址空间

 

 高位128MB内存建立临时映射,重复使用

可实现所有物理内存访问。

https://blog.csdn.net/tommy_wxie/article/details/17122923/可以看看这个博客

用户态地址空间

有代码段,数据段,BSS段,堆,栈

 

do_mmap()函数

Linux使用do_mmap()函数完成可执行映像向虚拟内存区域的映射。

unsigned long do_mmap
(struct file*file,
unsigned long addr,
unsigned long len,
unsigned long prot,
unsigned long flags,
unsigned long off)

 

 

 

 

 

 

 注意,do_mmap函数不一定创建新的vma。

如果创建的地址区间和一个已经存在的地址区间相邻,而且访问权限相同,那么这两个区间就会合并成一个。

 

三 物理内存的管理

因为Linux适用于广泛的体系结构,因此使用一种与体系无关的方式描述内存。

 Linux 2.6使用非一致内存访问,NUMA模型,给定CPU对不同内存单元访问时间可能不同。

 

系统的物理内存被划分为许多的节点,在一个节点内,给定CPU访问页面时间相同。

 

首先的划分就是内存节点(Node)

  使用数据结构struct pglist_dada实现1

  多cpu的时候,本地内存和远端内存就是不同的节点。

  内存节点再进行划分得到内存分区

内存分区(Zone)

  Linux内核使用struct zone_struct来描述内存分区。

  通常一个节点被分为DMANormalHigh Memory等内存分区

 

  ZONE_DMA(0~16MB)

  ZONE_NORMAL(16~896MB)

  ZONE_HIGHMEM(896MB以上)

页框(Page Frame)

  每个内存分区又由大量的页框组成。

  内核使用struct page来描述页框。

typedef struct page 
{  struct page *next,*prev; 
                               /*把page结构体链成双向循环链表*/
    struct inode *inode;
         /*若该页面的内容是文件,则是相关文件的inode*/
    unsigned long offset;          /* 在文件中的偏移量*/
    struct page *next_hash,*prev_hash;
                          /*把有关page结构体连成一个哈希表*/
    atomic_t count;            /*访问此页面的进程计数*/
    unsigned flags;                                /*页面标志*/
    unsigned dirty:16,    /*表示该页面是否被修改过*/
                     age:8; 
                            /*标志页面的“年龄”, 越小越先换出 */    
  struct wait_queue *wait; 
                    /*等待该页资源的进程等待队列的指针*/
  struct buffer_head * buffers;
                        /* 若该页面作为缓冲区,则指示地址 */
  unsigned long swap_unlock_entry;
  unsigned long map_nr;   /*该页面page结构在mem_map[]数组中的下标值,也就是物理页面的页号*/
} mem_map_t;

 

  页面标志flags的含义

 

 

 

  所有的struct page都保存在全局结构数组struct mem_map中,此数组

保存在ZONE_NORMAL的开头。

  在初始化的时候使用free_area_init()来创建。

  

 

 

四 Linux的内存的分配与释放算法

 

  Linux对物理内存的分配和释放使用基于分页管理的伙伴算法。

 

  页是虚拟内存概念,对应到具体的物理内存就是页框(或页帧page frame)。页框刚好

能够放下一个页。物理内存以页帧为基本单位。一个页大小4KB?

  而Linux是使用mem_map[]数组来管理物理块的。其数组元素就是page结构体。每一个

page结构体对应着一个物理页面。

  Linux对于内存空闲块的分配和回收,是以2的幂次方个连续的页帧为基本单位的。

 

  伙伴(buddy)算法

  这是Linux对内存空闲空间的管理算法。

  首先,形成一个数据结构。这个数据结构包含了11个链表。

  各个链表保存相同大小的页面块,简称页块。按照包含的页面的块数,分别叫做

1页块,2页块,直到1024页块,共11种页块。

  

 

   空闲页面的管理方法

  Linux通过基于free_area[]数组的数据结构,通过位图空闲页块链表

两种方法来管理空闲页面。

  数据结构如下

#define NR_MEM_LISTS 11
static struct free_area_struct   free_area[NR_MEM_LISTS];
struct free_area_struct
{   struct page *next;
     struct page *prev;
     unsigned int * map;   };

 

   位图

  free_area[]数组中的map指针指向了相应大小的页块的位图。

  位图表在内存中的位置就在管理页帧的mem_map数组之后。该表使用位示图

展现物理内存分配状况。

  该表由NR_MEM_LISTS个组组成。第k组每位表示2^k个页帧的内存块使用状

况。

  空闲块组链表

   即链表实现

这个图应该很明显了

 

 

  空闲块分配的具体步骤

  1,根据申请的大小确定大小在2^k到2^(k+1)大小之间,确定k

  2,从free_area中对应查找2^(k+1)大小的块

  3,如果找到2^(k+1)大小的块,那么直接从free_area数组中删除这个块,返回首地址

  4,好像是会继续找更大的块,一半用作实际使用,一般放到前面去。

  空闲块回收的具体步骤

  需要根据位图判断相邻的页是否空闲。

  如果空闲,还需要合并,直到不能合并为止。

 

  这个博客https://blog.csdn.net/wenqian1991/article/details/27968779讲的好。

 

  内存分配与回收的具体算法

Linux中具体与内存分配和回收有关的函数有kmalloc,和kfree

主要用于分配和释放内核内存,以块位单位。

void *kmalloc(size_t size, int priority)

size是分配内存大小,priority通常用

 

 

 

 

 

 

 

 

  各种数据结构

 

block_size表

 

   一个静态数组,似乎能够设置最小存储单元大小。

  使用kmalloc分配内存的时候,任然按照Buddy算法作为基础。

  kmalloc可以分配小于或等于一个页面的内存。这时必然从第一个free_area中的空闲页块分配。

  如果分配大于一个页面的话,显然blocksize需要使用后六个规格,在free_area中选择合适的

尺寸。

 

页描述符


  使用kmalloc分配的内存页面前面有一个信息头。

  后面是内存的可分配空间,这个信息头即为页描述符。

 

struct page_descriptor 
{  struct page_descriptor *next;   
                                                   /* 指向下一个页面块的指针 */
    struct block_header *firstfree; /* 本页中空闲块链表的头 */
    int order;                      /* 本页中块长度的级别 */
    int nfree;                      /* 本页中空闲块的数目 */
}; 

  

size数组

  对页面块进行描述。

  数组元素是size_descriptor结构体

struct size_descriptor
{  struct page_descriptor *firstfree;/*一般页块链表的头指针 */
   struct page_descriptor *dmafree; /*DMA页块链表的头指针*/
   int nblocks;           /* 页块中划分的块数目 */
   int nmallocs;          /* 链表中各页块中已分配的块总数 */
   int nfrees;            /* 链表中各页块中尚空闲的块总数 */
   int nbytesmalloced;    /* 链表中各页块中已分配的字节总数 */
   int npages;            /* 链表中页块数目 */
   unsigned long gfporder;     /* 页块的页面数目 */
};
static struct size_descriptor sizes[] ={
    {NULL, NULL, 127, 0, 0, 0, 0, 0 },
    {NULL, NULL, 63, 0, 0, 0, 0, 0 },
   { NULL, NULL, 31, 0, 0, 0, 0, 0 },
   { NULL, NULL, 16, 0, 0, 0, 0, 0 },
   { NULL, NULL, 8, 0, 0, 0, 0, 0 },
   { NULL, NULL, 4, 0, 0, 0, 0, 0 },
   { NULL, NULL, 2, 0, 0, 0, 0, 0 },
   { NULL, NULL, 1, 0, 0, 0, 0, 0 },
   { NULL, NULL, 1, 0, 0, 0, 0, 1 },
   { NULL, NULL, 1, 0, 0, 0, 0, 2 },
   { NULL, NULL, 1, 0, 0, 0, 0, 3 },
   { NULL, NULL, 1, 0, 0, 0, 0, 4 },
   { NULL, NULL, 1, 0, 0, 0, 0, 5 },
   { NULL, NULL, 0, 0, 0, 0, 0, 0 }
};

 

有关block_size和size这两个数组

  它们元素个数相同,并一一对应。

  kmalloc分配得到的块,连接成两种链表

  

 

 

  size还有个块头block_header

struct block_header
{ 
  unsigned
long bh_flags; /* 块的分配标志 */ union
  {
    unsigned
long ubh_length; /* 块长度 */ struct block_header *fbh_next; /*指向下一空闲块的指针 */ } vp;
};

 

 各数据结构关系

 

 

  还有一个kmalloc缓冲区,由kmalloc_cache管理。

 

 

 

 

 

使用较大的内存时,使用vmalloc和vfree,使用的连续的虚拟内存,在映射到物理内存的时候,可以是不连续的。

五  linux的内核内存管理

内核内存的特点

 

 因此,Linux采用了一套独立的机制来实现更细颗粒度的内存管理。

 

具体有:

  简单二次幂空闲表;

  Mckusic-Karels分配器;

  Buddy System;

  Lazy Buddy;

  Zone分配器;

  Slab分配器;

 

  Slab分配器详解

posted @ 2019-12-09 23:37  TheDa  阅读(1012)  评论(0编辑  收藏  举报