`
memorymyann
  • 浏览: 269650 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

linux学习一 对于intel i386的2层内存寻址模式详细过程

阅读更多

linux内存寻址详细过程:
下面我们看这条语句call 0x0804837c
整个内存寻址分为2个阶段,段式和页式。处理器是intel i386系列.操作系统会先将虚地址经过段式映射后变成线性地址,再通过页式变成物理地址。从而获取内存上的数据或者是代码.
这里执行的是call语句,转移指令。首先会找到cs寄存器,intel i386是32位处理器,CS仍然是16位。高13位存贮的是段描述位置(注意不是地址,读到后面就知道了)。低3位中,倒数第3位是指使用GDT寄存器还是LDT(寄存器)。后2位表示权限。(在linux中只用了2种权限,最高级别的系统权限00和用户权限最低11)。该寄存器内容是在建立进程的时候就被设置好的,其代码可以在include/asm-i386/processor.h中找到:
define start_thread(regs, new_eip, new_esp) do {        \
    __asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0));    \
    set_fs(USER_DS);                    \
    regs->xds = __USER_DS;                    \
    regs->xes = __USER_DS;                    \
    regs->xss = __USER_DS;                    \
    regs->xcs = __USER_CS;                    \
    regs->eip = new_eip;                    \
    regs->esp = new_esp;                    \
} while (0)
这里我们可以看到除了CS寄存器,其他的寄存器值都被设置成了__USER_DS,这与intel当初设计有出入。linux并没有买intel的帐。当然我们想知道具体的值,至于__USER_DS和__USER_CS的值我们可以在include/asm-i386/segment.h中找到:
#define __KERNEL_CS 0X10    //相应的2进制表示 0000 0000 0001 0000
#define __KERNEL_DS 0X18    //相应的2进制表示 0000 0000 0001 1000
#define __USER_CS 0X23      //相应的2进制表示 0000 0000 0010 0011
#define __USER_DS 0X2B      //相应的2进制表示 0000 0000 0010 1011
注意不同内核版本的值可能不一样,但这并不影响。看到后面你们会发现这些值是有自己的规则的。对应着上面说到的CS,DS等等寄存器的意义(DS和CS16位值意义是一样的),我们回忆可得知,最高13位表示段描述结构的位置,倒数第3位表示用GDT还是LDT,最后表示权限,我们很容易得到下面的值:
                段描述结构的位置    是GDT还是LDT(0表示用GDT,1表示LDT)    权限
__KERNEL_CS            2                0用GDT                                    00最高系统权限
__KERNEL_DS            3                0用GDT                                    00最高系统权限
__USER_CS             4                0用GDT                                    11用户权限
__USER_DS            5                0用GDT                                    11用户权限
这里我们看到linux只用了GDT寄存器,这违背了intel初衷(GDT表示全局段描述表,LDT是局部段描述表)。名字也反映了GDT和LDT的作用,他们是用来存贮段描述结构的地址起点的。看到这里可能有些人不明白,内存寻址不是段地址加上偏移吗,直接把CS的值加上程序中给出的偏移就可以找到了物理地址了吗?那是intel 8086和8088的虚地址模式,在intel 80286确切的说是80386更多的采用的是保护模式寻址,这种寻址方式改变了寄存器内部值的意义,至于什么是保护模式,大家GOOGLE下就知道了。
既然用到了GDT寄存器,那么在装入线程的时候肯定是要给这个寄存器所指向的表赋值了,这段代码是在arch/i386/kernel/head.S中:
ENTRY(cpu_gdt_table)    //定义了GDT表值
    .quad 0x00cf9a000000ffff    /* 0x60 kernel 4GB code at 0x00000000 */
    .quad 0x00cf92000000ffff    /* 0x68 kernel 4GB data at 0x00000000 */
    .quad 0x00cffa000000ffff    /* 0x73 user 4GB code at 0x00000000 */
    .quad 0x00cff2000000ffff    /* 0x7b user 4GB data at 0x00000000 */
我只贴出了关键的代码,其实在赋值时候,GDT指向的表的第一项没有被使用,原因是防止加电后段寄存器未经初始化就进入保护模式并使用GDT,第2项也没有被使用,那么我们找到我们程序要用的第4项,既是.quad 0x00cffa000000ffff    /* 0x73 user 4GB code at 0x00000000 */,这是个8字节64位的数。我们转换成相应的2进制就可以看到它的值是:0000 0000 1100 1111 1111 1010 0000 0000  0000 0000 0000 0000 1111 1111 1111 1111,我们看下它的意义。从低位向高位,从第16位(起始第0位),到第31位是0,从第32位到第39位是0,最高的8位还是0,这个就是我们以前所说的段址,也就是段的基地址(有人会问为什么地址搞这么复杂32位基地址连在一起不好吗,干嘛分成3段。16位16位分想信大家可以理解,至于后面又把16位分成了2个8位,是因为intel设计初衷是用于24位处理器,但很快意识到应该用于32位,于是修修补补就变成了现在这个样子了),在linux中他被设置成了0,也许有人会说这不会出问题吗,后面还有页式内存管理,这里不是最终的物理地址。接下来每个描述段都有自己的意义了,其实在linux中对这些2进制单元的内存设置是为了应付intelCPU基于段式内存管理的保护模式,最低16位加上,倒数第12位到15位都是1,他们合起来16进制值便是0xffffff表示段的上限,一旦用户越过了这个上限就会报非法越界了。我们看到倒数第8位是1表示4KB,0表示段长度单位是1B,1表示段长度单位为4KB,倒数第9为1表示当前指令为32,否则为16位。倒数第16位为1表示在内存,否则则不在内存。后面紧接着为11表示要求的权限,这里为11和段寄存器中相符,表示可以使用,没有越权。剩下的几个数位就不一一介绍了,读者可以自己去查相关的资料。到这里读者可能会想到,我们可以通过设置CS或者DS寄存器从而可以达到非法访问的目的,但linux内核会在页式映射里面继续执行一次检查,之所以在这里做一次是因为例行i386的公事,不得已而为之。大家也会看到由于段寄存器被指定了值,任何一个地址访问到的最后的段描述结构的值都将会是代码中列出的值。linux这样做只是由于i386的设计,必需先完成段式印射。
这样我们得到了段的基地址0,加上偏移就是线性地址了,很明显在linux中线性地址数值是等于虚地址的,因为加的是0.接下来要做的是把线性地址转换成物理地址。这里介绍下i386是如何把线性地址转换成物理地址的。线性地址是32位,I386将其看成10位,10位,12位。在i386中有个寄存器叫CR3。它指向一个地址,内存管理单元(mmu)找到这里,根据最高10位,得到下标,从而获取页面表位置,找到对应的页面表,在从第2个10位作为下标找到页面位置,加上最后12为偏移就是我们要找的物理地址了。CR3的设置是在include/asm-i386/mmu_context.h里面,代码为:
static inline void switch_mm(struct mm_struct *prev,
                 struct mm_struct *next,
                 struct task_struct *tsk){
                 ...
                 asm voliatile("movl %0, %%cr3"::"r" (__pa(next->pgd));
                 ...
                 }
大家也许没有看到有什么权限判断的地方,权限判断是在找页面表的时候完成的,页面表的大小占一个页面,在32位的i386中,一个页面为4K,那么页面的起始位置一定是4K的倍数,存贮页面表其实地址的表单里面,每个值(表示页面表的起始位置)最后的12必然是0,因此,抛弃这12个零,找到值后直接在后面加上12个0就是页面表的地址,同理,在页面表中低12位也是0,因此可以将这些必定是0的数位用到其它方面,比如权限判断。因此从线性地址到物理地址的过程就是到CR3指向地址,以最高10位为下标找到页面表地址,再在页面表中以第2个10位为下标找到页面位置,加上最后12位的位移便是物理地址了。

也许大家会觉得为了一个内存寻址,会访问内存到3次,效率是不是太低了。但是由于告诉缓存的作用,加上大部分操作是由硬件完成的,所以速度很快。

分享到:
评论

相关推荐

    i386手册——程序员必备的工具书

    - **32位体系结构**:80386是Intel首款完全支持32位数据处理能力的CPU,能够处理更大的地址空间(高达4GB),极大地扩展了系统的内存寻址能力。 - **保护模式**:80386引入了保护模式,该模式下操作系统可以控制...

    gcc中的内嵌汇编语言(Intel i386平台)

    10. **基址+索引寻址模式**:在AT&T格式中,基址+索引寻址模式使用括号表示,如`-4(%ebp)`对应Intel格式中的`[ebp-4]`。 11. **内存操作数的寻址方式**:AT&T格式中的内存操作数寻址方式更加灵活,可以使用不同的...

    linux内存管理

    #### 一、Intel x86保护模式下的分段与分页机制 在探讨Linux如何实现内存管理之前,我们首先需要理解Intel x86架构下的内存管理基础——分段与分页机制。 **分段机制**: - **段选择器**:包含16位的段选择器,...

    完整的I386安装包

    它支持32位寻址,理论上可以访问最多4GB的内存,这对于当时的计算机来说是一个巨大的飞跃。 I386架构不仅限于Intel处理器,还包括AMD、VIA等其他厂商生产的兼容处理器。这种架构在操作系统设计中占据重要地位,许多...

    I386资料大全.rar

    1. **i386架构**:i386是第一个支持32位寻址的x86处理器,能够处理最大4GB的内存。它的指令集扩展了16位80286的指令,引入了更多的32位指令,同时保持向后兼容。 2. **汇编语言**:与i386相关的编程通常涉及到汇编...

    intel 80386程序员参考手册

    2. **寻址模式**:80386支持多种寻址模式,如直接、间接、基址加偏移、相对寻址等,这些寻址模式为程序提供了灵活的内存访问方式。 3. **寄存器**:处理器内部的寄存器是数据处理的核心。80386有多个通用寄存器(如...

    陈老师讲linux内核1

    从陈老师的讲解中,我们可以深刻理解到Linux内核与硬件之间的紧密关系,特别是关于内存寻址的硬件机制。操作系统作为连接软件与硬件的桥梁,其设计需深度挖掘硬件潜能,而Linux内核的设计则尤为注重这一点,通过清晰...

    Linux内存管理机制文档

    ### Linux 2.6 内存管理机制解析 #### 一、地址类型的分类与功能 在深入探讨Linux 2.6版本的内存管理机制之前,我们先了解几个基本概念,尤其是不同类型地址的作用及其如何帮助系统高效地管理和使用内存资源。 **...

    Linux内核源代码情景分析 (上下册 高清非扫描 )

    - Intel X86架构的CPU提供了多种寻址方式,如直接寻址、间接寻址等。 - 寻址方式的选择直接影响到程序的效率和复杂度。 - **1.3 i386的页式内存管理机制** - i386架构下的内存管理采用分页机制,将物理内存分割成...

    Linux内核源代码情景分析.pdf

    第1章预备知识涵盖了Linux内核的基础,包括Linux内核简介、Intel X86 CPU系列的寻址方式、i386页式内存管理机制、以及Linux内核源代码中的编程语言(主要是C语言和汇编语言)。 在第2章存储管理中,文档分析了Linux...

    linux下的nasm汇编器

    在学习和使用NASM的过程中,了解处理器的架构、寻址模式、寄存器的用途以及如何调用系统调用等知识是非常重要的。同时,熟悉Linux的系统调用接口(如通过`int 0x80`或`sysenter`指令)对于编写系统级程序是必不可少...

    Linux中的汇编语言

    3. 内存寻址方式差异:在内存操作数的表示上,AT&T语法使用圆括号`()`来包含基址寄存器,而不是Intel语法中的方括号`[]`。同时,在AT&T语法中,段寄存器前会有`%`符号,而Intel语法中则不需要。例如,在Intel语法...

    Linux内核情景分析(非扫描版)

    - 寻址方式对于理解指令集、内存访问等方面非常重要,尤其是在低级别的系统编程中。 - **1.3 i386的页式内存管理机制** - 在i386架构中,内存被划分为大小固定的页(通常为4KB),这有助于提高内存使用的灵活性和...

    INTEL 80386 Programmer's Reference Manual

    ### INTEL 80386 Programmer's ...综上所述,《INTEL 80386 Programmer's Reference Manual》是一本非常宝贵的参考资料,对于希望深入了解 80386 微处理器及其在 Linux 内核开发中应用的读者来说,具有重要的参考价值。

    程序员参考手册.zip

    《Intel 80386 Programmer's Reference Manual》详细介绍了80386的体系结构、指令集、内存管理、中断处理、I/O操作、异常处理、保护模式等核心概念。英文版手册全面详实,适合有英文阅读能力的开发者深入学习。手册...

    第16章 存储管理1

    本章将探讨x86架构下的存储管理,特别是针对Intel 80386(简称i386)处理器的内存寻址机制,以及Linux系统在此基础上的分段和分页机制。 首先,x86架构的分段机制是为了实现内存保护和多任务并行而设计的。在i386中...

Global site tag (gtag.js) - Google Analytics