操作系统的内存模式

2015-04-28 15:25:40 sunmc1204953974 阅读数 2587
  • Linux操作系统介绍

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    1481人学习 CSDN就业班
    免费试看

操作系统概念学习笔记 4

操作系统结构和操作简述

操作系统结构

多道程序设计

多道程序设计指的是允许多个程序同时进入一个计算机系统的主存储器并启动进行计算的方法。也就是说,计算机内存中可以同时存放多道(两个以上相互独立的)程序,它们都处于开始和结束之间。只要有一个程序或任务可以执行,cpu就不会空闲。

但是不能提供与计算机系统直接交互的能力。

分时系统(多任务)

分时系统(多任务)是多道程序设计的延伸,在分时系统中,虽然cpu还是通过在作业之间的切换来执行多个作业,但是由于切换频率很高,用户可以在程序运行期间与之进行交互。

分时是指多个用户分享使用同一台计算机。多个程序分时共享硬件和软件资源。分时操作系统是指在一台主机上连接多个带有显示器和键盘的终端,允许多个用户通过主机的终端以交互方式使用计算机,共享主机中的资源。

共享需要一种交互式计算机系统,它能提供用户和系统之间的直接通信。

分时允许许多用户同时共享计算机,每个用户只要少量cpu时间,随着系统从一个用户切换到另一个用户,每个用户会感觉到整个系统只为自己所用。

装到内存并执行的程序通常被称为进程(process),当进程执行时,通常他只执行较短的一段时间,,此时他并未完成或者需要进行IO操作。由于交互IO通常按人的速度来运行,会运行很长时间,例如每秒5个字符的输入速度对计算机相当的慢了。用户交互输入时,系统为了不让cpu空闲,会将cpu 切换到其他用户的程序。

分时和多道程序设计需要在存储器中同时保存有几个作业。由于主存较小,通常在开始将作业存储在磁盘的作业池中。作业调度(job scheduling)按照一定的算法,从作业池或后备队列中选取某些作业调入内存,并为它们创建进程、分配必要的资源。

在分时操作系统中,操作系统必须保证合理的响应时间,这需要交换即进程在内存与磁盘之间的来回转移来实现。更常用的方法是使用虚拟内存(virtual memory),虚拟内存允许将一个执行的作业不完全放在内存里,且他可以比物理内存大。还有,虚拟内存将内存抽象成一个 庞大且统一的存储数组,将用户索理解的逻辑内存(logical memory)与真正的物理内存区别开来。

操作系统操作

现代操作系统是中断驱动的。如果没有进程可执行,没有I/O设备可服务,没有用户可响应,那么操作系统就会安静的等待事件的发生。事件总是由中断或陷阱引起。陷阱或异常是一种软件中断,由于出错比如除数为零或者源于用户程序的一个特别请求(完成操作系统服务)。对每一种中断,操作系统中不同的代码段决定将要采取的动作。

双重模式操作

采用共享,许多进程可能会受到一个程序的漏洞的不利影响,因此操作系统的设计必须保证一个错误的程序不会造成其他程序执行错误。

为保证操作系统的正常执行,必须区分操作系统代码和用户定义的代码。可以采用的方法是提供硬件支持以允许区分各种执行模式。

计算机硬件中增加一个称为模式位(mode bit) 的位以表示当前模式:监督程序模式0和用户模式1.有了模式位就可以区分操作系统和用户分别执行的任务。

这里写图片描述

双重模式操作系统为保护操作系统和用户程序不受错误用户程序影响的手段。实现方法是:将能引起损害机器的指令设为特权指令(privileged instruction),用户模式执行这些指令是非法的,并将其以陷阱的形式通知操作系统。

切换到用户模式就是一个特权指令,其他的例子包括I/O控制、定时器管理,中断管理。

当CPU运行于内核模式时,一切程序都可运行.任务可以执行特权级指令,对任何I/O设备有全部的访问权,还能够访问任何虚地址和控制虚拟内存硬件.这种模式对应80×86的ring0层,操作系统的核心部分,包括设备驱动程序都运行在该模式.

系统调用把应用程序的请求传给内核,调用相应的的内核函数完成所需的处理,将处理结果返回给应用程序。系统调用通常采用陷阱到中断向量的一个指定位置的方式。

为确保系统对cpu的控制、防止程序进入死循环或不调用系统服务,并且不将控制全返回到操作系统。为实现之一目标,可使用定时器。系统在将控制权交给用户之前,应确保设置好计时器以便产生中断。计时器产生中断,控制权自动交给操作系统。

2017-04-20 11:08:11 shanghairuoxiao 阅读数 4340
  • Linux操作系统介绍

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    1481人学习 CSDN就业班
    免费试看

