《深入理解Linux内核》学习笔记——第二章(未完待续)

第二章 内存寻址
1.三种地址:在使用80x86处理器时(注意这是硬件!!!)一定要区分这三个地址,即逻辑地址(logical address)线性地址(linear address)也叫虚拟地址(virtual address),最后一个是物理地址(physical address)。依次解释下这三个地址:
逻辑地址:在机器语言指令中用来指定一个操作数或一条指令的地址。(这里的地址已经是机器语言指令的地址了!已经经过硬件MMU的转化了)每个逻辑地址都是由一个段和一个偏移量组成的!
线性地址:是一个用来表达高达4GB的地址,由16进制数字来表示,值得范围从0x00000000到0xffffffff,注意不管实际物理地址多大,都能得到一个4GB的地址,这样就为操作系统使用内存带来了方便。
物理地址:既是从微处理器地址引脚发送到内存总线上的电信号相对应。
2.这里要强调下:不同的处理器的MMU是不同的!!我们这里研究的是X86架构的处理器,该处理器的MMU是由分段分页两个单元组成的,但是譬如ARM处理器只有分页单元。所以我们在研究内存分配时要关注处理器的类型!!!!
3.硬件MMU,通过分段单元的硬件电路把一个逻辑地址转换成线性地址,再通过分页单元把线性地址转换成物理地址。要注意两点:一.所有的转化都是通过硬件电路实现的!!!二.对于操作系统来说这个转化是已经成功的,不需要过问的。

4.Intel X86处理器有两种方式执行地址转换,分别称为实模式(real mode)和保护模式(protected mode),我们主要探讨保护模式下的地址转化!!
5.接下来就要说硬件MMU是如何将逻辑地址转换成线性地址的。首先我们必须要知道我们的对象,再复习下,逻辑地址时由段和偏移量组成,而线性地址是一个4GB大小的空间。我们通过硬件分段单元来实现这一变化!接下来我们就要对准我们的对象,也就是逻辑地址。逻辑地址的前半部分我们称为段选择符(由于地址空间很庞大,我们不可能直接将地址空间的值写进去,于是我们给空间先标上号,再通过偏移来找到我们所需要的空间),段选择符帮我们找到段,再加上偏移量,就可以编程线性地址了。
6.我们把段选择符存放在段寄存器中,包括代码段寄存器cs,栈段寄存器ss,数据段寄存器ds,其中cs寄存器有个很重要的功能,就是表示当前CPU的特权级,0为最高优先级,3为最低优先级,Linux就用0和3来代表内核态和用户态!!!这里给出段选择符的结构:

前13位为索引号,TI代表访问的是GDT(全局描述符表)还是LDT(局部描述符表)
7.由上图可知,我们要想把逻辑地址转换成线性地址只用段选择符是不够的,我们还需要段描述符!!段描述符存放在全局描述符表(GDT)或局部描述符表(LDT)中,通常只定义一个GDT,而每个进程还需创建附加的段,就可以有自己的LDT。GDT存放在gdtr寄存器,正在被使用的LDT存放在ldtr寄存器。具体的段描述符的机构,贴一张图,但不仔细介绍了。

为了实现快速访问段描述符,我们采用了一种新的机制!! 每当一个段选择符被装入段寄存器中,相应的段描述符就由内存装入到对应的非编程CPU寄存器。这样我们就不需要重复访问GDT/ LDT来获取段描述符。只有当段寄存器(也就是段选择符)发生变化时,我们才会重新访问GDT/LDT!!!!

