使用Rust开发操作系统(Canonical地址以及虚拟地址和物理地址操作)

释放双眼,带上耳机,听听看~!

地址操作

  • 地址空间

  • 虚拟地址空间

    • 物理地址
  • IA-32e模式寻址

  • IA-32e段描述符

  • 代码段描述符

  • 数据段描述符

  • 开始干活

  • 虚拟地址

  • 地址对齐

    • 物理地址
  • 下一步要做什么

在使用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空间属于无效地址空间


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1+---------------+<- 0xFFFFFFFF_FFFFFFFF
2|               |                      
3|   Canonical   |                      
4|               |<-0xFFFF8000_00000000
5+---------------+<-0xFFFF7FFF_FFFFFFFF
6|               |                      
7| Non-Canonical |                      
8|               |<-0x00008000_00000000
9+---------------+<-0x00007FFF_FFFFFFFF
10|               |                      
11|   Canonical   |                      
12|               |                      
13+---------------+<-0x00000000_00000000
14
15

但采用64位Canonical地址后页管理机制也改成4级,低48位参与页表索引,高16位依旧不参与页表空间索引,页管理机制支持在4KB页面基础上增加2MB和1GB物理页

Canonical地址结构


1
2
3
4
5
6
7
1              
2| 63 - 48        |   47 - 0              |
3+----------------+-----------------------+
4| Sign Extension |    Liner Address      |
5+----------------+-----------------------+
6
7

IA-32e段描述符

代码段描述符

结构如下


1
2
3
4
5
6
1| 63-56     |55|54 |53|52 |51-48   |47|46-45|44|43 |42|41|40|39-16       |15 - 0    |
2+-----------+--+---+--+---+--------+--+-----+--+---+--+--+--+------------+----------+
3|BaseAddr(H)|G |D/B|L |AVL|limit(H)|P |DPL  |S |C/D|C |R |A | BaseAddr(L)| limit(L) |
4+-----------+--+---+--+---+--------+--+-----+--+---+--+--+--+------------+----------+  
5
6

数据段描述符


1
2
3
4
5
6
1|   63-56   |55|54 |53|52 | 51-48  |47|46-45 |44|43 |42|41|40| 39-16      |15-0      |
2+-----------+--+---+--+---+--------+--+------+--+---+--+--+--+------------+----------+
3|BaseAddr(H)|G |D/B|L |AVL|limit(H)|P |DPL   |S |C/D|E |W |A | BaseAddr(L)| limit(L) |
4+-----------+--+---+--+---+--------+--+------+--+---+--+--+--+------------+----------+
5
6

有关实模式,保护模式,IA-32e模式寻址的详细的内容将在下一篇文章中讲述,在本章中我们只需要知道Canonical地址的结构即可

开始干活

好了,我们已经知道了关于在实模式,保护模式,IA-32e模式下的地址变化,我们现在开始着手编写IA-32e模式下的地址操作,我们可以构造虚拟地址和物理地址的结构VirtAddr和PhyisAddr,VirtAddr用于Canonical地址,对u64类型进行包装,在使用时会检查一段地址是否属于Canonical地址,并且我们要提供一些指针算术的操作(加,减)等操作,PhyisAddr跟VirtualAddr操作基本一致

首先我们在system项目中创建新的模块称为ia_32e并创建子模块addr.rs,目录结构如下


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1system
2|
3|__ src
4|      |
5|      |__ lib.rs
6|      |
7|      |__ bits
8|      |       |
9|      |       |__ mod.rs
10|     |
11|     |__ ia_32e
12|             |
13|             |__mod.rs
14|             |
15|             |__addr.rs
16|
17|__ Cargo.toml
18|
19|__ .gitignore
20
21

然后在src/lib.rs文件中添加以下内容


1
2
3
1pub mod ia_32e;
2
3

之后在src/ia_32e/mod.rs文件中添加以下内容


1
2
3
1pub mod addr;
2
3

虚拟地址

紧接着我们在src/ia-32e/addr.rs文件中创建VirtAddr和NoCanonicalAddr结构体,NoCanonicalAddr表示不属于Canonical地址,


