[RustSBI output]
...
yield
**************/
Rust user shell
>>
操作系统启动shell
后,用户可以在shell
中通过敲入应用名字来执行应用。
软件架构
改进OS
├── os
├── build.rs(修改:基于应用名的应用构建器)
└── src
├── loader.rs(修改:基于应用名的应用加载器)
├── main.rs(修改)
├── mm(修改:为了支持本章的系统调用对此模块做若干增强)
改进OS
├── os
└── src
├── syscall
├──fs.rs(修改:新增 sys_read)
├── mod.rs(修改:新的系统调用的分发处理)
└── process.rs(修改:新增 sys_getpid/fork/exec/waitpid)
├── task
├── manager.rs(新增:任务管理器,为上一章任务管理器功能的一部分)
├── mod.rs(修改:调整原来的接口实现以支持进程)
├── pid.rs(新增:进程标识符和内核栈的 Rust 抽象)
├── processor.rs(新增:处理器管理结构 ``Processor`` ,为上一章任务管理器功能的一部分)
└── task.rs(修改:支持进程管理机制的任务控制块)
└── trap
├── mod.rs(修改:对于系统调用的实现进行修改以支持进程系统调用)
应用角度:理解进程
OS角度:理解进程
/// 功能:当前进程 fork 出来一个子进程。
/// 返回值:对于子进程返回 0,对于当前进程则返回子进程的 PID
/// syscall ID:220
pub fn sys_fork() -> isize;
/// 功能:将当前进程的地址空间清空并加载一个特定的可执行文件,返回用户态后开始它的执行。
/// 参数:path 给出了要加载的可执行文件的名字;
/// 返回值:如果出错的话(如找不到名字相符的可执行文件)则返回 -1,否则不应该返回。
/// syscall ID:221
pub fn sys_exec(path: &str) -> isize;
/// 功能:当前进程等待一个子进程变为僵尸进程,回收其全部资源并收集其返回值。
/// 参数:pid 表示要等待的子进程的进程 ID,如果为 -1 的话表示等待任意一个子进程;
/// exit_code 表示保存子进程返回值的地址,如果这个地址为 0 的话表示不必保存。
/// 返回值:如果要等待的子进程不存在则返回 -1;否则如果要等待的子进程均未结束则返回 -2;
/// 否则返回结束的子进程的进程 ID。
/// syscall ID:260
pub fn sys_waitpid(pid: isize, exit_code: *mut i32) -> isize;
应用shell
的执行流程
sys_read
获取字符串(即文件名)sys_fork
创建子进程sys_exec
创建新应用的进程sys_waitpid
等待子进程结束在编译操作系统的过程中,会生成如下的 link_app.S 文件
3 _num_app:
4 .quad 15 #应用程序个数
7 ......
9 _app_names: #app0的名字
10 .string "exit"
12 ......
17 app_0_start: #app0的开始位置
18 .incbin "../user/target/riscv64gc-unknown-none-elf/release/exit"
19 app_0_end: #app0的结束位置
APP_NAMES
来按照顺序将所有应用的名字保存在内存中,为通过 exec 系统调用创建新进程做好了前期准备。进程抽象的对应实现是进程控制块 --TCB TaskControlBlock
pub struct TaskControlBlock {
// immutable
pub pid: PidHandle, // 进程id
pub kernel_stack: KernelStack, // 进程内核栈
// mutable
inner: UPSafeCell<TaskControlBlockInner>,//进程内部管理信息
}
进程抽象的对应实现是进程控制块 -- TaskControlBlock
pub struct TaskControlBlockInner {
pub trap_cx_ppn: PhysPageNum, // 陷入上下文页的物理页号
pub base_size: usize, // 进程的用户栈顶
pub task_cx: TaskContext, // 进程上下文
pub task_status: TaskStatus, // 进程执行状态
pub memory_set: MemorySet, // 进程地址空间
pub parent: Option<Weak<TaskControlBlock>>, // 父进程控制块
pub children: Vec<Arc<TaskControlBlock>>, // 子进程任务控制块组
pub exit_code: i32, // 退出码
}
管理进程的进程管理器 TaskManager
pub struct TaskManager {
ready_queue: VecDeque<Arc<TaskControlBlock>>, // 就绪态任务控制块的链表
}
处理器管理结构 Processor
描述CPU 执行状态
pub struct Processor {
current: Option<Arc<TaskControlBlock>>, // 在当前处理器上正在执行的任务
idle_task_cx: TaskContext, // 空闲任务
}
TaskManager
中分出去的维护 CPU 状态的职责Processor
有一个 idle 控制流,功能是尝试从任务管理器中选出一个任务来在当前 CPU 核上执行,有自己的CPU启动内核栈上initproc
sys_fork
/sys_exec
sys_exit
退出或进程终止后保存其退出码sys_waitpid
收集该进程的信息并回收其资源sys_read
系统调用获得字符输入创建初始进程
lazy_static! {
pub static ref INITPROC: Arc<TaskControlBlock> = Arc::new(
TaskControlBlock::new(get_app_data_by_name("initproc").unwrap()));
}
pub fn add_initproc() {
add_task(INITPROC.clone());
}
TaskControlBlock::new
会解析initproc
的ELF执行文件格式,并建立应用的地址空间、内核栈等,形成一个就绪的进程控制块add_task
会把进程控制块加入就绪队列中进程生成机制-- fork
: 复制父进程内容并构造新的进程控制块
pub fn fork(self: &Arc<TaskControlBlock>) -> Arc<TaskControlBlock> {...}
0
为fork
返回码进程生成机制-- exec
:用新应用的 ELF 可执行文件中的代码和数据替换原有的应用地址空间中的内容
pub fn exec(&self, elf_data: &[u8]) {...}
进程调度机制:暂停当前任务并切换到下一个任务
sys_yield
系统调用时suspend_current_and_run_next
函数
进程资源回收机制 -- exit_current_and_run_next
-- 进程退出
PROCESSOR
中取出,修改其为僵尸进程exit_code
写入进程控制块中initproc
的子进程集合中进程资源回收机制 -- sys_waitpid
-- 等待子进程退出
字符输入机制
pub fn sys_read(fd: usize, buf: *const u8, len: usize) -> isize {
c=sbi::console_getchar(); ...}
console_getchar
智商最高的白垩纪“伤齿龙” 操作系统 troodon
主要的事情就是**实现地址空间**
**程序设计**
**程序设计**
![bg right:50% 100%](figs/app-as-full.png)
**程序设计**