Linux虚拟内存管理进程地址空间2进程地址空间管理:管什么?1.管3GB的用户虚拟地址空间:•哪些区域被分配了,哪些区域是空闲的•被分配区域的用途、权限等属性信息2.页表的相应部分•需要创建映射时再创建页表,不需要时释放页表(而内核空间对应的页表永不释放)3.被映射给进程的物理内存•需要时动态申请,不需要时释放•不要求物理内存必须连续•回收暂时不用的页框(Ch.17)3进程地址空间管理2个原则1.“先申请,后使用”“不用了,及时释放”2.尽量推迟物理内存的分配时机4内存描述符(mm_struct)1.每个进程都有自己独立的3GB的线性地址空间。该空间不同的区域有不同的用途:代码区、全局数据区、栈区、堆区…2.与进程地址空间有关的全部信息都包含在一个叫内存描述符的数据结构中。5相关的数据结构1.task_struct用于描述一个进程,task_struct中有一个字段:structmm_struct*mm;2.mm_struct用于描述一个进程虚拟地址空间的布局。mm_struct中有一个字段mmap,指向了一个链表。structvm_area_struct*mmap3.mmap链表中的一个节点vm_area_struct记录了实际分配的一个内存区域。进程描述符(task_struct)structtask_struct{/*thesearehardcoded-don'ttouch*/volatilelongstate;/*-1unrunnable,0runnable,0stopped*/longcounter;longpriority;….…………/*filesysteminformation*/structfs_struct*fs;/*openfileinformation*/structfiles_struct*files;/*memorymanagementinfo*/structmm_struct*mm;….…………}内存描述符内存描述符(续)内存描述符(再续)10线性区(vm_area_struct)1.必须记录进程地址空间中哪些区域被使用了,而哪些区域空闲。2.如果进程地址空间中的一个区域被分配给了进程,则内核会创建一个对应的线性区,也叫虚拟内存区(VMA)3.属于同一个进程地址空间的线性区形成一个链表。4.不同的VMA绑定不同的访问权限和属性:可读、可写、可执行、私有、共享、锁定等。5.不同的VMA中存放了不同类型的数据:代码、全局变量、只读数据、动态库…6.为了加速查找空闲虚拟内存区,Linux同时将同一进程的所有线性区组织成一棵红黑树。7.线性区的开始和结束都必须4KB对齐VirtualMemoryAreas与进程地址空间相关的数据结构mm_struct13触发进程空间的内存动态分配的操作1.进程创建:fork,execv,clone2.扩充/紧缩堆内存:brk,sbrk3.创建文件内存映射或匿名映射:mmap,mremap,munmap4.创建进程间共享内存:shmat,shmdt5.当用户堆栈不够用的时候,需要扩展堆栈6.上述操作都可能引起线性区数据结构的创建和删除。14如何实现进程地址空间动态内存分配1.查找进程虚拟地址空间是否存在大小合适的空闲区域。2.如果有,则创建相应的线性区描述符vm_area_struct,并将其插入链表mmap3.线性区创建成功后,立即返回(注意,没有分配相应的物理页框,也没有修改页表!)4.当用户程序访问刚刚动态分配的内存时,会触发缺页异常。5.在缺页异常的中断服务程序中按“一次一页”的方式分配物理页框,并修改页表。2020/1/10LinuxOSanalysis15/24增加或删除一个线性区处理线性区的函数内核进程需要对一个线性区进行处理,比如确定一个给定线性地址是否存在于一个线性地址空间中find_vma(),查找一个线性地址所属的线性区两个参数:进程内存描述符的地址mm和线性地址addrfind_vma_intersection(),查找一个与给定地址区间重叠的线性区get_unmapped_area(),查找一个空闲的地址区间insert_vm_struct(),向内存描述符链表中插入一个线性区split_vma(),拆分线性区创建一个线性区间mmap()和do_mmap(),创建一个线性区根据file参数映射指定的文件中偏移量为offset,长度为len的一段内容addr参数指明从何处开始查找一段可用的空闲线性地址区间Prot参数指定这个区间所包含的页的存取权限flags参数指定这个创建的线性区本身的一些标志staticinlineunsignedlongdo_mmap(structfile*file,unsignedlongaddr,unsignedlonglen,unsignedlongprot,unsignedlongflag,unsignedlongoffset)include/linux/mm.h删除一个线性区间munmap()和do_munmap()do_munmap()函数从进程地址空间中删除一段线性空间mm参数指向了当前进程的内存描述符addr参数为线性区的起始地址len参数指明要删除的区间大小intdo_munmap(structmm_struct*mm,unsignedlongaddr,size_tlen)mm/mmap.c19mmap内存映射1.利用mmap函数可以将一个磁盘文件或者其它设备对象映射到虚拟内存空间。文件被映射到多个页上,所有页的大小之和大于等于文件大小。20mmap内存映射1.void*mmap(void*start,size_tlength,intprot,intflags,intfd,off_toffset)•start:要映射到的虚拟内存区域的起始地址,通常都是用NULL,NULL表示由内核来指定该内存地址•length:要映射的内存区域的大小•prot:期望的内存保护标志,是以下的某个值,可以通过or运算符组合到一起1.PROT_EXEC可执行;2.PROT_READ可读;3.PROT_WRITE可执•flags:指定映射对象的类型,映射选项和映射页是否可以共享。MAP_SHARED/MAP_PRIVATE/MAP_LOCKED/MAP_ANONYMOUS•fd:文件描述符(由open函数返回)•offset:表示被映射对象(即文件)从那里开始对映,通常都是用0。该值应该为大小应为PAGE_SIZE的整数倍•返回值:成功则返回被映射虚拟内存区的指针,失败返回-1。21mmap内存映射1.intmunmap(void*start,size_tlength)取消映射关系2.intmsync(constvoid*start,size_tlength,intflags)将内存中的内容同步到文件思考:mmap函数执行时会一次性将整个被映射文件读入内存吗?即用mmap函数映射一个大文件时会很耗时吗?22mmap的应用1.快速读写大文件,简化程序逻辑。2.快速读写I/O设备(如V4L库)3.创建进程间共享内存(可利用/dev/zero文件)4.在用户态的应用程序中直接读写物理内存(即读写指定物理地址上的内存单元)。•/dev/mem是整个物理内存的全映像文件•open(/dev/mem,O_RDWR|O_SYNC),然后mmap,接着就可以用mmap的地址来访问物理内存•只有root用户才能这样做!•如果CPU是内存与I/O统一编址方式,还可以利用上述方法访问外设的寄存器,编写用户态的设备驱动程序。5.快速分配大内存(mmap配合MAP_ANONYMOUS参数)23缺页异常(14号中断)24缺页异常(14号中断)