Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions document/Context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 执行上下文与切换机制

本文档定义了程序执行上下文(Execution Context)的构成及其在执行流切换(Context Switch)时的操作流程。该机制广泛应用于多任务操作系统中,用于任务调度和资源管理。

## 1. 执行上下文(Execution Context)的构成

执行上下文是 CPU 恢复一个任务(进程或线程)执行所需的所有状态信息的集合。它主要由以下几个部分组成:

| 要素 | 核心内容 | 存储位置/管理机制 | 切换需求 |
|----------------|---------------------------------------------|---------------------------------|----------------|
| **CPU 状态** | 包括通用寄存器、程序计数器、标志寄存器等 | 当前任务的内存区域中 | 每次切换时必需 |
| **地址空间** | 虚拟地址与物理地址的映射关系 | 特定的地址映射表 | 进程切换时必需 |
| **系统资源** | 文件描述符、网络连接等资源的引用 | 任务控制块(task control block) | 自动传递 |

### 解释
- **CPU 状态**:包括 CPU 寄存器的内容(如程序计数器、栈指针等),这些内容描述了任务在 CPU 执行期间的当前状态。
- **地址空间**:指任务的内存布局,包括虚拟地址到物理地址的映射。每个任务的地址空间独立,任务切换时需要切换到新任务的地址空间。
- **系统资源**:包括任务持有的资源,如文件、网络连接等。通常由任务控制块管理,切换时资源状态会自动继承。

## 2. 核心数据结构

在任务管理过程中,以下数据结构用于存储任务的状态和相关信息:

| 结构体/寄存器 | 作用 | 存储内容 | 所属模块 |
|------------------|--------------------------------|----------------------------------|-----------------|
| **任务控制块 (task control block)** | 管理任务状态、资源、调度信息 | 存储任务的所有信息,包括系统资源、CPU 状态等 | 任务管理 |
| **内存栈** | 任务的独立执行栈 | 存储上下文信息、局部变量等 | 内存管理 |
| **当前任务指针** | 标识当前执行的任务 | 指向当前任务的任务控制块 | 调度管理 |
| **地址映射信息** | 管理任务的内存映射信息 | 存储任务地址空间的映射信息 | 内存管理 |

### 解释
- **任务控制块 (task control block)**:每个任务都有一个独立的控制块,存储任务的所有状态信息和资源管理信息。
- **内存栈**:任务的执行栈,保存该任务的局部变量和中间计算结果。
- **当前任务指针**:全局指针,始终指向当前正在执行的任务,确保操作系统知道当前哪个任务在 CPU 上执行。

## 3. 执行流切换(Context Switch)的关键操作

当操作系统决定切换当前任务(任务 A)到另一个任务(任务 B)时,执行流切换涉及以下几个关键操作:

| 步骤 | 操作内容 | 触发条件 | 模块职责 |
|------------------|----------------------------------------------------------------------------|--------------------|--------------------|
| **步骤 1**: 保存与恢复状态 | 1. 将任务 A 的 CPU 寄存器和状态保存到任务 A 的内存区域中。<br>2. 从任务 B 的内存区域恢复 CPU 寄存器和状态。 | 每次任务切换时 | 调度管理/CPU核心 |
| **步骤 2**: 切换地址空间 | 更新任务的内存映射信息,确保任务 B 能访问到其专有的内存空间。 | 进程切换时 | 内存管理 |
| **步骤 3**: 更新当前任务 | 更新当前任务指针,指向任务 B 的控制块,确保调度器知道当前正在执行的是任务 B。 | 每次任务切换时 | 调度管理 |

### 解释
- **步骤 1:保存与恢复状态**:保存当前任务的状态(如寄存器的值),然后恢复新任务的状态。这确保了每个任务在执行流切换后能从它离开时的状态继续运行。
- **步骤 2:切换地址空间**:任务的地址空间必须被切换,以便确保每个任务访问的是它自己的内存区域。通常通过更新地址映射信息来完成这一操作。
- **步骤 3:更新当前任务**:调度器需要更新当前任务指针,指向新任务的控制块,确保下一次调度时可以恢复正确的任务。

---

### 总结