1
2
3
4
5
6
7
8
1#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
2#[repr(transparent)]
3pub struct VirtAddr(u64);
4
5#[derive(Debug)]
6pub struct NoCanonicalAddr(u64);
7
8

这几个宏的用法大家应该都知道了,就不再赘述

然后我们创建一个new_unchecked方法


1
2
3
4
5
6
7
8
9
10
11
12
13
1use crate::bits::BitOpt;
2impl VirtAddr{
3  pub fn new_unchecked(mut addr: u64) -> VirtAddr {
4    if addr.get_bit(47) {
5      addr.set_bits(48..64, 0xFFFF);
6    } else {
7      addr.set_bits(48..64, 0);
8    }
9    VirtAddr(addr)
10  }
11}
12
13

根据之前的Canonical地址结构我们知道第47位为P表示已存在标示,如果47位被置1表示该地址在内存中存在,我们使用直线写的bits::BitOpttrait来完成位的填充操作,位填充完毕后,我们将它用VirtAddr封装以下,我们可以发现new_unchecked方法对传入的地址不加以判断便直接修改,我还要提供一个会判断的方法


1
2
3
4
5
6
7
8
9
10
11
1 pub fn try_new(addr: u64) -> Result<VirtAddr, NoCanonicalAddr> {
2   // 获取[47,64)
3   match addr.get_bits(47..64) {
4     // 这里47位标示内存已存在
5     0 | 0x1FFFF => Ok(VirtAddr(addr)),
6     1 => Ok(VirtAddr::new_unchecked(addr)),
7     other => Err(NoCanonicalAddr(other)),
8   }
9}
10
11

在这里我们对[47,64)位进行的判断,如果传入的地址第47-63位均为0表示属于Canonical地址,相应的,第47位和第48-63位全是1也属于Canonical地址,如果传入的地址47位为1(48-63位均为0)我们需要对符号扩展做一些修改(把48-63位全部置1),其他地址均不属于Canonical地址

最后我们创建一个new方法


1
2
3
4
5
6
1pub fn new(addr: u64) -> VirtAddr {
2  // 给定的地址48-64位必须是不包含任何数据的(全0或全1)
3  Self::try_new(addr).expect("given address can not contain any data in bits 48 to 64")
4}
5
6

new方法在创建的地址的时候会检测地址是否属于Canonical地址,如果不属于将会Panic

我们再提供一个全0地址


1
2
3
4
5
1pub const fn zero() -> VirtAddr {
2  VirtAddr(0)
3}
4
5

我们的地址最终要专为u64类型的,所以我们提供这样的方法


1
2
3
4
5
1pub fn as_u64(&self) -> u64 {
2  self.0
3}
4
5

现在我们创建的VirtAddr支持u64类型转换,但是我们以后需要将指针转换成VirtAddr并且可以将VirtAddr转为指针

在转换的时候我们也需要转换过程是安全的,在这我们使用cast,在Cargo.toml文件中添加以下内容


1
2
3
4
5
1[dependencies.cast]
2version = "0.2.2"
3default-features = false
4
5

然后我们开始实现


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1pub fn from_pointer<T>(pointer: *const T) -> Self {
2  // 将裸指针转为usize,利用cast::u64转为u64类型
3  Self::new(cast::u64(pointer as usize))
4}
5
6#[cfg(target_pointer_width = "64")]
7pub fn as_ptr<T>(self) -> *const T {
8  cast::usize(self.as_u64()) as *const T
9}
10
11#[cfg(target_pointer_width = "64")]
12pub fn as_mut_ptr<T>(self) -> *mut T {
13  self.as_ptr::<T>() as *mut T
14}
15
16

我们的系统是x86-64位的,我们的指针宽度应该也是64位的,因此我们使用了target_pointer_width属性,来保证我们从u64类型转换后指针的宽度为64位

我们也提供了只读指针和可读写指针as_ptr和as_mut_ptr

最后我们为VirtAddr实现Add,AddAssign,Sub,SubAssign,Debug这几个trait

Add trait

