Skip to content
GitLab
Explore
Projects
Groups
Topics
Snippets
Projects
Groups
Topics
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
yonchicy
OSKernel2021-X
Commits
cc8695aa
Commit
cc8695aa
authored
4 years ago
by
yonchicy
Browse files
Options
Download
Patches
Plain Diff
更新文档
parent
c7d52c20
master
yonchicy-master-patch-28765
No related merge requests found
Pipeline
#1746
failed with stages
Changes
4
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
photo/内核低地址.png
+0
-0
photo/内核低地址.png
photo/内核高地址.png
+0
-0
photo/内核高地址.png
photo/用户态地址.png
+0
-0
photo/用户态地址.png
文档.md
+326
-0
文档.md
with
326 additions
and
0 deletions
+326
-0
photo/内核低地址.png
0 → 100644
+
0
−
0
View file @
cc8695aa
43.2 KB
This diff is collapsed.
Click to expand it.
photo/内核高地址.png
0 → 100644
+
0
−
0
View file @
cc8695aa
81.7 KB
This diff is collapsed.
Click to expand it.
photo/用户态地址.png
0 → 100644
+
0
−
0
View file @
cc8695aa
75.3 KB
This diff is collapsed.
Click to expand it.
文档.md
0 → 100644
+
326
−
0
View file @
cc8695aa
## 构建基本执行环境
### 移除标准库依赖
+
移除程序对标准库的依赖,为编译器手动添加入口函数。
```
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`
,然后依次往下存放用户态应用的内核栈。在低地址中通过恒等映射建立虚拟地址和物理地址的应黑色关系,存放代码段。


#### 应用地址空间设置
在64位虚拟地址空间中,最高一个虚拟页面用于存放跳板,第二个页面用于存放
`TrapContext`
。然后在低地址空间中,从虚拟地址为零开始存放代码,通过
`Framed`
映射的方式建立虚拟地址和物理地址的关系。

#### 权限切换时的地址空间转换
`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`
实现,但是本地的块设备驱动还未移植完成。
This diff is collapsed.
Click to expand it.
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment
Menu
Explore
Projects
Groups
Topics
Snippets