第八十四章:路标
斩灵期涉及内核源码:
一
林小源在森林的岔路口发现了一个路标。
路标是一块半透明的水晶碑,悬浮在半空中,碑面上刻着一行字:"home"。水晶碑的背面伸出一条细长的光线,光线的另一端连接着不远处的一棵古树。
"你就是 dentry?"林小源走近水晶碑,伸手触摸。
碑面微微震颤,一个清脆而活泼的声音从水晶中传出:"我是目录项!你叫我路标也行。我手里举着名字,背后连着 inode——你顺着我的光线走,就能找到那棵古树。"
林小源绕着路标走了一圈。他注意到,路标不是孤立的——它的底部有一条更粗的光线,向上连接着另一个路标。那个路标上刻着"/"。
"你的父目录?"
"对,"路标说,"d_parent——我爹。所有的路标都有爹,除了根路标。根路标的 d_parent 指向它自己——它是整个目录树的起点。"
林小源抬头望去,发现前方的岔路口还有更多路标。每个路标都刻着不同的名字:"user""file.txt""a.out""etc""config"——它们通过光线互相连接,形成一棵倒挂的树。
"这就是目录树,"他喃喃道。
"没错!"路标兴奋地说,"你打开 /home/user/file.txt 的时候,内核就是沿着路标一路找过来的。先找 '/',再找 'home',再找 'user',最后找 'file.txt'。四步,四次 dentry 查找。"
二
林小源在路标群中穿行,忽然注意到一个奇怪的现象。
有些路标的碑面是金色的,有些是银色的,还有些是暗淡的灰色。金色路标的光芒耀眼,银色路标微微发光,灰色路标几乎看不到。
"金色的是什么?"
"热门路标,"旁边一个银色路标回答,声音带着一丝嫉妒,"它们被反复查找,所以被放进了 dcache——路标缓存。下次有人查找同样的路径,直接从缓存里拿,不用再读磁盘。"
林小源伸手触摸一个金色路标。碑面滚烫,像一块烧红的铁。路标上刻着"/usr/lib/libc.so.6"。
"这个文件被访问了几百万次,"金色路标说,声音沉稳而自信,"每次程序启动都要找我。dcache 里有我的位置——内核不需要读磁盘,直接从缓存中返回我的 dentry。"
林小源又触摸了一个灰色路标。碑面冰冷,几乎没有温度。
"我是负缓存,"灰色路标说,声音虚弱,"有人找过 /tmp/不存在的文件——结果当然是找不到。但内核记住了这次失败,把我放进 dcache。下次再找同一个路径,直接返回'不存在',不用再读磁盘。"
"不存在也是信息?"林小源惊讶地问。
"当然,"灰色路标说,"你找一个不存在的文件,如果每次都读磁盘确认,太浪费了。负缓存记住'不存在',省掉了一次磁盘访问。"
三
林小源在路标群的边缘发现了一个奇特的结构。
两个路标并排站立,碑面上刻着不同的名字——"hard_link_a"和"hard_link_b"——但它们背后的光线汇聚到同一棵古树上。
"你们指向同一个 inode?"
"对,"左边的路标说,"我们是硬链接的两个名字。不同的名字,同一个 inode。"
"那你们和符号链接有什么区别?"
右边的路标抢着回答:"符号链接是一个独立的 inode,里面存着路径字符串。你读符号链接,它告诉你'去找那个路径'。我们不一样——我们直接指向同一个 inode,没有中间人。"
林小源点了点头。他蹲下来,在地上画了一张图:两个路标指向同一棵树,旁边一棵弯曲的指路树指向那两个路标中的一个。
"路径查找的时候,"他低声说,"内核从根路标开始,一级一级往下找。每一级都是一个 dentry,每个 dentry 连接一个名字和一个 inode。dcache 缓存了最近查找过的路标——热门路径秒级返回。"
他站起身,环顾四周。无数路标组成的网络在森林中延展开来,金色、银色、灰色交织在一起,像一张巨大的蛛网。每一条光线都连接着一个名字和一棵古树,每一条路径都是一串 dentry 的序列。
"路标也有记忆,"他感叹道。金色的、银色的、灰色的——每一种颜色都是一次查找的痕迹,叠在一起,就成了 dcache。
/*
* struct dentry 的关键字段:
*
* d_name — 文件名
* d_inode — 指向的 inode
* d_parent — 父目录的 dentry
* d_children — 子 dentry 链表
* d_sb — 所属的 superblock
*
* dentry 缓存 (dcache):
* 最近使用的 dentry 缓存在内存中
* 加速路径查找:/home/user/file.txt
* 避免每次都读磁盘
*
* 路径查找过程:
* /home/user/file.txt
* → 查找 "/" 的 dentry(根目录)
* → 查找 "home" 的 dentry
* → 查找 "user" 的 dentry
* → 查找 "file.txt" 的 dentry
* → 返回对应的 inode
*/
struct dentry {
char d_name[32];
int inode_no;
struct dentry *parent;
int nr_children;
struct dentry *children[8];
};
void print_tree(struct dentry *d, int depth) {
for (int i = 0; i < depth; i++) printf(" ");
printf("%s (inode %d)\n", d->d_name, d->inode_no);
for (int i = 0; i < d->nr_children; i++)
print_tree(d->children[i], depth + 1);
}
printf("=== dentry — 目录项与缓存 ===\n\n");
/* 构建目录树 */
struct dentry root = {"/", 1, NULL, 0};
struct dentry home = {"home", 2, &root, 0};
struct dentry user = {"user", 3, &home, 0};
struct dentry file1 = {"file.txt", 4, &user, 0};
struct dentry file2 = {"a.out", 5, &user, 0};
struct dentry etc = {"etc", 6, &root, 0};
struct dentry conf = {"config", 7, &etc, 0};
root.children[0] = &home;
root.children[1] = &etc;
root.nr_children = 2;
home.children[0] = &user;
home.nr_children = 1;
user.children[0] = &file1;
user.children[1] = &file2;
user.nr_children = 2;
etc.children[0] = &conf;
etc.nr_children = 1;
printf("目录树:\n");
print_tree(&root, 0);
printf("\n--- dentry 缓存 (dcache) ---\n");
printf("路径查找: /home/user/file.txt\n");
printf(" 1. 查找 \"/\" → inode 1 (缓存)\n");
printf(" 2. 查找 \"home\" → inode 2 (缓存)\n");
printf(" 3. 查找 \"user\" → inode 3 (缓存)\n");
printf(" 4. 查找 \"file.txt\" → inode 4 (缓存)\n\n");
printf("--- dcache 的好处 ---\n");
printf("1. 加速路径查找\n");
printf(" 避免每次都读磁盘\n\n");
printf("2. 缓存热门路径\n");
printf(" /usr/lib/libc.so 可能被频繁访问\n\n");
printf("3. 负缓存\n");
printf(" 记录不存在的文件\n");
printf(" 避免重复查找\n\n");
printf("--- dentry vs inode ---\n");
printf("dentry:\n");
printf(" 包含文件名\n");
printf(" 连接文件名和 inode\n");
printf(" 有缓存(dcache)\n\n");
printf("inode:\n");
printf(" 包含文件元数据\n");
printf(" 不包含文件名\n");
printf(" 一个 inode 可以有多个 dentry\n");#include <stdio.h>
#include <string.h>
/*
* struct dentry 的关键字段:
*
* d_name — 文件名
* d_inode — 指向的 inode
* d_parent — 父目录的 dentry
* d_children — 子 dentry 链表
* d_sb — 所属的 superblock
*
* dentry 缓存 (dcache):
* 最近使用的 dentry 缓存在内存中
* 加速路径查找:/home/user/file.txt
* 避免每次都读磁盘
*
* 路径查找过程:
* /home/user/file.txt
* → 查找 "/" 的 dentry(根目录)
* → 查找 "home" 的 dentry
* → 查找 "user" 的 dentry
* → 查找 "file.txt" 的 dentry
* → 返回对应的 inode
*/
struct dentry {
char d_name[32];
int inode_no;
struct dentry *parent;
int nr_children;
struct dentry *children[8];
};
void print_tree(struct dentry *d, int depth) {
for (int i = 0; i < depth; i++) printf(" ");
printf("%s (inode %d)\n", d->d_name, d->inode_no);
for (int i = 0; i < d->nr_children; i++)
print_tree(d->children[i], depth + 1);
}
int main() {
printf("=== dentry — 目录项与缓存 ===\n\n");
/* 构建目录树 */
struct dentry root = {"/", 1, NULL, 0};
struct dentry home = {"home", 2, &root, 0};
struct dentry user = {"user", 3, &home, 0};
struct dentry file1 = {"file.txt", 4, &user, 0};
struct dentry file2 = {"a.out", 5, &user, 0};
struct dentry etc = {"etc", 6, &root, 0};
struct dentry conf = {"config", 7, &etc, 0};
root.children[0] = &home;
root.children[1] = &etc;
root.nr_children = 2;
home.children[0] = &user;
home.nr_children = 1;
user.children[0] = &file1;
user.children[1] = &file2;
user.nr_children = 2;
etc.children[0] = &conf;
etc.nr_children = 1;
printf("目录树:\n");
print_tree(&root, 0);
printf("\n--- dentry 缓存 (dcache) ---\n");
printf("路径查找: /home/user/file.txt\n");
printf(" 1. 查找 \"/\" → inode 1 (缓存)\n");
printf(" 2. 查找 \"home\" → inode 2 (缓存)\n");
printf(" 3. 查找 \"user\" → inode 3 (缓存)\n");
printf(" 4. 查找 \"file.txt\" → inode 4 (缓存)\n\n");
printf("--- dcache 的好处 ---\n");
printf("1. 加速路径查找\n");
printf(" 避免每次都读磁盘\n\n");
printf("2. 缓存热门路径\n");
printf(" /usr/lib/libc.so 可能被频繁访问\n\n");
printf("3. 负缓存\n");
printf(" 记录不存在的文件\n");
printf(" 避免重复查找\n\n");
printf("--- dentry vs inode ---\n");
printf("dentry:\n");
printf(" 包含文件名\n");
printf(" 连接文件名和 inode\n");
printf(" 有缓存(dcache)\n\n");
printf("inode:\n");
printf(" 包含文件元数据\n");
printf(" 不包含文件名\n");
printf(" 一个 inode 可以有多个 dentry\n");
return 0;
}道藏笔记
内核启示
dentry 的活儿就是把文件名和 inode 连起来。它身上有 存文件名, 指向对应的 inode, 指向父目录, 串起子目录——这些一起构成了目录树。
但 dentry 最厉害的地方是 dcache。内核会把最近查过的 dentry 缓存在内存里,下次查同一个路径直接命中,不用读磁盘。更妙的是它还支持"负缓存"——查过一个不存在的文件,内核也记住了,下次再查直接返回"不存在",省掉一次磁盘访问。
路径查找的过程就是从根目录开始,沿着 dentry 一级一级往下找,每一级都先查 dcache。热门路径比如 /usr/lib/libc.so.6 可能被查了几百万次,dcache 里早就有了,秒级返回。
dentry 之试
dentry 只是路径路标,真正承载文件元数据并被它指向的对象是什么?