




已阅读5页,还剩32页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Linux存储器管理一、 linux存储器管理概述Linux的设计目标是支持绝大多数主流的CPU,而很多CPU使用的是RISC体系结构,并没有分段机制(采用虚拟分页存储管理方法),所以2.6版内核只有在80x86结构下才使用分段,而且只是象征性地使用了一下:所有Linux进程仅仅使用四种段来对指令和数据寻址。运行在用户态的进程使用所谓的用户代码段和用户数据段。类似地,运行在内核态的所有Linux进程都使用一对相同的段对指令和数据寻址:它们分别叫做内核代码段和内核数据段。下表显示了这四个重要段的段描述符字段的值:段BaseLimitDPL用户代码段0x000000000xfffff3用户数据段0x000000000xfffff3内核代码段0x000000000xfffff0内核数据段0x000000000xfffff0相应的段描述符由宏_USER_CS,_USER_DS,_KERNEL_CS,和_KERNEL_DS分别定义。例如,为了对内核代码段寻址,内核只需要把_KERNEL_CS宏产生的值装进cs段寄存器即可。 注意,与段相关的线性地址(段内地址)从0开始,达到232 -1的寻址限长。这就意味着在用户态或内核态下的所有进程可以使用相同的逻辑地址。所有段基址都是0x00000000,这可以得出另一个重要结论,那就是在Linux下逻辑地址与线性地址是一致的,即逻辑地址的偏移量字段的值与相应的线性地址的值总是一致的。如前所述,CPU的当前特权级(CPL)反映了进程是在用户态还是内核态,并由存放在cs寄存器中的段选择符的RPL字段指定。只要当前特权级被改变,一些段寄存器必须相应地更新。例如,当CPL=3时(用户态),ds寄存器必须含有用户数据段的段选择符,而当CPL=0时,ds寄存器必须含有内核数据段的段选择符。 类似的情况也出现在ss寄存器中。当CPL为3时,它必须指向一个用户数据段中的用户栈,而当CPL为0时,它必须指向内核数据段中的一个内核栈。当从用户态切换到内核态时,Linux总是确保ss寄存器装有内核数据段的段选择符。 当对指向指令或者数据结构的指针进行保存时,内核根本不需要为其设置逻辑地址的段选择符,因为cs寄存器就含有当前的段选择符。例如,当内核调用一个函数时,它执行一条call汇编语言指令,该指令仅指定它逻辑地址的偏移量部分,而段选择符不用设置,其隐含在cs寄存器中了。因为“在内核态执行” 的段只有一种,叫做代码段,由宏_KERNEL_CS定义,所以只要当CPU切换入内核态时足可以将_KERNEL_CS装载入cs。同样的道理也适用于指向内核数据结构的指针(隐含地使用ds寄存器)以及指向用户数据结构的指针(内核显式地使用es寄存器)。二、 linux进程的虚存区域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的存储管理主要是管理进程虚拟内存的用户区。进程虚拟内存的用户区分成代码段、数据段、堆栈以及进程运行的环境变量、参数传递区域等。2. linux 描述进程虚拟地址空间的数据结构(1)进程虚拟地址空间mm_struct结构每一个进程,用一个mm_struct结构体来定义它的虚存用户区。该结构体首地址在进程的任务结构体task_struct成员项mm中: struct mm_struct *mm;mm_struct结构定义在/include/linux/schedul.h中。struct mm_struct struct vm_area_struct mmap;/*进程虚存区域链表*/struct vm_area_struct mmap_avl;/*进程虚存区域的AVL树*/struct vm_area_struct mmap_cache;/*指向最近一次用到的虚存区域pgd_t * pgd; /*指向进程页目录表的指针,当内核调度一个进程进入运行时,将这个指针转化成物理地址,写入CR3*/atomic_t mm_users; /* 用户空间中的有多少用户*/atomic_t mm_count; /* 对struct mm_struct有多少引用计数*/int map_count; /* 虚拟区域的个数*/struct semaphore mmap_sem;/*对mmap操作的互斥信号量*/spinlock_t page_table_lock; /* 保护任务页表和 mm-rss */struct list_head mmlist; /*所有活动(active)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; /进程贮留在物理内存中的页面,进程所需的总的页面数,被锁定在物理内存中的页面数本文来源:/linux/linux3163.htmunsigned long def_flags;unsigned long cpu_vm_mask;unsigned long swap_cnt;/*反映一个进程在一轮换出页面的努力中尚未受到考察的页面数量*/unsigned long swap_address;mm_context_t context;/*有关进程的上下文,在进程切换的时候使用;其中mm_count由于所有进程页表中的内核部分都是相同的,内核线程与普通进程相比不需要mm_struct结构,普通进程切换到内核线程时,内核线程可以直接借用进程的页表,而不需要重新加载独立的页表。内核线程使用active_mm指针指向所借用进程的mm_struct结构,每次被active_mm引用都要将mm_count域加1.(2)虚存区域vm_area_struct结构进程的虚拟地址空间被划分为若干逻辑上独立的部分,分别用来描述程序中的不同段,如代码段、数据段、栈段等,称为vma。每个虚存区域vma都是一段具有相同属性的逻辑上连续的虚拟地址空间,比如属于同一进程、相同的访问权限、同时被锁定、同时受保护等。一个进程的所有vma组织成一个双向链表。Linux用vm_area_struct(include/linux/mm.h)来描述一个vma,如对该区域的起始和终止地址的描述。进程可以通过vm_operation_struct(include/linux/mm.h)对这些区域进行操作。当加载关于进程虚拟地址空间的页面时,一系列的vm_area_struct将自动生成,每一个vm_area_struct描述进程的一部分,如执行代码、数据等。Linux支持了多数标准的虚拟内存操作,如读取、关闭、共享、缺页等。一旦vm_area_struct结构生成,就可以通过该结构中的指向vm_operation_struct的指针进行虚拟内存操作了。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结构的指针pgprot_t vm_page_prot; /* 虚拟区域的保护掩码 */unsigned long vm_flags; /* vma属性可读、可写、可执行、可共享、可延伸、可加锁等, */rb_node_t vm_rb; /用于对VMA块进行红黑树操作的结构体struct vm_area_struct *vm_next_share, *vm_pprev_share;/当虚存区是文件映射空间的一部分时,这两个域将所有属于同一个文件(即同一个inode)的虚存区连接在一起。当虚存区是SYS V共享内存的一部分时,这两个域将所有属于同一块共享内存的vma连接在一起.struct vm_operations_struct * vm_ops; /*虚存区的函数开关表。这个表里给出了可以对虚存区中的页面进行的操作*/unsigned long vm_offset; /*是该区域的内容相对于文件起始位置的偏移量,或相对于共享内存首址的偏移量。*/struct file * vm_file; /*若此vma为某个文件的映射,该域指向这个文件结构*/;(3)vm_operation_structLinux对于虚存区的操作定义在vm_operation_struct数据结构中,通过在vm_area_struct结构中使用指针vm_ops来确定进程对该虚存区可以进行的一系列操作。struct vm_operations_struct /* 打开操作,当内核生成一个虚存区后或者当虚存区被复制后,就用该命令打开。*/ void (*open)(struct vm_area_struct * area); /* 关闭操作,当内核销毁一个虚存区时,就调用该命令。*/ void (*close)(struct vm_area_struct * area); /* 处理缺页异常,当进程访问一个不属于内存的有效页面时,就会调用该命令,返回该页的物理地址。*/ void (*nopage)(int error_code, struct vm_area_struct * area, unsigned long address); /* 处理写保护异常,当往一个被保护的页面上写入数据时,就会调用该命令。*/ void (*wppage)(struct vm_area_struct * area, unsigned long address); int (*share)(struct vm_area_struct * from, struct vm_area_struct * to, unsigned long address); /* 取消映射操作,当内核取消虚存区的部分或者全部映射时,调用该命令;当取消全部映射后,内核就会自动调用close()进行关闭操作。*/ int (*unmap)(struct vm_area_struct *area, unsigned long, size_t);如图所示,为虚存管理数据结构之间的关系。(4)虚存空间的映射和虚存区域的建立在虚拟存储技术中,用户的代码和数据(可执行映像)等并不是完整地装入物理内存,而是全部映射到虚拟内存空间。在进程需要访问内存时,在虚拟内存中“找到”要访问的程序代码和数据等,系统再把虚拟空间的地址转换成物理内存的物理地址。这种将进程映像链接到进程的虚拟地址空间的技术称为内存映射(memory mapping)。Linux支持多种格式的可执行文件,比如ELF和Java等,这些用户进程映像装载所使用的格式也是在不断变化的,使用较多的是被大多数Unix支持的ELF格式,下图给出了有ELF装入程序建立的典型的虚拟内存布局,可见一端是内核空间,另一端是保留区域,禁止用户进程访问,其余部分可以被用户进程使用:用户进程可使用空间的顶部是栈,用于保存在调用exec时程序的参数和环境变量等上下文现场。其他区域在虚拟内存的底端,包含程序代码或只读数据的部分二进制文件将作为写保护区映射到虚拟内存“程序文本”区域中。随后映射已经初始化的可写数据,最后所有未初始化的数据被映射到私有的按需填零区域。除了上述固定大小的区域,还有变长区域。在变长区域里,程序可以根据需要进行扩展,在运行时得到分配的数据。系统对于每个进程都设置一个数据区指针不brk,它指向这个变长数据区的当前范围(在mm_struct结构中)。进程可以利用系统调用动态扩大或缩小其区域。一旦这些映射被建立起来,装入程序就利用ELF头部记录的开始点初始化进程的程序计数器寄存器,这样进程就可以被调度执行了。每个进程的虚拟内存都用一个mm_struct描述,它包含了当前正在执行的进程映像的信息及指向vm_area_struct结构链表的指针,每个vm_area_struct结构定义了一段虚拟内存区域的起止信息,以及进程对该段虚拟内存的存取权限和允许的操作。当一个用户进程的可执行映像被映射到进程的虚拟地址空间时,系统将产生一组vm_area_struct结构,每个vm_area_struct结构表示可执行映像的一部分,可能是可执行代码,也可能是初始化的变量或者未初始化的数据。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是指向该文件结构体的指针,若file为NULL,称为匿名映射。off是相对于文件起始位置的偏移量。prot指定了虚存区域的访问特性: PROT_READ 0x1 对虚存区域允许读取 PROT_WEITE 0x2 对虚存区域允许写入 PROT_EXEC 0x4 虚存区域(代码)允许执行 PROT_NONE 0x0 不允许访问该虚存区域flag指定了虚存区域的属性: MAP_FIXED 指定虚存区域固定在addr的位置上。 MAP_SHARED 指定对虚存区域的操作是作用在共享页面上 MAP_PRIVATE指定了对虚存区域的写入操作将引起页面拷贝。三、 linux的分页存储管理1. Linux的三级分页结构(1)概述页表是分页系统中从线性地址向物理地址转换不可缺少的数据结构,而且它使用的频率较高。页表必须存放在物理存储器中。若虚存空间有4GB,按4KB页面划分页表可以有1M页。若采用一级页表机制,页表有1M个表项,每个表项4字节,这个页面就要占用4MB的内存空间。由于系统中每个进程都有自己的页表,如果每个页表占用4MB,对于多个进程而言就要占去大量的物理内存,这是不现实的。Linux采用了一种同时适用于32位和64位系统的普通分页模型。前面我们看到,两级页表对32位系统来说已经足够了,但64位系统需要更多数量的分页级别。直到2.6.10版本,Linux采用三级分页的模型。从2.6.11版本开始,采用了四级分页模型。内核作为必须保护的单独部分,它有自己独立的页表来映射内核空间(并非全部空间,仅仅是物理内存大小的空间),该页表 (swapper_pg_dir)被静态分配,它只来映射内核空间(swapper_pg_dir只用到768项以后的项768个页目录可映射3G空 间)。这个独立页表保证了内核虚拟空间独立于其他用户程序空间,也就是说其他进程通常状态下和内核是没有联系的(在编译内核的时候,内核代码被指定链接到 3G以上空间),因而内核数据也就自然被保护起来了。 那么在用户进程需要访问内核空间时如何做呢?Linux采用了个巧妙的方法:用户进程页表的前768项映射进程空间(3G,因为LDT 中只指定基地址为0,范围只能到0xc0000000),如果进程要访问内核空间,如调用系统调用,则进程的页目录中768项后的表项将指向 swapper_pg_dir的768项后的项,所以一旦用户陷入内核,就开始使用内核的页表swapper_pg_dir了,也就是说可以访问内核空间了。Linux三级分页管理把虚拟地址分成四个位段: 页目录PGD(PaGe Directory)、页中间目录PMD(Page Middle Directory)、页表PTE(Page TablE)、页内偏址。三级分页结构是Linux提供的与硬件无关的分页管理方式。当Linux运行在某种机器上时,需要利用该种机器硬件的存储管理机制来实现分页存储。Linux内核中对不同的机器配备了不同的分页结构的转换方法。对x86,提供了把三级分页管理转换成两级分页机制的方法,其中一个重要的方面就是把PGD与PMD合二为一,使所有关于PMD的操作变为对PGD的操作:在/include/asm-i386/pgtable.h中有如下定义: #define PTRS_PER_PTE 1024 #define PTRS_PER_PMD 1 #define PTRS_PER_PGD 1024从定义看出,页中间目录只有一项,在实施地址转换时,总是使页中间目录的这个表项与选中的页目录表项的内容一致,从而实现了在三级分页模式下,实施两级分页结构的地址转换。(2)页全局目录(即页目录)页全局目录表,最多可包含1024个页目录项,每个页目录项为4个字节,算起来正好一个页面,结构如图所示: 第0位:是存在位,Present标志:为1,所指的页(或页表页)就在主存中;为0,则所指页不在主存中,此时这个表项剩余的位可由操作系统用于自己的目的。如果执行一个地址转换所需的页表项或页目录项中Present标志被清0,那么分页单元就把该线性地址存放在控制寄存器cr2中,并产生14号异常:缺页异常。 第1位是读/写位,第2位是用户/管理员位,Read/Write标志:含有页或页表的存取权限(Read/Write或Read);User/Supervisor标志:含有访问页或页表所需的特权级。这两位为页目录项提供硬件保护。当特权级为3的进程要想访问页面时,需要通过页保护检查,而特权级为0的进程就可以绕过页保护,如图所示:第3位是PWT(Page Write-Through)位,表示是否采用写透方式,写透方式就是既写内存(RAM)也写高速缓存,该位为1表示采用写透方式。 第4位是PCD(Page Cache Disable)位,表示是否启用高速缓存,该位为1表示启用高速缓存。 第5位是访问位,Accessed标志:当对页目录项进行访问时,A位=1。每当分页单元对相应页框进行寻址时就设置这个标志。当选中的页被交换出去时,这一标志就可以由操作系统使用。分页单元从不重置这个标志;而是必须由操作系统去做。 第6位Dirty标志,对于页全局目录项,其始终为1。第7位是Page Size标志,只适用于页目录项。如果置为1,页目录项指的是4MB的页面,请看后面的扩展分页。第8位是Global 标志:只应用于页表项。这个标志是在Pentium Pro引入的,用来防止常用页从TLB高速缓存中刷新出去。只有在cr4寄存器的页全局启用(Page GlobalEnable ,PGE)标志置位时这个标志才起作用。 第911位由操作系统专用,Linux也没有做特殊之用(3)页表80386的每个页目录项指向一个页表,页表最多含有1024个页面项,每项4个字节,包含页面的起始地址和有关该页面的信息。页面的起始地址也是4K的整数倍,所以页面的低12位也留作它用,如图所示。第3112位是20位物理页面地址,除第6位外第05位及911位的用途和页目录项一样,第6位是页表项独有的,当对涉及的页面进行写操作时,D位被置1。4GB的存储器只有一个页目录,它最多有1024个页目录项,每个页目录项又含有1024个页面项,因此,存储器一共可以分成10241024=1M个页面。由于每个页面为4K个字节,所以,存储器的大小正好最多为4GB。(4)线性地址到物理地址的转换在两级页表时,当访问一个操作单元时,如何由分段结构确定的32位线性地址通过分页操作转化成32位物理地址呢?过程如图所示。第一步,CR3包含着页目录的起始地址,用32位线性地址的最高10位A31A22作为页目录的页目录项的索引,将它乘以4,与CR3中的页目录的起始地址相加,形成相应页目录项的地址。第二步,从指定的地址中取出32位页目录项,它的低12位为0,这32位是页表的起始地址。用32位线性地址中的A21A12位作为页表中的页面的索引,将它乘以4,与页表的起始地址相加,形成32位页表项地址。第三步,从指定的地址中取出32位页面地址,将A11A0作为相对于页面地址的偏移量,与32位页面地址相加,形成32位物理地址。下面,我们就通过一个实例来介绍一下常规分页是如何工作的。我们假定内核已给一个正在运行的进程分配的线性地址空间范围是0x20000000 到 0x2003ffff(3GB线性地址空间是一个上限,用户态进程只是引用其中的一个子集)。这个空间正好由64页面组成。其实我们并不必关心包含这些页的页框的物理地址,为什么呢?事实上,其中的一些页甚至可能不在主存中。我们只关注页表项中剩余的字段。让我们从分配给进程的线性地址的最高10位开始。这两个地址都以2开头后面跟着0,因此高10位有相同的值,即0x080或十进制的128。因此,这两个地址的页目录(Directory字段)都指向进程页目录的第129项。相应的目录项中必须包含分配给该进程的页表的物理地址。如果没有给这个进程分配其它的线性地址,页目录的其余1023项都填为0。中间10位的值(即Table字段的值)范围从0到0x03f,或十进制的从0到63。因而只有页表的前64个表项是有意义的,其余960表项都填0。假设进程需要读线性地址0x20021406中的字节。这个地址由分页单元按下面的方法处理:1) Directory字段的0x80用于选择页目录的第0x80目录项,此目录项指向和该进程的页相关的页表。2)Table字段0x21用于选择页表的第0x21表项,此表项指向包含所需页的页框。3)最后,Offet字段0x406用于在目标页框中读偏移量为0x406中的字节。如果页表第0x21表项的Present标志为0,则此页就不在主存中;在这种情况下,分页单元在线性地址转换的同时产生一个缺页异常。无论何时,当进程试图访问限定在0x20000000到0x2003ffff范围之外的线性地址时,都将产生一个缺页异常,因为这些页表项都填充了0,尤其是它们的Present标志都被清0。2. linux四级页表当今,Linux采用了一种同时适用于32位和64位系统的普通分页模型。前面我们看到,两级页表对32位系统来说已经足够了,但64位系统需要更多数量的分页级别。直到2.6.10版本,Linux采用三级分页的模型。从2.6.11版本开始,采用了四级分页模型:图中展示的4种页表分别被称作: 页全局目录(Page Global Directory) 页上级目录(Page Upper Directory) 页中间目录(Page Middle Directory) 页表(Page Table) 页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址。每一个页表项指向一个页框。线性地址因此被分成五个部分。图中没有显示位数,因为每一部分的大小与具体的计算机体系结构有关。 对于没有启用物理地址扩展的32位系统,两级页表已经足够了。从本质上说Linux通过使“页上级目录”位和“页中间目录”位全为0,彻底取消了页上级目录和页中间目录字段。不过,页上级目录和页中间目录在指针序列中的位置被保留,以便同样的代码在32位系统和64位系统下都能使用。内核为页上级目录和页中间目录保留了一个位置,这是通过把它们的页目录项数设置为1,并把这两个目录项映射到页全局目录的一个合适的目录项而实现的。 启用了物理地址扩展的32 位系统使用了三级页表。Linux 的页全局目录对应80x86 的页目录指针表(PDPT),取消了页上级目录,页中间目录对应80x86的页目录,Linux的页表对应80x86的页表。 最终,64位系统使用三级还是四级分页取决于硬件对线性地址的位的划分。 那么,为什么Linux是如此地热衷使用分页技术而对分段机制表现得那么地冷淡呢,因为Linux的进程处理很大程度上依赖于分页。事实上,线性地址到物理地址的自动转换使下面的设计目标变得可行: 给每一个进程分配一块不同的物理地址空间,这确保了可以有效地防止寻址错误。 区别页(即一组数据)和页框(即主存中的物理地址)之不同。这就允许存放在某个页框中的一个页,然后保存到磁盘上,以后重新装入这同一页时又被装在不同的页框中。这就是虚拟内存机制的基本要素。 每一个进程有它自己的页全局目录和自己的页表集。当发生进程切换时,Linux把cr3控制寄存器的内容保存在前一个执行进程的描述符中,然后把下一个要执行进程的描述符的值装入cr3寄存器中。因此,当新进程重新开始在CPU上执行时,分页单元指向一组正确的页表。 把线性地址映射到物理地址虽然有点复杂,但现在已经成了一种机械式的任务。Linux定义了大量的函数和宏来完成这项任务,其中大多数函数只有一两行。3.页面高速缓存由于在分页情况下,每次存储器访问都要存取两级页表,这就大大降低了访问速度。所以,为了提高速度,在386中设置一个最近存取页面的高速缓存硬件机制,它自动保持32项处理器最近使用的页面地址,因此,可以覆盖128K字节的存储器地址。当进行存储器访问时,先检查要访问的页面是否在高速缓存中,如果在,就不必经过两级访问了,如果不在,再进行两级访问。平均来说,页面高速缓存大约有98%的命中率,也就是说每次访问存储器时,只有2%的情况必须访问两级分页机构。这就大大加快了速度,页面高速缓存的作用如图所示。有些书上也把页面高速缓存叫做“联想存储器”或“转换旁视缓冲器(TLB)”。四 linux物理内存空间的管理1. zone的概念:首先,linux支持NUMA(非均质存储结构),物理内存管理的第一个层次就是介质的管理。pg_data_t结构就描述了介质。一般而言,我们的内存管理介质只有内存,并且它是均匀的,所以可以简单地认为系统中只有一个pg_data_t对象。 每一种介质下面有若干个zone。一般是三个:DMA、NORMAL和HighMem。l DMA:地址范围是0-16M,因为有些硬件系统的DMA总线比系统总线窄,所以只有一部分地址空间能够用作DMA,这部分地址被管理在DMA区域;l HighMem:物理地址超过896MB的高端内存。在32位系统中,进程虚拟地址空间是4G,其中内核规定34G的范围是内核空间,03G是用户空间(每个用户进程都有这么大的虚拟空间)。前面提到过内核的地址映射是写死的,就是指这34G地址空间对应的页表是写死的,它映射到了物理地址的01G上。(实际上没有映射1G,只映射了896M。剩下的空间留下来映射大于1G的物理地址,而这一部分显然不是写死的)。所以,大于896M的物理地址是没有写死的页表来对应的,内核不能直接访问它们(必须要建立映射),称它们为高端内存,仅由page cache和用户进程使用(当然,如果机器内存不足896M,就不存在高端内存。如果是64位机器,也不存在高端内存,因为地址空间很大很大,属于内核的空间也不止1G了);l NORMAL:不属于DMA或HighMem的内存就叫NORMAL,内核可直接映射。 在zone之上的zone_list代表了分配策略,即内存分配时的zone优先级。一种内存分配往往不是只能在一个zone里进行分配的,比如分配一个页给内核使用时,最优先是从NORMAL里面分配,不行的话就分配DMA里面的好了(HIGH就不行,因为还没建立映射),这就是一种分配策略。 每个内存介质维护了一个mem_map,为介质中的每一个物理页面建立了一个page结构与之对应,以便管理物理内存。 每个zone记录着它在mem_map上的起始位置。并且通过free_area串连着这个zone上空闲的page。物理内存的分配就是从这里来的,从 free_area上把page摘下,就算是分配了。(内核的内存分配与用户进程不同,用户使用内存会被内核监督,使用不当就段错误;而内核则无人监督,只能靠自觉,不是自己从free_area摘下的page就不要乱用。)2.物理内存的内核映射IA32架构中内核虚拟地址空间只有1GB大小(从3GB到4GB),因此可以直接将1GB大小的低端物理内存(从物理内存的最低地址0x00000000开始)映射到内核地址空间(这样,在内核空间(3GB到4GB)与物理内存(0x00000000到1GB)之间就建立了简单的线性映射关系,其中3GB(0xC0000000)就是物理地址与虚拟地址之间的位移量,称为PAGE_OFFSET),但超出1GB大小的物理内存(即高端内存)就不能映射到内核空间。为此,内核采取了下面的方法使得内核可以使用所有的物理内存: 高端内存不能全部映射到内核空间,也就是说这些物理内存没有对应的线性地址。不过,内核为每个物理页框都分配了对应的页框描述符,所有的页框描述符都保存在mem_map数组中,因此每个页框描述符的线性地址都是固定存在的。内核此时可以使用alloc_pages()和alloc_page()来分配高端内存,因为这些函数返回页框描述符的线性地址。 内核地址空间(从3GB到4GB)的后128MB专门用于映射高端内存,否则,没有线性地址的高端内存不能被内核所访问。这些高端内存的内核映射显然是暂时映射的,否则也只能映射128MB的高端内存。当内核需要访问高端内存时就临时在这个区域进行地址映射,使用完毕之后再用来进行其他高端内存的映射。 由于要进行高端内存的内核映射,因此直接能够映射的物理内存大小只有896MB,该值保存在high_memory中。内核地址空间的线性地址区间如下图所示: 从图中可以看出,内核采用了三种机制将高端内存映射到内核空间:永久内核映射,固定映射和vmalloc机制。3. 物理内存的页面管理:Linux对物理内存空间按照分页方式进行管理,在X86中一个页面大小是4KB,在Alpha、Sparc中时8KB。Linux设置了一个mem_map数组管理物理页面,mem_map在系统初始化时由free_area_init()函数创建,它存放在物理内存的底部(低地址部分)。Mem_map数组的元素是一个个page结构体,每一个page结构对应一个物理页面,定义如下:typedef struct page struct list_head list; /* 指向页面所在链表中的下一页 */struct address_space *mapping;/*指向正在映射的inode*/unsigned long index; /如果页面属于某个文件,代表页面在文件中的序号struct page *next_hash; /* 指向页高速缓存哈希表中下一项*/atomic_t count; /* 共享该物理页面的进程计数 */unsigned long flags; /* 页面各种不同的属性 */struct list_head lru; /* lru链表指针,指向active list */wait_queue_head_t wait; /* 指向等待该页面的进程等待队列 */struct page *pprev_hash; /* 与next_hash相对应 */ struct buffer_head * buffers;/* 当该页被用作磁盘块缓存时,指向缓存头部 */void *virtual; /* 页面对应的虚地址*/struct zone_struct *zone; /* 页所在的内存管理区zone */ mem_map_t;页面属性flags的含义:PG_locked: 当某个 IO 过程启动时,如果此页参与 IO 操作,需要设置。当 IO 操作完成的时候,清除该标志。 PG_error:当 IO 操作在该页上失败,设置该标志。 PG_referenced: 当 IO 操作刚访问过该页的时候,可以设置该标志位。当 kswapd 启动时,准备回收物理页的时候,将忽略该页,不会将其换出。但是 kswapd 会清除该标志。参考 refill_inactive 函数。 PG_uptodate:成功从磁盘读入该页,设置该标志。 PG_dirty:该页内容被修改,还没有更新到磁盘时,设置该标志。 PG_unused:保留,该标志真的没有用。 PG_lru:当该页在 inactive_list 或者 active_list 链表中时,设置该标志。 PG_active:当该页在 active_list 时,设置该标志位。 PG_slab:当该页被 slab 分配器使用时,设置该标志。 PG_skip:在 sparc/sparc64 平台上使用该标志。 PG_highmem:该页处在 high memory 区域,zone highmem 中的所有页框都要设置该标志。该标志一旦被设置,不能被修改。 PG_checked:只被 ext2 文件系统使用。 PG_arch_1:平台相关标志,x86 没有使用。 PG_reserved:具有该标志的物理页不能被交换到磁盘中。 PG_launder:当 shrink_cache 涉及的 IO 操作中涉及该页,设置该标志。4.物理内存的分配与回收:基于物理内存在内核空间中的映射原理,物理内存的管理方式也有所不同。内核中物理内存的管理机制主要有bootmem分配器、伙伴算法,slab高速缓存和vmalloc机制。其中伙伴算法和slab高速缓存都在物理内存映射区(Noamal分配物理内存,而vmalloc机制则在高端内存映射区分配物理内存:l bootmem分配器:是系统启动初期的内存分配方式,在伙伴系统、slab系统建立前内存都是利用bootmem分配器来分配的,只对低于 896M 的物理内存进行分配。伙伴系统框架建立起来后,bootmem会过度到伙伴系统,bootmem大致思想就是收集内存中的可用内存,然后建立bit位图,然后需要的内存从这些空闲内存中分配,分配了就标记占用,当然这种分配方式很低效,但是由于只占用启动阶段很少一部分,所以也大可接受了。l 伙伴算法:负责大块连续物理内存的分配和释放,以页框为基本单位。该机制可以避免外部碎片。l Slab高速缓存:负责小块物理内存(小于一个页框)的分配,并且它也作为高速缓存,主要针对内核中经常分配并释放的对象。l vmalloc机制:使得内核可通过连续的线性地址来访问非连续的物理页框,这样可以最大限度的使用高端物理内存。(1)伙伴算法:Linux2.6.18的伙伴算法把所有的空闲页面分为11个块组,每组中块的大小是2的幂次方个页面,例如,第0组中块的大小都为20(1个页面)第1组中块的大小都为21(2个页面),第10组中块的大小都为210(1024个页面)(因此,内核最大可以申请1024个连续页框,对应4MB大小的连续内存)。也就是说,每组中内存块的大小是相同的,且这些同样大小的内存块形成个双向循环链表,链表中元素的类型为men_map_t(即struct page结构)。Linux定义了一个元素类型为free_area_t的数组free_area来管理上述各空闲页块组,它采用了位图与链表相结合的方式。free_area_t结构的定义如下:Typedef struct free_area_structstruct list_head free_list;unsigned long *map;free_area_t;其中,free_list为双向链表的头指针,这些链表是由空闲页面的page结构体双向链接,每个节点中包含空闲页帧号;map:Linux对内存页面块的每种划分都对应一个位图,页块越小位图越长,如free_area0中的map指向内存按照1个页面划分时的位图,free_area1中的map指向内存按照2个页面划分时的位图,Free_areak项位图的每位,描述的就是大小为2k个页面的两个伙伴块的状态。位图中每一位(bit)表示一对Buddy页面的使用情况,当一对Buddy的两个页面块中有一个是空闲的,而另一个全部或部分被占用时,该位置1。当这两个页面块都是空闲,或都被全部或部分占用时, 对应的位置0。 指针map是指向相应页面块的位图。在请求内存分配时,系统按照Buddy算法,根据请求的页面数在free_area对应的空闲页块组中搜索。若请求的页面数不是2的整数次幂,则按照稍大于请求数的2的整数次幂的值搜索相应的页面块组。当相应的页块组中没有可使用的空闲页面块时就查询更大一些的页块组,在找到可利用的空闲页面块后,分配所需的页面。当某一空闲页面块被分配后,若仍有剩余的空闲页面,则根据剩余页面的大小把它们加入到相应的页块组中(通常是挂入下一级的空闲块链表中)。在内存页面释放时,系统将做为空闲页面看待。然后检查是否存在与这些页面相邻的其它空闲页块,若存在,则合为一个连续的空闲区按Buddy算法重新分组。 以上过程的逆过程就是块的释放过程,这也是该算法名字的来由。满足以下条件的两个块称为伙伴: (1)两个块的大小相同; (2)两个块的物理地址连续。 伙伴算法把满足以上条件的两个块合并为一个块,该算法是迭代算法,如果合并后的块还可以跟相邻的块进行合并,那么该算法就继续合并。图 伙伴系统使用的数据结构上图中,free_area数组的元素0包含了一个空闲页(页面编号为0);而元素2则包含了两个以4个页面为大小的空闲页面块,第一个页面块的起始编号为4,而第二个页面块的起始编号为56。buddy 的接口 api 如下:(定义在 mm/page_alloc.c 和inculude/linux/mm.h 中)alloc_page:分配一个页,返回页物理地址 alloc_pages:分配 2 的 n 次方个页,返回页物理地址 get_free_page :分配一个页并初始化为 0,返回页虚地址 _get_free_page:分配一个页,返回页虚地址 _get_free_pages:分配 2 的 n 次方个页并初始化为 0,返回页虚地址 _get_dma_page:从 zone dma 中分配 2 的 n 次方个页,返回页物理地址 _free_pages:从指定的物理地址,回收 2 的 n 次方个页 _free_page:从指定的物理地址,回收一个页 free_page:从指定的虚地址,回收一个页 不管使用哪个分配 api,这些 api 最终都调用_alloc_pages 来获取空闲物理页。_alloc_pages 不允许直接调用,必须要通过上述 api 提出分配请求。同样,free api 的核心是_free_pages_ok 函数,同样也不允许直接调用。 (2)Slab分配器伙伴系统是以页块为单位进
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025标准版公寓式住宅购房合同样本
- 兼职员工签订协议要点解析
- 2025年高压电工高压设备故障诊断与应急处理试题型
- 2025年消防执业资格考试题库基础知识试题解析
- 2025年机械安全操作规范考试题库(机械自动化维修工认证)试题
- 2025年高压电工考试题库:基础理论知识应用与深化
- 2025年护士执业资格考试内科护理学专项护理心理辅导试题解析
- 2025年咖啡师职业技能测试卷:咖啡豆品种识别与搭配试题
- 2025年托福口语模拟测试卷:食品安全与消费者权益试题
- 2025年语言学章节试题及答案
- 2025年全国中小学校党组织书记网络培训示范班在线考试题库及答案
- 全国2025年质量月活动知识竞赛题库及答案
- 2025全国农业(水产)行业职业技能大赛(水生物病害防治员)选拔赛试题库(含答案)
- Unit 4 Reading and Thinking 学案-高中英语人教版(2019) 选择性必修第一册
- 中国新生儿复苏指南解读(2021修订)
- 广告及宣传印刷品制作服务方案
- 安全评价工作程序框图流程图
- 医共体成员单位人力资源工作制度
- 西式烹调师中级理论试卷 答案
- 如何建立高效学习小组
- 汽车系统动力学与控制 教学大纲
评论
0/150
提交评论