7. 同步互斥

实验要求

本章编程问题较简单,主要难度在阅读代码与问答题

  1. 编程:将在实验指导中提供 Mutex 的实现框架、sleep 的实现、spawn 的实现和哲学家就餐问题测试,请将它们复制到你的代码中,并完成 Mutex 中的 TODO 部分。(8 分)
  2. 回答:Mutex 的实现中,为什么需要引入 MutexGuard ?(3 分)
  3. 回答:Mutex 的实现中,为什么需要修改 yield_now 并增加 park ,如果都仍然使用旧的 yield_now 会出现什么问题?(3 分)
  4. 回答:sleep 的实现中,为什么需要在 idle_main 中增加一瞬间的中断开启,不增加这部分会出现什么问题?(3 分)
  5. 回答:在哲学家就餐测试中,为什么需要修改 spie ,如果不进行修改可能会出现什么问题?(3 分)

为了让做题体验更好,我会在下面的代码后再复制一遍上面的题目。

实验指导

Mutex 框架

互斥锁(Mutex),用于保护资源不会被多个线程同时操作。

  • sync/mod.rs
  1. pub mod condvar;
  2. pub use self::mutex::{Mutex as SleepLock, MutexGuard as SleepLockGuard};
  3. mod mutex;
  • 创建 sync/mutex.rs

这里给出了 rust std 库中 Mutext 实现的魔改版,你只需要完成两处(写了 TODO 的地方)简单的填空即可。

Mutex<T> 被一个线程占用时,其它试图访问该线程的线程会主动让出自己的时间片,给其它线程调度。

而当资源使用完毕时,应该能够自动释放资源。

互斥锁框架代码:mutex.rs

为什么需要引入 MutexGuard ?(3 分)

  • 修改 yield_now
  1. // in process/processor.rs
  2. impl Processor {
  3. pub fn yield_now(&self) {
  4. let inner = self.inner();
  5. if !inner.current.is_none() {
  6. unsafe {
  7. let flags = disable_and_store();
  8. let current_thread = &mut inner.current.as_mut().unwrap().1;
  9. current_thread.switch_to(&mut *inner.idle);
  10. restore(flags);
  11. }
  12. }
  13. }
  14. }
  • 实现 park
  1. // in process/mod.rs
  2. pub fn park() {
  3. CPU.park();
  4. }
  5. impl Processor {
  6. pub fn park(&self) {
  7. self.inner().pool.set_sleep(self.current_tid());
  8. self.yield_now();
  9. }
  10. }
  11. // in process/thread_pool.rs
  12. impl ThreadPool {
  13. pub fn set_sleep(&mut self, tid: Tid) {
  14. let proc = self.threads[tid].as_mut().expect("thread not exist");
  15. proc.status = Status::Sleeping;
  16. }
  17. }
  • 将之前所有用到 yield_now 的地方(除了 Mutex.rs)替换为 park
  1. // in sync/condvar.rs
  2. impl Condvar {
  3. pub fn wait(&self) {
  4. self.wait_queue.lock().push_back(current_tid());
  5. park();
  6. }
  7. }
  8. // syscall.rs
  9. fn sys_exec(path: *const u8) -> isize {
  10. let valid = process::execute(unsafe { from_cstr(path) }, Some(process::current_tid()));
  11. if valid {
  12. process::park();
  13. }
  14. return 0;
  15. }

为什么需要修改 yield_now 并增加 park ,如果都仍然使用旧的 yield_now 会出现什么问题?(3 分)

实现 sleep

  • 增加计时器 process/timer.rs

计时器代码:timer.rs

到时间后会触发回调函数,对于后面 sleep 的用法,在线程睡眠时间到了的时候将其唤醒(回调函数设置为 wakeup(tid) )。

  • 增加 sleep
  1. // timer.rs
  2. pub fn now() -> u64 {
  3. get_cycle() / TIMEBASE
  4. }
  5. // process/mod.rs
  6. pub mod timer;
  7. use self::timer::Timer;
  8. use crate::timer::now;
  9. use lazy_static::lazy_static;
  10. use spin::Mutex;
  11. lazy_static! {
  12. static ref TIMER: Mutex<Timer> = Mutex::new(Timer::default());
  13. }
  14. pub fn tick() {
  15. CPU.tick();
  16. TIMER.lock().tick(now());
  17. }
  18. pub fn sleep(sec: usize) {
  19. let tid = current_tid();
  20. TIMER
  21. .lock()
  22. .add(now() + (sec * 100) as u64, move || wake_up(tid));
  23. park();
  24. }
  • 修改 idle
  1. // in interrupt.rs
  2. #[inline(always)]
  3. pub fn enable() {
  4. unsafe {
  5. asm!("csrsi sstatus, 1 << 1" :::: "volatile");
  6. }
  7. }
  8. // in process/processor.rs
  9. impl Processor {
  10. pub fn idle_main(&self) -> ! {
  11. let inner = self.inner();
  12. disable_and_store();
  13. loop {
  14. if let Some(thread) = inner.pool.acquire() {
  15. inner.current = Some(thread);
  16. inner
  17. .idle
  18. .switch_to(&mut *inner.current.as_mut().unwrap().1);
  19. let (tid, thread) = inner.current.take().unwrap();
  20. inner.pool.retrieve(tid, thread);
  21. enable();
  22. disable_and_store();
  23. } else {
  24. enable_and_wfi();
  25. disable_and_store();
  26. }
  27. }
  28. }
  29. }

为什么需要在 idle_main 中增加一瞬间的中断开启,不增加这部分会出现什么问题?(3 分)

实现 spawn

用于通过函数创建一个内核线程。

  • process/mod.rs
  1. /// Spawn a new kernel thread from function `f`.
  2. pub fn spawn<F>(f: F)
  3. where
  4. F: FnOnce() + Send + 'static,
  5. {
  6. let f = Box::into_raw(Box::new(f));
  7. let new_thread = Thread::new_kernel(entry::<F> as usize);
  8. new_thread.append_initial_arguments([f as usize, 0, 0]);
  9. CPU.add_thread(new_thread);
  10. // define a normal function, pass the function object from argument
  11. extern "C" fn entry<F>(f: usize) -> !
  12. where
  13. F: FnOnce() + Send + 'static,
  14. {
  15. let f = unsafe { Box::from_raw(f as *mut F) };
  16. f();
  17. exit(0);
  18. unreachable!()
  19. }
  20. }

哲学家就餐问题测试

测试文件

将该文件直接替换 init.rs ,没有哲学家拿错叉子且五个内核线程均正常退出则表示测试通过。

  • 修改 spie
  1. impl ContextContent {
  2. fn new_kernel_thread(entry: usize, kstack_top: usize, satp: usize) -> ContextContent {
  3. let mut content = ContextContent {
  4. ra: __trapret as usize,
  5. satp,
  6. s: [0; 12],
  7. tf: {
  8. let mut tf: TrapFrame = unsafe { zeroed() };
  9. tf.x[2] = kstack_top;
  10. tf.sepc = entry;
  11. tf.sstatus = sstatus::read();
  12. tf.sstatus.set_spp(sstatus::SPP::Supervisor);
  13. tf.sstatus.set_spie(false); // changed
  14. tf.sstatus.set_sie(false);
  15. tf
  16. },
  17. };
  18. content
  19. }
  20. }

为什么需要修改 spie ,如果不进行修改可能会出现什么问题?(3 分)