复习
本次课回答的问题
本次课主要内容
虽然不太愿意承认,但
始终假设自己的代码是错的 。
然后呢?
软件是需求 (规约) 在计算机数字世界的投影。
只管 “翻译” 代码,不管和实际需求 (规约) 是否匹配
balance
代表 “余额”三十年后的编程语言和编程方法?
你知道很多变量的
#define CHECK_INT(x, cond) \
({ panic_on(!((x) cond), "int check fail: " #x " " #cond); })
#define CHECK_HEAP(ptr) \
({ panic_on(!IN_RANGE((ptr), heap)); })
变量有 “typed annotation”
CHECK_INT(waitlist->count, >= 0);
CHECK_INT(pid, < MAX_PROCS);
CHECK_HEAP(ctx->rip); CHECK_HEAP(ctx->cr3);
A deadlock is a state in which each member of a group is waiting for another member, including itself, to take action.
出现线程 “互相等待” 的情况
假设你的 spinlock 不小心发生了中断
yield()
void os_run() {
spin_lock(&list_lock);
spin_lock(&xxx);
spin_unlock(&xxx); // ---------+
} // |
// |
void on_interrupt() { // |
spin_lock(&list_lock); // <--+
spin_unlock(&list_lock);
}
void swap(int i, int j) {
spin_lock(&lock[i]);
spin_lock(&lock[j]);
arr[i] = NULL;
arr[j] = arr[i];
spin_unlock(&lock[j]);
spin_unlock(&lock[i]);
}
上锁的顺序很重要……
swap
本身看起来没有问题swap(1, 2)
; swap(2, 3)
, swap(3, 1)
→ 死锁死锁产生的四个必要条件 (Edward G. Coffman, 1971):
“理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。” ——
Bullshit .
AA-Deadlock
if (holding(lk)) panic();
ABBA-Deadlock
Textbooks will tell you that if you always lock in the same order, you will never get this kind of deadlock.
The best locks are encapsulated: they never get exposed in headers, and are never held around calls to non-trivial functions outside the same file. You can read through this code and see that it will never deadlock, because it never tries to grab another lock while it has that one. People using your code don't even need to know you are using a lock.
—— Unreliable Guide to Locking by Rusty Russell
不上锁不就没有死锁了吗?
不同的线程 同时访问同一段内存 ,且至少有一个是写 。
Peterson 算法告诉大家:
以下代码概括了你们遇到数据竞争的大部分情况
// Case #1: 上错了锁
void thread1() { spin_lock(&lk1); sum++; spin_unlock(&lk1); }
void thread2() { spin_lock(&lk2); sum++; spin_unlock(&lk2); }
// Case #2: 忘记上锁
void thread1() { spin_lock(&lk1); sum++; spin_unlock(&lk1); }
void thread2() { sum++; }
回顾我们实现并发控制的工具
忘记上锁——原子性违反 (Atomicity Violation, AV)
忘记同步——顺序违反 (Order Violation, OV)
Empirical study: 在 105 个并发 bug 中 (non-deadlock/deadlock)
“ABA”
有时候上锁也不解决问题
“BA”
还是得
始终假设自己的代码是错的 。
然后呢?
例如:用 lock ordering 彻底避免死锁?
Lockdep 规约 (Specification)
检查方法:printf
为所有事件建立 happens-before 关系图
在事件发生时记录
解析记录检查问题
付出的代价和权衡
没用过 lint/sanitizers?
-fwrapv
编译只不过不需要我亲自动手把代码改得乱七八糟了……
Canary (金丝雀) 对一氧化碳非常敏感
计算机系统中的 canary
#define MAGIC 0x55555555
#define BOTTOM (STK_SZ / sizeof(u32) - 1)
struct stack { char data[STK_SZ]; };
void canary_init(struct stack *s) {
u32 *ptr = (u32 *)s;
for (int i = 0; i < CANARY_SZ; i++)
ptr[BOTTOM - i] = ptr[i] = MAGIC;
}
void canary_check(struct stack *s) {
u32 *ptr = (u32 *)s;
for (int i = 0; i < CANARY_SZ; i++) {
panic_on(ptr[BOTTOM - i] != MAGIC, "underflow");
panic_on(ptr[i] != MAGIC, "overflow");
}
}
msvc 中 debug mode 的 guard/fence/canary
0xcccccccc
0xcdcdcdcd
0xfdfdfdfd
0xdddddddd
(b'\xcc' * 80).decode('gb2312')
手持两把锟斤拷,口中疾呼烫烫烫
脚踏千朵屯屯屯,笑看万物锘锘锘
(它们一直在无形中保护你)
不必大费周章记录什么上锁顺序
int spin_cnt = 0;
while (xchg(&locked, 1)) {
if (spin_cnt++ > SPIN_LIMIT) {
printf("Too many spin @ %s:%d\n", __FILE__, __LINE__);
}
}
内存分配要求:已分配内存 $S = [\ell_0, r_0) \cup [\ell_1, r_1) \cup \ldots$
// allocation
for (int i = 0; (i + 1) * sizeof(u32) <= size; i++) {
panic_on(((u32 *)ptr)[i] == MAGIC, "double-allocation");
arr[i] = MAGIC;
}
// free
for (int i = 0; (i + 1) * sizeof(u32) <= alloc_size(ptr); i++) {
panic_on(((u32 *)ptr)[i] == 0, "double-free");
arr[i] = 0;
}
本次课回答的问题
Take-away message