为什么叫内存的抽象?

如果看过设计模式的人可能会知道,设计模式中提到最多的概念之一就是抽象,纯虚的基类作为接口就是对各种派生类对象的抽象。调用接口的用户,并不知道内部如何实现,因此内部实现的方法可能也有多种。地址空间也可以这样理解,32位机上,创建进程时操作系统为进程分配4GB的独立地址空间,用户可以使用这4GB的独立地址空间。但是,反过来一想,给每个进程都分配4GB地址空间,对于8GB内存的计算机而言岂不也就能同时运行两个进程。对于现代计算机而言,这显然是不可能的。所以实际上,用户能使用的4GB地址空间并不是对应物理内存的4GB,具体怎么实现被封装了,所以叫内存抽象。

多道程序实现

现代操作系统能够同时运行多个程序,程序被运行时,需要占用内存的一块空间,如果同时运行的程序太多,物理内存装不下了怎么办?因此出现了两种技术,交换技术和虚拟内存。

交换技术

交换技术:就是指当内存满了以后,就将一个程序从内存换出,将另一个程序放入内存,换出的内存数据保存在硬盘上,当该程序再次被换入的时候,就将硬盘上的数据拷贝到内存。

如下图,蓝色区域表示空闲的内存,绿色区域表示被某个进程占用的内存。刚开始装入A进程,然后装入B进程,再装入C进程。对于进程B而言假设其地址空间为0x0000-0xFFFF,对于其地址0x0000而言,对应的物理地址肯定不是0x0000,那应该是进程A的首地址。所以如果进程B要访问其首地址0x0000,就必须加上一个偏移量,而这样偏移量保存在一个基址寄存器中,除了基址寄存器还有一个界限寄存器防止访问越界。

再接着上面的说,如果这时候来了一个进程D,剩余的内存放不下进程D了,这时候可以选择将进程B交换出去,将进程B的数据保存到硬盘上,将进程D装入内存运行,如果CPU重新调度到进程B然后再采用同样的方式将某个进程交换出去,保存到硬盘上,把进程B装入内存中,这样的过程就叫交换技术。

内存交换

虚拟内存

交换技术似乎解决了多道程序运行的问题,但是实际上如果每次交换一整个进程的数据,CPU需要花费数秒的时间来处理,这显然是不能被容忍的。因此,需要提出虚拟内存的概念。

虚拟内存:操作系统为了管理内存,给每个进程都分配独立的地址空间,对32位的系统而言,这个空间的大小是4GB。这4GB并不是实际的物理内存,实际上并不存在,因此有虚拟内存这一名称。

虚拟地址空间的地址称为逻辑地址,实际物理内存(就是内存条的大小)的地址空间称为物理地址。虚拟地址空间被分割成多个大小相同的页面(比如4k为一个页面),物理地址空间被分割成同样大小的页框。虚拟地址的页面通过一个页表映射物理内存的页框,页表中保存着两者的对应关系。逻辑地址是CPU使用的地址,当进程要访问该进程地址空间里的某个地址时时候,将该地址的值传递给CPU,CPU访问该地址时,会经过MMU将逻辑地址转换为物理地址,之前说的页表就保存在MMU中,操作系统为每个进程都维护一个页表。

MMU

说了这么多,我们还是不清楚为什么用虚拟内存就能实现多个程序同时运行,并且切换性能很高呢?

我们刚刚讲了,MMU把虚拟地址空间的页表和物理地址空间的页框关联起来了,如果页表中所有的数据都在页框中有对应项,那虚拟地址就没有任何意义了。实际上,程序运行的时候只需要部分数据存在内存中就可以了,因此只有部分页面和页框有对应值,其余的页表的数据保存在硬盘一块固定的地方(在Linux里叫swap分区,window里保存在C盘里)。当访问到某个页面在物理内存中没有对应的页框时就会发生缺页中断,这时候操作系统就将该页面保存在硬盘中的数据拷贝到物理内存中,并更新页表建立该页面和对应页框之间的映射关系。

这样做就实现了每次交换的代价很小,但是物理地址空间还是可能不够用,因此操作系统交换一些数据进物理内存的时候,也会从物理内存中移除部分页框数据到硬盘上,那到底该移出谁呢?这就涉及到页面交换算法了。

Linux内存管理

以Linux系统为例谈谈操作系统对内存的管理,一点皮毛,用于梳理自己的思路,使得面试的时候能够思路更清晰。