8.说了那么多,我们必须要把段描述符合段选择符何在一起说一下。先上图:
要想从逻辑地址到线性地址需要经过一下几步:
一、检查逻辑地址段选择符的TI确定是那个段描述符表(GDT or LDT)
二、将段选择符index*8(即左移3位)加上段描述符表寄存器里的值确定段描述符的位置。
三、将逻辑地址的偏移量与段描述符的Base字段相加就得到线性地址!!
9.再回顾下分段和分页,分段可以给每个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间。Linux更偏重于分页方式。2.6版的Linux只有在80x86结构下才使用分段。
10.对于Linux来说我们要实现分段,当然还是要关注一个最核心的东西就是段描述符!!!因为段描述符就是从逻辑地址到线性地址的中间量,就像桥梁一般存在!!Linux最重要的4个段描述符的是用户代码段,用户数据段,内核代码段,内核数据段。相应的段选择符由宏__USER_CS,__USER_DS,__KERNEL_CS,__KERNEL_DS定义。对于内核代码段寻址,内核只需要把__KERNEL_CS宏装入到cs段寄存器即可。
11.我们尤其要注意一个很重要的事。在Linux下所有段描述符的Base位都是0x00000000,这就意味着逻辑地址偏移量的值就是线性地址的值。所以这样就可以用线性地址代替逻辑地址,也就间接地消除了分段的影响。让各个处理器中调用地址保持一致。实现地址统一!不得不说这是个很聪明的涉及,即保存了分段单元,又让分段单元不影响地址访问。
12.现在来说说LinuxGDT,在单处理器系统中只有一个GDT而在多处理器系统中,每个处理器都对应一个GDT,GDT都存放在cpu_gdt_table的数组里,在arch/i386/kernel/head.s 中定义的。每个GDT包含18个段描述符和14个空的,具体的就不解释了。对于LDT来说,大多数用户态下的Linux程序不使用局部描述符表(这就解释了Linux对分段不是很感冒,所以都不太愿意使用它),不过linux还是预留了缺省的LDT,可以用modify_ldt()系统调用来创建自己的局部描述符表。
13.接下来来说分页单元,这里继续先说硬件的实现再说Linux的实现,对于Linux来说,分页显得尤为重要,因为Linux的一般使用线性内存(虚拟内存),再回顾下分页单元的目的:把线性地址转换成物理地址!!其中有个很关键的任务就是把所请求的访问类型和线性地址的访问权限相比较,如果这次内存访问无效,就产生一个缺页异常。这个知识点很重要,因为正因为这样才实现了写时复制的特性。(回顾下上章所说的,接下来还会详细介绍这个特性。)
14.为了效率,线性地址空间(也就是从0x00000000-0xFFFFFFFF)被分成以固定长度为单位的组,称为页(page)页内部连续的线性地址被映射到连续的物理地址中。分页单元把所有的RAM分成固定长度的页框(page farme)(也叫物理页)每一个页框包含一个页,所以页框的长度与页的长度一致。千万不要把页框混淆,只要记住一点,页是在线性地址里的而页框是在物理地址
15.回忆一下分段环节,分段环节最重要的东西是段描述符,而段描述符存放在段描述符表中(GDT/LDT),相对着在分页环节中,存放的数据结构称为页表(page table),页表存放在主存中,在启用分页单元之前必须由内核对页表进行适当的初始化。
16.接下来将会花大量的时间介绍硬件MMU分页单元,我们将会按照处理器发展的这条主线依次介绍分页单元式如何工作的。当然也会略过一些不是很重要的环节。
首先介绍最早的分页单元,在80386时代,采用的是常规分页,将处理4KB的页,对象:线性地址,目标:物理地址。32位的线性地址将被分成3个域:Directory(目录)最高10位,Table(页表)中间10位,Offset(偏移量)最低12位。线性地址将会进行两次转化,第一次转化用的是页目录表(page directory)第二次转化使用页表(page table),那为什么要转化两次呢,其目的在于减少每个进程页表所需要RAM的数量。具体的转化过程请看图:

正在使用的页目录的物理地址存放在控制寄存器cr3中,线性地址内的Directory字段决定页目录表中的目录项,而目录项指向适当的页表,地址的Table字段依次又决定页表中的表项,而表项含有页所在页框的物理地址。Offset字段决定页框的相对位置,由于它是12位长,故每个页含有4096个字节(这里就解释了为什么只处理4KB的页)
我们可以发现 分页的思想和分段的思想还是比较接近 的。都是通过一层层表加
上偏移量,最终定位到需要变成的地址。
17.页目录和页表的结构是一样的,也十分复杂。这里只说一下比较重要的字段:
Present标志,该标志被置1的时候该页(或页表)就在主存中,否则就不在,所以当去执行一个被清0的页,那么就会产生一个缺页异常(这里已经第三次提到关于缺页的问题了,一定要铭记这个!!)
Dirty标志,只用于页表,当一个页框进行写操作时 (即物理地址中页框中的数据发送改变) 设置这个标志
Read、Write标志 含有页或页表的存取权限,这个是十分重要的,之后会细讲。
先说这几个我认为比较重要的。别的当遇到的时候再提。
18.说了这么多概念,提个例子来说明下,假设内核给一个正在运行的进程分配的线性地址空间范围是0x20000000到0x2003ffff,那这个空间由几个页组成?答案是64个,我来解释下,先看线性地址的最高10位,我们可以发现都是0x200,所以directory是相同的,Table位范围从0到0x03f,因而十进制是从0到63.所以就有64个页。
19.扩展分页是pentium开始引入的。它允许页框大小为4MB而不是4KB。扩展分页,用于把大段连续的线性地址转换成相应的物理地址。在这种情况下,内核可以不用中间页表进行地址转换。转换的思想还是和常规转换是一致的。

20.之前说的都是32位的处理器,到了如今64位的处理器,分页也发生了相应的变换,之前的处理器普遍采用两级分页,如果64位的处理器依旧采用两级分页的方式,那么页目录和页表将会无比庞大,所以在x86_64的处理器中普遍采用4级分页。我们只使用64位中的48位,把线性地址分为9+9+9+9+12.
21.我们都知道内存的读取速度和CPU的读取不在一个数量级上,所以我们会采用Cache来取匹配这个速度。他的全名叫硬件高速缓存内存(hardware cache memory)。那cache的原理是基于著名的局部性原理:用一句话来阐明,就是最近最常用的相邻地址,在最近的将来被用到的可能性极大。所以80x86体系结构里引入一个叫行(line)的新单位。




  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值