haribote&&linux0.11内存地址转换笔记整理抽取

Posted 资质平庸的程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了haribote&&linux0.11内存地址转换笔记整理抽取相关的知识,希望对你有一定的参考价值。

haribote和linux0.11由Intel 80x86 CPU执行,程序需契合该CPU对外提供的编程机制才能使用其相应功能。此文意在总结下x86 CPU对外提供的内存地址转换机制。

1 逻辑层面总结CPU执行内存中可执行程序过程

|--------| 0____________  |----------------------|
|   PC   | ------------>  |   |--|      0  |--|  |
|   DR   | n____________  |   |  | ==>  .  |--|  |
|        | 0============  |   |--|      y  |--|  |
| others | <------------  |addr decoder nr cells |
|--------| m============  |----------------------|
CPU        地址&&数据总线 物理内存器件 

__: 地址总线;
==: 数据总线;
-->: 数据走向;
==>:选中。

[1] CPU读取程序指令过程
CPU根据程序计数寄存器(PC)当前值转换得到即将读取程序指令内存地址,该内存地址可直接由内存地址总线传输给物理内存器件译码。

译码器对内存地址译码后将选中包含该程序指令的内存单元,被选中内存单元中的程序指令将由数据总线传输回CPU,同时更新PC,在所读指令执行完毕后再根据PC继续读取程序指令。

   |---------|
   |converted|
PC -----------> 内存地址

|---------------------------------|
|to address decoder by address bus|
-----------------------------------> 程序指令

|------------------------------|
|transferred to CPU by data bus|
--------------------------------> PC更新&&指令执行

[2] CPU读取程序数据过程
CPU在执行包含操作数的指令时,可能涉及从内存中读取程序数据。CPU根据数据寄存器当前值转换得到即将读取程序数据内存地址,该内存地址可直接由内存地址总线传输给物理内存器件译码。

物理内存器件对该地址译码后将选中包含程序数据的内存单元,被选中内存单元中的数据由数据总线传输回CPU。

          |---------|
          |converted|
数据寄存器 ----------> 内存地址 

|---------------------------------|
|to address decoder by address bus|
-----------------------------------> 选中包含数据的内存单元

|------------------------------|
|transferred to CPU by data bus|
--------------------------------> CPU(通用寄存器)

2 实模式内存地址转换方式

此处的内存地址转换即前一节所涉及转换(converted)环节。

后期CPU会尽量兼容前期CPU对外提供的编程契口,鼻祖8086 CPU对外提供的编程契口及对应功能在后期CPU中被称为实模式。

在实模式下,CPU提供的可编程寄存器为16位。用于传输内存地址的导线共20根,即内存地址空间范围为[0x0, 0x100000)。

CPU通过**“16位段基址 << 4 + 16位偏移”**方式转换得到20位内存地址。段基址用段寄存器(CS, SS, DS, ES)承载,他们分别默认搭配(IP, SP, SI, DI)寄存器作为偏移。这些寄存器搭配起来的作用如下。

CS:IP, 程序计数寄存器(PC), CS << 4 + IP 即为程序指令内存基址;
DS:SI, 程序数据寄存器, DS << 4 + SI 即为程序数据内存基址;
ES:DI, 程序数据寄存器, ES << 4 + DI 即为程序数据内存基址;
SS:SP, 栈维护寄存器组, SS << 4 + SP 即为栈顶基址。

如在程序中通过段间跳转指令隐式修改CS和IP的值

# CS=0, IP=3,根据实模式地址转换方式
# 将得到跳转内存地址为 CS << 4 + IP = 0x00003
jmpi 0x0003, 0x0000

3 保护模式内存地址转换方式

保护模式是对最初CPU功能的扩展,其中增加了能提供更强大功能的硬件资源和相应的编程契口。

保护模式增加了额外的寄存器和数据结构用作内存地址转换和访问限制(此文不涉及),比实模式稍复杂一点。

3.1 寄存器扩展

IP, SP, SI, DI被分别扩展为32位的EIP, ESP, ESI, EDI(另外的一些寄存器也是32位或大于32位的)。

保护模式下的段寄存器对外开放16位,段寄存器由段基址角色转换为选择符,其位格式如下。

|15         3| 2|  0|
---------------------
|  selector  |TI|RPL|
--------------------- 
RPL(bit[1..0]),请求特权级(0 > 1 > 2 > 3);
TI(bit[2]),段寄存器类型,0-GDT选择符,1-LDT选择符;
selector(bit[15..3]),GDT/LDT选择符,
CPU解析bit[15..3]时自动补0充当其低3位。
3.2 GDT

在实模式下,由于内存地址转换方式“16位段基址 << 4 + 16位偏移“中的偏移为16位,所以在一个段基址下最多可访问64Kb内存。