前面讲了进程具有独立的地址空间,对于32位的系统而言,该地址空间的大小是4GB。Linux将这4GB的地址空间分为两部分,一个是用户地址空间,一个是内核地址空间。内核地址空间的地址范围范围为3G到4G,用户地址空间的地址范围为0G到3G。这里所讲的0G到4G都是虚拟地址,也称为逻辑地址。

Linux对内核空间和用户空间是分别管理的,因为进程要么运行在用户态,要么运行在内核态,进程通过系统调用陷入内核态。

这里写图片描述
(借用网上一张图片说明一下,侵删)

内核空间

内核空间的逻辑地址范围在3GB到4GB,并且内核空间是线性映射到物理空间的。何为线性映射,举例说明,内核空间逻辑地址0xc0000000对应的物理地址是0x00000000,逻辑地址0xc0000001对应的物理地址是0x00000001,也就是说逻辑地址到物理都减了一个0xc0000000的偏移量。如果1GB都是这样映射的话,那么内核空间能使用物理地址范围在0x00000000到0x40000000之间,不能访问所有的物理地址了。

为了解决这个问题,内核空间就将物理内存分为三个区:ZONE_DMA,ZONE_NORMAL,ZONE_HIHGEM。DMA区是用于一些特殊设备的,我们不过多追究。主要讨论高端内存(ZONE_HIHGEM),对于内核空间而言高于896M的空间称为高端内存,低于896M的自然就可以称为低端内存了,低端内存的范围上,逻辑地址与物理地址是线性映射的。对于内核空间896M以上剩余的128M是用来访问高端内存的。这128M里的页面到物理页框随机映射的,和用户空间的映射是一样的。低端内存是自动永久映射的,高端内存可以永久映射也可以零时映射。

前面两端主要将的是对页表页框的管理,后面再将如何分配内存,也就是如果内核需要一定大小的内存的时,在3GB到4GB的范围里取出拿一块给它。内核空间分配内存可以按页分配,采用alloc_pages()和free_pages()函数分配多个连续页大小的内存,也可以通过kmalloc()分配指定大小的内存。

内核分配内存时很多时候都是分配固定大小的内存块,比如为每一个进程维护的task_struct结构体等。频繁分配这样的小块,很容易造成内存碎片,自然想到用内存池的方法来解决内存碎片的问题,只不过在Linux中给其取了一个更高大上的名字,叫高速缓存cache与slab层。一个高速缓存中有多个slab,分为三类:满的,部分满,和空的。每个slab就是一个链表,链表的每个节点就是一块固定大小的内存。和内存池是一样的。

用户空间

看过操作系统书的人肯定看到过下面这样图。
这里写图片描述

这张图解释了,一个进程将数据分为代码段,数据段,BSS段,堆和栈。实际上这些数据分享了0GB到3GB的地址范围。Linux管理这些段采用分区的结构,为每一个段维护一个vm_area_struct的结构体。这些结构体中保存了指向下一个指针因此形成了链表,还有另外一个指针使其构成红黑树,用户快速查找。

对于用户空间不得不谈到malloc函数,malloc函数是动态分配内存,内存来自用户空间的堆区。操作系统通过链表的形式将堆区的空间贯穿起来,当需要动态分配内存时就去查询该链表,找到空闲块,如果堆区满了,就调用sbrk函数扩大堆的范围。
当分配内存时,操作系统去查询该链表,找到一块能容纳下的地方放进去,将剩余的返还给空闲表。如何找到这个容纳的地方有出现了多种算法:首次适配算法,第二次首次适配,最佳适配算法,最差适配算法。这四个算法你可以去细讲差别,首次适配第一次找到第一个大于需要的地址空间的块,每次都从表头开始找,第二次着从上次找到的位置开始找,最佳适配找一个和需要大小最接近比需要的大的,最差每次早最大的。实际上对于进程内部堆的分配,页可以采取同样类似的办法。具体可以去看malloc的源码。

另外还有一点很重要的是内存映射文件,内存映射文件通过mmap函数实现,将文件映射到内存中,读写文件通过操作指针就能实现。实际上,内存映射文件并不是调用mmap的时候就将该文件拷贝到内存中,而是建立逻辑地址到文件地址之间的映射关系,但访问这段内存的数据时还是引发缺页中断,然后将该页的数据换到物理地址上。可以直接使用mmap实现进程间内存共享,XSI的内存共享实现的原理也是基于mmap,只是映射一种特殊文件系统的文件到内存中,该文件不能通过read和write调用来访问。

最后用一张图来结束本篇文章
这里写图片描述

如有错误欢迎指正!

参考文章:
http://blog.csdn.net/yusiguyuan/article/details/12045255#comments
http://www.secretmango.com/jimb/Whitepapers/slabs/slab.html

