2021年操作系统夏令营结营总结
学习rust语言
rust语言有很多独特的设计,在开始上手rust操作系统之前,最好先学习一些这些特性,否则遇到的时候会抓狂……
rust语言的学习资源目前已经很多,想快速入门的话,可以试一试Tour of Rust.
所有权
每次我们遇到所有权的问题是,都是根据编译器提示一通乱改,这样治标不治本。我们只要理解了设计所有权的目的,就可以少在这方面栽跟头。
宏
rust宏不是简单地替换文本。可以看看这篇教程:
什么是 Rust 宏? —— 浅谈 Rust 宏 - HoshinoTented 的博客 - 洛谷博客 (luogu.com.cn)
最终成果总览
进程状态监控
系统调用的次数与时间获取
构建一个结构体用来存储系统调用的次数与时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| pub struct SyscallCount{ pub syscall_dup: (usize, usize), pub syscall_open: (usize, usize), pub syscall_close: (usize, usize), pub syscall_pipe: (usize, usize), pub syscall_read: (usize, usize), pub syscall_write: (usize, usize), pub syscall_yield: (usize, usize), pub syscall_get_time: (usize, usize), pub syscall_getpid: (usize, usize), pub syscall_fork: (usize, usize), pub syscall_exec: (usize, usize), pub syscall_waitpid: (usize, usize), }
|
元组的第一个变量为次数,第二个为时间
在每个系统调用的开始时获取当前时间
在每个系统调用即将结束时再次获取当前时间并做差,将其赋给结构体中,并将系统次数加一
具体示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| pub fn sys_dup(fd: usize) -> isize { let start_time = get_time_ms(); let task = current_task().unwrap(); let mut inner = task.acquire_inner_lock(); if fd >= inner.fd_table.len() { return -1; } if inner.fd_table[fd].is_none() { return -1; } let new_fd = inner.alloc_fd(); inner.fd_table[new_fd] = Some(Arc::clone(inner.fd_table[fd].as_ref().unwrap())); let mut syscall_inner = inner.syscall.lock(); syscall_inner.syscall_dup.0 += 1; syscall_inner.syscall_dup.1 += get_time_ms() - start_time; new_fd as isize }
|
注意:当前系统调用的统计只支持同步系统调用的时间获取,目前暂不支持异步
进程名称的获取
只有进程在sys_exec
时会接触到进程的名字,所以我们选择在那里完成对进程名字的获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| pub fn sys_exec(path: *const u8, mut args: *const usize) -> isize { let start_time = get_time_ms(); let token = current_user_token(); let path = translated_str(token, path); let mut args_vec: Vec<String> = Vec::new(); loop { let arg_str_ptr = *translated_ref(token, args); if arg_str_ptr == 0 { break; } args_vec.push(translated_str(token, arg_str_ptr as *const u8)); unsafe { args = args.add(1); } } if let Some(app_inode) = open_file(path.as_str(), OpenFlags::RDONLY) { let all_data = app_inode.read_all(); let task = current_task().unwrap(); let argc = args_vec.len(); task.set_name(path); task.exec(all_data.as_slice(), args_vec); argc as isize } else { -1 } }
pub fn set_name(&self, name: String){ self.acquire_inner_lock().name = name; }
|
进程 id
与父进程 id
的获取
进程控制块 TaskControlBlock
实现了 getpid()
方法,我们只需调用该方法即可得到进程的pid。在一个进程控制块被创建时,我们将它的父进程 id
写入 ppid
成员变量中,并编写 get_ppid()
方法来得到父进程 id
。
进程状态的获取
枚举类型 TaskStatus
表示了进程的状态:
1 2 3 4 5 6
| #[derive(Copy, Clone, PartialEq,Serialize)] pub enum TaskStatus { Ready, Running, Zombie, }
|
TaskControlBlockInner
里记录了 TaskStatus
. 我们只需编写 get_status()
方法即可获得:
1 2 3 4 5 6
| impl TaskControlBlockInner { pub fn get_status(&self) -> TaskStatus { self.task_status } }
|
cpu状态监控
cpu运行的顺序图
设计结构体来存储时间
1 2 3 4 5 6 7
| struct CpuStatInner{ time_stamp: usize, sample_duration: usize, sample_busy_duration: usize, sample_busy_want: usize, busy_start: usize, }
|
cpu运行总时间的获取
在run
函数中获取时间以100ms
为一个时间段向结构体内存储数据
cpu忙运行时间的获取
在idle切换进入进程前记录时间,放入busy_start
中,当进程结束或主动yield
时,进程会进入schedule
函数并从进程重新切换回idle
,所以在切换回idle
之前取进程结束时间做差就可以得到cpu的忙运行时间
通过文件读取性能监测信息
rCore-Tutorial
已经实现了 pipe
, stdio
两个抽象文件,我们再添加ProcInfoList
文件和CPUInfo
文件。这两个文件的 read()
方法调用内核中的信息收集函数,将收集到的信息转换为 json
文本并写入缓冲区。
在新建进程控制块时,我们添加这两个文件。用户态程序读取这两个文件时使用 sys_read()
系统调用,该系统调用最终调用了ProcInfoList
文件和CPUInfo
文件的 read()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| impl TaskControlBlock { pub fn new(elf_data: &[u8]) -> Self { fd_table: vec![ Some(Arc::new(Stdin)), Some(Arc::new(Stdout)), Some(Arc::new(Stdout)), Some(Arc::new(ProcInfoList {})), Some(Arc::new(CPUInfo {})), ],
};
|
用户态监控程序
缓冲区
我们新建一个缓冲区来接收 read()
获取到的信息。
接下来,我们将缓冲区中的json
数据解析并输出。
输出缓冲
我们可以在解析出数据后直接输出,但由于 println!
宏运行速度较慢,逐行输出会导致滚屏的现象。因此我们需要将要输出的字符串先存储在一个输出缓冲区里,待要更新的所有行都进入缓冲区再统一输出:
1 2 3 4 5 6
| pool.push(format_to_string_ln!("---------")); let whole = pool.join(""); print!("\x1b[2J"); print!("\x1b[1;1H"); print!("{}",whole);
|
宏
我们编写了 format_to_string_ln!
宏将格式化输出转换为自字符串:
1 2 3 4 5 6 7 8 9 10
| macro_rules! format_to_string_ln { ($fmt: literal $(, $($arg: tt)+)?) => { format_args!(concat!($fmt, "\n") $(, $($arg)+)?).to_string() }; } macro_rules! format_to_string { ($fmt: literal $(, $($arg: tt)+)?) => { format_args!($fmt $(, $($arg)+)?).to_string() }; }
|
制造负载
fork 多个子进程,进行性能消耗。
其他收获
合作
多问问题很重要。比如两位助教同学提醒我们可以用控制字符,使得软件界面好看不少。每个人的背景知识都不一样,因此对于同一个问题,不同人产生的思路也不一样。多人合作可以说是解决问题的一种捷径。
当前疫情管控比较严格,所以我们用腾讯会议交流代码。比起微信聊天,通过语音交流信息量会大一些。
坚持
一款精心设计的游戏,总能在适当的时候给予玩家激励,让玩家百玩不厌,沉迷其中。但操作系统(至少在入门阶段)并不是这样。操作系统机制复杂,理解起来需要花一些时间,编程的时候失败是常态。所以,想像写前端应用那样,十分钟搭好框架,一天就能出个大致成果,是不太现实的。和其他组相比,我们的题目简单一些,但是从选题到写出第一个能勉强运行的demo也尽全力花了六天时间才搞定。因此,除去对技术本身的热爱,坚持也是十分重要的。
坚持的秘诀在于休息。感到压力太大的话,可以吃点甜食或者出门走走。休息的时候,人脑也会无意识地进行思考,这时往往能产生一些意想不到的突破(这叫做发散模式)。
记笔记
人遗忘得很快,因此很有必要记录下自己的思路,方便后期复用。概念图,思维导图,大纲笔记······只要是能呈现自己思路的笔记方法都可以。有兴趣的话,还可以浏览一下《卡片笔记写作法》这本书。