性能优化之后,跑 ls | grep foo 时发现三类崩溃,分别来自三个不同的地方。
崩溃一:内核栈溢出
ls | grep foo 在 busybox sh 里需要同时运行三个进程:sh、ls、grep,加上 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 #
管道、路径回退、多进程全部正常。