2014-04-24 23:44:02 yang_yulei 阅读数 12783
  • Linux操作系统介绍

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    1481人学习 CSDN就业班
    免费试看


物理内存


Linux为了有效使用机器中的物理内存,在系统初始化阶段内存被划分成几个功能区域:



其中,Linux内核程序占据在物理内存的开始部分,接下来是供硬盘等块设备使用的高速缓冲区部分(其中要扣除显卡内存和ROM BIOS所占用的内存地址范围)

当一个进程需要读取块设备中的数据时,系统会首先把数据读到高速缓冲区中。当有数据需要写到块设备上去时,系统也是先将数据放到高速缓冲区中,然后由块设备驱动程序写到相应的设备上。

内存的最后部分是可供所有程序随时申请和使用的主内存区。内核在使用主内存区时,首先要向内核内存管理模块提出申请,并在申请成功后方能使用。

 

在Intel 80386及以后的CPU中提供了两种内存管理(地址变换)系统:内存分段系统分页系统。其中分页管理系统是可选择的,由系统程序员通过编程来确定是否采用。Linux同时采用了分段和分页机制。

 



内存地址空间概念


在Linux内核中,在进行地址映射操作时,我们要分清3种地址的概念:进程的虚拟(逻辑)地址、CPU的线性地址、实际的物理地址。


虚拟地址


虚拟地址是指由程序产生的段选择子和段内偏移地址两部分组成的地址。

(因为这两部分组成的地址并没有直接用来访问物理内存,而是需要通过分段地址变换机制或映射后才对应到物理内存地址上,故被称为虚拟地址。)

虚拟地址空间GDT映射的全局地址空间和由LDT映射的局部地址空间组成。

【关于GDT和LDT,请移步-<CPU的实模式与保护模式>】

 

Intel 80x86 CPU可以索引16384个选择符。若每个段的长度都取最大值4GB,则最大虚拟地址空间范围是16384*4GB=64TB

 

CPU的内存管理给程序员提供了这样一个抽象的内存模型:



程序员(无论是汇编的还是高级语言的)可以把内存分布看做是如上图所示,可以认为内存中只有自己的程序,自己独占CPU

这是硬件和操作系统一起提供给程序员的简单抽象。

(底层的实现:地址变换、任务切换等对程序员是透明的)

 

逻辑地址是指由程序产生的与段相关的偏移地址部分。在Intel保护模式下即是指程序执行代码段限长内的偏移地址

应用程序员仅需与逻辑地址打交道,而分段和分页机制对他来说完全透明的。(由加载器来设置分段)

Linux 0.12给每个进程都划分了容量为64MB的虚拟内存空间。因此出现的逻辑地址范围是0x0000000到0x4000000。(即设置一个LDT所管辖的范围为64MB)

(现在的操作系统给进程划定的虚拟内存是4GB,当然也可以更多。)

有些资料不区分逻辑地址和虚拟地址的概念,统称逻辑地址。

 

线性地址


线性地址是虚拟地址到物理地址变换之间的中间层,是CPU可寻址的内存空间中的地址

程序代码会产生逻辑地址(或者说是段中的偏移地址),加上相应段的基地址就生成了一个线性地址。(一个段最大有4G的虚拟地址空间,在加载器把程序加载时,很容易选择一个空闲的段,填好GDT和LDT)

如果启用了分页机制,那么线性地址可以再经变换产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。

Intel 80386的线性地址空间容量为4GB。

 

物理地址


物理地址是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。

 


内存分段机制


在实模式下,寻址一个内存地址主要是使用段基址和偏移值,段基址被存放在段寄存器中(如ds),并且段的长度被固定为64KB。段内偏移地址存放在任意一个可用于寻址的寄存器中(如si)。


 

在保护模式下,段寄存器中存放的不再是被寻址段的基地址,而是一个段描述符表中某一描述符项在表中的索引值。索引值指定的段描述符项中含有需要寻址的内存段的基地址、段的长度值、段的访问特权级等信息。(这样通过段描述符访问段中的信息,可检查安全性,则就是所谓的保护模式)

 

这样,在保护模式下寻址一个内存地址就需要比实模式下多一个环节,即需要使用段描述符表

注意,如果你不在一个段描述符中定义一个内存线性地址空间区域,那么该地址区域就完全不能被寻址,CPU将拒绝访问该地址区域。



保存描述符项的描述符表有3种类型,每种用于不同目的。

IDT中断描述符表(Interrupt Descriptor Table),该表保存了定义中断或异常处理过程的段描述符。IDT表直接替代了8086系统中的中断向量表。(使得在跳转到中断程序时也进行权限检查。)