执行上下文切换是多任务操作系统中的关键机制,它确保操作系统能够在多个任务之间切换,保持各任务的独立性和执行状态。通过管理任务的 CPU 状态、地址空间和系统资源,操作系统能够高效地实现任务调度、资源分配与切换。本流程涉及的关键操作包括保存当前任务的状态、切换内存空间和更新任务指针,确保每个任务的状态能够正确恢复。
11 changes: 6 additions & 5 deletions os/src/arch/riscv/mm/page_table.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use super::PageTableEntry;
use crate::mm::address::{AlignOps, PageNum, Ppn, PpnRange, UsizeConvert, Vaddr, Vpn, VpnRange, Paddr, ConvertablePaddr};
use crate::mm::address::{
AlignOps, ConvertablePaddr, Paddr, PageNum, Ppn, PpnRange, UsizeConvert, Vaddr, Vpn, VpnRange,
};
use crate::mm::frame_allocator::FrameTracker;
use crate::mm::page_table::{
PageSize, PageTableInner as PageTableInnerTrait, PagingError, PagingResult, UniversalPTEFlag, PageTableEntry as PageTableEntryTrait
PageSize, PageTableEntry as PageTableEntryTrait, PageTableInner as PageTableInnerTrait,
PagingError, PagingResult, UniversalPTEFlag,
};
use alloc::vec::Vec;

Expand Down Expand Up @@ -158,9 +161,7 @@ impl PageTableInnerTrait<super::PageTableEntry> for PageTableInner {
) -> PagingResult<()> {
// Validate flags: leaf pages must have at least one of R/W/X set
if !flags.intersects(
UniversalPTEFlag::Readable
| UniversalPTEFlag::Writeable
| UniversalPTEFlag::Executable,
UniversalPTEFlag::Readable | UniversalPTEFlag::Writeable | UniversalPTEFlag::Executable,
) {
return Err(PagingError::InvalidFlags);
}
Expand Down
15 changes: 9 additions & 6 deletions os/src/arch/riscv/mm/page_table_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ bitflags::bitflags! {
}
}



/*
* SV39 Page Table Entry (PTE) format:
* ------------------------------------------------
Expand All @@ -35,7 +33,7 @@ bitflags::bitflags! {
*/

const SV39_PTE_FLAG_MASK: usize = 0xff; // Lower 8 bits for SV39 PTE flags
const SV39_PTE_PPN_OFFSET: usize = 10; // PPN starts from bit 10
const SV39_PTE_PPN_OFFSET: usize = 10; // PPN starts from bit 10
const SV39_PTE_PPN_MASK: u64 = 0x000f_ffff_ffff_c00; // Bits 10-53 for PPN