Add trait类似于C++中的操作符重载,实现了Add trait可以完成对+的操作,例如


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1use std::ops::Add;
2
3#[derive(Debug, PartialEq)]
4struct Point {              
5    x: i32,                
6    y: i32,                
7}  
8
9impl Add for Point {                    
10    type Output = Self;                
11                                        
12    fn add(self, other: Self) -> Self {
13        Self {                          
14            x: self.x + other.x,        
15            y: self.y + other.y,        
16        }                              
17    }                                  
18}
19
20assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 },Point { x: 3, y: 3 });                      
21
22

AddAssign trait

实现了AddAssign trait我们可以完成+=操作,例如


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1use std::ops::AddAssign;
2
3#[derive(Debug, PartialEq)]
4struct Point {
5    x: i32,
6    y: i32,
7}
8
9impl AddAssign for Point {
10    fn add_assign(&mut self, other: Self) {
11        *self = Self {
12            x: self.x + other.x,
13            y: self.y + other.y,
14        };
15    }
16}
17
18let mut point = Point { x: 1, y: 0 };
19point += Point { x: 2, y: 3 };
20assert_eq!(point, Point { x: 3, y: 3 });
21
22

Sub trait


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1use std::ops::Sub;                                                  
2                                                                    
3#[derive(Debug, PartialEq)]                                        
4struct Point<T> {                                                  
5    x: T,                                                          
6    y: T,                                                          
7}                                                                  
8                                                                    
9// Notice that the implementation uses the associated type `Output`.
10impl<T: Sub<Output = T>> Sub for Point<T> {                        
11    type Output = Self;                                            
12                                                                    
13    fn sub(self, other: Self) -> Self::Output {                    
14        Point {                                                    
15            x: self.x - other.x,                                    
16            y: self.y - other.y,                                    
17        }                                                          
18    }                                                              
19}                                                                  
20                                                                    
21assert_eq!(Point { x: 2, y: 3 } - Point { x: 1, y: 0 },Point { x: 1, y: 3 });                                  
22
23

SubAssign参考AddAssign

知道了这几个trait的用途之后我们开始实现这几个trait


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
1impl fmt::Debug for VirtAddr {
2    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3        write!(f, "Virtual Address: {:#x}", self.0)
4    }
5}
6
7impl Add<u64> for VirtAddr {
8    type Output = Self;
9
10    fn add(self, rhs: u64) -> Self::Output {
11        VirtAddr::new(self.0 + rhs)
12    }
13}
14
15impl AddAssign<u64> for VirtAddr {
16    fn add_assign(&mut self, rhs: u64) {
17        *self = *self + rhs;
18    }
19}
20
21#[cfg(target_pointer_width = "64")]
22impl Add<usize> for VirtAddr {
23    type Output = Self;
24
25    fn add(self, rhs: usize) -> Self::Output {
26        self + cast::u64(rhs)
27    }
28}
29
30#[cfg(target_pointer_width = "64")]
31impl AddAssign<usize> for VirtAddr {
32    fn add_assign(&mut self, rhs: usize) {
33        self.add_assign(cast::u64(rhs));
34    }
35}
36
37impl Sub<u64> for VirtAddr {
38    type Output = Self;
39
40    fn sub(self, rhs: u64) -> Self::Output {
41        VirtAddr::new(self.0.checked_sub(rhs).unwrap())
42    }
43}
44
45
46impl SubAssign<u64> for VirtAddr {
47    fn sub_assign(&mut self, rhs: u64) {
48        *self = *self - rhs;
49    }
50}
51
52#[cfg(target_pointer_width = "64")]
53impl Sub<usize> for VirtAddr {
54    type Output = Self;
55
56    fn sub(self, rhs: usize) -> Self::Output {
57        self - cast::u64(rhs)
58    }
59}
60
61#[cfg(target_pointer_width = "64")]
62impl SubAssign<usize> for VirtAddr {
63    fn sub_assign(&mut self, rhs: usize) {
64        self.sub(cast::u64(rhs));
65    }
66}
67
68impl Sub<VirtAddr> for VirtAddr{
69    type Output = u64;
70
71    fn sub(self, rhs: VirtAddr) -> Self::Output {
72        self.as_u64().checked_sub(rhs.as_u64()).unwrap()
73    }
74}
75
76

