块设备、inode 与超级块设计

本节目标

三大件:

// 运行环境:Node.js 14+
// 保存为 aj-l9.js,执行:node aj-l9.js
const BLOCK = 16 // 演示用小块大小
class MiniFS {
  constructor(totalBlocks) {
    this.super = { magic: 0x1234, totalBlocks, freeBitmap: new Array(totalBlocks).fill(1) } // 1=空闲
    this.blocks = Array.from({ length: totalBlocks }, () => Buffer.alloc(BLOCK))
    this.inodes = {} // id -> {size, blocks:[...]}
    this.nextInode = 1
  }
  allocBlock() {
    const i = this.super.freeBitmap.indexOf(1)
    if (i === -1) throw new Error('磁盘满')
    this.super.freeBitmap[i] = 0
    return i
  }
  writeFile(name, content) {
    const data = Buffer.from(content)
    const blocks = []
    for (let off = 0; off < data.length; off += BLOCK) {
      const b = this.allocBlock()
      data.copy(this.blocks[b], 0, off, off + BLOCK)
      blocks.push(b)
    }
    this.inodes[name] = { size: data.length, blocks }
    return this.nextInode++
  }
  readFile(name) {
    const inode = this.inodes[name]
    const out = Buffer.alloc(inode.size)
    let w = 0
    for (const b of inode.blocks) { this.blocks[b].copy(out, w); w += BLOCK }
    return out.slice(0, inode.size).toString()
  }
}

// 调用示例
const fs = new MiniFS(8)
fs.writeFile('hello.txt', '你好,文件系统!')
console.log(fs.readFile('hello.txt')) // 你好,文件系统!

名词解释

课后练习

  1. 为什么文件系统用"块"而不用"字节"管理空间?
    • 答案:按字节管理元数据开销爆炸;以块为单位(如 4KB)既能减少寻址次数,又让位图/索引规模可控,是空间与效率的折中。
  2. inode 损坏会怎样?
    • 答案:虽然文件名还在目录里,但"文件对应哪些块、多大"的信息丢失,文件系统无法拼出内容,文件相当于"找不回来"(需靠备份或 fsck 修复)。

总结

手写迷你文件系统,是把"磁盘"从黑盒变成可触摸对象的一课。你会发现,所谓"保存一个文件",底层不过是在一张位图里找几个空闲块、把字节写进去、再在 inode 里记下"这些块属于这个文件"。超级块则是整个系统的"户口本"——它一坏,所有户口都查不到。我特别想点出 inode 这个设计的精妙:它把"文件内容"和"文件名"解耦,于是同一份数据可以有多个名字(硬链接),删除文件名也不等于删除数据。理解块/inode/超级块,你看 ls -i(看 inode 号)、df(看块用量)、fsck(修超级块)就不再是天书,而是你亲手实现过的概念。这种"从字节到文件"的认知,是后端、存储、甚至数据库索引设计的共同地基。