修完 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 的速度明显提升——从能感知到的卡顿,变成接近即时响应。