在 使用Rust编写操作系统(位运算)一章中我们实现了基本的位操作,在本节中我们使用之前写好的位操作开始实现地址的操作,我们先了解一下地址的理论知识
地址空间
地址空间在一般情况下分为两类:虚拟地址空间,物理地址空间,虚拟地址空间氛围逻辑地址,有效地址,线性地址.这些地址可以相互转换
虚拟地址空间
虚拟地址空间是一个抽象的地址,大多不能独立转换为物理地址,逻辑地址,有效地址,线性地址和平坦地址都属于虚拟地址的范畴
- 逻辑地址:指应用程序角度看到的内存单元,存储单元,一个逻辑地址由两部份组成,段标识符和段内偏移量,段标识符是由一个16位长的字段组成
- 线性地址: 线性地址是通过逻辑地址中的段基址和段偏移组合而成,使得程序无法字节访问线性地址
- 平坦地址:是一个种特殊的线性地址,将段基址和段长度覆盖整个线性地址空间
物理地址
物理地址是真实存在硬件设备上的地址,通过处理的引脚直接或间接与外部设备,RAM,ROM相连接,在物理地址空间中除了物理内存还有硬件设备,在处理开启分页的情况下线性地址需要经过页表映射才能转为物理地址
- I/O地址: I/O地址空间与内存地址空间相互隔离,必须IN/OUT指令才能访问,I/O地址空间由65536个可独立寻址的I/O端口组成,寻址范围为0-0xFFFF,0xF8和0xFF保留使用
- 内存地址: 内存地址不仅只有物理内存,还有外部的硬件设备地址空间(例如之前的VGA地址)
IA-32e模式寻址
IA-32e模式的线性地址位宽64位,但是线性寻址只有48位,低48位用于线性地址寻址,高16位用于符号扩展(将第47位数值扩展到64位,全为0或全为1),这种地址称为Canonical
地址,在IA-32e模式下,只有Canonical地址空间是可用地址空间,而No-Canonical空间属于无效地址空间
+---------------+<- 0xFFFFFFFF_FFFFFFFF
| |
| Canonical |
| |<-0xFFFF8000_00000000
+---------------+<-0xFFFF7FFF_FFFFFFFF
| |
| Non-Canonical |
| |<-0x00008000_00000000
+---------------+<-0x00007FFF_FFFFFFFF
| |
| Canonical |
| |
+---------------+<-0x00000000_00000000
但采用64位Canonical地址后页管理机制也改成4级,低48位参与页表索引,高16位依旧不参与页表空间索引,页管理机制支持在4KB页面基础上增加2MB和1GB物理页
Canonical地址结构
| 63 - 48 | 47 - 0 |
+----------------+-----------------------+
| Sign Extension | Liner Address |
+----------------+-----------------------+
IA-32e段描述符
代码段描述符
结构如下
| 63-56 |55|54 |53|52 |51-48 |47|46-45|44|43 |42|41|40|39-16 |15 - 0 |
+-----------+--+---+--+---+--------+--+-----+--+---+--+--+--+------------+----------+
|BaseAddr(H)|G |D/B|L |AVL|limit(H)|P |DPL |S |C/D|C |R |A | BaseAddr(L)| limit(L) |
+-----------+--+---+--+---+--------+--+-----+--+---+--+--+--+------------+----------+
数据段描述符
| 63-56 |55|54 |53|52 | 51-48 |47|46-45 |44|43 |42|41|40| 39-16 |15-0 |
+-----------+--+---+--+---+--------+--+------+--+---+--+--+--+------------+----------+
|BaseAddr(H)|G |D/B|L |AVL|limit(H)|P |DPL |S |C/D|E |W |A | BaseAddr(L)| limit(L) |
+-----------+--+---+--+---+--------+--+------+--+---+--+--+--+------------+----------+
有关实模式,保护模式,IA-32e模式寻址的详细的内容将在下一篇文章中讲述,在本章中我们只需要知道Canonical地址的结构即可
开始干活
好了,我们已经知道了关于在实模式,保护模式,IA-32e模式下的地址变化,我们现在开始着手编写IA-32e模式下的地址操作,我们可以构造虚拟地址和物理地址的结构VirtAddr
和PhyisAddr
,VirtAddr用于Canonical地址,对u64类型进行包装,在使用时会检查一段地址是否属于Canonical地址,并且我们要提供一些指针算术的操作(加,减)等操作,PhyisAddr
跟VirtualAddr
操作基本一致
首先我们在system
项目中创建新的模块称为ia_32e
并创建子模块addr.rs
,目录结构如下
system
|
|__ src
| |
| |__ lib.rs
| |
| |__ bits
| | |
| | |__ mod.rs
| |
| |__ ia_32e
| |
| |__mod.rs
| |
| |__addr.rs
|
|__ Cargo.toml
|
|__ .gitignore
然后在src/lib.rs文件中添加以下内容
pub mod ia_32e;
之后在src/ia_32e/mod.rs文件中添加以下内容
pub mod addr;
虚拟地址
紧接着我们在src/ia-32e/addr.rs文件中创建VirtAddr
和NoCanonicalAddr
结构体,NoCanonicalAddr表示不属于Canonical地址,
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct VirtAddr(u64);
#[derive(Debug)]
pub struct NoCanonicalAddr(u64);
这几个宏的用法大家应该都知道了,就不再赘述
然后我们创建一个new_unchecked方法
use crate::bits::BitOpt;
impl VirtAddr{
pub fn new_unchecked(mut addr: u64) -> VirtAddr {
if addr.get_bit(47) {
addr.set_bits(48..64, 0xFFFF);
} else {
addr.set_bits(48..64, 0);
}
VirtAddr(addr)
}
}
根据之前的Canonical地址结构我们知道第47位为P表示已存在标示,如果47位被置1表示该地址在内存中存在,我们使用直线写的bits::BitOpt
trait来完成位的填充操作,位填充完毕后,我们将它用VirtAddr封装以下,我们可以发现new_unchecked
方法对传入的地址不加以判断便直接修改,我还要提供一个会判断的方法
pub fn try_new(addr: u64) -> Result<VirtAddr, NoCanonicalAddr> {
// 获取[47,64)
match addr.get_bits(47..64) {
// 这里47位标示内存已存在
0 | 0x1FFFF => Ok(VirtAddr(addr)),
1 => Ok(VirtAddr::new_unchecked(addr)),
other => Err(NoCanonicalAddr(other)),
}
}
在这里我们对[47,64)位进行的判断,如果传入的地址第47-63位均为0表示属于Canonical地址,相应的,第47位和第48-63位全是1也属于Canonical地址,如果传入的地址47位为1(48-63位均为0)我们需要对符号扩展做一些修改(把48-63位全部置1),其他地址均不属于Canonical地址