我们除了实现对u64类型的指针运算以外还实现了了对usize类型的指针运算,这样我们可以使用对裸指针进行运算了,最后对VirtAddr自身实现了Sub方法这样我们可计算指针的偏移量

我们src/ia-32e/addr.rs文件内容就是这样的


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
1use crate::bits::BitOpt;
2use core::result::Result;
3use core::fmt;
4use core::ops::{Add, AddAssign, Sub, SubAssign};
5use core::convert::TryInto;
6
7#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
8#[repr(transparent)]
9pub struct VirtAddr(u64);
10
11#[derive(Debug)]
12pub struct NoCanonicalAddr(u64);
13
14
15impl VirtAddr {
16    /// 创建一个Canonical地址,传入的地址不会进行检查
17    /// 该方法会检查第47位(P:已存在标示),48-63位将会被重写
18    pub fn new_unchecked(mut addr: u64) -> VirtAddr {
19        if addr.get_bit(47) {
20            addr.set_bits(48..64, 0xFFFF);
21        } else {
22            addr.set_bits(48..64, 0);
23        }
24        VirtAddr(addr)
25    }
26
27    /// 该函数尝试创建一个Canonical地址,
28    /// 如果48位到64位是正确的符号扩展名(即47位的副本)或全部为空,将成功返回
29    pub fn try_new(addr: u64) -> Result<VirtAddr, NoCanonicalAddr> {
30        // 获取[47,64)
31        match addr.get_bits(47..64) {
32            // 这里47位标示内存已存在
33            0 | 0x1FFFF => Ok(VirtAddr(addr)),
34            1 => Ok(VirtAddr::new_unchecked(addr)),
35            other => Err(NoCanonicalAddr(other)),
36        }
37    }
38
39    pub fn new(addr: u64) -> VirtAddr {
40        // 给定的地址48-64位必须是不包含任何数据的
41        Self::try_new(addr).expect("given address can not contain any data in bits 48 to 64")
42    }
43
44
45    /// 创建全0地址
46    pub fn zero() -> VirtAddr {
47        VirtAddr(0)
48    }
49
50    pub fn as_u64(&self) -> u64 {
51        self.0
52    }
53    /// 从给定的指针中创建虚拟地址
54    pub fn from_pointer<T>(pointer: *const T) -> Self {
55        Self::new(cast::u64(pointer as usize))
56    }
57
58    #[cfg(target_pointer_width = "64")]
59    pub fn as_ptr<T>(self) -> *const T {
60        cast::usize(self.as_u64()) as *const T
61    }
62
63    #[cfg(target_pointer_width = "64")]
64    pub fn as_mut_ptr<T>(self) -> *mut T {
65        self.as_ptr::<T>() as *mut T
66    }
67}
68
69impl fmt::Debug for VirtAddr {
70    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71        write!(f, "Virtual Address: {:#x}", self.0)
72    }
73}
74
75/// 地址添加计算
76impl Add<u64> for VirtAddr {
77    type Output = Self;
78
79    fn add(self, rhs: u64) -> Self::Output {
80        VirtAddr::new(self.0 + rhs)
81    }
82}
83
84impl AddAssign<u64> for VirtAddr {
85    fn add_assign(&mut self, rhs: u64) {
86        *self = *self + rhs;
87    }
88}
89
90#[cfg(target_pointer_width = "64")]
91impl Add<usize> for VirtAddr {
92    type Output = Self;
93
94    fn add(self, rhs: usize) -> Self::Output {
95        self + cast::u64(rhs)
96    }
97}
98
99#[cfg(target_pointer_width = "64")]
100impl AddAssign<usize> for VirtAddr {
101    fn add_assign(&mut self, rhs: usize) {
102        self.add_assign(cast::u64(rhs));
103    }
104}
105
106impl Sub<u64> for VirtAddr {
107    type Output = Self;
108
109    fn sub(self, rhs: u64) -> Self::Output {
110        VirtAddr::new(self.0.checked_sub(rhs).unwrap())
111    }
112}
113
114
115impl SubAssign<u64> for VirtAddr {
116    fn sub_assign(&mut self, rhs: u64) {
117        *self = *self - rhs;
118    }
119}
120
121#[cfg(target_pointer_width = "64")]
122impl Sub<usize> for VirtAddr {
123    type Output = Self;
124
125    fn sub(self, rhs: usize) -> Self::Output {
126        self - cast::u64(rhs)
127    }
128}
129
130#[cfg(target_pointer_width = "64")]
131impl SubAssign<usize> for VirtAddr {
132    fn sub_assign(&mut self, rhs: usize) {
133        self.sub(cast::u64(rhs));
134    }
135}
136
137impl Sub<VirtAddr> for VirtAddr{
138    type Output = u64;
139
140    fn sub(self, rhs: VirtAddr) -> Self::Output {
141        self.as_u64().checked_sub(rhs.as_u64()).unwrap()
142    }
143}
144
145

地址对齐

地址对齐是内存中排列和访问数据的一种方式,

在编程语言中,一个对象通常包含2个属性,自身的值和存储的地址,地址对齐表示数据的地址可以被1,2,4,8整除,换句话说
数据大小都会按照1字节,2字节,4字节,8字节进行对齐(2的次方即可)
CPU每次读取内存的时候不会一字节一字节读取,相反,CPU每次会读取2,4,8,16或32字节,这样可以减少内存访问频率,提供更高的性能(每次读取4字节肯定比每次读取1字节快)

我们假设CPU每次读取是4字节,我们的数据data也是4字节的
在字节对齐的情况下是这样的

我们可以看到CPU只需要读取一次便可读取完整个data的内容,假如我们的data没有按照4字节对齐,那么将会是这样子的

当发生这样的情况,CPU需要读取2次,并且还要移除掉不需要的字节然后合并在一起,这样的读取速度会很低,有些CPU甚至拒绝读取没有对齐的数据

在32位x86系统中,对齐方式与其数据类型的大小基本相同。编译器会使用变量自然的长度进行对齐。 CPU将正确处理未对齐的数据,因此无需显式对齐地址

例如C语言中数据类型如下

char
1
short
2
int
4
float
4
double
4或8

但是,对于struct,union或class对象中的成员数据而言,情况略有不同

struct,union或class的成员变量必须与任何成员变量大小的最高字节对齐,以防止性能下降

如果结构中有1个char变量(1字节)和1个int变量(4字节),则编译器将在这两个变量之间填充3个字节

此struct变量的总大小为8个字节,而不是5个字节
这样,该结构数据的地址就可以被4整除。这称为结构成员对齐。当然,结构的大小将因此而增长

例如:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1// 大小 = 2 字节, 对齐方式 = 1字节, 地址可以被1整除
2struct S1 {
3    char m1;    // 1字节
4    char m2;    // 1字节
5};
6
7// 大小 = 4 字节, 对齐方式 = 2字节, 地址可以被2整除
8struct S2 {
9    char m1;    // 1字节
10                // 填充 1字节
11    short m2;   // 2字节
12};
13
14// 大小 = 8 字节, 对齐方式 = 4字节, 地址可以被4整除
15struct S3 {
16    char m1;    // 1字节
17                // 填充 3字节
18    int m2;     // 4字节
19};
20
21// 大小 = 16 字节, 对齐方式 = 8字节, 地址可以被8整除
22struct S4 {
23    char m1;    // 1字节
24                // 填充 7字节
25    double m2;  // 8字节
26};
27
28// 大小 = 16 字节, 对齐方式 = 8字节, 地址可以被8整除
29struct S5 {
30    char m1;    // 1字节
31                // 填充 3字节
32    int m2;     // 4字节
33    double m2;  // 8字节
34};
35
36

现在有这样的内存结构


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1   +-----+<- 0x7200 0007 (H)
2   |     |
3   +-----+<- 0x7200 0006
4   |     |
5   +-----+<- 0x7200 0005
6   |     |
7   +-----+<- 0x7200 0004
8   |     |
9   +-----+<- 0x7200 0003
10   |     |
11   +-----+<- 0x7200 0002
12   |     |
13   +-----+<- 0x7200 0001
14
15

我们需要做的就是获取某个地址的对齐地址,例如我们按照4字节对齐,0x7200 0002的对齐地址(低)为0x7200 0000

0x7200 0002的对齐地址(高)为0x7200 0004

如果我们按照2字节对齐,0x7200 0002的对齐地址(低)为0x7200 0002,对齐地址(高)为0x7200 0003

7200 0002 转为2进制数为,采取的是4字节对齐

0111 0010 0000 0000 0000 0000 0000 0010


1
2
3
4
5
6
1    0111 0010 0000 0000 0000 0000 0000 0010
2and 0000 0000 0000 0000 0000 0000 0000 0011
3--------------------------------------------
4    0000 0000 0000 0000 0000 0000 0000 0010
5
6

如果and以后为0 表示刚好是2的次方的结果,如果不是则不为0,我们再以0x7200 0004举例


1
2
3
4
5
6
1    0111 0010 0000 0000 0000 0000 0000 0100
2and 0000 0000 0000 0000 0000 0000 0000 0011
3--------------------------------------------
4    0000 0000 0000 0000 0000 0000 0000 0000
5
6

这样我们就可以判断当前的地址是否是对齐后的地址,如果不是对齐后的地址我们可以通过以下方式算出对齐地址(高)和对齐地址(低)

高对齐地址计算方式如下

mask = 对齐字节 – 1

对齐地址(高) = (原地址 | 对齐字节) + 1

对齐地址(低) = 原地址 & !(对齐字节 – 1)

其中对齐字节必须是2次幂的结果

我们接下来开始编写VirtAddr的对齐字节计算方法的函数,VirtAddr和PhysAddr都将会使用该函数,因此我们做成单独的函数


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1pub fn align_down(addr: u64, align: u64) -> u64 {
2    assert!(align.is_power_of_two(), "`align` must be a power of two");
3    addr & !(align - 1)
4}
5
6pub fn align_up(addr: u64, align: u64) -> u64 {
7    assert!(align.is_power_of_two(), "`align` must be a power of two");
8    let mask = align - 1;
9    if addr & mask == 0 {
10        addr
11    } else {
12        (addr | mask) + 1
13    }
14}
15
16

我们添加了1个断言,利用align & (align – 1) == 0来判断该数是否属于2的次方

之后我们为VirtAddr实现对应的方法


1
2
3
4
5
6
7
8
9
10
11
12
13
1pub fn align_up<U>(self, align: U) -> Self where U: Into<u64> {
2  VirtAddr(align_down(self.0, align.into()))
3}
4
5pub fn align_down<U>(self, align: U) -> Self where U: Into<u64> {
6  VirtAddr(align_up(self.0, align.into()))
7}
8
9pub fn is_aligned<U>(self, align: U) -> bool where U: Into<u64> {
10  self.align_down(align) == self
11}
12
13

我们为这几个方法增加了范型参数,并对范型参数做了约束,要求传入的类型实现Into<u64>trait

这样我们的虚拟地址的包装就做好了

物理地址

跟虚拟地址类似有些函数可以根据做好的拷贝过来

结构体和初始化函数如下


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
2#[repr(transparent)]
3pub struct PhysAddr(u64);
4
5pub struct NoInvalidPhysAddr(u64);
6
7impl PhysAddr{
8  pub fn new(addr: u64) -&gt; PhysAddr {
9  assert_eq!(addr.get_bits(52..64), 0, &quot;physical addresses must not have any bits in the range 52 to 64 set&quot;);
10  PhysAddr(addr)
11}
12
13  pub fn try_new(addr: u64) -&gt; Result&lt;PhysAddr, NoInvalidPhysAddr&gt; {
14    match addr.get_bits(52..64) {
15      0 =&gt; Ok(PhysAddr(addr)),
16      other =&gt; Err(NoInvalidPhysAddr(other)),
17    }
18  }
19}
20
21

