01-linux内存管理之二在x86上的虚拟内存_第1页
01-linux内存管理之二在x86上的虚拟内存_第2页
免费预览已结束,剩余1页可下载查看

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、nLinux 内存管理之二:Linux 在 X86 上的虚拟内存管理本文档来自网络,并稍有改动。前言Linux 支持很多硬件运行,常用的有: el X86,Alpha,Sparc 等。对于不能够通用的一些功能,Linux 必须依据硬件的特点来具体实现。本文的目的是简要探讨 Linux在 X86 保护模式上如何实现虚拟内存管理功能。为简化和方便叙述,本如下限定:X86处理器为 80486 和其后的处理器,X86 工作在保护模式,不采用物理内存扩展(使用 32bits物理地址),不使用扩展页(页大小为 4K)。凡是与限定模式无关的内容,本文都尽量略过。 Linux 的虚拟内存管理中与硬件无关的内容

2、在本文中也被略过。本文所援引的 Linux 内核源代码版本为 Linux 2.2.5。X86 的分段和分页机制I. X86 的分段机制和相应系统结构X86 的分段机制就是将X86 的线性地址空间分成许多小空间-段(segment),利用这些(段的作用和)代码和数据,通过对段的保护来提供一种对数据或代码的保护。根据每个内容的不同,X86 将段分为三类进程段(代码段、数据段和堆栈段)和两类系统段:任务状态段(TSS,Task-Se Segment)和 LDT 段(由于 GDT 不是通过段描述符和段选择符来,所以 X86 没有认为存在一个 GDT 段;同理,也不存在 IDT 段)。在分段机制,X86