GDT全局描述符表(Globle Descriptor Table),该表可被所有程序用于引用访问一个内存段。

LDT局部描述符表(Local Descriptor Table),通常每个任务使用一个LDT表。每个LDT表为对应任务提供了更多的可用描述符项,因为也为每个任务提供了可寻址内存空间的范围

 

这些表可以保存在线性地址空间的任何地方。为了让CPU能定位GDT表、IDT表和当前的LDT表,需要为CPU分别设置GDTRIDTRLDTR三个特殊寄存器。

 


可以看出,每个任务的局部描述符表LDT本身也是由GDT中描述符定义的一个内存段,在该段中存放着对应任务的代码段和数据段描述符,因此LDT段很短。同样,每个任务的任务状态段TSS也是由GDT中描述符定义的一个内存段。(TSS用于在任务切换时CPU自动保存或恢复相关任务的当前执行上下文)

 


内存分页管理


内存分页管理机制的基本原理是将CPU整个线性地址内存区域划分成4KB为1页的内存页面。程序申请使用内存时,系统就以内存页为单位进行分配。

为了在80x86保护模式下使用分页机制,需要把控制寄存器CR0的最高位置位

 



对于Intel 80386系统,其CPU可以提供多达4GB的线性地址空间。

 

 

操作系统的内存管理(以下为一家之言,仅供参考)

有两套对内存管理的方式:

一、以Linux 0.12 内核为例(点我)

二、以Linux 2.x 内核为例(点我)





2013-01-30 10:03:39 rosetta 阅读数 4034
  • Linux操作系统介绍

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    1481人学习 CSDN就业班
    免费试看

Linux操作系统基础(三)保护模式内存管理(1)

 

转载请注明出处:http://blog.csdn.net/rosetta

保护模式内存管理

    本节描述IA-32架构保护模式下内存管理机制,包括段机制和页机制。

内存管理简介

         IA-32架构内存管理机制分两部分:分段机制和分页机制。分段机制使代码段、数据段和堆栈段相互独立,这样多任务才可以在一个处理器上运行还不会被其它程序干扰。分页机制为传统需求页、虚拟内存系统提供了实现机制。其中虚拟内存系统用于实现程序代码按需映射到物理内存中。分页也使多任务之前相关隔离。在保护模式下,分段机制是必须实现的,没有模式位用来关键分段;但是分页机制是可选的。

         这两种机制可以支持单任务系统、多任务系统或者支持共享内存的多处理器系统。如图6所示,分段制机制提供处理器可寻址内存空间(线性地址)到段地址空间的地址变换。段可用来存放数据、代码、堆栈以及系统数据结构TSS和LDT。假如一个处理器上有多个任务(程序)同时运行,那么每个任务都有自己段集。然后处理器可以加强这些段之前的界线,并且确保一个程序不会通过写入另一个程序的段而干扰程序的执行。段机制对段进行了分类,这样对特别类型的段的访问能够受限制。

         一个系统中的所有段都在处理器的线性地址空间中。为了定位段中的一个字节,必须提供逻辑地址(logical address,也叫远指针)。一个逻辑地址由段选择符和偏移值组成。段选择符唯一标志一个段,除此之外,段选择符还提供描述符表(比如GDT)中称为段描述符的数据结构的偏移地址。每一个段都有一个段描述符,它用来指定段的大小,访问权限,段的特权级,段类型和线性地址空间中的段基地址。逻辑地址中的偏移地址加上段基地址就可以定位段中的一个字节。所以基地址加上偏移地址就形成了处理器线性地址空间中的线性地址(linear address)。

         假如没有启用分页,那么处理器的线性地址就直接映射到物理地址空间。物理地址空间的大小范围由处理器的数据总线位数决定。

         因为多任务系统定义的线性地址空间通常比实际物理内存大,所以需要某种虚拟化线性地址空间的方法。虚拟化线性地址空间就是通过处理器的分页机制实现的。

         分页支持的虚拟内存环境,需要由小块的物理内存(RAN或ROM)和一些硬盘存储介质来模拟大容量的线性地址空间。当使用分页时,每个段被分成很多页(典型的页大小为4KB),页存储在物理内存或者硬盘中。操作系统通过维护一个页目录和一组页表集来跟踪页。当一个程序尝试访问线性地址空间中的地址时,处理器通过页目录和页表把线性地址空间转换到物理地址空间中,然后在物理内存位置完成相应的操作请求(读或写)。

         假如被访问的页不在当前物理内存中,那么处理器将中断当前程序执行(通过产生页错误异常),然后操作系统或执行指令从硬盘读取页到物理内存中并继续执行当前中断的程序。

         当操作系统正确的完成分页时,硬盘和物理内存的交换对于程序正确的执行是透明的。

