从零写 OS 内核-第二十二篇:设备模型与 devfs —— 统一管理所有硬件设备
"你的内核有 UART、VGA、IDE、TTY,但它们各自为政,无法统一管理。
今天,我们构建设备模型,实现 devfs,让所有设备像文件一样被发现和操作!"
在前面的篇章中,我们实现了多个硬件驱动:
- UART(串口)
- VGA(显卡)
- IDE(硬盘)
- TTY(终端)
- PIT(时钟)
但这些驱动都是孤立的:
- 没有统一的设备注册机制
- 用户无法通过
/dev查看可用设备 - 新增驱动需硬编码路径(如
open("/dev/tty0"))
真正的操作系统,必须提供统一的设备抽象,并支持动态设备发现。
今天,我们就来:
✅ 设计设备模型(Device Model)
✅ 实现 devfs(设备文件系统)
✅ 自动挂载 /dev 并列出所有设备
让你的 OS 拥有现代设备管理能力!
🧩 一、为什么需要设备模型?
当前驱动的问题:
| 问题 | 后果 | |——|——| | 无统一接口 | 每个驱动实现自己的 open/read | | 设备路径硬编码 | 用户需记住 /dev/tty0、/dev/hda | | 无法动态发现 | 新增设备需修改内核代码 |
设备模型的核心思想:
"一切设备皆对象,一切操作皆文件"
- 设备注册:驱动启动时向内核注册设备
- 设备类(Class):按类型分组(如 tty、block、input)
- 设备文件系统(devfs):自动在
/dev创建设备节点
💡 Linux 的 sysfs + devtmpfs 正是基于此思想!
🏗️ 二、设备模型核心数据结构
1. 设备结构体(device)
#define DEVICE_NAME_LEN 32
struct device {
char name[DEVICE_NAME_LEN]; // 设备名(如 "tty0")
dev_t devt; // 设备号(主设备 << 8 | 次设备)
struct device_driver *driver; // 所属驱动
void *driver_data; // 驱动私有数据
struct device *parent; // 父设备(如 IDE 控制器)
struct list_head children; // 子设备列表
struct list_head sibling; // 兄弟设备链表
struct list_head global_link; // 全局设备链表
};
2. 设备驱动(device_driver)
struct device_driver {
char name[32];
int (*probe)(struct device *dev); // 探测设备
int (*remove)(struct device *dev); // 移除设备
struct file_operations *fops; // 文件操作
};
3. 设备号管理
// 主设备号分配
#define MAJOR_TTY 4
#define MAJOR_BLOCK 3
#define MAJOR_IDE 3 // 与 block 共享,次设备区分
dev_t mkdev(unsigned int major, unsigned int minor) {
return (major << 8) | minor;
}
unsigned int major(dev_t devt) { return devt >> 8; }
unsigned int minor(dev_t devt) { return devt & 0xFF; }
🔌 三、设备注册与驱动匹配
1. 注册设备
// 驱动初始化时调用
void device_register(struct device *dev) {
// 1. 加入全局设备链表
list_add_tail(&dev->global_link, &global_device_list);
// 2. 通知 devfs 创建设备节点
devfs_add_device(dev);
// 3. 触发驱动 probe(如果驱动已注册)
if (dev->driver && dev->driver->probe) {
dev->driver->probe(dev);
}
}
2. TTY 设备注册示例
// drivers/tty.c
static struct device_driver tty_driver = {
.name = "tty",
.fops = &tty_fops,
.probe = tty_probe,
};
void tty_init() {
// 注册驱动
driver_register(&tty_driver);
// 创建设备
struct device *tty0 = kmalloc(sizeof(struct device));
strcpy(tty0->name, "tty0");
tty0->devt = mkdev(MAJOR_TTY, 0);
tty0->driver = &tty_driver;
tty0->driver_data = &tty_instances[0];
device_register(tty0);
}
3. 块设备注册示例(IDE)
// drivers/ide.c
void ide_init() {
struct device *hda = kmalloc(sizeof(struct device));
strcpy(hda->name, "hda");
hda->devt = mkdev(MAJOR_BLOCK, 0); // 次设备 0 = hda
hda->driver = &block_driver;
hda->driver_data = &ide_disks[0];
device_register(hda);
}
✅ 所有设备通过统一接口注册!
📁 四、实现 devfs:设备文件系统
devfs 是一个内存中的虚拟文件系统,自动在 /dev 创建设备节点。
1. devfs 超级块与 inode
struct devfs_inode_info {
struct device *dev; // 指向注册的设备
};
struct inode *devfs_get_inode(struct super_block *sb, struct device *dev) {
struct inode *inode = alloc_inode();
inode->i_mode = S_IFCHR | 0666; // 字符设备
inode->i_rdev = dev->devt;
struct devfs_inode_info *info = kmalloc(sizeof(*info));
info->dev = dev;
inode->i_private = info;
return inode;
}
2. 自动创建设备节点
void devfs_add_device(struct device *dev) {
// 1. 获取 devfs 超级块
struct super_block *sb = devfs_get_sb();
// 2. 在根目录创建 dentry
struct dentry *dentry = d_alloc_name(sb->s_root, dev->name);
// 3. 创建 inode
struct inode *inode = devfs_get_inode(sb, dev);
dentry->d_inode = inode;
// 4. 加入目录项
d_add(sb->s_root, dentry);
}
3. 挂载 devfs
void devfs_init() {
// 创建 /dev 目录
struct inode *dev_dir = vfs_create_dir(vfs_root, "dev");
// 挂载 devfs 到 /dev
struct super_block *sb = devfs_mount(NULL, NULL);
sb->s_root = dev_dir;
}
🧪 五、用户空间:通过 /dev 访问设备
1. 列出所有设备
myos$ ls /dev
tty0
hda
console
null
2. 操作设备(与之前相同)
// 用户程序
int fd = open("/dev/tty0", O_RDWR); // 自动匹配 TTY 设备
write(fd, "Hello", 5);
int disk = open("/dev/hda", O_RDONLY); // 自动匹配 IDE 设备
read(disk, buffer, 512);
3. VFS 如何找到设备?
open("/dev/tty0")→ VFS 查找 dentry- dentry 的 inode 包含
i_rdev = mkdev(4, 0) - VFS 调用
chrdev_open,根据主设备号 4 找到 TTY 驱动 - 驱动的
fops被用于后续read/write
🔑 设备号是 VFS 与驱动之间的桥梁!
🧱 六、字符设备与块设备框架
1. 字符设备注册表
#define CHRDEV_MAX 256
static struct file_operations *chrdev_fops[CHRDEV_MAX];
int register_chrdev(unsigned int major, const char *name,
struct file_operations *fops) {
if (major == 0) {
// 动态分配主设备号
for (major = 1; major < CHRDEV_MAX; major++) {
if (!chrdev_fops[major]) break;
}
}
chrdev_fops[major] = fops;
return major;
}
2. 块设备注册表
#define BLKDEV_MAX 256
static struct block_device_operations *blkdev_ops[BLKDEV_MAX];
int register_blkdev(unsigned int major, const char *name,
struct block_device_operations *ops) {
blkdev_ops[major] = ops;
return 0;
}
3. 驱动注册
// TTY 驱动注册
static int __init tty_init(void) {
register_chrdev(MAJOR_TTY, "tty", &tty_fops);
// ... 创建设备
}
// IDE 驱动注册
static int __init ide_init(void) {
register_blkdev(MAJOR_BLOCK, "block", &ide_bdev_ops);
// ... 创建设备
}
🧪 七、测试:动态设备发现
内核启动日志:
[INIT] Registering TTY driver (major 4)
[INIT] Registering IDE driver (major 3)
[DEVFS] Created /dev/tty0
[DEVFS] Created /dev/hda
[DEVFS] Created /dev/console
[DEVFS] Created /dev/null
用户空间验证:
myos$ ls /dev
console
hda
null
tty0
myos$ echo "test" > /dev/tty0
test
✅ 所有设备自动出现在 /dev,无需硬编码路径!
⚠️ 八、高级话题:设备树与热插拔
- 设备树(Device Tree)
- 用于描述硬件拓扑(ARM 常用)
- 内核解析设备树,自动创建设备
- 热插拔(Hotplug)
- USB 设备插入时,动态创建
/dev/sda - 需要 netlink 事件通知用户空间
- USB 设备插入时,动态创建
- udev 替代 devfs
- Linux 后期用 udev(用户空间)替代 devfs
- 支持更灵活的命名规则(如
/dev/disk/by-id/...)
💡 我们的 devfs 是简化版,但核心思想一致!
💬 写在最后
设备模型是操作系统硬件抽象的巅峰。
它让驱动开发者专注硬件细节,
让用户和应用程序以统一方式访问设备。
今天你创建的 /dev/tty0,
正是 Linux 中 /dev 目录的简化起源。
🌟 统一的抽象,是复杂系统的优雅解药。
📬 动手挑战:
为你的 VGA 驱动添加设备注册,并在 /dev 中创建 vga 节点。
欢迎在评论区分享你的设备模型扩展!
👇 下一篇你想看:udev 用户空间设备管理,还是 USB 驱动框架?
#操作系统 #内核开发 #设备模型 #devfs #驱动架构 #硬件抽象 #从零开始
📢 彩蛋:关注后回复关键词 "devfs",获取:
- 完整设备模型代码(device/device_driver)
- devfs 实现(含自动节点创建)
- 字符/块设备注册框架模板