目录

手写迷你文件系统:基于 FUSE + Go 实现自定义 FS

学习文件系统最好的方式就是自己写一个。本文使用 FUSE + Go,实现一个支持基本读写操作的内存文件系统,过程中深入理解 inode、block 等核心概念。

一、为什么要手写文件系统?

1.1 学习目的

理解文件系统,是理解以下技术的基础:

  • 容器存储卷 (Volume)
  • 分布式存储 (Ceph, GlusterFS)
  • 对象存储 (S3 FUSE)
  • 数据库存储引擎

1.2 FUSE 的优势

FUSE (Filesystem in Userspace) 允许在用户态实现文件系统:

  • 不需要写内核模块
  • 开发调试方便
  • 可以用高级语言 (Go, Python, Rust)

二、文件系统核心概念

2.1 Inode

Inode (Index Node) 是文件系统的核心数据结构,存储文件的元数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type Inode struct {
    ID         uint64       // inode 编号
    Type       InodeType    // 文件 or 目录
    Size       uint64       // 文件大小
    Mode       os.FileMode  // 权限
    Uid, Gid   uint32       // 所有者
    Atime      time.Time    // 访问时间
    Mtime      time.Time    // 修改时间
    Ctime      time.Time    // 创建时间
    Links      uint32       // 硬链接数
    Blocks     []uint64     // 数据块索引
}

关键点

  • 文件名不在 inode 里!文件名在目录项
  • 一个 inode 可以有多个文件名(硬链接)
  • ls -i 可以查看 inode 编号

2.2 Block

Block 是存储数据的基本单位,通常 4KB:

1
2
3
4
5
const BlockSize = 4096

type Block struct {
    Data [BlockSize]byte
}

文件的内容被切分成多个 Block 存储。

2.3 目录

目录本质上也是一个文件,内容是"目录项"列表:

1
2
3
4
5
6
type DirEntry struct {
    Name   string  // 文件名
    Inode  uint64  // 指向的 inode
}

// 目录的内容就是 []DirEntry

2.4 关系示意

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
           ┌─────────────────────────────────────────┐
           │              Superblock                 │
           │  (文件系统元信息: block 数量, inode 数量)  │
           └─────────────────────────────────────────┘
     ┌────────────────────────┼────────────────────────┐
     ▼                        ▼                        ▼
┌─────────┐            ┌─────────┐             ┌─────────┐
│ Inode 1 │            │ Inode 2 │             │ Inode 3 │
│ (根目录) │            │ (文件a) │             │ (文件b) │
│ Type=Dir│            │ Type=Reg│             │ Type=Reg│
└────┬────┘            └────┬────┘             └────┬────┘
     │                      │                       │
     ▼                      ▼                       ▼
┌─────────┐            ┌─────────┐             ┌─────────┐
│ Block 0 │            │ Block 1 │             │ Block 2 │
│ DirEntry│            │ 文件内容 │             │ 文件内容 │
│ a→Inode2│            │  "hello" │             │ "world" │
│ b→Inode3│            └─────────┘             └─────────┘
└─────────┘

三、FUSE 架构

3.1 工作原理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
┌──────────────────────────────────────────────────────────┐
│                       User Space                          │
│  ┌──────────┐    ┌──────────┐    ┌──────────────────┐    │
│  │ Your App │───▶│  glibc   │───▶│ FUSE 用户态程序   │    │
│  │ (cat, ls)│    │ open/read│    │ (你写的代码)      │    │
│  └──────────┘    └────┬─────┘    └────────▲─────────┘    │
│                       │                    │              │
└───────────────────────│────────────────────│──────────────┘
                        │ VFS               │ /dev/fuse
                        ▼                    │
┌───────────────────────────────────────────────────────────┐
│                       Kernel                              │
│  ┌──────────────────────────────────────────────────┐    │
│  │                   FUSE 内核模块                    │    │
│  │  (把 VFS 调用转发到用户态,再把结果返回)            │    │
│  └──────────────────────────────────────────────────┘    │
└───────────────────────────────────────────────────────────┘