为提供更强大的内存访问机制,保护模式以GDT(Global Descriptor Table)为中心对内存转换方式进行了扩展。

                                |----------------|
                                |        .       |
                                |        .       |<--|
|-----------------|             |        .       |   |
|    GDTR=n       |             |n|------------| |   |
|                 |             | |  GDT item  | |   |
|                 |GDTR+selector| |      .     | |   |
|  CS  SS  DS  ES |------------>| |      .     | |---|
| EIP ESP ESI EDI |             | |      .     | |base addr+offset
|                 |             | |  GDT item  | |
|     others      |             | |------------| |
|-----------------|             | GDT            |
CPU                             |        .       |
                                |        .       |
                                |        .       |
                                |----------------|
                                内存

GDTR是保护模式下用于保存GDT信息的寄存器,由LGDT/SGDT指令访问,其位格式如下。

|47         16|15         0|
----------------------------
| GDT address | GDT length |
----------------------------
GDTR[15..0]:  GDT长度; 
GDTR[47..16]: GDT内存基址。

GDT item是组成GDT的基本项,包含多种类型,CPU会自动识别GDT item中的位格式。由于此处只理解由寄存器转换内存地址的方式,所以可只关心与内存相关字段。

|31           |23              |15             |7         0
|----------------------------------------------------------
| base addr   | | | |A|  limit | |   | | TYPE  | base addr|
| [31..24]    |G|D|0|V|[19..16]|P|DPL|S|T|     | [23..16] |
|             | | | |L|        | |   |1|1|C|R|A|          |
|---------------------------------------------------------|4
|                              |                          |
|      base addr[15..0]        |      limit[15..0]        |
|                              |                          |
-----------------------------------------------------------0 
G(Granularity): 内存段颗粒度, 
G=0, limit[19..0]值为段限长, 单位字节; 
G=1, CPU会自动补12位1到limit的低12位中, 
即段限长=limit[19..0] << 12 + 0xfff, 单位字节。

base addr[31..0]: 所描述内存段的起始地址。
limit[19..0]: 段限长, 段限长大小与G有关。

在保护模式下,内存转换方式为“选择符 + 偏移地址”。如在程序中通过段间跳转指令修改CS和EIP的值,内存地址转换大体过程如下。

# CS=0x08, EIP=0x0
jmpi 0, 8

# [1] 由选择符获取GDT item地址:GDT_item = GDTR + CS << 3;
# [2] 由GDT item和EIP获取内存地址:GDT_item.base_addr + EIP。
# 若保护模式下的页机制没有开启,则由第[2]步获取到的内存地址
# 可直接送往内存地址总线上传输给物理内存器件译码。
3.3 页机制

若将可直接送往内存地址总线上用于索引物理内存的内存地址视为物理内存地址,其余内存地址都可视为一种在特定上下文中的逻辑内存地址。

若开启了保护模式下的页机制,由GDT item转换出来的逻辑内存地址还需页变换才能得到物理内存地址。

CPU根据规定的页目录和页表数据结构对GDT item输出的逻辑内存地址进行页变换。当CR0.bit[31]=1时开启页机制,此时CR3用于保存页目录基址。

在了解页变换前先了解下页目录和页表数据结构体吧。

同GDT,由编程者在内存任意设置页目录和页表数据结构,
页目录基址由CR3保存,页表数据数据结构基址保存在页目录项中。
                    |------------------|
                    |       ...        |
                    |t|----------------|<--|
                    | |PAGE table item |   |
                    | |     ...        |   |
|-----------------| | |PAGE table item |   |
|  CR0.bit[31]=1  | | |----------------|   |
|     CR3=p       | | PAGE table       |   |
|     others      | |        .         |   |
|-----------------| |        .         |   |
CPU                 |        .         |   |
                    |p|-------------|  |   |
                    | |PAGE DIR item|  |---|
                    | |     ...     |  |
                    | |PAGE DIR item|  |
                    | |-------------|  |
                    | PAGE DIR         |
                    |       ...        |
                    -------------------|
                    内存

页目录共一页(4Kb)大小,其由32位的页目录项组成。

页目录项用于描述页表信息,其位格式如下。
|31                     12|    9|              0|
-------------------------------------------------
|                         |     |  | | |  |U|R| |
| page table addr[31..12] | AVL |00|D|A|00|/|/|P|
|                         |     |  | | |  |S|W| |
------------------------------------------------- 
页表基址page table addr[11..0]由CPU自动补0。

页表共一页大小,其由32位的页表项组成。

页表项用于描述内存页信息,其位格式如下。
|31               12|    9|              0|
-------------------------------------------
|                   |     |  | | |  |U|R| |
| page addr[31..12] | AVL |00|D|A|00|/|/|P|
|                   |     |  | | |  |S|W| |
------------------------------------------- 
内存页基址page addr[11..0]由CPU自动补0。

