虚拟存储器是操作系统中的重要内容,也是理解的难点和重点,虽然程序员不用直接和虚拟存储器打交道,但是理解虚拟存储器能够更好的理解操作系统的存储器管理。虚拟存储器实质就是硬盘中的一部分存储器,可以将其作为缓存。
虚拟存储器的最大好处是实现在小内存系统(较小的物理内存)的应用。比如一个linux进程的虚拟存储器大小是4G,其中前3G作为用户空间,后1G作为内存的空间。但是实际的物理内存是一个2G甚至更小的物理内存时如何处理呢??这时虚拟存储器的概念就体现其巨大的优势。
虚拟地址和物理地址之间又是如何的转换呢?这些都是需要了解的。虚拟地址是由CPU产生,然后虚拟内存需要通过MMU转换为物理内存。
虚拟存储器和物理存储器的关系:
虚拟存储器文件系统将物理存储器和虚拟存储器分成等大小的页面,比如4K一页,这样物理存储器和虚拟存储器分解成页数量不同的存储器页。通过一种叫做页表(PTE)的结构体实现虚拟页到物理页的联系。具体的联系就是:
页表常住在内存中,页表的大小一般等于虚拟存储器的页数。每一个页表由一个有效位和一个地址位构成。当有效位是1事,说明虚拟页中的数据缓存到物理内存中的起始地址,而如果有效位为0,后面的地址位为null,则说明该虚拟页还没有分配。如果地址位不为null,则指出了该页表中在虚拟存储器的起始地址,但是还没有缓存到物理内存中。这样就通过了页表实现了虚拟页和物理页之间的联系。因此理解页表是很重要的。多级页表结构以及STB等技术都是为了减少常驻空间或者提速。
虚拟地址由VPN和VPO构成,而物理地址由PPN和PPO,其中PPO=VPO,其中VPN是指虚拟存储器中的页号,而PPN是物理存储器的页号,这个页号的转换关系与页表有关。VPO和PPO都是在该也中的偏移量。通过页表和偏移量就能实现具体位置的访问。
具体的加载方法就是将某个虚拟存储页得数据缓存到任意的物理存储页,这样就实现了在虚拟存储器中连续页,映射到物理内存中并不一定是连续。这样也减小了对连续内存的要求。通过一个页表的引入就能实现虚拟页到物理页得映射关系。
在linux中内核为每个进程分配一个单独的页表。这样每个进程都有了相同大小的虚拟存储空间(4G)。每个虚拟存储的空间分配也是相同的,每个段区的起始地址也是相同的,这样就简化了共享、加载、链接等过程。
linux采用mmap实现文件到进程虚拟存储器的加载过程,加载两种类型的文件(对象):普通文件、匿名文件(二进制)。
同时对象的形式也是多样的,主要是理解共享对象和私有对象,私有对象又主要理解私有写时页保护。
共享对象是指将一个共享对象映射到各个进程的共享段中,然后通过页表将各个进程的共享段加载到统一的物理存储器中,这样各个进程都能实现对共享,且在物理存储器中只有一个对象的拷贝。各个进程对共享对象的写操作都会反应到其他的进程中以及磁盘文件中。
多个进程对一个似有对象的映射比较有意思。私有对象的开始生命周期的方式与共享对象相似,即在物理存储器中只有一个私有对象的拷贝,但是设置访问的权限为可读。如果只是读过程,所有进程对似有对象的访问与共享对象没有差别,但是如果是某一个进程试图对私有对象进行写操作就会引发保护故障,然后在故障处理程序中将(内存中)被写的页面内容也拷贝到一个新的存储页面中,并设置好PTE,将新创建的页面设置为可写。然后对新创建的页面进行写操作,这样的操作就不会导致对私有对象的写操作反应到磁盘文件中。这种技术称之为“写时页拷贝”。
fork函数的理解主要就是弄清楚两种方式的差别。fork中创建了一个子进程,其中子进程和父进程的内容相同,因此开始是两个进程共享一个空间,但是两个进程分别会试图写一些段(data、bss等),这时发生了“写时页拷贝”,将需要写的页内容拷贝到另一个新创建的页中,此时再次发生写操作,这也就是为什么打开的一些文本描述符在子进程也能找到,就是因为两个进程对一个私有对象没有进行写操作时是对一个物理存储器的一份拷贝,当某一个进程写操作时,这时发生了写时页拷贝,再次对新的页进行操作即可完成且不会影响别的进程。但是本来的共享区仍然满足共享的特征。但是.text等段不会发生写时页拷贝,因此父子进程还是共享代码段。这就是为什么fork函数后两个进程是共享代码段的原因(发生了私有对象的写时页拷贝)。
阅读(764) | 评论(0) | 转发(0) |