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;
/*该结构体中包含着指向各种操作的函数的指针*/
<span style="color: #008000;">/*</span><span style="color: #008000;"> 后援存储器的信息</span><span style="color: #008000;">*/</span><span style="color: #000000;">
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.youkuaiyun.com/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来描述内存分区。
通常一个节点被分为DMA,Normal,High 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.youkuaiyun.com/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分配器;