了解以上内容后(不尝试了解除跟地址变换有关以外的信息应该可以完成尝试),尝试看看逻辑内存地址页变换过程吧(假设页目录和页表数据结构体及相关寄存器已被正确初始化)。

设GDT item输出的逻辑地址为0x00001000。页变换是将逻辑地址拆分后在页数据结构中索引的过程。

页变换机制将逻辑地址bit[31…22]作为页目录项在页目录中的索引,将bit[21…12]作为页表项在页表中的索引,将bit[11…0]作为其在内存页中的偏移。

|0000000000|0000000001|000000000000| 
逻辑内存地址0x00001000经页变换后得到物理内存地址:
PAGE_DIR[0][1].page_addr + 0。

若在初始化页目录和页表数据结构体时,页目录中第1个页表项存储了页表x的地址,
页表x中第2个页表项存储的内存页地址为y,则0x00001000经页变换后得到物理内存地址y。

最后看看一页页目录数据结构可映射多大的物理内存。1页页目录可存储1Kb页表地址,每个页表可包含1Kb页表项,每页内存共4Kb大小。所以,在不重叠映射情况下,一页页目录数据结构可映射1Kb x 1Kb x 4Kb=4Gb物理内存。另外,linux0.11中进程的写时拷贝就是基于页机制实现的呢。

3.4 LDT

在“寄存器扩展”和“GDT”小节有提到,段寄存器TI位即bit[2]=1时,段寄存器将成为LDT(Local Descriptor Table)项选择符。

LDT用于描述应用程序内存段,段寄存器作为LDT项选择符时,逻辑地址变换会多一些环节。

                    |------------------|base addr+offset
                   h|       ...        |<----|
                    |k|--------------| |     |
                    | |   LDT item   | |     |
                    | |     ...      | |     |
                    | |   LDT item   | |     |
                    | |--------------| |     |
                    | LDTk             |     |
                    |       ...        |     |
                    |m|--------------| |<--| |
                    | |     LDT item | |---|-|
                    | |     ...      | |   |
|-----------------| | |     LDT item | |   |
|      GDTR=n     | | |--------------| |   |
|      LDTR=0     | | LDTm             |   |
|  CS=0x07 EIP=0  | |        .         |   |
|     others      | |        .         |   |
|-----------------| |        .         |   |
CPU                 |n|-------------|  |   |
                    | |  GDT item   |  |---|
                    | |     ...     |  |base addr
                    | |  GDT item   |  |
                    | |-------------|  |
                    | GDT              |
                    |       ...        |
                    -------------------|
                    内存

每个应用程序都拥有一个LDT,其内存基址和限长设置在GDT表中。LDTR寄存器用作LDT在GDT中的选择符。

如 jmpi 0, 0x07 将引发CPU做完成以下流程。
[1] 获取当前LDTR对应的LDT的GDT item地址;
[2] 从+ LDTR的GDT iterm中获取所描述LDT基址,假设为m;
[3] 获取选择符CS对应的LDT item:m + CS << 3;
[4] 根据LDT item中的基址字段获取选择符CS对应的内存基址h,结合EIP提供的偏移地址,最终输出逻辑地址h;
[5] h经页变换后得到最终物理内存地址。

LDT item位格式跟GDT item位格式一致呢。描述LDT的GDT item成为系统段描述符,除类型相关字段外,其余位格式其余GDT item。

|31           |23              |15             |7         0
|----------------------------------------------------------
| base addr   | | | | |  limit | |   | | TYPE  | base addr|
| [31..24]    |G| |0| |[19..16]|P|DPL|S|       | [23..16] |
|             | | | | |        | |   |0|       |          |
|---------------------------------------------------------|4
|                              |                          |
|      base addr[15..0]        |      limit[15..0]        |
|                              |                          |
-----------------------------------------------------------0
S=0时, CPU将GDT段描述符视为系统段描述符。
系统段描述符的TYPE类型较多, 只列描述LDT和TSS描述符的TYPE值。

TYPE=0010(0x2), 系统段描述符描述LDT所在内存段;
TYPE=1001(0x9), 系统段描述符描述32位可用TSS描述符所在内存段。

若可理解LDT选择符内存转换过程,另外跟IDT相关的内存转换也应该不难理解啦,此文不再敲敲叨叨了。

以上是关于haribote&&linux0.11内存地址转换笔记整理抽取的主要内容,如果未能解决你的问题,请参考以下文章

haribote&&linux0.11并发笔记整理抽取

haribote&&linux0.11引导程序阅读笔记整理抽取

haribote dsctbl.c 设置GDT和IDT程序阅读注释

学习&理解基于TSS和基于内核栈两种机制的进程切换

linux0.11的进程1的创建和执行

linux0.11的进程1的创建和执行