Mlock分析报告.docx
《Mlock分析报告.docx》由会员分享,可在线阅读,更多相关《Mlock分析报告.docx(13页珍藏版)》请在冰豆网上搜索。
Mlock分析报告
Mlock分析报告
第一部分 Linux存储管理机制
一80386体系结构的内存管理功能
80386有两种工作模式:
实地址模式和虚拟地址模式(又称保护模式)。
实地址模式是为了与8086兼容而设置的。
这时,只能寻址1MB的地址空间,不能启用分页机制,不区分特权,分段功能也受到了极大的限制。
而在保护模式下,分段机制得到加强。
虚地址空间可为16K个段,每个段的大小可变,最大达到4GB。
段具有独立的属性,包括段长、起始地址、段类型和保护位等。
而且80386提供片内页管理机制,通过两级页表来实现线性地址和物理地址之间的映射,为LINUX的虚拟内存管理提供了直接的支持。
80386的虚拟地址模式同时使用了分段机制和分页机制两级地址转换机制来进行地址转换。
第一级使用分段机制,把包含段地址和段内偏移量的虚拟地址,转换为一个线性地址。
第二级使用分页机制,把线性地址转换为物理地址。
如图所示。
存储管理子系统实现以下这些功能:
1.更大的地址空间:
操作系统似的系看起来拥有一个比它实际拥有的地址大的多的地址空间,虚存能够实
现比物理存储空间许多倍的地址空间.
2.保护
每一个进程在系统中都有它自己的地址空间,虚地址空间使得各个进程完全分离开来,
因此一个进程运行一个应用程序完全不会影响到另一个进程.另外,硬件级的虚存机制为内存提供了写保护,这就保证数据不会被非法的应用程序重写.
3.存储映像
存储映像用来把图象或数据文件映像到一个进程的地址空间.在存储映像中,文件的内容直接和进程的虚拟地址空间相连
4.平衡物理存储空间的分配
存储管理子系统允许每一个运行的进程公平地竞争系统的物理存储空间.
5.共享虚存空间
二LINUX的分页管理机制
在LINUX中,每一个用户进程都可以访问4GB的线性虚拟内存空间。
其中从0到3GB的虚拟内存地址是用户空间,用户进程可以直接对其进行访问。
从3GB到4GB的虚拟内存地址为系统空间,存放仅供核心态访问的代码和数据。
当用户进程试图对其访问时,就会触发处理器的特权级转换(从处理器的特权级3切换到特权级0),即从操作系统的用户态切换到核心态。
所有进程从3GB到4GB的虚拟空间映像都是一样的,LINUX以此方式让核心态进程共享代码段和数据段。
核心态虚拟空间从3GB到3GB+4M的一段(也就是进程页目录第768项所管辖的范围),被影射到物理空间0到4M段。
因此,进程处于核心态时,只要通过访问虚拟空间3GB到3GB+4M段,即访问了物理空间0到4M段。
上述两种空间对用户进程来说都是透明的,用户进程所访问的内存地址都是连续的4GB线性虚拟地址。
因此,我们首先关心的是LINUX是如何划分虚拟空间的。
LINUX采用“按需调页”(DemandPaging)技术管理虚拟内存。
标准LINUX的虚存页表应为三级页表,依次为页目录(PGD,PageDirectory)、中间页目录(PMD,PageMiddleDirectory)和页表(PTE,PageTable)。
PGDPMDPTEPageFrame
图1.LINUX的三级页表结构
每一个page都有一个标识符PFN(PageFrameNumber),因此,虚地址也就由两部分组成:
一个页内偏移地址和一个PFN,每当处理器遇到一个虚地址,它就将虚地址的PFN转化成一个物理页面的起始地址,然后,在根据page_offset的值,找到实际的物理地址,这个过程中间要通过一个页表来实现,其转换过程如下:
图3.虚地址经过页表转换成实地址的过程
在一个页表中,每一行包含了下列数据:
●有效位:
用来描述页表的这个入口是否可用
●这个入口所对应的物理页表数
●存储控制信息:
其中包括是否可写,是否可执行
页表的访问是通过把虚地址的PFN作为页表的偏移量来访问的.例如PFN=5,则访问的页表的入口为第六个表项(0为第一个表项).
处理器处理一个页表的地址时,首先必须算出页表的PFN和页内偏移量.要实现表的大小为2的幂级大小,这个可以通过掩码(masking)和移位(shifting)来实现.处理器利用虚地址的 PFN作为目录来检索要处理处的页表的入口.如果这个入口是可用的,处理器从页表中取出这个页表项所对应的物理地址的PFN.如果这个入口不可用,则说明进程试图存取一个不在内存中的页面.在这种情况下,处理器不能处理,应该叫给操作系统来实现,以便操作系统能够处理它.进程通过发出一个 page fault来通知操作系统,然后操作系统就能够解释发出页面失配及其原因,
如果进程从页表中访问一个可用的入口,则进程从页表中取得物理地址的PFN,然后把这个数乘上页表的大小,以便的到页表在物理内存中的起始地址,最后在加上偏移量就得到了要取数据或指令的正确内存地址.
三 Linux的虚存管理
1.共享虚存
虚存使得几个进程更容易共享存储区.所有存储操作都是通过页表来实现的,每一个进程都有它私人的页表.如果两个进程想要共享一页物理内存,它的页帧号(PFN)必须在两个进程的一个页表入口中能够找得到.
每一个进程的虚存都是有一个 mm_struct结构来描述的,其中包括正在执行的映像的信息和一个指向许多vm_area_struct结构的指针,每一个vm_area_struct描述一段虚存的起始地址和结束地址,进程对那一段的存取权限和对这段存储区的一组操作。
当一个映像被映射到一个进程的虚地址中,一组vm_area_struct结构就产生了,每一个vm_area_struct结构都代表一段可执行的映像,可执行代码、初始化数据(变量)、未初始化数据等等。
Linux 支持许多标准的虚存操作,当 vm_area_struct被产生后,一组对于虚存的正确的操作也就跟他联系起来。
2.按需调页中用到的虚存段的操作
当一个影响被影射进进程的虚地址后,它就可以执行了。
只要影象的起始地址被印象进物理内存,它就要存取一个尚不在内存中的虚存段。
当一个进程访问一个尚没有合法的页表入口的虚地址,它就回向操作系统报告一个页面失配的错误。
页面失配错误描述发生页面失配的虚地址和引起页面失配的存储类型。
Linux必许找到表示发生页面失配的存储区的虚存段,由于沿着 vm_area_struct结构搜索对于处理页面失配的效率有非常重要的关系,这些虚存段被连接成AVL树结构,如果表示页面失配的虚存段,则这个进程访问了一个非法的虚地址。
Linux将用信号通知进程,让进程发出一个SIGSEGV信号,如果进程不能处理这样的信息,则进程将要终止。
3.SystemV共享内存页面
SystemV共享内存是一种内处理通讯机制(inter-processcommunicationmechanism),允许两个或多个共享同一段虚存的进程互相通讯。
进程如何用这种方式共享内存在IPC-chapter一章中有详细的说明,现在可以很清楚地说SystemV的每一块被共享的内存都是由一个shmid_ds的数据结构描述,这个数据结构中包含一个指针指向一个vm_area_struct 数据结构形成的队列,每一个共享这一段虚存的进程在这一个链表中都有一个节点,vm_area_struct数据结构描述了在进程的什么地方开始使用共享的虚存。
对于SystemV中的共享虚存,指向它的所有vm_area_struct 结构都是通过vm_next_shared和vm_prev_shared连接起来的。
每一个shmid_ds数据结构也包含一个页表入口的链表,其中每一个shmid_ds数据结构包含了一个共享的虚页所映像到的物理页面入口。
尽管虚存管理允许进程有不同的地址空间(虚),但有时也需要进程共享同一段存储空
间.例如有可能有几个进程运行于系统中且同时使用bash命令的 shell.此时并不是各进程在自己的地址空间中分别拥有一个bash的拷贝,更好的方法是在物理空间上有一个 bash的拷贝,所有运行bash的进程读共享它,动态链接库是另一个多进程共享执行代码的例子.存储共享机制也可以用来实现进程内部通讯,两个或多个进程通过共享的存储空间来交换信息,
在INTEL微机上,LINUX的页表结构实际为两级。
80386体系结构之页管理机制中的页目录就是PGD,页表就是PTE。
而PMD和PGD实际上是合二为一的。
在虚存段的加锁操作中,主要涉及到的是对于页表的操作:
在/include/linux/page.h中通过一些宏定义来实现,
#definePAGE_SHIFT12
#definePAGE_SIZE(1UL<#definePAGE_MASK(~(PAGE_SIZE-1))
在page.h中通过定义PAGE_SHIFT为12,然后定义PAGE_SIZE为一个无符号长整型左移PAGE_SHIFT位,从而固定page的大小为4K,而PAGE_MASK的作用比较多,它本身是一个无符号长整型数,其低十二位都为0,高位读为1。
它的作用在mlock.c的分析中将要进行详细说明。
虚存空间的分配和管理:
每当启动一个新进程,LINUX都为其分配一个task_struct结构,内含saved_kernel_stack、ldt、tss、mm等内存管理信息,详见进程管理部分。
其中,task_struct结构内嵌mm_struct结构,此结构包含了用户进程中与存储有关的信息。
structmm_struct{
intcount;
pgd_t*pgd;/*进程页目录的起始地址,如图X-6所示*/
unsignedlongcontext;
unsignedlongstart_code,end_code,start_data,end_data;
unsignedlongstart_brk,brk,start_stack,start_mmap;
unsignedlongarg_start,arg_end,env_start,env_end;
unsignedlongrss,total_vm,locked_vm;
unsignedlongdef_flags;
structvm_area_struct*mmap;/*指向vma双向链表的指针*/
structvm_area_struct*mmap_avl;/*指向vmaAVL树的指针*/
structsemaphoremmap_sem;
}
●start_code、end_code:
进程代码段的起始地址和结束地址。
●start_data、end_data:
进程数据段的起始地址和结束地址。
●arg_start、arg_end:
调用参数区的起始地址和结束地址。
●env_start、env_end:
进程环境区的起始地址和结束地址。
●rss:
进程内容驻留在物理内存的页面总数。
在mm_struct中,有一个mmap指针,用来指向vma双向链表,用来表明这个进程所涉及的页面的虚存段表示,其中vm_area_struct的数据结构描述见下面,vm_area_struct中同样也有一个指针vm_mm用来指向mm_struct,这就把虚存段的描述同用户进程联系起来。
structvm_area_struct{
structmm_struct*vm_mm;/*VMareaparameters*/
unsignedlongvm_start;
unsignedlongvm_end;
/*linkedlistofVMareaspertask,sortedbyaddress*/
structvm_area_struct*vm_next;
pgprot_tvm_page_prot;
unsignedshortvm_flags;
/*AVLtreeofVMareaspertask,sortedbyaddress*/
shortvm_avl_height;
structvm_area_struct*vm_avl_left;
structvm_area_struct*vm_avl_right;
/*Forareaswithinode,thelistinode->i_mmap,forshmareas,
*thelistofattaches,otherwiseunused.
*/
structvm_area_struct*vm_next_share;
structvm_area_struct**vm_pprev_share;
structvm_operations_struct*vm_ops;
unsignedlongvm_offset;
structfile*vm_file;
unsignedlongvm_pte;/*sharedmem*/
};
●vm_mm指向这个虚存段所属的mm_struct结构的指针
●vm_start,vm_end:
虚存段的起始、结束地址
●vm_next:
同属于一个mm_struct的vm_area_struct形成一个链表,vm_next指向下一个此链表中的vm_area_struct节点
●vm_page_prot:
●vm_flags:
虚存段的标志位,表示虚存段是否加锁
●vm_avl_height、vm_area_left、vm_area_right:
为了便于查找,同一个mm_struct中的vm_area_struct组织成一个avl树,三个参数分别表示avl树的高度、当前节点在avl树中的左儿子节点和右儿子节点.
●vm_next_share、vm_pprev_share:
●vm_ops:
指向一个vm_area_struct操作集的指针,在此操作中,包含了对vm_area_atruct的操作.
●vm_offset:
由于虚存段的组织需要映射到页的存储机构中,其中的vm_offset表示虚存段的起始地址对于此虚存段起始地址所在也的偏移量.
●vm_file:
指向虚存段所在文件的指针,同时对应的file中也有一个计数器f_count用来表示属于这个文件的虚存段的数目.
●vm_pte:
所在页表的入口
在Linux中采用段页式来进行存储区的管理
其中进程的虚存管理的数据结构图示如下:
图2.进程的虚存管理数据结构
第二部分 虚存加锁
1.虚存加锁的作用
虚存加锁在进程的页面换入和换出的工作中发挥了重要作用,在页面的换出时,如果表示页面的虚存段被加锁,则虚存段所表示的页面不能被换出,这种机制在使一些较常用的进程固定于内存,似的调用这些进程的书牍大大加快,同时也减少了许多页面调入和换出的工作,从而似的CPU的利用率更高。
进程进行页面的换出时,首先要找到表示该页面的vm_area_struct结构,如果vm_area_struct结构中有加锁的标志,则该页面不能被换出。
操作系统在检查其他的页面,其采用的算法是LRU(LeastRecentlyUsed)算法。
器具体的实现在页面的管理机制一章中有详细的说明。
2.原代码分析(mlock.c中的函数分析)
1)staticinlineintmlock_fixup_all(structvm_area_struct*vma,intnewsflags)
该函数用于对整个虚存段进行加锁,其中vma指向所要加锁的虚存段,newsflags表示对虚存段进行操作后所加的标志。
其中当newsflags=VM_LOCKED时,表示对虚存段加锁;当newsflags中的第十二位数位0时,则表示对内存解锁。
VM_LOCKED的定义见/linux/include/linux/mm.h
其定义为:
#defineVM_LOCKED0x2000
即将标志为的第十二位赋值为1(从左到右)则表示虚存段为加锁状态,如此位为0,则表示虚存段为解锁状态。
函数图示如下:
2)staticinlineintmlock_fixup_start(structvm_area_struct*vma,
unsignedlongend,intnewflags)
该函数用于对虚存段的一部分加锁,加锁的部分为从虚存段的起始地址开始,到end地址结束,参数的含义跟上一函数的相同,其中end表示要加锁的虚存段的结束地址。
图示如下:
3)mlock_fixup_end(structvm_area_struct*vma,
unsignedlongstart,intnewflags)
该函数用于对虚存段的一部分加锁,其加锁的部分为从虚存段中的一个地址start开始,到虚存段的结束地址这一段进行加锁。
其中start表示要加锁的起始地址
图示如下:
4)mlock_fixup_middle(structvm_area_struct*vma,unsignedlongstart,
unsignedlongend,intnewflags)
该函数用于对虚存段中从start开始到end 结束的一段进行加锁,其中start表示加锁的开始地址,end表示加锁的结束地址。
图示如下:
5)mlock_fixup(structvm_area_struct*vma,unsignedlongstart,
unsignedlongend,unsignedintnewflags)
此函数用于虚存段的加锁,其中start表示要加锁的虚存段的起始地址end表示要加锁的虚存段的结束地址并且根据条件,调用不同的函数来完成加锁操作。
函数流程如下:
插入函数流程图说明:
函数中的细节说明
1.计算页表的表达式:
pages=(end-start)>>PAGE_SHIFT;
由于PAGE_SHIFT在/linux/include/page.h中定义为:
#definePAGE_SHIFT12
前面以述及通过PAGE_SHIFT的定义,规定了页面的大小为4k
这就使得加锁的虚存段的长度(end-start)向右移动了十二位,即使这个长度进行4k的求模运算,最后得到的结果正好是进行加锁操作的页面数。
如果加锁标志说明为解锁,这加锁的页面数目应当减少pages,此时令
pages=-pages;
如果为加锁操作,则加锁的页面数应当加上pages,此时pages不变号,最后当
vma->am_mm->locked_vm+=pages;
这条语句执行后,最后所得的即为加锁的页面总数
6)do_mlock(unsignedlongstart,size_tlen,inton)
此函数调用用于对于一个知道起始地址和要进行加锁操作的虚存段的长度,其中start表示要加锁的虚存段的起始地址,len表示要加锁的长度,on表示对虚存段的操作:
如果on=1,则对虚存段进行加锁;如果on=0,则对虚存段进行解锁.
函数流程图如下: