os/mm/frame_allocator/
mod.rs

1// HACK: 在一个模块目录/文件的顶层又声明了一个同名子模块,这会造成 “module inception”。
2// 虽然功能上可行,但会引起 API/模块层次混淆,Clippy 建议消除这种重复。
3#![allow(clippy::module_inception)]
4//! 帧分配器模块
5//!
6//! 本模块提供物理内存帧的分配和跟踪功能。
7//!
8//! # 模块组成
9//!
10//! - [`FrameTracker`]:用于单个已分配帧的 **RAII** 封装器。
11//! - [`FrameRangeTracker`]:用于已分配帧范围的 **RAII** 封装器。
12//! - [`init_frame_allocator`]:初始化全局帧分配器。
13//! - [`alloc_frame`]:分配单个帧。
14//! - `alloc_frames`:分配多个(非连续)帧。
15//! - `alloc_contig_frames`:分配多个连续帧。
16//! - `alloc_contig_frames_aligned`:分配带对齐要求的多个连续帧。
17
18mod frame_allocator;
19
20use alloc::vec::Vec;
21pub use frame_allocator::{FrameRangeTracker, FrameTracker, TrackedFrames};
22
23use crate::mm::{
24    address::{Paddr, PageNum, Ppn, UsizeConvert},
25    frame_allocator::frame_allocator::FRAME_ALLOCATOR,
26};
27
28/// 使用可用的物理内存范围初始化全局帧分配器。
29///
30/// # 参数
31///
32/// * `start_addr` - 可用物理内存的起始地址
33/// * `end_addr` - 可用物理内存的结束地址
34pub fn init_frame_allocator(start_addr: usize, end_addr: usize) {
35    // 将起始地址向上取整到页号
36    let start_ppn = Ppn::from_addr_ceil(Paddr::from_usize(start_addr));
37    // 将结束地址向下取整到页号
38    let end_ppn = Ppn::from_addr_floor(Paddr::from_usize(end_addr));
39
40    let mut allocator = FRAME_ALLOCATOR.lock();
41    allocator.init(start_ppn, end_ppn);
42}
43
44/// 分配一个物理帧。
45///
46/// # 返回
47///
48/// 如果分配成功,返回 `Some(FrameTracker)`;否则返回 `None`。
49pub fn alloc_frame() -> Option<FrameTracker> {
50    FRAME_ALLOCATOR.lock().alloc_frame()
51}
52
53/// 分配多个物理帧(不保证连续)。
54///
55/// # 参数
56///
57/// * `num` - 需要分配的帧数量。
58///
59/// # 返回
60///
61/// 如果分配成功,返回 `Some(Vec<FrameTracker>)`;否则返回 `None`。
62pub fn alloc_frames(num: usize) -> Option<Vec<FrameTracker>> {
63    FRAME_ALLOCATOR.lock().alloc_frames(num)
64}
65
66/// 分配指定数量的**连续**物理帧。
67///
68/// # 参数
69///
70/// * `num` - 需要分配的帧数量。
71///
72/// # 返回
73///
74/// 如果分配成功,返回 `Some(FrameRangeTracker)`;否则返回 `None`。
75pub fn alloc_contig_frames(num: usize) -> Option<FrameRangeTracker> {
76    FRAME_ALLOCATOR.lock().alloc_contig_frames(num)
77}
78
79/// 分配指定数量的**连续**物理帧,并确保起始地址对齐。
80///
81/// # 参数
82///
83/// * `num` - 需要分配的帧数量。
84/// * `align_pages` - 对齐的页数(必须是 2 的幂)。
85///
86/// # 返回
87///
88/// 如果分配成功,返回 `Some(FrameRangeTracker)`;否则返回 `None`。
89pub fn alloc_contig_frames_aligned(num: usize, align_pages: usize) -> Option<FrameRangeTracker> {
90    FRAME_ALLOCATOR
91        .lock()
92        .alloc_contig_frames_aligned(num, align_pages)
93}
94
95/// 回收一个物理帧。此函数由 FrameTracker 的 Drop 实现调用。
96fn dealloc_frame(frame: &FrameTracker) {
97    FRAME_ALLOCATOR.lock().dealloc_frame(frame);
98}
99
100/// 回收多个物理帧(不保证连续)。
101fn dealloc_frames(frames: &[FrameTracker]) {
102    let mut allocator = FRAME_ALLOCATOR.lock();
103    for frame in frames {
104        allocator.dealloc_frame(frame);
105    }
106}
107
108/// 回收一个连续的物理帧范围。此函数由 FrameRangeTracker 的 Drop 实现调用。
109fn dealloc_contig_frames(frame_range: &FrameRangeTracker) {
110    FRAME_ALLOCATOR.lock().dealloc_contig_frames(frame_range);
111}
112
113/// 获取总的物理帧数
114pub fn get_total_frames() -> usize {
115    FRAME_ALLOCATOR.lock().total_frames()
116}
117
118/// 获取已分配的帧数
119pub fn get_allocated_frames() -> usize {
120    FRAME_ALLOCATOR.lock().allocated_frames()
121}
122
123/// 获取空闲的帧数
124pub fn get_free_frames() -> usize {
125    FRAME_ALLOCATOR.lock().free_frames()
126}
127
128/// 获取帧分配器的当前状态
129///
130/// # 返回值
131/// - 当前分配指针的 Ppn
132/// - 物理帧的结束 Ppn (不包含)
133/// - 回收栈的长度
134/// - 已分配的帧数
135/// - 空闲的帧数
136pub fn get_stats() -> (usize, usize, usize, usize, usize) {
137    FRAME_ALLOCATOR.lock().get_stats()
138}
139
140#[cfg(test)]
141mod frame_allocator_tests {
142    use super::*;
143    use crate::{kassert, mm::address::ConvertablePaddr, test_case};
144
145    // 1. 单帧分配测试
146    test_case!(test_single_frame_alloc, {
147        let frame = alloc_frame().expect("分配失败");
148        let ppn = frame.ppn();
149
150        kassert!(ppn.as_usize() > 0);
151
152        // 帧已自动清零 - 需要转换为 vaddr 才能访问
153        let vaddr = ppn.start_addr().to_vaddr();
154        let page_ptr = vaddr.as_ptr::<u64>();
155        unsafe {
156            for i in 0..512 {
157                kassert!(*page_ptr.add(i) == 0);
158            }
159        }
160        // frame 在此丢弃,自动回收
161    });
162
163    // 2. 多帧分配测试
164    test_case!(test_multiple_frames_alloc, {
165        let frames = alloc_frames(5).expect("分配失败");
166        kassert!(frames.len() == 5);
167
168        // 每个帧都应该是有效的
169        for frame in &frames {
170            kassert!(frame.ppn().as_usize() > 0);
171        }
172    });
173
174    // 3. 连续帧分配测试
175    test_case!(test_contig_frames_alloc, {
176        let frames = alloc_contig_frames(4).expect("分配失败");
177        let start_ppn = frames.range().start().as_usize();
178
179        // 验证连续性
180        for i in 0..4 {
181            let expected = start_ppn + i;
182            kassert!(frames.range().start().as_usize() + i == expected);
183        }
184    });
185
186    // 4. 帧自动回收测试 (RAII)
187    test_case!(test_frame_auto_reclaim, {
188        // 分配一个帧并保存其 PPN
189        let first_ppn = {
190            let frame = alloc_frame().expect("分配失败");
191            frame.ppn()
192        }; // frame 在此丢弃,应被回收至回收栈
193
194        // 再次分配 - 应该从回收栈中获取相同的帧
195        let frame2 = alloc_frame().expect("分配失败");
196        kassert!(frame2.ppn() == first_ppn); // 验证重用
197    });
198
199    // 5. 对齐分配测试
200    test_case!(test_aligned_alloc, {
201        let frames = alloc_contig_frames_aligned(4, 16).expect("分配失败");
202        let ppn = frames.range().start().as_usize();
203
204        // 验证对齐
205        kassert!(ppn % 16 == 0);
206    });
207
208    // 6. 大量分配测试
209    test_case!(test_large_alloc, {
210        let frames = alloc_frames(100).expect("分配 100 帧");
211        kassert!(frames.len() == 100);
212    });
213}