在创建PhysAddr的时候我们需要检查52-63位全部为0new函数在检测到52-63位不为0是将会抛出断言异常

跟VirtAddr一样我们也需要实现,Debug,Add,AddAssgin,Sub,SubAddsign,和地址对齐,如果你懒得写的话可以把VirtAddr的实现拷贝过来,改下名字就行了(果然CV大法好)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
1impl PhysAddr{
2   pub fn as_u64(self) -&gt; u64 {
3        self.0
4    }
5
6    pub fn is_null(&amp;self) -&gt; bool {
7        self.0 == 0
8    }
9  
10    pub fn align_up&lt;U&gt;(self, align: U) -&gt; Self where U: Into&lt;u64&gt;,
11    {
12        PhysAddr(align_up(self.0, align.into()))
13    }
14
15    pub fn align_down&lt;U&gt;(self, align: U) -&gt; Self where U: Into&lt;u64&gt;,
16    {
17        PhysAddr(align_down(self.0, align.into()))
18    }
19
20    pub fn is_aligned&lt;U&gt;(self, align: U) -&gt; bool where U: Into&lt;u64&gt;,
21    {
22        self.align_down(align) == self
23    }
24}
25
26impl fmt::Debug for PhysAddr {
27    fn fmt(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {
28        write!(f, &quot;Physical Address({:#x})&quot;, self.0)
29    }
30}
31
32impl Add&lt;u64&gt; for PhysAddr {
33    type Output = Self;
34    fn add(self, rhs: u64) -&gt; Self::Output {
35        PhysAddr::new(self.0 + rhs)
36    }
37}
38
39impl AddAssign&lt;u64&gt; for PhysAddr {
40    fn add_assign(&amp;mut self, rhs: u64) {
41        *self = *self + rhs;
42    }
43}
44
45#[cfg(target_pointer_width = &quot;64&quot;)]
46impl Add&lt;usize&gt; for PhysAddr {
47    type Output = Self;
48    fn add(self, rhs: usize) -&gt; Self::Output {
49        self + cast::u64(rhs)
50    }
51}
52
53#[cfg(target_pointer_width = &quot;64&quot;)]
54impl AddAssign&lt;usize&gt; for PhysAddr {
55    fn add_assign(&amp;mut self, rhs: usize) {
56        self.add_assign(cast::u64(rhs))
57    }
58}
59
60impl Sub&lt;u64&gt; for PhysAddr {
61    type Output = Self;
62    fn sub(self, rhs: u64) -&gt; Self::Output {
63        PhysAddr::new(self.0.checked_sub(rhs).unwrap())
64    }
65}
66
67impl SubAssign&lt;u64&gt; for PhysAddr {
68    fn sub_assign(&amp;mut self, rhs: u64) {
69        *self = *self - rhs;
70    }
71}
72
73#[cfg(target_pointer_width = &quot;64&quot;)]
74impl Sub&lt;usize&gt; for PhysAddr {
75    type Output = Self;
76    fn sub(self, rhs: usize) -&gt; Self::Output {
77        self - cast::u64(rhs)
78    }
79}
80
81#[cfg(target_pointer_width = &quot;64&quot;)]
82impl SubAssign&lt;usize&gt; for PhysAddr {
83    fn sub_assign(&amp;mut self, rhs: usize) {
84        self.sub_assign(cast::u64(rhs))
85    }
86}
87
88impl Sub&lt;PhysAddr&gt; for PhysAddr {
89    type Output = u64;
90    fn sub(self, rhs: PhysAddr) -&gt; Self::Output {
91        self.as_u64().checked_sub(rhs.as_u64()).unwrap()
92    }
93}
94
95

这样我们就完成了针对内存地址的抽象

下一步要做什么

在下一篇文件中,我们开始着手编写基本的描述符以及GDT功能,我们在编写的过程中会涉及到汇编,因此还会讲述Rust内嵌汇编的使用(大家加油!)

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C++ explicit关键字

2022-1-11 12:36:11

安全技术

探索C++虚函数在内存中的表现形式及运行机制(二)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索