互斥
并发编程
大家已经掌握了越来越多编写并发程序的工具了!
复习:操作系统内核中的并发编程
学习 (操作系统内核中) 自旋锁的实现
多处理器系统
AbstractMachine 和多处理器
bool mpe_init (void (*entry)()); // 启动多处理器
int cpu_count (void); // 返回 CPU 数量
int cpu_current (void); // 返回当前 CPU 编号
int atomic_xchg (int *addr, int newval); // xchg()
typedef struct {
int locked;
} lock_t;
#define LOCK_INIT() ((lock_t) { .locked = 0 })
void lock(lock_t *lk) { while (atomic_xchg(&lk->locked, 1)); }
void unlock(lock_t *lk) { atomic_xchg(&lk->locked, 0); }
static lock_t lk = LOCK_INIT();
lock(&lk);
sum++;
unlock(&lk);
通过一个 Easy Test Case 很容易:
void *malloc(size_t sz) {
void *ret;
lock(&malloc_lock);
ret = trivial_malloc(sz);
unlock(&malloc_lock);
return ret;
}
void free(void *ptr) {
lock(&malloc_lock);
trivial_free(ptr);
unlock(&malloc_lock);
}
typedef struct {
int locked;
} lock_t;
#define LOCK_INIT() ((lock_t) { .locked = 0 })
void lock(lock_t *lk) { while (atomic_xchg(&lk->locked, 1)); }
void unlock(lock_t *lk) { atomic_xchg(&lk->locked, 0); }
似乎挺不错的……
和上节课一样:“一核有难,他人围观” 或者 “全体摸鱼”
void spawn() {
lock(&procs->lock);
// 中断/yield
// ... schedule()
// ... lock(&procs->lock);
unlock(&procs->lock);
}
关中断 + 自旋:关中断可以保证单处理器的互斥。
void spin_lock(spinlock_t &lk) {
iset(false);
while (atomic_xchg(&lk->locked, 1)) ;
}
void spin_unlock(spinlock_t &lk) {
atomic_xchg(&lk->locked, 0);
iset(true);
}
细节 (为什么?)
void foo() {
/* +-----*/ spin_lock(&lock_a);
/* | */
/* | +--*/ spin_lock(&lock_b);
/* | | */ ...
/* | +--*/ spin_unlock(&lock_b);
/* | */ assert(ienabled() == false); // 仍然在临界区
/* +-----*/ spin_unlock(&lock_a);
/* */ assert(ienabled() == true); // 出临界区
}
(如何修复这个问题?)
void on_interrupt(_Event *ev, _Context *ctx) {
assert(ienabled() == false);
// 刚才原封不动的代码
/* +-----*/ spin_lock(&lock_a);
/* | */
/* | +--*/ spin_lock(&lock_b);
/* | | */ ...
/* | +--*/ spin_unlock(&lock_b);
/* | */ assert(ienabled() == false);
/* +-----*/ spin_unlock(&lock_a);
assert(ienabled() == false);
}
正确的实现
FL_IF
in %rflags
)、关闭中断pushcli
”popcli
”flags 栈应该保存在何处?
struct spinlock_t
)?struct task
) 里?struct cpu_local
) 里?A standard joke is that a bug can be turned into a feature simply by documenting it (then theoretically no one can complain about it because it’s in the manual), or even by simply declaring it to be good. “That’s not a bug, that’s a feature!” is a common catchphrase.
// It's not a bug, it's a feature!
lock(&lk);
lock(&lk);
printf(...);
unlock(&lk);
unlock(&lk);
(不要笑……经过三次函数调用你也不知道是否持有 lk
了)
关中断 + 自旋;用来保护一段较短的临界区
- 持有锁期间,线程 (处理器) 不能被中断
- 其他等待锁的线程 (处理器) 在关中断的前提下自旋
“无法等待、必须立即执行否则不能继续” 的场景
自旋锁在操作系统中的应用
Xv6 is a teaching operating system developed in the summer of 2006, which we ported xv6 to RISC-V for a new undergraduate class 6.S081.
作者: Russ Cox, Frans Kaashoek, Robert Morris
今年跟上 RISC-V
使用到的工具 (apt 安装即可)
项目组织
Makefile
- 比 AbstractMachine 友好多了make -nB qemu | grep -v objdump | vim -
make qemu
.gdbinit
noff
(number of off
s) - 关中断计数intena
(interrupt enabled) - 锁释放时是否需要开中断locked
- 锁变量name
, cpu
- 调试信息panic_on(holding(lk), "acquire");
panic_on(!holding(lk), "release");
panic_on(intr_get(), "pop_off - interruptible");
panic_on(mycpu()->noff < 1, "pop_off");
make qemu-gdb
.gdbinit
等)调试的一些小技巧
set scheduler-locking on
(我们额外添加到 .gdbinit
)info threads
thread X
本次课内容与目标
Take-away messages