段的使用

         IA-32架构支持的段机制可以使用在多种系统设计中。从最小的平坦模型仅仅使用分段保护程序到多段模型利用段创造壮健的操作环境从而使多任务程序可靠的执行。

         以下几节将给出一些例子用于说明系统是如何利用段来提高内存管理的性能和可靠性。

图6 分段和分页

基本的平坦模型

         系统最简单的内存模型称为“基本平坦模型”,这种模型下操作系统和应用程序都可以访问连续的、不分段的地址空间。对于系统设计者和应用程序开发者来说最大限度的隐藏了分段机制。

         为了实现内存基本平坦模型,至少需要两个段描述符,一个用来指向代码段,另一个指向数据段,如图7所示。这两个段都映射到整个线性地址空间:也就是说,两个段描述符都有同样的基地址为0和同样的段限长4GBytes。通过设置4GBytes的段限长,当访问超过出了段限长分段机制也不会产生异常,甚至所访问的地址并没有物理内存。ROM(EPROM)通常过被放在物理内存最高地址处,因为处理器一开始执行FFFFFFF0H处RAM(DRAM)被放在地址空间最低处,因为在系统复位后DS数据段基地址被初始化为0。

保护模式平坦模型

         保护模式平坦模型和基本平坦模型类似,只不过段限长的范围设置成实际物理内存大小。

当访问实际不存在的物理地址空间时会生成一个普通的保护异常,如图8所示。这种模型提供了最低级别的硬件保护来访问一些程序bug。

         这种保护模型平坦模型可以变得更复杂来提供更多保护。例如,为了分页机制中提供普通用户和超级用户的代码和数据,必须定义四个段:普通用户特权级为3的代码和数据段,超级用户特权级为0的代码和数据段。通常这些段相互重叠并且从线性地址0开始。这种平坦分段模型加上一个简单的分页结构就可以在应用程序和操作系统之间起保护作用,如果为每个任务增加一个单独的页结构,那么就可以给应用程序之前提供保护了。同样的设计被用于一些流行的多任务操作系统。

图7 基本平坦模型

图8 保护模式平坦模型

多段模型

         一个多段模型,如图9所示,充分发挥了段机制的对代码、数据结构和程序提供硬件保护的能力。每个程序都有自己的段描述符表和自己的段。段可以完全属于程序私有也可以和其它程序之前共享。

         访问权限的检查不仅仅用来保护地址越界,也可以保护某一特定段不允许操作。例如代码段是只读段,硬件可以阻击向代码段进行写操作。为段建立的访问权限信息也可以用来建议保护级别。保护级别可以用来防止未认证的应用层程序访问操作系统程序。

分页和分段

         分页可以用于以上描述的图7,图8,图9分段模型。处理器的分页机制把线性地址空间分为许多页(那些段映射的地方),如图6所示。这些线性地址空间中的页然后映射到物理内存地址空间。分页机制提供一些页级别保护功能,它可以和段保护功能一起使用或者直接代替段保护功能。例如,它可以让读写保护强制使用到基础page-by-page(?)。页机制也提供两级别保护(普通用户和超级用户),这也在基础page-by-page中指定。

 

图9 多段模型

 参考:《Intel SystemProgramming Guide》

          《Linux内核完全剖析》赵炯编著

 

2016-10-05 00:52:55 cn_wk 阅读数 2220
  • Linux操作系统介绍

    掌握Linux系统常用基命令 掌握文件操作、打包与解压 掌握vi编辑器的使用 掌握用户与权限管理 掌握进程管理和软件安装

    1481人学习 CSDN就业班
    免费试看

本文内容参考自如下博客与书籍:

http://blog.csdn.net/windowseight/article/details/8279863

http://bbs.chinaunix.net/thread-2083672-1-1.html

http://blog.csdn.net/erazy0/article/details/6457626#comments

http://blog.csdn.net/drshenlei/article/details/4261909

http://duartes.org/gustavo/blog/post/memory-translation-and-segmentation

《操作系统概念》第六版中文版,高等教育出版社,第九章,郑扣根译。

《Operating System Concepts》7th Edition, 高等教育出版社。

文中很多处是根据自己理解所写,若有错误,欢迎指出和探讨。

1. 物理地址和逻辑地址

物理地址加载到内存地址寄存器中的地址,内存单元的真正地址。在前端总线上传输的内存地址都是物理内存地址,编号从0开始一直到可用物理内存的最高端。这些数字被北桥(Nortbridge chip)映射到实际的内存条上。物理地址是明确的、最终用在总线上的编号,不必转换,不必分页,也没有特权级检查(no translation, no paging, no privilege checks)。

