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来描述内存分区。
通常一个节点被分为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.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分配器;