目录即文件:Ext 文件系统的最后一块拼图(附 cd 命令的实现原理)
我的github:(https://github.com/xcx55/ubuntu-linux-project)
感谢各位大佬参观我的github!!
这是 Ext 系列笔记的收尾篇。前面几篇把磁盘硬件、块、分区、inode、位图、路径缓存都过了一遍,
剩下的最后一个疙瘩是:目录,到底是不是文件? 以及我们天天敲的
cd ..,内核到底做了什么?
一、目录就是文件,只是「解读方式」不同
先给出结论,然后逐条验证:
文件和目录,在磁盘和文件系统的视角下完全平等。它们都是「文件项」,都由 inode(属性)+ data blocks(数据)构成。
数据块里存什么,才是二者唯一的分野:
| 普通文件 | 目录 | |
|---|---|---|
| inode | 属性(权限、大小、时间...) | 属性(多一个「我是目录」的标志位) |
| data blocks 内容 | 文件正文 | 「文件名 → inode number」映射表 |
| 谁负责解读 | 文件系统 + 应用 | 内核(系统调用层用 if/else 分流) |
那我们在 ls 时看到的「目录带颜色、带 / 尾缀」是怎么回事?
- 颜色和
/是 shell 为了人眼友好故意打印的,不是磁盘上的事实; - 内核判断「这是目录」靠的是 inode 里的标志位,而不是内容长相。
换句话说:「是不是目录」这个身份,是从 inode 读出来的;「目录里有什么」,是从数据块里读出来的。 前者是属性,后者是映射关系。
二、路径背后站着的,是那棵 dentry 树
前面我们讲过:路径解析的产物是 struct dentry 组成的全局多叉树(外加 hash 加速)。
所以「路径」这个东西,本质上只是给用户看的一串字符串,它背后真正的对应物是:
"/home/user/a.txt" ──解析──▶ dentry 树上的一个节点 ──▶ struct inode(+inode number)
而 PCB 里的 pwd 指针,就是这棵树上的一个「当前位置坐标」 (注意区分:shell 里的 PWD 只是个字符串环境变量,内核的 pwd 是指针)。
三、cd .. 的一瞬间,内核做了哪些事
把一个命令拆开看,逻辑非常清爽:
- shell 解析命令行参数 ,识别出
cd和参数..; - 因为
..是父目录这一硬链接的映射 ,内核到当前目录 inode 的数据块里,查「..对应的 inode number」; - 顺着该 inode number 拿到父目录 inode(可能触发一次磁盘 IO,构建/命中 dentry 缓存);
- 在 dentry 多叉树中移动位置,修改 PCB 的
pwd指针,指向父目录的 dentry; - shell 把新目录的字符串结果写进 1 号文件(stdout),于是你在屏幕上看到提示符变了。
cd .、cd -、cd /abs/path 同理,只是走的树路径不同。所谓「切换目录」,就是换个指针指向,加一次 stdout 写入------没有复制、没有搬移数据。
四、顺带说清:指令从哪来,参数又从哪来
这一条经常把人绕晕,其实是两条独立的路:
- 命令本身 :shell 拿着你敲的命令名,去
PATH环境变量列出的目录里逐个查找可执行文件; - 命令要用到的文件和参数 :它不走 PATH ,直接用自己进程 PCB 里继承来的
pwd指针(以及相对路径解析)就能定位。
这也是为什么 ./a.out 与 a.out 行为不同:
a.out→ 去PATH里的目录找(当前目录通常不在 PATH 里,所以常报 command not found);./a.out→ 明确以「当前工作目录」为起点解析,也就是显式使用那个pwd指针。
这就是
.存在的意义:把「当前目录」和「PATH 搜索」彻底分开。
五、Ext 系列小结
| 层次 | 核心结论 |
|---|---|
| 硬件 | 磁盘是黑盒,只认 LBA;机械访问慢,要少碰 |
| 抽象 | 扇区 → 块(4KB)→ 块号即「文件系统的指针」 |
| 组织 | 分区 → 块组 → Super Block / GDT / 双位图 / Inode Table / Data Blocks |
| 索引 | 文件 = inode(属性)+ Data Blocks(内容),靠 i_block[] 关联,大文件用多级间接 |
| 命名 | 目录 = 「名字 → inode number」映射表;硬链接复用 inode,软链接存路径 |
| 加速 | dentry 多叉树 + hash 路径缓存,一切性能优化都是空间换时间 |
| 统一 | 目录与文件同构,路径机制先于分区机制,挂载把文件系统接进目录树 |
到这里,从磁头下的一块扇区 到你敲下 cd .. 时指针的一次移动,整条链路就闭合了。
本文基于 2026-07-18 的学习笔记整理。下一篇进入「库文件」系列:从 .o 到静态库、动态库,再到 ELF 与进程地址空间。