从零写OS(二十七):字符设备 —— /dev/null 和 /dev/zero

Linux 里有个特殊目录 /dev/,里面住着各种设备文件: /dev/null ← 写什么扔什么,读永远 EOF /dev/zero ← 读出来全是 \0 /dev/random ← 读出来是随机字节 /dev/tty ← 当前终端 对用户程序来说,它们和普通文件没区别:open、read、write、close,完全一样的接口。 这一章实现字符设备(char device)框架,让 VFS 能路由 /dev/ 路径,并内置 null 和 zero 两个最基础的设备。 什么是字符设备 字符设备(character device)的特点: 按字节读写,没有块/扇区的概念 没有随机寻址(不像磁盘文件可以 seek) 读写是即时的:写进去就消失(null)或立刻可读(zero/tty) 与之对应的是块设备(block device),如硬盘,以固定大小的块为单位操作。 设备注册表 核心数据结构 cdev_t: typedef struct { char name[16]; // 设备名,如 "null"、"zero" int (*open) (void); int (*read) (void *buf, uint32_t len); int (*write)(const void *buf, uint32_t len); void (*close)(int fd); int used; } cdev_t; static cdev_t devs[CDEV_MAX]; // 全局设备表 通过函数指针实现多态——不同设备注册不同的 read/write 实现。 chardev_register 向 devs[] 添加一个设备,chardev_open 按名字查找并调用 open()。 ...

May 14, 2026 · 2 min · 大飞

从零写OS(二十三):procfs 进程文件系统

上一章有了 pipe 和 fd,现在进程可以互相传数据了。但还有一个问题:用户程序没有办法查询"系统里现在有哪些进程"——这类信息只存在内核里,外面拿不到。 加专用 syscall 当然可以,但 Linux 选择了一个更优雅的方案:procfs。 一切皆文件 procfs 的思路是:把内核状态伪装成文件,挂在 /proc/ 目录下。 /proc/0/status → 进程 0 的信息 /proc/1/status → 进程 1 的信息 用户程序用完全一样的 open/read/close 就能读到,不需要学新 API: int fd = open("/proc/1/status"); read(fd, buf, 128); // buf 里是 "pid: 1\nstate: running\nparent: 0\n" 这就是 Linux “一切皆文件” 哲学的体现——统一接口,背后实现可以完全不同。 虚拟文件和真实文件的区别 普通文件的 read:从磁盘读数据。 procfs 文件的 read:内核现场生成内容,没有磁盘,没有 inode,数据就是 procs[] 数组里的字段格式化出来的字符串。 open("/proc/1/status") → vfs_open 识别 "/proc/" 前缀 → procfs_open 解析 pid,格式化状态字符串存入内核 buf → 返回 VFile(type=VFILE_PROC) read(fd, buf, len) → vfs_read 识别 VFILE_PROC → 从内核 buf 按 offset 拷贝给用户 实现 procfs 内核结构 typedef struct { int used; uint32_t pid; char buf[256]; // open 时格式化好的字符串 uint32_t buf_len; uint32_t offset; // 支持分段 read } proc_fd_t; open 时一次性生成完整字符串,后续 read 只是按 offset 切片拷贝——和普通文件行为完全一致,可以多次 read。 ...

May 14, 2026 · 2 min · 大飞

从零写OS(二十一):文件描述符 fd

前几章的用户程序如果想读文件,得直接传 inode 号给内核——read(ino, offset, buf, len)。这意味着用户程序要自己记当前读到哪了,而且根本不知道文件名,只有一个数字。 这一章引入 文件描述符(fd),让用户程序用熟悉的方式操作文件: int fd = open("hello.txt"); int n = read(fd, buf, 64); close(fd); fd 是什么 fd 是一个进程内的整数,通常从 0 开始分配(0=stdin,1=stdout,2=stderr,之后是普通文件)。 它背后有三层: 进程 fd_table[] 内核 open_file 磁盘 fd=3 ──────────→ { ino=42, offset=0 } ──→ hello.txt fd=4 ──────────→ { ino=43, offset=0 } ──→ readme.txt fd 本身只是下标,真正存 offset 的是内核里的"打开文件"结构。这样的好处: offset 由内核自动推进,用户不用管 fork 后父子可以共享同一个打开文件(共享 offset) 文件、管道、设备用同一套接口,背后实现不同 实现:每个进程一张 fd 表 在 process_t 里加一个数组: typedef struct { // ... int32_t fd_table[PROC_MAX_FD]; // fd → vfs_fd,-1 表示空槽 } process_t; fd_table[fd] 存的是 VFS 层的内部 fd(VFile 数组下标)。 ...

May 14, 2026 · 2 min · 大飞

从零写OS(十五):挂载 ext2,读真实文件系统

前几章的文件系统是存在内存里的——重启数据就没了,文件名也是硬编码的。这一章做真实的:挂载一个 ext2 磁盘镜像,让 Shell 能读取里面的文件。 上一章已经有了 ATA 驱动,能按扇区号读磁盘。现在的问题是:磁盘上的数据是怎么组织的? ext2 的结构 ext2 是 Linux 最经典的文件系统,ext3/ext4 都是在它基础上演化来的。理解 ext2,基本上就理解了现代文件系统的核心思路。 磁盘从偏移 1024 字节开始是 Superblock,存整个文件系统的基本参数:block 大小是多少、有多少 inode、magic number 是 0xEF53(用来确认这确实是 ext2)。 接下来是 Group Descriptor,告诉你 inode table 在哪个 block。 然后才是真正的数据区:inode table 和数据块。 Inode 是文件的"身份证"。每个文件有一个唯一的 inode 号,inode 里存着文件大小、权限,以及最重要的——i_block[0..11],12 个直接指向数据块的指针。想读文件内容,就顺着这些指针去读对应的数据块。 目录也是文件,它的数据块里存的是一条条 ext2_dir_entry:每条记录包含 inode 号、文件名长度、文件名。ls 就是读根目录的数据块,遍历这些记录。 读文件的完整路径 Superblock → 拿到 block_size、inodes_per_group GroupDesc → 拿到 inode table 的起始 block 号 Inode[ino] → 拿到文件大小和 i_block[] DataBlock → 真正的文件内容 读目录(ls)就在最后一步多做一件事:把数据块里的 ext2_dir_entry 链遍历一遍。 每一步都需要从磁盘读若干个扇区——这正是上一章 ATA 驱动的用武之地。 ...

May 6, 2026 · 1 min · 大飞

从零写OS(十二):VFS,让系统调用不认识具体文件系统

上一章我们写了一个 SimpleFS,可以创建文件、读写内容。但现在有个问题:open() 系统调用直接调的是 fs_create、fs_read 这些 SimpleFS 专属函数。 如果哪天要支持 FAT32,就得去改系统调用的代码。这显然不对。 这一章加一层 VFS(Virtual File System,虚拟文件系统),把系统调用和具体文件系统隔开。 先把概念搞清楚 间接层解耦 软件里有句老话:任何问题都可以通过加一层间接层解决。VFS 就是这层间接层。 用户进程 ↓ open / read / write VFS(统一接口) ↓ ↓ SimpleFS FAT32 系统调用只跟 VFS 说话,VFS 再转发给具体 FS。新增一种文件系统,只需要实现 VFS 要求的那几个函数,不动系统调用层。 file_operations:C 语言的多态 VFS 要求每种文件系统提供一张函数指针表: typedef struct { int (*read) (...); int (*write)(...); uint32_t (*lookup)(...); uint32_t (*create)(...); } FileOps; VFS 调 fops->read(...) 时,实际执行的是哪个函数,取决于挂载时注册的是哪张表。这是 C 语言实现"多态"的标准手法,Linux 内核里到处都是这个模式。 vnode:VFS 的通用 inode 具体 FS 有自己的 inode,VFS 层包一层 vnode,里面放着具体 FS 的 inode 号和对应的函数指针表。对上层完全屏蔽了底层差异。 ...

May 6, 2026 · 2 min · 大飞
京ICP备14031575号-3