【UCB操作系统CS162项目】Pintos Lab1:线程调度 Threads

实验文档链接:Lab1: Threads

我的实现(更新至Lab 2):Altair-Alpha/pintos

开始之前

如文档所述,在开始编写代码之前需要先阅读掌握 GETTING STARTED 部分和附录中 Code Guide 部分的前四节(重点是 Synchronization)、4.4BSD Scheduler 部分以及 C Standards 部分内容。

博主自己整理的一些本节中你可能会用到的命令行:

# docker运行容器
docker run -it --rm --name pintos --mount type=bind,source="你的Pintos路径",target=/home/PKUOS/pintos pkuflyingpig/pintos bash
# 启动另一个命令行
docker exec -it pintos bash
# 启动gdb
cd pintos/src/threads/build && pintos-gdb kernel.o
# 编译并运行单个测试(根据需要更换测试名,注意要有.result后缀)
make && make tests/threads/priority-donate-multiple.result
# 编译并调试单个测试(根据需要更换测试名)
make && pintos --gdb -- run priority-donate-multiple

# 打印list的第一个元素的某成员,这里以打印ready_list的第一个线程的名称为例
# 使用原因是访问列表元素的list_entry是宏,无法直接打印(除非改编译选项),这里的命令是手动展开后的结果
# 可以将list_begin(&ready_list)换成任何返回值为list_elem的表达式
((struct thread *) ((uint8_t *) &(list_begin(&ready_list))->next - ((size_t)&(((struct thread*)0)->elem.next))))->name

Task 1: Alarm Clock

Exercise 1.1

  • Reimplement timer_sleep(), defined in devices/timer.c.
    • Although a working implementation is provided, it “busy waits,” that is, it spins in a loop checking the current
    • time and calling thread_yield() until enough time has gone by. Reimplement it to avoid busy waiting.

本节实验的热身环节,要求将 timer.c 中已经用忙等待实现的线程睡眠函数 timer_sleep 改为用非忙等实现。timer_sleep 接受一个参数,为请求睡眠的 tick 数。timer.c 中其它基于物理时间的睡眠函数最终也是通过该函数实现。原代码为:

/** Sleeps for approximately TICKS timer ticks. Interrupts must
   be turned on. */
void
timer_sleep (int64_t ticks) 
{
   
  int64_t start = timer_ticks ();

  ASSERT (intr_get_level () == INTR_ON);
  while (timer_elapsed (start) < ticks) 
    thread_yield ();
}

timer_ticks 函数返回自开机以来经历的 tick 数,time_elapsed 则是将当前 tick 数与参数 tick 数相减得到 tick 的时间差,原实现通过一个 while 循环询问已经历的时间直到 ticks,如果没到达则调用 thread_yield,该函数让出 CPU 但仍将自身设为随时可被调度的 THREAD_READY 状态,因此是 busy wait 的。

分析

实现的要点在于将不断主动询问的行为改为先将自身阻塞,然后等待一个管理者被动地唤醒。如此,就必须要有这样一个高频掌握时间更新的管理者,其是否存在呢?当然。考虑时间系统如何更新,关注到 timer.c 中有一个全局变量 ticks 记录了开机至今的 tick 数。它是如何被更新的?答案是在 timer_interrupt 函数中,该函数正是时间中断的 handler,也就是 CPU 在每个时间片都会调用该函数通知时间的更新。时间中断是一种外部中断,我们在 timer_init 初始化函数中也可以看到该 handler 函数的注册:

intr_register_ext (0x20, timer_interrupt, "8254 Timer");

于是,我们可以在 timer_sleep 中将线程和睡眠时间记录起来,然后转为阻塞状态。在被高频调用的 timer_interrupt 中,更新所有睡眠线程的时间,如果降至 0,则将其唤醒。

实现

首先从设计上,我不希望为了睡眠这样的非核心功能而向 thread 结构体添加成员,因此我选择仅在 timer.c 中添加代码。定义如下结构:

/** Record sleeping threads. */
struct sleep_thread_entry
{
   
  struct thread *t;
  int64_t sleep_ticks;
  struct list_elem elem;
};
static struct list sleep_list;
  • 关于 list 的实现请详细阅读源码,使用上与 C++ 的 std::list 有明显区别

该结构由一个线程和其请求的睡眠时间构成,然后以一个 sleep_list 记录所有的项。

timer_init 中,初始化 sleep_list

list_init(&sleep_list);

在新版的 timer_sleep 中,构建一个记录项,放入 sleep_list 中,然后转为阻塞状态即可:

void
timer_sleep (int64_t ticks) 
{
   
  if (ticks < 0) {
   
    return;
  }

  enum intr_level old_level = intr_disable();

  struct sleep_thread_entry *entry = (struct sleep_thread_entry *)
                                      malloc(sizeof(struct sleep_thread_entry));
  entry->t = thread_current();
  entry->sleep_ticks = ticks;
  list_push_back(&sleep_list, &entry->elem);
  thread_block();

  intr_set_level(old_level);
}
  • 当 ticks < 0 时,测试期望的行为是正常返回而非 Kernel Panic,所以不能使用 ASSERT
  • 当 ticks = 0 时,参考 Linux 编程 sleep(0) 的意义,应该也执行后续代码让出 CPU,此时相当于执行了 thread_yield
  • thread_block 需要在关闭中断状态下被调用

编写一个新函数 sleep_tick,遍历更新 sleep_list,并唤醒到时间的线程:

/** Tick the sleep list. Threads whose sleep tick becomes zero 
 * will be unblocked. This process shouldn't be interrupted. */
static void
sleep_tick(void)
{
   
  struct list_elem *e = list_begin(&sleep_list);
  while (e != list_end(&sleep_list)) {
   
    struct sleep_thread_entry *entry = list_entry(e, struct sleep_thread_entry, elem);
    if (--(entry->sleep_ticks) <= 0) {
   
      e = list_remove(e);
      thread_unblock(entry->t);
    } else {
   
      e = list_next(e);
    }
  }
}
  • Core Guide / Interrupt Handling 文档最后部分说明了 “The handler will run with interrupts disabled”,因此这里无需再手动关闭中断</
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值