逻辑地址CPU所生成的地址。逻辑地址是内部和编程使用的、并不唯一。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址(偏移地址),不和绝对物理地址相干。

为什么会有这两种地址?

个人觉的原因在于逻辑地址分配更加灵活,可以允许不唯一,看起来也较为直观,例如,一段代码中分配数组,逻辑地址上是连续的,然而在物理地址上,这个数组所占用的页可能分散开来,物理地址上就是不连续的,这样对程序的可理解性上有影响。另外,有了逻辑地址这个概念,才能使用虚拟内存技术。

2. Paging,分页内存管理方案

(1) 分页的最大作用就在于:使得进程的物理地址空间可以是非连续的。

物理内存被划分为一小块一小块,每块被称为帧(Frame)。分配内存时,帧是分配时的最小单位,最少也要给一帧。在逻辑内存中,与帧对应的概念就是页(Page)。

逻辑地址的表示方式是:前部分是页码后部分是页偏移。

例如,已知逻辑空间地址为2^m个字节(也就是说逻辑地址的长度是m位),已知页大小是2^n字节。那么一共可以有2^(m-n)个页。因此页码部分会占m-n位,之后的n位,用来存储页偏移。

举个例子, 页大小为4B,而逻辑内存为32B(8页),逻辑地址0的页号为0,页号0对应帧5,因此逻辑地址映射为物理地址5*4+0=20。逻辑地址3映射物理地址5*4+3=23。逻辑地址13(4*3+1,页号为3,偏移为1,因此帧号为2),映射到物理地址9。

采用分页技术不会产生外部碎片(内存都被划分为帧),但可能产生内部碎片(帧已经是最小单元,因此帧内部可能有空间没有用到)。

按概率计算下来,每个进程平均可有半个帧大小的内部碎片。

(2) 页表的硬件实现

上一小节中写到页表是逻辑地址转化到物理地址的关键所在。那么页表如何存储?

每个操作系统都有自己的方法来保存页表。绝大多数都会为每个进程分配一个页表。现在由于页表都比较大,所以放在内存中(以往是放在一组专用寄存器里),其指针存在进程控制块(PCB)里,当进程被调度程序选中投入运行时,系统将其页表指针从进程控制块中取出并送入用户寄存器中。随后可以根据此首地址访问页表。

页表的存储方式是TBL(Translation look-aside buffer, 翻译后备缓冲器)+内存。TBL实际上是一组硬件缓冲所关联的快速内存。若没有TBL,操作系统需要两次内存访问来完成逻辑地址到物理地址的转换,访问页表算一次,在页表中查找算一次。TBL中存储页表中的一小部分条目,条目以键值对方式存储。

(3) 页表的数据结构

a.

今年是2013年,现有的笔记本电脑,内存地址空间一般为2^32字节以上。对于具有32位逻辑地址空间的计算机系统,如果系统的页大小为4KB(2^12B),那么页表可以拥有2^(32-12)个,也就是一百多万个条目,假设每个条目占有4B,那每个进程都需要4MB的物理地址空间来存放页表本身。而且,页表本身需要分配在连续内存中。

为此,Hierarchical Paging(层次化分页)被提出,实际上就是将页号分为两部分,第一部分作为索引,第二部分作为页号的偏移。

以一个4kb页大小的32位系统为例。一个逻辑地址被分为20位的页码和12位的页偏移。因为要对页表进行再分页,所以该页号可分为10位的页码和10位的页偏移。这样一个逻辑地址就表示如下形式:

 

地址转换过程如下:

 

地址由外向内转换,因此此方法也被称为forward-mapped page table(向前映射表)

b. Hashed Page Tables 哈希页表

处理超过32位地址空间的常用方法是使用hashed page table(哈希页表),并以虚拟页码作为哈希值。哈希页表的每一条目都包括一个链表的元素,这些元素哈希成同一位置。每个元素有三个域:虚拟页码,所映射的帧号,指向链表中下一个元素的指针。

个人看来,哈希页表的地址转换方式,实际上是Chaining(链接)方式,也就是一种哈希函数的溢出处理方式(另一种溢出处理方式叫做Open Addressing,开放寻址),具体过程如下:

逻辑地址需要大于32bit的地址空间来表示,但是操作系统仍只有32bit来表示地址。此时人们便想到虚拟页地址,虚拟地址可以在32bit表示范围之内,然后利用哈希函数完成逻辑地址到虚拟地址的映射,由于虚拟地址更少,哈希函数会出现溢出,这里使用Chaining来解决溢出。

