修完 static 缓冲区的 bug 之后,发现加载 busybox 明显变慢了——因为 kmalloc/kfree 在每次 ext2 读取时都要分配释放缓冲区,调用次数多了开销就出来了。

更根本的问题是块缓存(bcache)效率太低:64 个槽位,每次查找都要线性扫描。

这章做两个性能优化。

bcache:哈希表替代线性扫描

原来的实现

uint8_t *bcache_get(uint32_t lba) {
    for (int i = 0; i < BCACHE_SLOTS; i++) {   // O(n) 扫描
        if (slots[i].valid && slots[i].lba == lba) {
            slots[i].lru_time = clock;
            return slots[i].data;
        }
    }
    // cache miss...
}

64 个槽位,命中率低,每次 miss 还要再扫一遍找 LRU victim,加载 busybox 要读几百个 sector,扫描次数非常多。

改造:256 桶哈希表

#define BCACHE_SLOTS      512   // 槽位 64 → 512
#define BCACHE_HASH_SIZE  256
#define BCACHE_HASH(lba)  ((lba) & (BCACHE_HASH_SIZE - 1))

typedef struct bcache_slot {
    uint32_t  lba;
    uint8_t  *data;              // 改为指针,pmm_alloc 独立分配
    int       valid, dirty;
    uint64_t  lru_time;
    struct bcache_slot *hash_next;   // 哈希链表
} bcache_slot_t;

static bcache_slot_t *hash_table[BCACHE_HASH_SIZE];

查找变成 O(1) 均摊:

uint8_t *bcache_get(uint32_t lba) {
    uint32_t h = BCACHE_HASH(lba);
    for (bcache_slot_t *s = hash_table[h]; s; s = s->hash_next) {
        if (s->lba == lba) {
            s->lru_time = clock_tick++;
            return s->data;   // 命中
        }
    }
    // cache miss:找 LRU victim,驱逐,读入新 sector
    // 驱逐时从哈希表摘除,新 slot 插入哈希表
}

驱逐时先从哈希表摘除旧 slot,读入新数据后再插入:

if (victim->valid) {
    if (victim->dirty) ata_write_sector(victim->lba, victim->data);
    hash_remove(victim);   // 从哈希表摘除
}
ata_read_sector(lba, victim->data);
victim->lba = lba; victim->valid = 1; victim->dirty = 0;
hash_insert(victim);   // 插入哈希表

data 改为指针,初始化时每个 slot 用 pmm_alloc() 分配独立的 4KB 页,避免 struct 太大(512 个 slot × 512B = 256KB,放在 BSS 里太占空间)。

ELF 写入:按页 chunk

加载 ELF 时需要把文件数据写入用户页表:

// 修改前:逐字节,每字节调一次 vmm_virt_to_phys
for (uint32_t i = 0; i < len; i++) {
    uint64_t page = vmm_virt_to_phys(pml4, (va + i) & ~0xFFFULL);
    *(uint8_t *)(page + ((va + i) & 0xFFF)) = src[i];
}

加载 512KB 的 busybox 要调 52 万次 vmm_virt_to_phys,每次走四级页表,慢。

改为按页 chunk:

// 修改后:每页只查一次
while (len > 0) {
    uint32_t off   = (uint32_t)(va & 0xFFF);   // 页内偏移
    uint32_t chunk = 0x1000 - off;             // 到页边界
    if (chunk > len) chunk = len;

    uint64_t page = vmm_virt_to_phys(pml4, va & ~0xFFFULL);
    if (page) {
        uint8_t *dst = (uint8_t *)(page + off);
        for (uint32_t i = 0; i < chunk; i++) dst[i] = src[i];
    }
    va += chunk; src += chunk; len -= chunk;
}

vmm_virt_to_phys 调用次数从 O(len) 降到 O(len/4096),减少 4096 倍。

效果

两个优化叠加,busybox exec 的速度明显提升——从能感知到的卡顿,变成接近即时响应。