3、 使用了如下几种主要数据结构:全局描述符表(GDT,Global Describtor Table):存放系统用的段描述符和各项任务共用的段描述符,可以是上述的任何一类段的段描述符,最大表长 64KB;局部描述符表(LDT,Local Describtor Table):存放某个任务的各段的段描述符,只能是三类进程段的段描述符和调用门描述符,最大表长 4GB; 段描述符(Segment Describtor):64bits,地址),该段的类型,对该段操作的限制;一个段的址(该地址是线性 门描述符(Gate Describtor):64bits,一种特殊的描述符,为处于不同级的系统调用或程序的调

4、用或提供保护;分为四类:调用门描述符(Call Gate Describtor)、中断门描述符( errupt Gate Describtor)、陷阱门描述符(Trap Gate Describtor)、任务门描述符(Task Gate Describtor);段选择符(Segment Selector):16bits,用于在 GDT 或 LDT 中索引相应的段描述符;中断描述表(IDT, errupt Describer Table):存放门描述符,只能是中断门描述符,陷阱门描述符和任务门描述符,最大表长 64KB;同时,X86 提供了如下几个用于支持分段机制的寄存器: 全局描述符表寄存器(

5、GDTR,GDT Register):48bits,32bits 为 GDT 的址(线性地址),16bits 为 GDT 的表长;GDTR 的初始值为:址 0,表长 0 xF; 局部描述符表寄存器(LDTR,LDT Register):80bits,16bits 为 LDT 段选择符,64bits为该 LDT 段的段描述符; 中断描述符表寄存器(IDTR,IDT Register):48bits,32bits 为 IDT 的址(线性地址),16bits 为 IDT 的表长;IDTR 的初始值为:址 0,表长 0 xF;任务寄存器(TR,Task Register):80bits,16bits

6、为任务状态段选择符,64bits 为该任务状态段的段描述符;六个段寄存器(Segment Register):分为可见部分和隐藏部分,可见部分为段选择符,隐藏部分为段描述符;六个段寄存器分别为 CS、SS、DS、ES、FS、GS;关于这些段寄存器的作用参见1中 3.4.2 Segment Register;86 工作在保护模式时,进程使用的 48bits 逻辑地址(Logical address)。逻辑地址的高16bits 为段选择符,低 32bits 是段内的偏移量。通过段选择符在 GDT 或 LDT 中索引相应的段描述符(得到该段的址),再加上偏移量得到逻辑地址对应的线性地址( Linea

7、rAddress)。如果没有采用分叶管理,线性地址是直接物理地址(Physical Address),于是可以直接用线性地址理地址。内存;否则,还要通过 X86 的分页转换,将线性地址转换为物以上是对 X86 分段相关内容的简要描述,对于各数据结构、寄存器的细节和逻辑地址转换为线性地址的细节,请查阅 1。II. X86 的分页机制和相应系统结构32bits 的线性地址空间可以直接到物理地址空间,也可以间接到许多小块的物空间)上。这种间接方式就是分页机制。X86 可用页大小为 4KB、理空间(磁盘2MB 和 4MB(2MB 和 4MB 只能在 Pentium 和 Pentium Pro 处理器中

8、使用,本文中限定采用4KB 页)。在分页机制,X86 使用了四种数据结构: 页目录项(PDE,Page Directory Entry):32bits 结构,高 20bits 为页表址(物理地址),以 4KB 为递增,低 12bits 为页表属性,具体换算参见后面初始化部分; 页目录(Page directory):页目录项,位于一页中,总共可容纳 1024 个页目录项; 页表项(PTE,Page Table Entry):32bits 结构,高 20bits 为页低 12bits 为页属性;址(物理地址), 页表(Page table):页表项,位于一页中,总共可容纳 1024 个页表项;页

9、(Page):4KB 的连续地址空间;为了实现分页机制和提高地址转换的效率,X86 提供和使用了如下的硬件结构:页标志位(PG,Page):该标志位为 1,说明采用页机制;实际就是控制寄存器 CR0的第 31bit; 页缓存/快表(TLBs,Translation Lookaside Buffers):以提高地址转换的效率;最近使用的 PDE 和 PTE, 页目录址寄存器(PDBR,Page Directory Base Register):用于页目录的基地址(物理地址),实际就是控制寄存器 CR3;为了实现将线性地址到物理地址,X86 将 32bits 线性地址解释为三部分:第 31bit到

10、第 22bit 为页目录中的偏移,用于索引页目录项(得到对应页表的址);第 21bit 到第12bit 为页表中的偏移,用于索引页表项(得到对应页的址);第 11bit 到第 0bit 为页中的偏移。这样,通过两级索引和页中的偏移量,最后能正确得到线性地址对应的物理地址。关于分页机制的详细描述和作用,请查阅参考文档1。LINUX 的分段策略Linux 在 X86 上采用最低限度的分段机制,其目的是为了避开复杂的分段机制,提高Linux 在其他不支持分段机制的硬件的可移植性,同时又充分利用 X86 的分段机制来隔离用户代码和内核代码。因此,在 Linux 上,逻辑地址和线性地址具有相同的值。由于

11、 X86 的 GDT 最大表长为 64KB,每个段描述符为 8B,所以 GDT 最多能够容纳 8192个段描述符。每产生一个进程,Linux 为该进程在 GDT 中创建两个描述符:LDT 段描述符和 TSS 描述符,除去 Linux 在 GDT 中保留的前 12 项,GDT 实际最多能容纳 4090 个进程。Linux 的内核自身有独立的代码段和数据段,其对应的段描述符分别在 GDT 中的第 2项和第 3 项。每个进程也有独立的代码段和数据段,对应的段描述符中。有关 LinuxGDT 表项和 DLT 表项分布情况参见附表 1,附表 2 所示。在它自己的 LDT在 Linux 中,每个用户进程都

12、可以4GB 的线性地址空间。其中 0 x00 xBF的 3GB 空间为用户态空间,用户态进程可以直接。从 0 xC00000000 x3F 的 1GB。当用户进程通级 3 切换到空间为内核态空间,存放内核的代码和数据,用户态进程不能直接过中断或系统调用内核态空间时,会触发 X86 的级 0),即从用户态切换到内核态。级转换(从LINUX 的分页策略标准 Linux 的分页是三级页表结构,除了 X86 支持的页目录和页,还有一级被称为中间页目录。因此,线性地址在转换为物理地址的过程中,线性地址就被解释为四个部分(不是 X86 所认识的三个部分),增加了页中间目录中的索引。当运行在 X86上时,L

13、inux通过将中间页目录最大的页目录项个数定义为 1,并提供一组相关的宏(这些宏将中间页目录用页目录来替换)将三级页面结构分解过程完美的转换为 X86 使用的二级页面分解。这样,无需改动内核中页面解释的主要代码(这些代码都是认为线性地址由四个部分组成)。关于这些宏定义参见Linux 源码/include/asm/pgtable.h,/include/asm/page.h。内核态虚拟空间从 3GB 到 3GB+4MB 的一段(对应进程页目录第 768 项指引的页表),被到物理地址 0 x00 x3FF(4MB)。因此,进程处于内核态时,只要通过3GB到 3GB+4MB 就可物理内存的低 4MB

14、空间。所有进程从 3GB 到 4GB 的线性空间都是一样的,由同样的页目录项,同样的页表,到相同的物理内存段。Linux 以这种方式让内核态进程共享代码和数据。Linux 分段分页初始化无论 Linux 系统如何被引导,经过 zImage(参见 arch/i386/boot/bootsect.s)或经过 LILO,最后都会跳转执行arch/i386/boot/setup.s(被装载到 SETUPSEG,物理地址 0 x90200),setup.s从 BIOS 中获取计算机系统的硬件参数(如硬盘参数),放到内存参数区(临时寄放),同时做一些初步的状态检查,为进入保护模式做准备。关于引导过程和 s

15、etup.s 的具体执行参见 2。保护模式下的内核初始化模块从物理地址 0 x100000 开始执行,该地址开始的代码和数据结构都对应在 arch/i386/kernel/head.s 中,参见附表 3。初始化模块主要功能是对相关寄存器 IDT,GDT,页目录及页表等进行初始化。下面,忽略 head.s 执行流程的细节,概要阐述 head.s 主要的初始化功能。1. 部分寄存器的初始化:将段寄存器 DS、ES、GS 和 FS 用 KERNEL_DS(0 x18, include/asm-i386/segment.h)来初始化(通过前面对段寄存器的描述和段选择符的介绍可知道,其作用是将定位到 G

16、DT 中的第三项(内核数据段),并设置对该段的操作特限级为 0);置位 CR0 的 PG 位,并根据 CPU 的型号选择置位 AM, WP, NE 和 MP;用 0 x101000 初始化 CR3(页目录 swapper_pg_dir 的地址);置 ESP 高 32bits 为 KERNEL_DS(0 x18),低32bits 为 init_user_stack;LDTR 初始化为 0。2. 有关 IDT 的初始化:这只是临时初始化 IDT,进一步的操作在 start_kernel 中进行;用于表示 IDT 的变量(idt_table )在 arch/i386/kenel/traps.c 中定

17、义,变量类型(desc_struct)定义在 include/asm-i386/desc.h。IDT 共有 IDT_ENTRIES(256)个中断描述符,属性字均为0 x8E00,每个中断描述符都指向同一个中断服务程序 ignore_init。Ignore_的功能仅仅是输出消息_msg(unknownerrupt)。而 IDTR 的值为通过命令 lidt idt_descr 实现。通过在 head.s 中查看 idt_descr 的值可以计算得知,IDT 的IDT_ENTRIES*8-1(0 x7FF)。址为 idt_table 的地址,表长3. 有关 GDT 的初始化:GDT 共有 GDT_

18、ENTRIES 个段描述符。GDT_ENTRIES 的计算公式为:12+2*NR_TASKS。其中 12 表示前面提到的 Linux 在 GDT 中保留的 12 项, NR_TASKS(512)指系统设定容纳的进程数,定义在 include/linux/tasks.h。GDT 在 head.s直接分配单元(标号为 gdt_table)。初始化后的 GDT 如附表 1 所示。GDTR 的值通过命令 lgdt gdt_descr 实现。通过在 head.s 中查看 gdt_descr 的值可以计算得知,GDT 的gdt_table 的地址,表长 GDT_ENTRIES*8-1(0 x205F)。址

19、为4. 页目录的初始化:页目录由变量 swapper_pg_dir 表示,共有 1024 个页目录项。其第0 项和第 768指向 pg0(第 0 页),初始化值为 0 x00102007(根据其高 20bits 的值 0 x102换算:0 x102*4KB=0 x102000,第 0 页紧跟页目录后,物理地址为 0 x102000),由此可知,Linux 4GB 空间中的虚拟地址 0 x0 和 0 xBF(3GB)均由 pg0(物理地址0 x00 x3FF(4MB);其他页目录项初始值为 0 x0;5. pg0 的初始化:第 n 项对应第 n 页,属性为 0 x007;即第 n 项的初始化值的

20、高 20bits值为 n,底 12bits 值为 0 x007;由此可见 pg0了物理空间的低 4MB 空间;6. 初始化 empty_zero_page:该页的前 2KB 空间用来来自 BIOS 的系统硬件参数;后 2KB 空间作为命令行缓冲区;setup.s 保存在内存参数区的head.s 进行完初始化后调用 start_kernel(init/main.c)继续各方面的初始化,主要是调用各方面函数初始化内核的数据结构,下面对与 X86 系统相关的调用函数简述其(与本文相关的)功能。1. setup_arch() (arch/i386/kernel/setup.c) ; 设 置 内 核 可

21、 用 物 理 地 址 范 围( memory_startmemory_end ); 设 置 init_task.mm 的 范 围 ; 调 用 request_region(kernel/resource.c)申请 I/O 空间,参见附表 4。2. paging_init() (arch/i386/mm/init.c);取消虚拟地址 0 x0 对物理地址的;根据物理地址的实际大小初始化所有的页表。4MB 空间的3. trap_init() (arch/i386/kernel/traps.c);在 IDT 中设置各种地址,如异常事件处理程序,系统调用,调用门等。其中,trap0trap17 为各种

22、错误(溢出,0 除,页错误等,错误处理函数定义在 arch/i386/kernel/entry.s);trap18trap47 保留;设置系统调用(0 x80)的为 system_call(arch/i386/kernel/entry.s);在 GDT 中设置 0 号进程的 TSS段描述符和LDT 段描述符。4. init_IRQ() (arch/i386/kernel/irq.c);初始化 IDT 中 0 x200 xff 项。5. time_init() (arch/i386/kernel/time.c);实时时间,重新设置时钟中断 irq0 的中断服务程序。6. mem_init() (

23、arch/i386/mm/init.c);初始化 empty_zero_page;标记已被占用的页。Linux 进程和分段分页每当启动一个新的进程, Linux 都为其创建一个进程控制块( task_struct ,include/linux/sched.h)。task_struct 中最重要的与有关的成员为 mm(mm_struct* mm,include/linux/sched.h)和 tss(thread_struct tss,include/asm-i386/pro系统所涉及的(与分段分页相关)功能包括:sor.h)。在创建过程中,1. 每个进程(根据需要)建立新页目录(mm 成员 pgd_t * pgd),并将其地址置入寄存器 CR3 中;相关代码:new_page_tables(mm/memo

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论