逻辑地址中的页号(下图中的p)经过哈希函数的计算,算出虚拟地址中的页号,根据虚拟页号可以在哈希表中以O(1)方式寻址,用p与链表中的每一个元素的第一个域相比较。如果匹配,那么相应的帧号就用来形成物理地址。如果不匹配,就对链表中的下一个节点进行比较,以寻找一个匹配的页号。

c. Inverted page table 反向页表

时间关系,这段暂时略过。

3. Segmentation,分段内存管理方案

采用分页内存管理有一个不可避免的问题:用户视角的内存和实际内存的分离。设想一段main函数代码,里面包含Sqrt函数的调用。按照编写者的理解,这段代码运行时,操作系统应该分配内存给:符号表(编译时使用),栈(存放局部变量与函数参数值),Sqrt代码段,主函数代码段等。这样,编写者就可以方便地指出:"函数sqrt内存模块的第五条指令",来定位一个元素。而实际上,由于采用Paging的管理方式,所有的一切都只是散落在物理内存中的各个帧上,并不是以编写者的理解来划分模块。

Segmentation的内存管理方式可以支持这种思路。逻辑地址空间由一组段组成。每个段都有名字和长度。地址指定了段名称和段内偏移。因此用户通过两个量来指定地址:段名称和偏移。段是编号的,通过段号而非段名称来引用。因此逻辑地址由有序对构成:

 <segment-number,offset>(<段号s, 段内偏移d>)

段偏移d因该在0和段界限之间,如果合法,那么就与基地址相加而得到所需字节在物理内存中的地址。因此段表是一组基地址和界限寄存器对。

 

例如下图,有5个段,编号0~4,例如段2为400B开始于位置4300,对段2第53字节的引用映射成位置4300+53=4353。而段0字节1222的引用则会触发地址错误,因为该段的仅为1000B长(界限为1000)。

4. 合并分段和分页的管理方案

在现有的Intel兼容计算机(x86)上,采用的内存管理方案是分段和分页合并的管理方案。

在这个方案中,逻辑地址,如前一节中所说,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。

这样的逻辑地址转换的过程是怎样呢?如下图所示:

CPU要执行一条引用了内存地址的指令时,转换过程就开始了。第一步是把逻辑地址转换成线性地址。但是,为什么不跳过这一步,而让软件直接使用线性地址(或物理地址呢?)原因主要是因为:

(1) Intel的更新是渐进式而非革命式,新的处理器需要兼容和保留过往的设置。具体的原因,博文Memory Translation and Segmentation (http://blog.csdn.net/drshenlei/article/details/4261909) 中讲的较为清楚。

(2) 如上节所说,采用段内存管理,可以跟方便地进行地址保护(同一类型的地址逻辑地址在一起)。

下面讲逻辑地址到线性地址的部分。

在IBM OS/2 32位版本的操作系统,和Intel 386的环境下。操作系统采用的内存分配方式就是分段和分页合并的方式。

逻辑地址的实际上是一对<选择符,偏移>。

选择符的内容如下:

从左开始,13位是索引(或者称为段号),通过这个索引,可以定位到段描述符(segment descriptor),而段描述符是可以真正记载了有关一个段的位置和大小信息, 以及访问控制的状态信息。段描述符一般由8个字节组成。由于8B较大,而Intel为了保持向后兼容,将段寄存器仍然规定为16-bit(尽管每个段寄存器事实上有一个64-bit长的不可见部分,但对于程序员来说,段寄存器就是16-bit的),那么很明显,我们无法通过16-bit长度的段寄存器来直接引用64-bit的段描述符。因此在逻辑地址中,只用13bit记录其索引。而真正的段描述符,被放于数组之中。

这个内存中的数组就叫做GDT(Global Descriptor Table,全局描述表),Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址。程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。

除了GDT之外,还有LDT(Local Descriptor Table,本地描述表),但与GDT不同的是,LDT在系统中可以存在多个,每个进程可以拥有自己的LDT。LDT的内存地址在LDTR寄存器中。

在之前图中的TI位,就是用来表示此索引所指向的段描述符是存于全局描述表中,还是本地描述表中。=0,表示用GDT,=1表示用LDT。

RPL位,占2bit,是保护信息位,还没有仔细了解过这一块,暂时先不写。

找到,段描述符后,加上偏移量,便是线性地址。转换过程如下:

在Intel 386的环境下,线性地址转换为物理地址的过程,和第二节分页式内存管理中,层次分页中,逻辑地址转换为物理地址的方法类似。如下图。

 

Intel 80386的地址转换全过程如下图:

实模式和保护模式

阅读数 10805