Linux存储管理

Linux存储管理    Linux操作系统采用了请求式分页虚拟 存储管理方法。 系统为每个进程提供了4GB的虚拟内存空间。各个进程的虚拟内存彼此独立。 §4.1  进程虚存空间的管理 一.内核空间和用户空间 l         进程运行时能访问的存储空间只是它的虚拟内存空间。对当前该进程而言只有属于它的虚拟内存是可见的。 l         在进程的虚拟内存包含着进程本身的程序代码和数据。 l         进程在运行中还必须得到操作系统的支持。进程的虚拟内存中还包含着操作系统内核。 l          Linux把进程的虚拟内存分成两部分,内核区和用户区。 l         操作系统内核的代码和数据等被映射到内核区。 l         进程的可执行映像(代码和数据)映射到虚拟内存的用户区。 l         进程虚拟内存的内核区的访问权限设置为0级,用户区为3级。 l         内核访问虚存的权限为0级,而进程的访问权限为3级 Linux运行在x86时,进程的虚拟内存为4GB。 进程虚存空间的划分在系统初始化时由GDT确定, 它定义在/arch/i386/kernel/head.S文件中:     .quad 0x0000000000000000    /* NULL 描述符 */     .quad 0x0000000000000000    /* 未使用*/     .quad 0xc0c39a000000ffff    /* 内核代码段1GB在0xc0000000 */     .quad 0xc0c392000000ffff    /* 内核数据段1GB在0xc0000000 */     .quad 0x00cbfa000000ffff    /* 用户代码段3GB在0x00000000 */     .quad 0x00cbf2000000ffff    /* 用户数据段3GB在0x00000000 */     .quad 0x0000000000000000    /* 未使用 */     .quad 0x0000000000000000    /* 未使用 */    .fill 2*NR_TASKS,8,0     /* 各个进程LDT描述符和TSS描述符的空间 */ Linux存储管理主要是管理进程虚拟内存的用户区。 进程虚拟内存的用户区分成代码段、数据段、堆栈 以及进程运行的环境变量、参数传递区域等。 每一个进程,用一个mm_struct结构体来定义它的虚存用户区。 mm_struct结构体首地址在任务结构体task_struct成员项mm中:         struct mm_struct  *mm; mm_struct结构定义在/include/ linux/schedul.h中。 struct mm_struct {         int count;         pgd_t * pgd;         unsigned long context;         unsigned long start_code, end_code, start_data, end_data;         unsigned long start_brk, brk, start_stack, start_mmap;         unsigned long arg_start, arg_end, env_start, env_end;         unsigned long rss, total_vm, locked_vm;         unsigned long def_flags;         struct vm_area_struct * mmap;         struct vm_area_struct * mmap_avl;         struct semaphore mmap_sem; }; pgd 为指向进程页目录表的指针。 context 是进程上下文的地址。 start_code, end_code, start_data, end_data      分别为代码段、数据段的首地址和终止地址。 start_stack 是进程堆栈的首地址。 arg_start, arg_end, env_start, env_end   分别为参数区、环境变量区的首地址和终止地址。 二.进程的虚存区域 一个虚存区域是虚存空间中一个连续的区域,在这个区域中的信息具有相同的操作和访问特性。每个虚拟区域用一个vm_area_struct结构体进行描述.它定义在/include/ linux/mm.h中: struct vm_area_struct {     struct mm_struct * vm_mm;     unsigned long vm_start;     unsigned long vm_end;     pgprot_t vm_page_prot;     unsigned short vm_flags;     short vm_avl_height;         struct vm_area_struct * vm_avl_left;         struct vm_area_struct * vm_avl_right;         struct vm_area_struct * vm_next;         struct vm_area_struct * vm_next_share;         struct vm_area_struct * vm_prev_share;         struct vm_operations_struct * vm_ops;         unsigned long vm_offset;         struct inode * vm_inode;         unsigned long vm_pte; }; (1)vm_mm指针指向进程的mm_struct结构体。 (2)vm_start和vm_end 虚拟区域的开始和终止地址。 (3)vm_flags指出了虚存区域的操作特性: VM_READ              虚存区域允许读取 VM_WRITE             虚存区域允许写入 VM_EXEC             虚存区域允许执行 VM_SHARED            虚存区域允许多个进程共享 VM_GROWSDOWN    虚存区域可以向下延伸 VM_GROWSUP      虚存区域可以向上延伸 VM_SHM         虚存区域是共享存储器的一部分 VM_LOCKED            虚存区域可以加锁 VM_STACK_FLAGS  虚存区域做为堆栈使用 (4)vm_page_prot 虚存区域的页面的保护特性。 (5)若虚存区域映射的是磁盘文件或设备文件的的内容,则vm_inode指向这个文件的inode结构体,否则vm_inode为NULL。 (6)vm_offset是该区域的内容相对于文件起始位置的偏移量,或相对于共享内存首址的偏移量。 (7)所有vm_area_struct结构体链接成一个单向链表,vm_next指向下一个vm_area_struct结构体。链表的首地址由mm_struct中成员项mmap指出。 (8)vm_ops是指向vm_operations_struct结构体的指针。该结构体中包含着指向各种操作的函数的指针。 (9)所有vm_area_struct结构体组成一个AVL树。         (Adelson-Velskii and Landis)      AVL树是一种具有平衡结构的二叉树。      vm_avl_left 左指针指向相邻的低地址虚存区域,      vm_avl_right右指针指向相邻的高地址虚存区域      mmap_avl    表示进程AVL树的根,      vm_avl_hight表示AVL树的高度。 (10)vm_next_share和vm_prev_share,把有关的vm_area_struct       结合成一个共享内存时使用的双向链表。 §4.2  虚存空间的映射和虚存区域的建立 一.虚拟空间的地址映射 在多进程操作系统中,同时运行多个用户的程序,系统分配给用户的物理地址空间放不下代码和数据等。为了解决这个矛盾而出现了虚拟存储技术.在虚拟存储技术中,用户的代码和数据(可执行映像)等并不是完整地装入物理内存,而是全部映射到虚拟内存空间。在进程需要访问内存时,在虚拟内存中“找到”要访问的程序代码和数据等。 系统再把虚拟空间的地址转换成物理内存的物理地址。 二.虚存区域的建立 Linux使用do_mmap()函数完成可执行映像向虚存区域的映射, 由它建立有关的虚存区域 do_mmap()函数定义在/mm/mmap.c文件中     unsigned long do_mmap(struct file * file, unsigned long addr,                       unsigned long len,unsigned long prot,                       unsigned long flags, unsigned long off) addr虚存区域在虚拟内存空间的开始地址, len是这个虚存区域的长度。 file是指向该文件结构体的指针, off是相对于文件起始位置的偏移量。 若file为NULL,称为匿名映射(anonymous mapping)。 prot指定了虚存区域的访问特性:   PROT_READ   0x1  对虚存区域允许读取   PROT_WEITE  0x2  对虚存区域允许写入   PROT_EXEC   0x4  虚存区域(代码)允许执行   PROT_NONE   0x0  不允许访问该虚存区域 flag指定了虚存区域的属性:   MAP_FIXED   指定虚存区域固定在addr的位置上。   MAP_SHARED  指定对虚存区域的操作是作用在共享页面上      MAP_PRIVATE指定了对虚存区域的写入操作将引起页面拷贝。         §4.3 Linux的分页式 存储管理 一.      Linux的三级分页结构 l         页表是从线性地址向物理地址转换中不可缺少的数据结构,而且它使用的频率较高。页表必须存放在物理存储器中。 l         虚存空间有4GB,按4KB页面划分页表可以有1M页。 若采用一级页表机制,页表有1M个表项,每个表项4字节, 这个页面就要占用4MB的内存空间。 l         由于系统中每个进程都有自己的页表,如果每个页表占用4MB,    对于多个进程而言就要占去大量的物理内存,这是不现实的。 l         在目前用户的进程不可能需要使用4GB这么庞大的虚存空间,    若使用1M个表项的一级页表,势必造成物理内存极大的浪费。 l         为此, Linux采用了三级页表结构,以利于节省物理内存。 l         三级分页管理把虚拟地址分成四个位段:    页目录、页中间目录、页表、页内偏址。 l         系统设置三级页表系列:    页目录PGD(PaGe Directory)、    页中间目录PMD(Page Middle Directory)    页表PTE(Page TablE)。 l         三级分页结构是 Linux提供的与硬件无关的分页管理方式。 l         当 Linux运行在某种机器上时,需要利用该种机器硬件的 存储管理机制来实现分页存储。 l          Linux内核中对不同的机器配备了不同的分页结构的转换方法。 l         对x86,提供了把三级分页管理转换成两级分页机制的方法。 l         其中一个重要的方面就是把PGD与MGD合二为一,使所有关于PMD的操作变为对PGD的操作 l         在/include/asm-i386/pgtable.h中有如下定义:     #define PTRS_PER_PTE    1024     #define PTRS_PER_PMD    1        #define PTRS_PER_PGD       1024 二、地址映射 地址映射就是在几个存储空间(逻辑地址空间、线形地址空间、物理地址空间) 或存储设备之间进行的地址转换。    §4.4 物理内存空间的管理 一.物理内存的页面管理 l          Linux对物理内存空间按照分页方式进行管理,把物理内存划分成大小相同的物理页面。 l         在x86机器中一个页面的大小为4KB,    定义在include/asm-i386/page.h文件中:         #define PAGE_SHIFT  12         #define PAGE_SIZE   (1UL l         在Alpha、Sparc中一个页面大小8KB:         #define PAGE_SHIFT  13         #define PAGE_SIZE   (1UL l          Linux设置了一个mem_map[]数组管理内存页面。    mem_map[]在系统初始化时由free_area_init()函数创建,它存放在物理内存的底部(低地址部分) l         mem_map[]数组的元素是一个个的page结构体,每一个page结构体它对应一个物理页面。 l         page结构进一步被定义为mem_map_t类型,其定义在/include/ linux/mm.h中:    typedef struct page {        struct page *next;        struct page *prev;        struct inode *inode;        unsigned long offset;        struct page *next_hash;        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;       } mem_map_t; ①   count:是共享该页面的进程计数。 ②   age:标志页面的“年龄”。 ③   dirty:表示该页面是否被修改过。 ④   prev和next:   把page结构体链接成一个双向循环链表。 ⑤   prev_hash和next_hash: 把有关page结构体连成一个哈希表 ⑥   inode和offset:   有关文件的inode和它们在文件中的偏移量。 ⑦   wait:是等待该页资源的进程等待队列的指针。 ⑧   flag:页面标志: 符号常量 意   义 PG_locked          页面处于闭锁状态,正在装入该页面 PG_error            页面装入时发生错误 PG_referenced       页面已装入,可以访问 PG_uptodate      页面内容更新过 PG_free_after       关于页面的I/O过程结束,页面被释放 PG_decr_after       关于页面的I/O过程结束,页面计数减少 PG_swap_unlock_after    读出交换页面后,页面解除闭锁 PG_DMA             页面可以用于DMA传送 PG_reserved      页面被保留以后使用,当前禁止使用 ⑨   map_nr:该页面page结构体在mem_map[]数组中的下标值,   也就是物理页面的页号。 二.空闲页面的管理—Buddy算法 l          Linux对内存空闲空间的管理采用Buddy算法,Buddy是“伙伴”、“搭档”的意思。 l         Buddy算法是把内存中的所有页面按照2n划分,其中n=0~5, l         对一个内存空间按1个页面、2个页面、4个页面、8个页面、16个页面、32个页面进行六次划分。 l         划分后形成了大小不等的存储块,称为页面块,简称页块。包含1个页面的页块称为1页块,包含2个页面的称为2页块,依此类推。 l          Linux把物理内存划分成了1、2、4、8、16、32六种页块。 l         对于每种页面块按前后顺序两两结合成一对Buddy“伙伴”    按照1页面划分后,0和1页、2和3页…是1页块Buddy。    按照2页面划分,0-1和2-3、4-5和6-7…是2页块Buddy。 Linux把空闲的页面按照页块大小分组进行管理, 数组free_area[]来管理各个空闲页块组。 在/mm/page_alloc.c中定义如下: #define NR_MEM_LISTS 6     static struct free_area_struct free_area[NR_MEM_LISTS];        struct free_area_struct {         struct page *next;         struct page *prev;         unsigned int * map;     }; Linux通过free_area[]数组采取两种方法管理空闲页面, 一种是使用位图, 另一种是使用空闲页块组链表。 Linux对内存页面块的每种划分都对应一个位图map(bitmap), free_area[]各个元素中的指针map指向相应页面块的位图, free_area[0]中的map指向内存按照1个页面划分时的位图, free_area[1]中的map指向内存按照2个页面划分时的位图…。 l         位图中每一位(bit)表示一对Buddy页面的使用情况 l         当一对Buddy的两个页面块中有一个是空闲的,而另一个全部或部分被占用时,该位置1。 l         当这两个页面块都是空闲,或都被全部或部分占用时, 对应的位置0。 l         free_area[]数组指向的六个位图, 放在内存mem_map的上方bitmap区域内。 l         用来记录页块组使用情况的位图的长度不同,    页块越小位图越长。    当内存区域的开始地址为start_mem,结束地址为end_mem,页面尺寸为PAGE_SIZE时,    每种页块组的位图的长度为:     (end_mem-start_mem)/PAGE_SIZE/2i+3(字节)   其中i=0~5,分别对应1~32六种页块组的map长度。 l         系统按照Buddy关系把具有相同大小的空闲页面块组成页块组,即1页块组、2页块组……32页块组。 l         每个页块组用一个双向循环链表进行管理,共有6个链表,分别为1、2、4、8、16、32页块的链表。 l         这些链表是由空闲页面的page结构体双向连接而成,分别挂到free_area[] 数组上。 l         free_area[]数组元素中指针prev和next指向链表。    free_area[0]的指针指向20,即1页面块的链表,    free_area[1]的指针指向21,即2页面块的链表    ……,    free_area[5]的指针指向25,即32页面块的链表 l         在页块组链表中的page结构体通过prev和next相互链接。 l         在请求内存分配时,系统按照Buddy算法,根据请求的页面数在free_area[]对应的空闲页块组中搜索。 l         若请求的页面数不是2的整数次幂,则按照稍大于请求数的2的整数次幂的值搜索相应的页面块组。 l         当相应的页块组中没有可使用的空闲页面块时就查询更大一些的页块组, l         在找到可利用的空闲页面块后,分配所需的页面。 l         当某一空闲页面块被分配后,若仍有剩余的空闲页面,    则根据剩余页面的大小把它们加入到相应的页块组中。 l         在内存页面释放时,系统将做为空闲页面看待。    然后检查是否存在与这些页面相邻的其它空闲页块,    若存在,则合为一个连续的空闲区按Buddy算法重新分组。 §4.5 内存的分配与释放 Linux中用于内存分配和释放的函数主要是kmalloc()和kfree(),它们用于分配和释放连续的内存空间。 一.     内存分配与释放的数据结构 1.blocksize表 l         kmalloc()和kfree()分配和释放内存是以块(block)为单位进行的。 l         可以分配的空闲块的大小记录在blocksize表中,它是一个静态数组,定义在/mm/kmalloc.c中:     #if PAGE_SIZE == 4096     static const unsigned int blocksize[] = {         32,64,128,252,508,1020,2040,         4096-16,8192-16,16384-16,         32768-16,65536-16,131072-16,         0 }; l         在使用kmalloc()分配空闲块时仍以Buddy算法为基础,即以free_area[]管理的空闲页面块做为分配对象。 l         重新制定了分配的单位, blocksize[]数组中的块长度。 l         它可以分配比1个页面更小的内存空间。 l         blocksize[]中的前7个是在1个空闲页面内进行分配,其后的6种分别对应free_area[]的1至32空闲页面块, l         当申请分配的空间小于或等于1个页面时,从free_area[] 管理的1页面块中查找空闲页面进行分配。 l         若申请的空间大于一个页面时,按照blocksize[]后六个块单位进行申请,从free_area[]中与该块长度对应的空闲页块组中查找空闲页面块。 2.page_descriptor l         对kmalloc()分配的内存页面块中加上一个信息头,它处于该页面块的前部。 l         页面块中信息头后的空间是可以分配的内存空间。 l         加在页面块前部的信息头称为页描述符,定义在/mm/kmalloc.c中:     struct page_descriptor {        struct page_descriptor *next;   /* 指向下一个页面块的指针 */         struct block_header *firstfree; /* 本页中空闲块链表的头 */         int order;                      /* 本页中块长度的级别 */         int nfree;                      /* 本页中空闲的数目 */ }; l         具有相同块单位和使用特性的页面块组成若干个链表。 3.sizes表 l          Linux设置了sizes[]数组,对页面块进行描述。 数组元素是size_descriptor结构体, 定义在/mm/kmalloc.c中:     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}     }; l         blocksize[]与sizes[]元素数目相同,它们一一对应。 l         由kmalloc()分配的每种块长度的页面块链接成两个链表,一个是DMA可以访问的页面块链表,dmafree指向这个链表。一个是一般的链表, firstfree指向这个链表。 l         成员项gfporder是0~5,它做为2的幂数表示所含的页面数。 4.block_header l         由sizes[]管理的各个页面块中每个块(空闲块和占用块)的头部还有一个对该块进行描述的块头block_header: struct block_header {     unsigned long bh_flags;  /* 块的分配标志 */     union {       unsigned long ubh_length;      /* 块长度 */         struct block_header *fbh_next; /*指向下一空闲块的指针 */    } vp; }; bh_flages是块的标志,有三种:    MF_FREE指明该块是空闲块,    MF_USED表示该块已占用,    MF_DMA 表示该块是DMA可访问。 l         ubh_length和fbh_next是联合体成员项,    当块占用时使用ubh_length表示该块的长度。    当块空闲时使用fbh_next链接下一个空闲块。 l         在一个页块中的空闲块组成一个链表,    表头由页块的page_descriptor中firstfree指出。 二.内存分配函数kmalloc() void *kmalloc(size_t size, int priority) l         参数size是申请分配内存的大小, l         priority是申请优先级。 l         priority常用的值为GFP_KERNEL和GFP_ATOMIC    内存不够时:    GFP_KERNEL:当前申请进程暂时被挂起而等待换页。    GPF_ATOMIC:该函数不允许推迟,而立即返回0值。 l         priority还取值GPF_DMA,表示申请的内存用于DMA传送。 l         kfree()用于释放由kmalloc()分配的内存空间:         void kfree(void *__ptr) l         ptr是kmalloc()分配的内存空间的首地址。 l         当kmalloc管理的一个页面块中的占用块全部被释放后,    它就成为一个空闲页面块。 l         系统把这个空闲页面块从sizes[]管理的相应链表中删除,把它交给free_area[]数组按照buddy算法管理。 l         kmalloc()和kfree()还共同维护一个kmalloc缓冲区,由kmalloc_cache的数组进行管理,定义如下:    #define MAX_CACHE_ORDER 3    struct page_descriptor * kmalloc_cache[MAX_CACHE_ORDER]; l         kmalloc_cache[]3个元素,分别指向一个空闲的1、2、4页面块。 l         由sizes[]管理的内存中有1页块、2页块或4页块被释放时,    它们不立即交还free_area[]管理,先交给kmalloc_cache[]管理。 l         sizes[]中有新的1页块、2页块或4页块被释放时,    把kmalloc_cache[]当前指向的空闲页块交给free_area[]管理,    然后指向新释放的空闲页块。 三.虚拟内存的申请和释放 l         在申请和释放较小且连续的内存空间时,使用kmalloc()和kfree()在物理内存中进行分配。 l         申请较大的内存空间时,使用vmalloc()。由vmalloc()申请的内存空间在虚拟内存中是连续的,它们映射到在物理内存时,可以使用不连续的物理页面,而且仅把当前访问的部分放在物理页面中。 l         由vmalloc()分配的虚存空间称为虚拟内存块(虚存块)。 l         由vmalloc()分配的虚存块用一个链表来管理,    系统定义的指针变量vmlist指向链表的表头,    在mm/vmalloc.c中定义如下:         static struct vm_struct * vmlist = NULL; 结构vm_struct描述由vmalloc()分配的虚存块:         struct vm_struct {             unsigned long flags;  /* 虚存块的标志 */             void * addr;           /* 虚存块起始地址 */             unsigned long size;   /*虚存块大小 */             struct vm_struct * next; /* 指向下一个虚存块的指针 */         }; vmalloc()和vfree()定义在mm/vmalloc.c中:         void * vmalloc(unsigned long size)         void vfree(void * addr) l         可以看到vmalloc()参数size指出申请的内存的大小。 l         分配成功后返值为在虚存空间分配的虚存块首地址,    失败返值为0。 l         vfree()用来释放由vmalloc()分配的虚存块,    参数addr是要释放的虚存块首址。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值