性能优化之后,跑 ls | grep foo 时发现三类崩溃,分别来自三个不同的地方。

崩溃一:内核栈溢出

ls | grep foo 在 busybox sh 里需要同时运行三个进程:shlsgrep,加上 fork 的中间状态,进程数量一下上来了。

运行时随机崩溃,串口输出乱码。通过 QEMU 调试发现是内核栈溢出——某个进程的内核栈被覆盖了。

原来内核栈只有 4KB:

#define STACK_SIZE   4096
p->stack = (uint8_t *)pmm_alloc();   // 只有 1 个物理页

busybox 的调用链比较深(sh → fork → exec → elf_load → ext2_read → bcache_get → …),4KB 撑不住。

改成 8KB,用 kmalloc 分配(pmm_alloc 只能分配 4KB):

#define STACK_SIZE   8192
p->stack = (uint8_t *)kmalloc(STACK_SIZE);

崩溃二:cd .. 跳到根目录

/ # mkdir -p /usr/bin
/ # cd /usr/bin
/usr/bin # cd ..
/ #   ← 应该到 /usr,结果跳到 /

路径解析遇到 .. 时,原来直接跳回根目录:

if (strcmp(part, "..") == 0) {
    dir_ino = vfs_root_inum;   // 错误!
    continue;
}

修复方法是用一个栈记录路径历史:

uint32_t stk[32];
int stk_top = 0;
stk[stk_top++] = dir_ino;

// 遇到 ..:出栈
if (strcmp(part, "..") == 0) {
    if (stk_top > 1) stk_top--;
    dir_ino = stk[stk_top - 1];
}
// 正常路径段:查找后入栈
else {
    dir_ino = ext2_lookup(dir_ino, part);
    if (stk_top < 32) stk[stk_top++] = dir_ino;
}

/a/b/c/.. 解析:入栈 root→a→b→c,遇到 .. 出栈,栈顶变成 b,dir_ino = b

崩溃三:管道死锁

ls | grep foo 有时会卡死,两个进程都挂着不动。

原因是管道缓冲区只有 256 字节:

#define PIPE_BUF_SIZE  256

ls 输出一个目录内容可能超过 256 字节。写端(ls)把管道写满后阻塞,等读端读走数据;但读端(grep)还没启动(调度器还没切过去),两个进程互相等,死锁。

改成 4KB:

#define PIPE_BUF_SIZE  4096

4KB 能容纳 ls 的典型输出,写端不会过早阻塞,死锁窗口大幅缩小。

验收

/ # ls | grep bin
bin
/ # mkdir -p /tmp/test
/ # cd /tmp/test
/tmp/test # cd ..
/tmp #

管道、路径回退、多进程全部正常。