3.2 需要实现的操作

操作对应系统调用说明
Lookupopen(dir)在目录中查找文件
Getattrstat获取文件属性
Readdirreaddir列出目录内容
Readread读取文件内容
Writewrite写入文件内容
Createcreat/open创建新文件
Mkdirmkdir创建目录
Unlinkunlink删除文件
Rmdirrmdir删除目录

四、Go 实现

4.1 依赖

使用 bazil.org/fuse 库:

1
go get bazil.org/fuse

4.2 数据结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
    "os"
    "sync"
    "time"
)

type InodeType int

const (
    TypeFile InodeType = iota
    TypeDir
)

type MemFS struct {
    mu       sync.RWMutex
    inodes   map[uint64]*Inode
    data     map[uint64][]byte  // inode -> 文件内容
    nextIno  uint64
}

type Inode struct {
    ID       uint64
    Type     InodeType
    Mode     os.FileMode
    Size     uint64
    Atime    time.Time
    Mtime    time.Time
    Children map[string]uint64  // 目录项: 名字 -> inode
}

func NewMemFS() *MemFS {
    fs := &MemFS{
        inodes:  make(map[uint64]*Inode),
        data:    make(map[uint64][]byte),
        nextIno: 2,
    }
    // 创建根目录 (inode 1)
    fs.inodes[1] = &Inode{
        ID:       1,
        Type:     TypeDir,
        Mode:     os.ModeDir | 0755,
        Atime:    time.Now(),
        Mtime:    time.Now(),
        Children: make(map[string]uint64),
    }
    return fs
}

4.3 实现 FUSE 接口

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
import (
    "bazil.org/fuse"
    "bazil.org/fuse/fs"
    "context"
)

// Dir 实现目录节点
type Dir struct {
    fs    *MemFS
    inode *Inode
}

func (d *Dir) Attr(ctx context.Context, attr *fuse.Attr) error {
    attr.Inode = d.inode.ID
    attr.Mode = d.inode.Mode
    attr.Atime = d.inode.Atime
    attr.Mtime = d.inode.Mtime
    return nil
}

func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
    d.fs.mu.RLock()
    defer d.fs.mu.RUnlock()
    
    childIno, ok := d.inode.Children[name]
    if !ok {
        return nil, fuse.ENOENT
    }
    
    child := d.fs.inodes[childIno]
    if child.Type == TypeDir {
        return &Dir{fs: d.fs, inode: child}, nil
    }
    return &File{fs: d.fs, inode: child}, nil
}

func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
    d.fs.mu.RLock()
    defer d.fs.mu.RUnlock()
    
    var entries []fuse.Dirent
    for name, ino := range d.inode.Children {
        child := d.fs.inodes[ino]
        var t fuse.DirentType
        if child.Type == TypeDir {
            t = fuse.DT_Dir
        } else {
            t = fuse.DT_File
        }
        entries = append(entries, fuse.Dirent{
            Inode: ino,
            Name:  name,
            Type:  t,
        })
    }
    return entries, nil
}

func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
    d.fs.mu.Lock()
    defer d.fs.mu.Unlock()
    
    ino := d.fs.nextIno
    d.fs.nextIno++
    
    now := time.Now()
    inode := &Inode{
        ID:    ino,
        Type:  TypeFile,
        Mode:  req.Mode,
        Atime: now,
        Mtime: now,
    }
    d.fs.inodes[ino] = inode
    d.fs.data[ino] = []byte{}
    d.inode.Children[req.Name] = ino
    
    file := &File{fs: d.fs, inode: inode}
    return file, file, nil
}

func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
    d.fs.mu.Lock()
    defer d.fs.mu.Unlock()
    
    ino := d.fs.nextIno
    d.fs.nextIno++
    
    now := time.Now()
    inode := &Inode{
        ID:       ino,
        Type:     TypeDir,
        Mode:     req.Mode | os.ModeDir,
        Atime:    now,
        Mtime:    now,
        Children: make(map[string]uint64),
    }
    d.fs.inodes[ino] = inode
    d.inode.Children[req.Name] = ino
    
    return &Dir{fs: d.fs, inode: inode}, nil
}

