导引:007 留下的雷有多响
读完你会理解:Cinux 的自旋锁为什么有两种 RAII、调度器为什么必须关着中断加锁、为什么「持着自旋锁去阻塞」是不可饶恕的死罪,以及我们怎么用三层测试证明这套锁在真·抢占环境下站得住。
007 留下的雷,到底有多响
先回顾一个容易被忘掉的事实:调度器从 020 起就是抢占式的——RoundRobin,每个任务跑 DEFAULT_TIME_SLICE = 2 个 tick 就被换下;PIT 从 011 起就在以 100 Hz 发 IRQ0。也就是说,内核线程随时会被时钟中断打断、切到另一个线程去跑。
而 PMM、堆、调度运行队列、fd 表这些全局可变状态,在 007 之前是完全裸奔的。单线程时代这没事,但抢占式调度一来,就是定时炸弹。举三个真实的雷:
雷一:PMM 的 find-then-set。 alloc_page 的核心是「找一个空闲 bit、把它置位、返回对应的物理地址」。如果两个线程几乎同时进来,都跑到 bm_find_first_free,都看到 bit N 是 0(空闲),于是各自置位、各自返回 N * 4096。结果:同一个物理页被分给了两个线程。后面谁先写谁先占,另一个的内容就被无声覆盖。这种 bug 不会立刻崩,而是「偶发、跑久了才炸、换个编译选项又消失」——最难调的那一种。
雷二:堆的 free_list 指针竞争。 堆的空闲块是条单链表。两个线程同时 alloc/free,一个正在改 free_list_ 指针,另一个读到的就是改到一半的指针——要么野指针触发 page fault,要么链表被接成环,后面所有分配都在环里打转。
雷三:调度运行队列被两种上下文碰。 它不只被线程碰(yield/add_task/block 会调 enqueue/dequeue),还被中断上下文碰:时钟中断 → tick() → 时间片到了 → schedule() → pick_next(),全在改同一条运行队列。要是 pick_next 在你改队列改到一半时插进来……
DONE.md 把它们归成一句:「TIER 0 核心分配器——数据竞争必然崩溃」。不是危言耸听,是这一类共享状态在并发下一定会出问题,只是早晚。