Commit cc8695aa authored by yonchicy's avatar yonchicy
Browse files

更新文档

No related merge requests found
Pipeline #1746 failed with stages
Showing with 326 additions and 0 deletions
+326 -0
photo/内核低地址.png

43.2 KB

photo/内核高地址.png

81.7 KB

photo/用户态地址.png

75.3 KB

文档.md 0 → 100644
## 构建基本执行环境
### 移除标准库依赖
+ 移除程序对标准库的依赖,为编译器手动添加入口函数。
```rust
#![no_std]
#![no_main]
#[no_mangle]
pub fn rust_main() -> ! {
}
```
### 构建裸机运行环境
* 书写链接脚本调整链接器的行为,完成程序内存布局。
* 为运行操作系统完成栈空间的初始化。
* 实现clear_bss() 函数,可以实现清零.bss段的 功能。
* 对sbi层提供的接口进行封装,完成基础的关机和打印字符串功能。
* 完善操作系统报错的异常处理能力。
```rust
#![feature(panic_info_message)]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
if let Some(location) = info.location() {
println!("Panicked at {}:{} {}", location.file(), location.line(), info.message().unwrap());
} else {
println!("Panicked: {}", info.message().unwrap());
}
shutdown()
}
```
```rust
struct AppManager {
inner: RefCell<AppManagerInner>,
}
struct AppManagerInner {
num_app: usize,
current_app: usize,
app_start: [usize; MAX_APP_NUM + 1],
}
unsafe impl Sync for AppManager {}
```
### 实现特权级间的切换
* 创建用户栈和内核栈,实现两个级别的切换,保证系统安全。
* 实现中断现场的上下文保存。
```assembly
# os/src/trap/trap.S
.macro SAVE_GP n
sd x\n, \n*8(sp)
.endm
.align 2
__alltraps:
csrrw sp, sscratch, sp
# now sp->kernel stack, sscratch->user stack
# allocate a TrapContext on kernel stack
addi sp, sp, -34*8
# save general-purpose registers
sd x1, 1*8(sp)
# skip sp(x2), we will save it later
sd x3, 3*8(sp)
# skip tp(x4), application does not use it
# save x5~x31
.set n, 5
.rept 27
SAVE_GP %n
.set n, n+1
.endr
# we can use t0/t1/t2 freely, because they were saved on kernel stack
csrr t0, sstatus
csrr t1, sepc
sd t0, 32*8(sp)
sd t1, 33*8(sp)
# read user stack from sscratch and save it on the kernel stack
csrr t2, sscratch
sd t2, 2*8(sp)
# set input argument of trap_handler(cx: &mut TrapContext)
mv a0, sp
call trap_handler
```
* 完成syscall函数,可以根据输入的参数,分发中断至各具体函数完成相应功能。
## 物理内存管理
### 总览
- 借助`buddy_system_allocator`实现了动态内存分配,从而可以使用rust相关数据结构,提高了编码的灵活性。
- 建立物理地址管理,实现页式内存管理,实现`StackFrameAllocator`进行页面的分配回收工作
- 建立SV39多级页表机制。实现了`identical`恒等映射和`framed`按页映射两种映射机制。
- 建立`MemorySet/MapArea`等数据结构,用于管理地址空间,按段进行映射等。
- 将用户地址空间与内核地址空间隔离开。
### 动态内存分配
采用`buddy_system_allocator`
```rust
//os/src/heap_allocator.rs
pub fn init_heap() {
unsafe {
HEAP_ALLOCATOR
.lock()
.init(HEAP_SPACE.as_ptr() as usize, KERNEL_HEAP_SIZE);
}
}
```
### 地址空间
采用分页式内存管理,实现SV39三级页表
`os/src/config.rs`定义了包括页面大小、地址起始等数据设置。
`os/src/mm/frame_allocator.rs`实现`FrameTracker`用于跟踪页面的使用情况,实现`StackFrameAllocator`用于页面的管理。
`os/src/mm/address.rs`中实现地址相关的物理地址、虚拟地址等数据结构抽象与类型定义和转换。
`os/src/mm/page_table.rs`中实现页表项、页表的定义以及相关操作。通过`bitflags`这个crate来设置比特标志位,实现页表项的权限操作。
`os/src/mm/memory_set.rs`实现了`MemrySet``MapArea`等数据结构的定义和相关操作,用于地址空间、地址映射段的管理。
### 应用和内核地址空间设置
本内核采用应用空间和内核地址分离的设置。当进入内核态的时候,需要进行地址空间的切换,对于应用而言,内核和其他应用的地址空间被标记为未映射,保证了数据的安全性。
#### 内核地址空间设置
在64位虚拟地址空间中,最高一个虚拟页面用于存放跳板`Trampoline`,然后依次往下存放用户态应用的内核栈。在低地址中通过恒等映射建立虚拟地址和物理地址的应黑色关系,存放代码段。
![image-20210529170715149](photo/内核高地址.png)
![img](photo/内核低地址.png)
#### 应用地址空间设置
在64位虚拟地址空间中,最高一个虚拟页面用于存放跳板,第二个页面用于存放`TrapContext`。然后在低地址空间中,从虚拟地址为零开始存放代码,通过`Framed`映射的方式建立虚拟地址和物理地址的关系。
![img](photo/用户态地址.png)
#### 权限切换时的地址空间转换
`os/src/trap.S`中设置了`_alltraps`函数,这个函数是存放在跳板`trampoline`中的,通过这个跳板,实现保存上下文,切换栈地址为内核栈,刷新TLB等任务。
## 进程管理
### 总览
- 实现`TaskManager``Processor``Task`等数据结构,实现了一些必要的操作。
- `fork/exec/waitpid`三个系统调用的实现
- 初始进程的自动创建、进程切换、进程调度、资源回收。
### 代码文件解析
系统支持线程,采用`TaskManager`对线程进行统一管理
`os/src/context.rs`中定义了线程切换时需要保存的内核栈的内容。
`os/src/manager.rs`定义了`TaskManager`数据结构以及相关的操作。`TaskManager`用于管理所有进程,负责切换、调度进程。
`os/src/pid.rs`定义了`PidAllocator`用于`pid`的分配。同时给`KernelStack`中记录下对应的`pid`
`os/src/processor.rs`定义`Processor`用来维护当前CPU运行的进程。包括记录当前进程的任务管理块`TCB`,通过一个空闲执行流`idle`和当前任务执行流的切换来保证两个任务的执行流切换互不可见,保证安全性。
`os/src/switch.rs`声明了`__switch`函数,具体实现是在`os/src/switch.S`用汇编编写。这个函数实现了交换两个执行流的上下文。
```asm
__switch:
# __switch(
# current_task_cx_ptr2: &*const TaskContext,
# next_task_cx_ptr2: &*const TaskContext
# )
# push TaskContext to current sp and save its address to where a0 points to
addi sp, sp, -13*8
sd sp, 0(a0)
# fill TaskContext with ra & s0-s11
sd ra, 0(sp)
.set n, 0
.rept 12
SAVE_SN %n
.set n, n + 1
.endr
# ready for loading TaskContext a1 points to
ld sp, 0(a1)
# load registers in the TaskContext
ld ra, 0(sp)
.set n, 0
.rept 12
LOAD_SN %n
.set n, n + 1
.endr
# pop TaskContext
addi sp, sp, 13*8
ret
```
### fork/exec/waitpid
#### fork
* 复制进程控制块
* 地址空间
* 执行流的上下文
* 获取pid,内核栈
* 维护父子关系
* 修改trap上下文来控制对于子进程和父进程的不同返回值
#### exec
* 加载文件、构建新的地址空间
* 原有地址空间废弃。
#### waitpid
* 父进程负责维护对子进程的回收
* 在子进程中查找符合pid条件且僵死的进程,并释放。
#### exit
实现进程主动结束
* 标记当前任务为僵死状态,记录退出值
* 把此任务的子进程添加到`initproc`的子进程中,由`initproc`维护
* 清除地址空间
## 系统调用
### 功能概述
实现了` sys_dup ` ` sys_open ` ` sys_close ` ` sys_pipe ` `sys_read` `sys_write` `sys_exit` `sys_yield` `sys_get_time` `sys_getpid` `sys_fork` `sys_exec` `sys_waitpid` 等系统调用。
`os/src/trap/mod.rs`当trap类型是`Exception::UserEnvCall`时进入系统调用。
`os/src/syscall/mod.rs`定义了系统调用号并根据调用号调用相应的系统调用。
`os/src/syscall/fs.rs`实现了文件系统相关的系统调用。
`os/src/syscall/process.rs`实现了进程相关的系统调用。
### 用户程序中调用系统调用
在用户程序中实现系统调用比较容易,就像之前在操作系统中使用 `sbi_call` 一样,只需要符合规则传递参数即可。而且这一次我们甚至不需要参考任何标准,每个人都可以为自己的操作系统实现自己的标准。
系统调用的编号使用了 musl 中的编码和参数格式。但实际上,在实现操作系统的时候,编码和参数格式都可以随意调整,只要在用户程序中的调用和操作系统中的解释相符即可。
代码示例
```rust
// musl 中的 sys_read 调用格式
llvm_asm!("ecall" :
"={x10}" (/* 返回读取长度 */) :
"{x10}" (/* 文件描述符 */),
"{x11}" (/* 读取缓冲区 */),
"{x12}" (/* 缓冲区长度 */),
"{x17}" (/* sys_read 编号 63 */) ::
);
```
### 操作系统中实现系统调用
在操作系统中,系统调用的实现和中断处理一样,有同样的入口,而针对不同的参数设置不同的处理流程。为了简化流程,我们不妨把系统调用的处理结果分为三类:
- 返回一个数值,程序继续执行
- 程序进入等待
- 程序将被终止
### 系统调用的处理流程
- 首先,从相应的寄存器中取出调用代号和参数
- 根据调用代号,进入不同的处理流程,得到处理结果
- 返回数值并继续执行:
- 返回值存放在 `x10` 寄存器,`sepc += 4`,继续此 `context` 的执行
- 程序进入等待
- 同样需要更新 `x10``sepc`,但是需要将当前线程标记为等待,切换其他线程来执行
- 程序终止
- 不需要考虑系统调用的返回,直接删除线程
* 不支持的系统调用
* 直接panic!
* 调用完成后,设置跳板地址并切换到用户地址空间
## 文件系统
文件系统采用crate`fat32` 实现,但是本地的块设备驱动还未移植完成。
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment