VFS 里的 dev_t:文件系统设备号 vs 真实设备号

4 阅读4分钟

下面是把你这一串讨论压缩、梳理后的版本,我会按“概念 → 各字段含义 → 虚拟 FS 的 dev_t 冲突问题”来整理。


一、统一认知:dev_t 是啥

  • dev_t 本质:主设备号 + 次设备号
  • 原始用途:驱动用来唯一标识一个具体设备实例(某块盘、某个分区、某个串口等)。
  • 后来:VFS / 用户态接口也“复用”这个统一的编号方式,在不同层面用它标识:
    • 块设备上的文件系统实例;
    • 虚拟文件系统实例;
    • 设备文件代表的真实设备等。

类型始终是同一个 dev_t,关键是:当前这个号“在标识哪一层的对象”。


二、st_dev / s_dev VS st_rdev / i_rdev:两个层面的“设备号”

1. s_dev / st_dev:文件系统实例所在的“设备号”

  • super_block.s_dev
    这个文件系统实例(super_block)对应的 dev_t。

  • 对不同类型 FS:

    • 块设备上的文件系统(ext4/xfs 等)

      • s_dev = 底层块设备的 dev_t(比如 /dev/sda18:1)。
      • 这就是你熟悉的“给驱动用来标识这块设备的号”。
    • 虚拟/内存文件系统(tmpfs/procfs/sysfs 等)

      • 没底层块设备,内核会给它分配一个匿名 dev_t
      • 这个 dev_t 只用来标识这个文件系统实例,对实际硬件无意义。
  • 用户态 stat() 的:

    st.st_dev  // = inode->i_sb->s_dev
    

    表示:这个文件所在的文件系统实例是哪个(用 dev_t 表示)。

换句话说,st_dev / s_dev 是“文件所在文件系统的设备号”。
对有块设备的情况,可以直观理解成“所在块设备的设备号”。


2. i_rdev / st_rdev:设备文件所代表的“真实设备号”

  • inode.i_rdev
    对于设备文件(字符设备/块设备),存的是它所代表的真实设备的 dev_t

  • stat() 的:

    st.st_rdev // = inode->i_rdev
    
  • 含义:

    • 对普通文件/目录:无意义,通常为 0
    • 只对 /dev/sda1/dev/ttyS0 这种设备节点有意义:
      • st_rdev = 设备驱动用来识别这个设备的 dev_t。

简化记忆:

  • dev(st_dev):这个文件“在哪个文件系统 / 设备上”。
  • rdev(st_rdev):这个文件如果是设备节点,“它代表哪一个真实设备”。

三、具体例子串一下

假设:

  • /:挂在 /dev/sda1 上,为 ext4;
  • /dev:是 devtmpfs;
  • 存在设备文件 /dev/sda1

1. 普通文件 /home/a.txt

  • 所在文件系统:挂在 /dev/sda1 上的 ext4。

  • super_block:

    sb_root->s_dev = 8:1    // 来自底层块设备 /dev/sda1
    
  • inode:

    inode->i_sb   = sb_root
    inode->i_rdev = 0       // 普通文件,不是设备节点
    
  • stat("a.txt")

    • st_dev = 8:1 ← “在 /dev/sda1 这个文件系统上”
    • st_rdev = 0 ← 不是设备文件

2. 设备文件 /dev/sda1

  • /dev 的 super_block(devtmpfs):

    sb_dev->s_dev = 某个匿名 dev_t(虚拟号,比如 0:18
  • /dev/sda1 的 inode:

    inode->i_sb   = sb_dev
    inode->i_rdev = 8:1   // 指向真实块设备 /dev/sda1
    
  • stat("/dev/sda1")

    • st_dev = sb_dev->s_dev(虚拟 dev_t,表示“它在 devtmpfs 文件系统上”)
    • st_rdev = 8:1(真正的块设备号,驱动用它识别设备)

可以看到:同一个 8:1,同时扮演两种角色:

  • 对驱动 & 块层:这是真实设备号
  • 对 VFS:它也可以是某个文件系统实例的 s_dev(比如挂在这块盘上的 ext4)。

四、虚拟文件系统的 dev_t 会不会和真实的冲突?

问题核心:

tmpfs/procfs 这种虚拟文件系统,用的“匿名 dev_t”会不会和真实块设备号(如 8:1)撞在一起?

正常内核实现里:不会。

原因:

  1. 匿名 dev_t 从专门的号段里分配

    • 内核有专门给“匿名 super_block / 虚拟 FS”保留的 dev_t 号段(特定 major)。
    • 实际块设备驱动通过 register_blkdev() 分配的 major 不会用到这段号
  2. 匿名 dev_t 在所有 super_block 间保证唯一

    • 分配时会检查已用 dev_t,不会给两个 super_block 分同一个 dev_t

因此:

  • tmpfs 的 st_dev 不会等于某个真实块设备文件系统的 st_dev(例如 8:1)。
  • 如果你看到两个不同的文件系统实例 st_dev 都是 8:1,那要么:
    • 它们其实是同一个块设备上的不同挂载点 / 同一实例的多挂载情况;
    • 要么是那个系统的内核被修改过 dev_t 分配逻辑,已经偏离主线实现。

五、最后帮你用两句“记忆版”总结

  1. st_dev / s_dev:标识“这个文件在哪个文件系统实例上”,背后用 dev_t 表示该实例所依托的设备(真实的或匿名的)。
  2. st_rdev / i_rdev:只在设备文件上有意义,用来指向它代表的真实设备的 dev_t,驱动就是靠它识别设备。

再加一句:

  • 虚拟文件系统的 dev_t 来自单独号段,不和真实块设备 dev_t 混用,因此不会出现 tmpfs 的 st_dev = 8:1 这种和真实盘冲突的情况。