impl UniversalConvertableFlag for SV39PTEFlags {
Expand Down Expand Up @@ -84,8 +82,12 @@ impl PageTableEntryTrait for PageTableEntry {

fn is_huge(&self) -> bool {
// In SV39, we can't directly determine huge pages from the PTE alone.
let sv39_flags = SV39PTEFlags::from_bits((self.0 & SV39_PTE_FLAG_MASK as u64) as usize).unwrap();
sv39_flags.intersects(SV39PTEFlags::union(SV39PTEFlags::READ, SV39PTEFlags::EXECUTE).union(SV39PTEFlags::WRITE))
let sv39_flags =
SV39PTEFlags::from_bits((self.0 & SV39_PTE_FLAG_MASK as u64) as usize).unwrap();
sv39_flags.intersects(
SV39PTEFlags::union(SV39PTEFlags::READ, SV39PTEFlags::EXECUTE)
.union(SV39PTEFlags::WRITE),
)
}

fn is_empty(&self) -> bool {
Expand All @@ -98,7 +100,8 @@ impl PageTableEntryTrait for PageTableEntry {
}

fn flags(&self) -> UniversalPTEFlag {
let sv39_flags = SV39PTEFlags::from_bits((self.0 & SV39_PTE_FLAG_MASK as u64) as usize).unwrap();
let sv39_flags =
SV39PTEFlags::from_bits((self.0 & SV39_PTE_FLAG_MASK as u64) as usize).unwrap();
sv39_flags.to_universal()
}

Expand Down
9 changes: 7 additions & 2 deletions os/src/kernel/cpu.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
use crate::{arch::kernel::context::Context, kernel::task::Task};
use alloc::sync::Arc;

use crate::{arch::kernel::context::Context, kernel::task::Task, mm::memory_space::MemorySpace};

/// CPU 结构体
pub struct Cpu {
/// 任务上下文
/// 用于在调度器中保存和恢复 CPU 寄存器状态
pub context: Context,
/// 当前运行的任务
pub current_task: Option<Task>,
pub current_task: Option<Arc<Task>>,
/// 当前内存空间
pub cur_memory_space: Option<Arc<MemorySpace>>,
}

impl Cpu {
pub fn new() -> Self {
Cpu {
context: Context::zero_init(),
current_task: None,
cur_memory_space: None,
}
}
}
Expand Down
54 changes: 48 additions & 6 deletions os/src/kernel/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

use core::sync::atomic::AtomicPtr;

use crate::arch::{kernel::context::Context, trap::kerneltrap::TrapFrame};
use alloc::sync::Arc;

use crate::{
arch::{kernel::context::Context, trap::kerneltrap::TrapFrame},
mm::{frame_allocator::FrameTracker, memory_space::MemorySpace},
};

/// 任务
/// 存放任务的核心信息
Expand All @@ -26,14 +31,20 @@ pub struct Task {
/// 任务的所属进程id
/// NOTE: 由于采用了统一的任务模型,一个任务组内任务的 pid 是相同的,等于父任务的 pid 而父任务的 pid 等于自己的 tid
pub pid: usize,
/// 父任务的id
pub ptid: usize,
/// 内核栈基址
kstack_base: usize,
pub kstack_base: usize,
/// 内核栈跟踪器
pub kstack_tracker: FrameTracker,
/// 中断上下文。指向当前任务内核栈上的 TrapFrame,仅在任务被中断时有效。
trap_frame_ptr: AtomicPtr<TrapFrame>,
/// 父任务的id
parient_tid: usize,
/// XXX: AtomicPtr or *mut?
pub trap_frame_ptr: AtomicPtr<TrapFrame>,
/// 任务的内存空间
/// 对于内核任务,该字段为 None
pub memory_space: Option<Arc<MemorySpace>>,
/// 退出码
exit_code: isize,
pub exit_code: isize,
// TODO: 由于部分相关子系统尚未实现,暂时留空
}

Expand All @@ -50,6 +61,37 @@ pub enum TaskState {
Uninterruptable,
}

/// 创建一个新的内核线程并返回其 Arc 包装
///
/// 该函数负责:
/// 1. 分配 Task 结构体本身,并用 Arc 包装
/// 2. 分配内核栈物理页帧 (FrameTracker)
/// 3. 将内核栈映射到虚拟地址空间 (VMM 逻辑)
/// 4. 初始化 Task Context,设置栈指针和入口点
/// 5. 将新的 Task 加入调度器队列
///
/// # 参数
/// * `entry_point`: 线程开始执行的函数地址
///
/// # 返回值
/// 包含新创建任务的 Arc<Task>
pub fn kthread_spawn(_entry_point: fn()) -> Arc<Task> {
// 1. 分配内核栈 (假设 FrameTracker::alloc_one() 存在)
// let kstack_tracker = FrameTracker::alloc_one().expect("Failed to allocate kernel stack");
// let kstack_paddr = kstack_tracker.get_paddr();

// 2. 将物理页映射到连续的虚拟地址 (kstack_base)
// NOTE: 内核线程共享内核地址空间,映射逻辑相对简单

// 3. 构建 Task 实例
// let task = Task { /* ... 初始化字段 ... */ };

// 4. 将任务加入全局任务队列
// SCHEDULER.add_task(task.clone());

unimplemented!("kthread_spawn 核心逻辑尚未实现")
}

// /// 关于任务的管理信息
// /// 存放与调度器、任务状态、队列相关的、需要高频访问和修改的数据。
// /// 主要由调度器子系统使用。
Expand Down
1 change: 1 addition & 0 deletions os/src/mm/frame_allocator/frame_allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::config::PAGE_SIZE;
use crate::mm::address::{ConvertablePaddr, Paddr, PageNum, Ppn, PpnRange, UsizeConvert};
use alloc::vec::Vec;

#[derive(Debug)]
pub struct FrameTracker(Ppn);

impl FrameTracker {
Expand Down
2 changes: 2 additions & 0 deletions os/src/mm/frame_allocator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ mod frame_allocator;
pub fn init_frame_allocator(start_addr: usize, end_addr: usize) {
frame_allocator::init_frame_allocator(start_addr, end_addr);
}

pub use frame_allocator::FrameTracker;
81 changes: 81 additions & 0 deletions os/src/mm/heap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 内核堆(Kernel Heap)的公共接口,提供了 C 风格的 kmalloc 家族函数。

// 引入在 kalloc.rs 中实现的全局分配器
// #[global_allocator]
// static ALLOCATOR: ...;
//
// 假设全局分配器 ALLOCATOR 已经被正确设置和初始化。

// -------------------------------------------------------------------
// C 风格的 kmalloc 家族接口 (Wrapper Functions)
// -------------------------------------------------------------------

/// kmalloc: 内核动态内存分配函数。
///
/// 功能: 分配至少 `size` 字节的内存块。返回的内存是**未初始化**的。
///
/// # Arguments
/// * `size`: 需要分配的字节数。
///
/// # Safety
/// 这是一个不安全函数,因为分配失败可能返回空指针,且使用者必须负责调用 kfree 释放。
///
/// # Returns
/// 分配内存块的指针 (*mut u8);失败则返回 ptr::null_mut()。
#[inline]
pub unsafe fn kmalloc(_size: usize) -> *mut u8 {
todo!()
}

/// kcalloc: 内核动态内存分配并清零函数。
///
/// 功能: 分配 `count * size` 字节的内存块,并将分配的内存**清零**。
///
/// # Arguments
/// * `count`: 元素数量。
/// * `size`: 每个元素的字节数。
///
/// # Safety
/// 必须负责释放。如果乘法溢出或分配失败,返回空指针。
///
/// # Returns
/// 分配内存块的指针 (*mut u8);失败则返回 ptr::null_mut()。
#[inline]
pub unsafe fn kcalloc(_count: usize, _size: usize) -> *mut u8 {
todo!()
}

/// kfree: 释放之前由 kmalloc/kcalloc/krealloc 分配的内存块。
///
/// # Arguments
/// * `ptr`: 待释放的内存块指针。
/// * `size`: 释放的内存块大小。
///
/// # Safety
/// * `ptr` 必须是由本分配器分配的。
/// * `ptr` 必须是非空且尚未被释放。
/// * `size` 必须与分配时的 `size` 相同。
#[inline]
pub unsafe fn kfree(_ptr: *mut u8, _size: usize) {
todo!()
}

/// krealloc: 重新调整内存块的大小。
///
/// 功能: 调整 `old_ptr` 指向的内存块大小为 `new_size`。
///
/// # Arguments
/// * `old_ptr`: 原始内存块的指针。
/// * `old_size`: 原始内存块的大小。
/// * `new_size`: 新需要的大小。
///
/// # Safety
/// * `old_ptr` 必须是本分配器分配的,且未释放。
/// * `old_size` 必须与分配时的大小相同。
///
/// # Returns
/// 新的内存块指针 (*mut u8);失败则返回 ptr::null_mut()。
#[inline]
pub unsafe fn krealloc(_old_ptr: *mut u8, _old_size: usize, _new_size: usize) -> *mut u8 {
todo!()
}
5 changes: 5 additions & 0 deletions os/src/mm/memory_space/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// MemorySpace:进程虚拟地址空间管理器。
///
/// 封装了进程的页表、所有虚拟内存区域 (VMA) 列表以及相关的同步和统计信息。
#[derive(Debug)]
pub struct MemorySpace {}
2 changes: 2 additions & 0 deletions os/src/mm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
pub mod address;
pub mod frame_allocator;
pub mod global_allocator;
pub mod heap;
pub mod memory_space;
pub mod page_table;

pub use frame_allocator::init_frame_allocator;
Expand Down
1 change: 0 additions & 1 deletion os/src/mm/page_table/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,3 @@ pub enum PagingError {
}

pub type PagingResult<T> = Result<T, PagingError>;

Loading