跳转至

分段和地址


内存地址类型

地址类型 描述
逻辑地址 机器语言指令使用的地址,指定一个操作数的地址或一条指令的地址
线性地址 线性地址是一个无符号整数,范围0x00~0xffffffff(ffffffff),
由逻辑地址的段基址+段偏移计算得到
若无分页机制,线性地址即物理地址
物理地址 内存单元的实际地址,用于芯片级内存单元寻址

地址范围由操作系统上虚拟地址长度物理地址长度决定

Bash
1
2
3
4
#amd64 ubuntux系统上,查看CPU信息
lscpu 
#Address sizes:         45 bits physical, 48 bits virtual
#可见该系统上。虚拟地址是48bit,物理地址45bit

8086分段

由于8086有20跟地址总线,但是寄存器只有16bit.
无法通过单一寄存器寻址20地址线对应的空间
所以将存储分段,使用段地址+段内偏移的方式寻址.

8080 将内存分为4个段:

  • 数据段
  • 代码段
  • 堆栈段
  • 额外数据段

segment

8086地址计算

段地址左移4位+段内偏移 address

CPU工作模式

实地址模式

即早期8086的工作模式,
早期8086有16位数据总线,20位地址总线,
16位地址可寻址64KB地址空间,
在有20位地址线的情况下,使用段地址+段内偏移的方式可寻址1M地址空间. 详细可参见上一节。

保护模式

目前寄存器和地址总线目前已达到32bit/64bit,(主流操作系统已经放弃对32系统的支持)
32bit地址总线即可寻址4GB地址空间,64bit地址总线可寻址更大内存空间
如果使用段地址+段内偏移的方式寻址,每个程序都可以访问整个内存空间
因此,增加保护模式。

保护模式寻址

将段的信息(即段描述符,包含:段基址,长度,属性等)放到段描述符表,
段描述符表又分为全局段描述符表(GDT)局部段描述符表(LDT)
全局描述符表位于内存中,表的地址放到GDTR寄存器中,
使用16位段选择子(即原来的段寄存器CS/DS/ES/GS的低16位,保护模式下改了用处)从描述符表里选段信息,

段描述符

8字节

字段 长度 描述
segment limit 20bit 段长
base address 32bit 段基地址
type 4bit 段类型,自行查文档
S 1bit 0:系统段描述符
1代码段或者数据段描述符
DPL 2bit 特权级,0为最高特权级,3为最低,表示访问该段时CPU所需处于的最低特权级
P 1 1:有效
0:无效
A
DB
G 1bit 范围粒度
0:粒度为1字节,20bit可寻址1MB
1:粒度为4KB,20bit可寻址4GB

gdt
gdt

段选择子

gdt

段寻址

Linux64位系统下,段基址都为0
gdt
ldt

分页

寻址

寻址语法

1.立即数寻址

直接使用数字
格式:$number

GAS
1
2
#将数字1赋值到ax寄存器
movl $1, %eax 

2.寄存器寻址

数据直接存寄存器里
格式:%寄存器名字

GAS
1
2
#%eax直接访问寄存器
movl $1, %eax 

3.直接寻址

访问并使用变量

GAS
1
2
3
4
5
# 定义变量tmp
tmp:
 .int 1
#将tmp的值1赋值到ax寄存器
movl tmp, %eax 

4.寄存器间接寻址

寄存器里储存的是变量的地址,通过寄存器访问变量
格式:(%寄存器名)

GAS
1
2
3
4
5
6
7
# 定义变量tmp
tmp:
 .int 1
#将tmp的地址赋值到edi寄存器
move $tmp, %edi
#将立即数1复制到edi存的地址的位置,即tmp置为1
move $1, (%edi) 

5.寄存器相对寻址

寄存器里存储的变量的相对地址,通过寄存器加偏移访问变量
格式:偏移量(%寄存器名)

GAS
1
    movl 4(%esp), %eax

6.基址变址寻址方式

格式:base_addrsss(offset_address, index, size)
数值:base_address + offset_address + index * size
其中0值可省略
offset_address和index是寄存器类型

GAS
1
2
3
4
5
tmp:
.int 1,2,3,4
movl $2, %edi
# 寻址tmp的第3个元素
movl tmp(,edi,4), %eax 

参考

Segmentation in Intel x64(IA-32e) architecture - explained using Linux
Linux内存寻址二三事