4.4 文件操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// File 实现文件节点
type File struct {
    fs    *MemFS
    inode *Inode
}

func (f *File) Attr(ctx context.Context, attr *fuse.Attr) error {
    attr.Inode = f.inode.ID
    attr.Mode = f.inode.Mode
    attr.Size = f.inode.Size
    attr.Atime = f.inode.Atime
    attr.Mtime = f.inode.Mtime
    return nil
}

func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
    f.fs.mu.RLock()
    defer f.fs.mu.RUnlock()
    
    data := f.fs.data[f.inode.ID]
    if req.Offset >= int64(len(data)) {
        return nil
    }
    
    end := req.Offset + int64(req.Size)
    if end > int64(len(data)) {
        end = int64(len(data))
    }
    
    resp.Data = data[req.Offset:end]
    return nil
}

func (f *File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
    f.fs.mu.Lock()
    defer f.fs.mu.Unlock()
    
    data := f.fs.data[f.inode.ID]
    
    // 扩展文件大小
    newLen := int(req.Offset) + len(req.Data)
    if newLen > len(data) {
        newData := make([]byte, newLen)
        copy(newData, data)
        data = newData
    }
    
    copy(data[req.Offset:], req.Data)
    f.fs.data[f.inode.ID] = data
    f.inode.Size = uint64(len(data))
    f.inode.Mtime = time.Now()
    
    resp.Size = len(req.Data)
    return nil
}

4.5 主程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func main() {
    mountpoint := "/tmp/memfs"
    os.MkdirAll(mountpoint, 0755)
    
    c, err := fuse.Mount(mountpoint, fuse.FSName("memfs"), fuse.Subtype("memfs"))
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()
    
    fmt.Printf("已挂载到 %s\n", mountpoint)
    fmt.Println("按 Ctrl+C 退出")
    
    memfs := NewMemFS()
    
    // 处理信号,优雅卸载
    go func() {
        sigChan := make(chan os.Signal, 1)
        signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
        <-sigChan
        fuse.Unmount(mountpoint)
    }()
    
    err = fs.Serve(c, &FS{memfs: memfs})
    if err != nil {
        log.Fatal(err)
    }
}

type FS struct {
    memfs *MemFS
}

func (f *FS) Root() (fs.Node, error) {
    return &Dir{fs: f.memfs, inode: f.memfs.inodes[1]}, nil
}

五、测试运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 编译运行
go build -o memfs
./memfs &

# 测试文件操作
cd /tmp/memfs
echo "hello world" > test.txt
cat test.txt
mkdir subdir
ls -la

# 卸载
fusermount -u /tmp/memfs

六、进阶扩展

6.1 持久化到磁盘

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 将 inode 和 data 序列化到磁盘
func (fs *MemFS) SaveToDisk(path string) error {
    f, _ := os.Create(path)
    defer f.Close()
    return gob.NewEncoder(f).Encode(fs)
}

func LoadFromDisk(path string) (*MemFS, error) {
    f, _ := os.Open(path)
    defer f.Close()
    var fs MemFS
    err := gob.NewDecoder(f).Decode(&fs)
    return &fs, err
}

6.2 网络文件系统

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 将读写操作转发到远程服务器
func (f *File) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
    // 通过 gRPC 从远程读取
    data, err := f.client.ReadFile(ctx, &pb.ReadRequest{
        Inode:  f.inode.ID,
        Offset: req.Offset,
        Size:   int64(req.Size),
    })
    resp.Data = data.Content
    return err
}

七、总结

概念实现
Inode结构体存储元数据
Blockbyte 切片存储数据
目录map[name]inode
FUSE实现 Attr/Read/Write 等接口

收获

  1. 深入理解了 inode 和目录项的关系
  2. 了解了 VFS 到 FUSE 的调用链路
  3. 为理解分布式存储打下基础

相关文章