7. 同步互斥
实验要求
本章编程问题较简单,主要难度在阅读代码与问答题
- 编程:将在实验指导中提供
Mutex
的实现框架、sleep
的实现、spawn
的实现和哲学家就餐问题测试,请将它们复制到你的代码中,并完成Mutex
中的TODO
部分。(8 分) - 回答:
Mutex
的实现中,为什么需要引入MutexGuard
?(3 分) - 回答:
Mutex
的实现中,为什么需要修改yield_now
并增加park
,如果都仍然使用旧的yield_now
会出现什么问题?(3 分) - 回答:
sleep
的实现中,为什么需要在idle_main
中增加一瞬间的中断开启,不增加这部分会出现什么问题?(3 分) - 回答:在哲学家就餐测试中,为什么需要修改
spie
,如果不进行修改可能会出现什么问题?(3 分)
为了让做题体验更好,我会在下面的代码后再复制一遍上面的题目。
实验指导
Mutex 框架
互斥锁(Mutex),用于保护资源不会被多个线程同时操作。
sync/mod.rs
pub mod condvar;
pub use self::mutex::{Mutex as SleepLock, MutexGuard as SleepLockGuard};
mod mutex;
- 创建
sync/mutex.rs
这里给出了 rust std
库中 Mutext 实现的魔改版,你只需要完成两处(写了 TODO
的地方)简单的填空即可。
当 Mutex<T>
被一个线程占用时,其它试图访问该线程的线程会主动让出自己的时间片,给其它线程调度。
而当资源使用完毕时,应该能够自动释放资源。
互斥锁框架代码:mutex.rs
为什么需要引入
MutexGuard
?(3 分)
- 修改
yield_now
// in process/processor.rs
impl Processor {
pub fn yield_now(&self) {
let inner = self.inner();
if !inner.current.is_none() {
unsafe {
let flags = disable_and_store();
let current_thread = &mut inner.current.as_mut().unwrap().1;
current_thread.switch_to(&mut *inner.idle);
restore(flags);
}
}
}
}
- 实现
park
// in process/mod.rs
pub fn park() {
CPU.park();
}
impl Processor {
pub fn park(&self) {
self.inner().pool.set_sleep(self.current_tid());
self.yield_now();
}
}
// in process/thread_pool.rs
impl ThreadPool {
pub fn set_sleep(&mut self, tid: Tid) {
let proc = self.threads[tid].as_mut().expect("thread not exist");
proc.status = Status::Sleeping;
}
}
- 将之前所有用到
yield_now
的地方(除了Mutex.rs
)替换为park
// in sync/condvar.rs
impl Condvar {
pub fn wait(&self) {
self.wait_queue.lock().push_back(current_tid());
park();
}
}
// syscall.rs
fn sys_exec(path: *const u8) -> isize {
let valid = process::execute(unsafe { from_cstr(path) }, Some(process::current_tid()));
if valid {
process::park();
}
return 0;
}
为什么需要修改
yield_now
并增加park
,如果都仍然使用旧的yield_now
会出现什么问题?(3 分)
实现 sleep
- 增加计时器
process/timer.rs
计时器代码:timer.rs
到时间后会触发回调函数,对于后面 sleep
的用法,在线程睡眠时间到了的时候将其唤醒(回调函数设置为 wakeup(tid)
)。
- 增加
sleep
// timer.rs
pub fn now() -> u64 {
get_cycle() / TIMEBASE
}
// process/mod.rs
pub mod timer;
use self::timer::Timer;
use crate::timer::now;
use lazy_static::lazy_static;
use spin::Mutex;
lazy_static! {
static ref TIMER: Mutex<Timer> = Mutex::new(Timer::default());
}
pub fn tick() {
CPU.tick();
TIMER.lock().tick(now());
}
pub fn sleep(sec: usize) {
let tid = current_tid();
TIMER
.lock()
.add(now() + (sec * 100) as u64, move || wake_up(tid));
park();
}
- 修改
idle
// in interrupt.rs
#[inline(always)]
pub fn enable() {
unsafe {
asm!("csrsi sstatus, 1 << 1" :::: "volatile");
}
}
// in process/processor.rs
impl Processor {
pub fn idle_main(&self) -> ! {
let inner = self.inner();
disable_and_store();
loop {
if let Some(thread) = inner.pool.acquire() {
inner.current = Some(thread);
inner
.idle
.switch_to(&mut *inner.current.as_mut().unwrap().1);
let (tid, thread) = inner.current.take().unwrap();
inner.pool.retrieve(tid, thread);
enable();
disable_and_store();
} else {
enable_and_wfi();
disable_and_store();
}
}
}
}
为什么需要在
idle_main
中增加一瞬间的中断开启,不增加这部分会出现什么问题?(3 分)
实现 spawn
用于通过函数创建一个内核线程。
process/mod.rs
/// Spawn a new kernel thread from function `f`.
pub fn spawn<F>(f: F)
where
F: FnOnce() + Send + 'static,
{
let f = Box::into_raw(Box::new(f));
let new_thread = Thread::new_kernel(entry::<F> as usize);
new_thread.append_initial_arguments([f as usize, 0, 0]);
CPU.add_thread(new_thread);
// define a normal function, pass the function object from argument
extern "C" fn entry<F>(f: usize) -> !
where
F: FnOnce() + Send + 'static,
{
let f = unsafe { Box::from_raw(f as *mut F) };
f();
exit(0);
unreachable!()
}
}
哲学家就餐问题测试
将该文件直接替换 init.rs
,没有哲学家拿错叉子且五个内核线程均正常退出则表示测试通过。
- 修改
spie
impl ContextContent {
fn new_kernel_thread(entry: usize, kstack_top: usize, satp: usize) -> ContextContent {
let mut content = ContextContent {
ra: __trapret as usize,
satp,
s: [0; 12],
tf: {
let mut tf: TrapFrame = unsafe { zeroed() };
tf.x[2] = kstack_top;
tf.sepc = entry;
tf.sstatus = sstatus::read();
tf.sstatus.set_spp(sstatus::SPP::Supervisor);
tf.sstatus.set_spie(false); // changed
tf.sstatus.set_sie(false);
tf
},
};
content
}
}
为什么需要修改
spie
,如果不进行修改可能会出现什么问题?(3 分)