第八十三章:古树之躯
斩灵期涉及内核源码:
一
林小源走到一棵古树跟前,将手掌贴在树干上。
树皮温热,像活物的皮肤。他闭上眼睛,神识渗入树干内部——眼前的景象让他倒吸一口凉气。树干内部不是木质纤维,而是一个精密的结构:核心是一个发光的球体,球体表面刻满了数字和标志。
"i_ino = 1048577,"他念出球体上最大的一行字。
"我的 inode 号,"古树开口了,声音低沉而内敛,像一个沉默多年的老者终于等到有人来问,"整个文件系统里,我是第一百零四万八千五百七十七号。"
林小源继续观察。球体表面还有其他铭文:"i_mode = 0100644""i_uid = 1000""i_gid = 1000""i_size = 4096""i_nlink = 1"。
"i_mode 是什么?"
"文件类型和权限,"古树说,"0100 表示普通文件,644 表示所有者可读写,其他人只读。你看到目录的 i_mode 是 040755——开头的 040 就是目录的标志。"
林小源点了点头,又问:"i_nlink 呢?"
古树沉默了一瞬,声音变得有些落寞:"硬链接数。现在是 1——只有一个路标指着我。如果有人给我起第二个名字,这个数字就变成 2。减到 0 的那天,我就该倒了。"
林小源把手从树干上拿开,环顾四周。森林中每棵古树都是一个 inode,每棵树的内部结构大同小异——但铭文各不相同。有的树干上刻着"i_mode = 040755",那是目录;有的刻着"i_mode = 0120777",那是符号链接,树干弯曲,指向另一棵树。
"你的名字呢?"林小源忽然问,"你的 i_ino 我看到了,但你叫什么?"
古树发出一声苦笑:"我没有名字。名字不长在树上。你去路口找路标——dentry 上才有名字。一棵树可以有十个路标指着它,每个路标上的名字都不一样。但树还是那棵树。"
二
林小源在森林中走了很远。
他遇到了两棵紧挨着的古树,树干几乎贴在一起。奇怪的是,两棵树的内部发光球体竟然完全相同——i_ino 都是 2097152。
"你们是同一棵树?"林小源困惑地问。
左边的树开口了:"我们是硬链接。两个不同的路标,指向同一个 inode。"
右边的树接着说:"你删除我,只是减掉一个路标。i_nlink 从 2 变成 1。数据还在——只要还有一个路标指着,inode 就不会释放。"
林小源伸手同时触碰两棵树干。两棵树的内部结构完全一致——权限、大小、数据块的位置,全都相同。唯一的区别是,它们各自有一个 dentry 指向自己。
"这就是硬链接的本质,"他低声说,"不是复制数据,是多一个名字。"
"对,"左边的树说,"你用 ln 命令创建硬链接,不会复制任何数据块。只是在目录里多加一个 dentry,指向同一个 inode。"
林小源蹲下来,用手指在落叶上画了一个示意图:两个方框,箭头都指向同一个圆圈。方框是 dentry,圆圈是 inode。
"那符号链接呢?"他问。
右边的树回答:"符号链接是另一棵树——它有自己的 inode,但树干里存的不是数据块,是路径字符串。你读符号链接,它告诉你'去找那棵树'。硬链接是同一棵树的两个名字,符号链接是一棵指路树。"
三
林小源在森林深处找到了一棵倒下的古树。
树干已经腐朽,内部的发光球体暗淡无光。球体上的铭文模糊不清,但"i_nlink = 0"三个字依然清晰可见。
"它死了,"身后传来一个声音。
林小源回头,看到一棵年轻的古树站在他身后。年轻的树干光滑,铭文鲜亮。
"它怎么死的?"
"所有的路标都拆了,"年轻古树说,"i_nlink 归零,inode 被释放。数据块回到空闲池,inode 号回到 inode bitmap。现在这个号码可以重新分配给别人。"
林小源蹲下来,仔细观察那棵倒下的古树。他注意到,树干内部的结构还没有完全消散——数据块的痕迹还在,只是已经无人认领。
"ls -i 命令可以看到 inode 号,"年轻古树说,"你用它就能确认两个文件名是不是指向同一个 inode。如果 inode 号相同,就是硬链接。"
林小源站起身,拍了拍手上的泥土。他看着年轻古树,忽然问:"你有名字吗?"
年轻古树笑了:"我没有。但我知道有三个路标指着我——所以我的 i_nlink 是 3。只要还有一个路标在,我就不会倒。"
说到底,树的躯干是 inode,名字反而在路标上——这倒是反直觉。
/*
* struct inode 的关键字段:
*
* i_ino — inode 号
* i_mode — 文件类型和权限
* i_uid, i_gid — 所有者
* i_size — 文件大小
* i_atime, i_mtime, i_ctime — 时间戳
* i_blocks — 占用的块数
* i_op — inode 操作函数表
* i_fop — 文件操作函数表
* i_mapping — address_space(页缓存)
*
* inode 操作 (inode_operations):
* create — 创建文件
* lookup — 查找文件
* link — 创建硬链接
* unlink — 删除文件
* mkdir — 创建目录
* rmdir — 删除目录
*
* inode 不包含文件名!
* 文件名在 dentry 中。
*/
struct inode {
unsigned long i_ino;
unsigned short i_mode;
unsigned int i_uid;
unsigned int i_gid;
long long i_size;
unsigned long i_blocks;
int i_nlink; /* 硬链接数 */
};
printf("=== inode — 文件的元数据 ===\n\n");
/* 模拟几个 inode */
struct inode inodes[] = {
{1, 040755, 0, 0, 4096, 8, 2}, /* 目录 */
{2, 0100644, 1000, 1000, 1024, 2, 1}, /* 普通文件 */
{3, 0100755, 0, 0, 8192, 16, 1}, /* 可执行文件 */
{4, 0120777, 0, 0, 11, 1, 1}, /* 符号链接 */
};
const char *names[] = {"/home", "file.txt", "a.out", "link"};
int nr = sizeof(inodes) / sizeof(inodes[0]);
printf("inode 列表:\n");
printf("%-8s %-10s %-8s %-8s %-10s %-6s\n",
"ino", "文件名", "权限", "大小", "块数", "链接");
printf("%-8s %-10s %-8s %-8s %-10s %-6s\n",
"---", "---", "---", "---", "---", "---");
for (int i = 0; i < nr; i++) {
printf("%-8lu %-10s %06o %-8lld %-10lu %-6d\n",
inodes[i].i_ino, names[i],
inodes[i].i_mode, inodes[i].i_size,
inodes[i].i_blocks, inodes[i].i_nlink);
}
printf("\n--- inode 的关键信息 ---\n");
printf("i_ino: inode 号(唯一标识)\n");
printf("i_mode: 文件类型和权限\n");
printf("i_size: 文件大小\n");
printf("i_nlink: 硬链接数\n\n");
printf("--- inode 不包含文件名 ---\n");
printf("文件名在 dentry 中\n");
printf("一个 inode 可以有多个文件名(硬链接)\n");
printf("删除文件 = 减少 i_nlink\n");
printf("当 i_nlink = 0 时,inode 被释放\n\n");
printf("--- inode 操作 ---\n");
printf("create: 创建新文件\n");
printf("lookup: 在目录中查找文件\n");
printf("link: 创建硬链接\n");
printf("unlink: 删除文件\n");
printf("mkdir: 创建目录\n");#include <stdio.h>
/*
* struct inode 的关键字段:
*
* i_ino — inode 号
* i_mode — 文件类型和权限
* i_uid, i_gid — 所有者
* i_size — 文件大小
* i_atime, i_mtime, i_ctime — 时间戳
* i_blocks — 占用的块数
* i_op — inode 操作函数表
* i_fop — 文件操作函数表
* i_mapping — address_space(页缓存)
*
* inode 操作 (inode_operations):
* create — 创建文件
* lookup — 查找文件
* link — 创建硬链接
* unlink — 删除文件
* mkdir — 创建目录
* rmdir — 删除目录
*
* inode 不包含文件名!
* 文件名在 dentry 中。
*/
struct inode {
unsigned long i_ino;
unsigned short i_mode;
unsigned int i_uid;
unsigned int i_gid;
long long i_size;
unsigned long i_blocks;
int i_nlink; /* 硬链接数 */
};
int main() {
printf("=== inode — 文件的元数据 ===\n\n");
/* 模拟几个 inode */
struct inode inodes[] = {
{1, 040755, 0, 0, 4096, 8, 2}, /* 目录 */
{2, 0100644, 1000, 1000, 1024, 2, 1}, /* 普通文件 */
{3, 0100755, 0, 0, 8192, 16, 1}, /* 可执行文件 */
{4, 0120777, 0, 0, 11, 1, 1}, /* 符号链接 */
};
const char *names[] = {"/home", "file.txt", "a.out", "link"};
int nr = sizeof(inodes) / sizeof(inodes[0]);
printf("inode 列表:\n");
printf("%-8s %-10s %-8s %-8s %-10s %-6s\n",
"ino", "文件名", "权限", "大小", "块数", "链接");
printf("%-8s %-10s %-8s %-8s %-10s %-6s\n",
"---", "---", "---", "---", "---", "---");
for (int i = 0; i < nr; i++) {
printf("%-8lu %-10s %06o %-8lld %-10lu %-6d\n",
inodes[i].i_ino, names[i],
inodes[i].i_mode, inodes[i].i_size,
inodes[i].i_blocks, inodes[i].i_nlink);
}
printf("\n--- inode 的关键信息 ---\n");
printf("i_ino: inode 号(唯一标识)\n");
printf("i_mode: 文件类型和权限\n");
printf("i_size: 文件大小\n");
printf("i_nlink: 硬链接数\n\n");
printf("--- inode 不包含文件名 ---\n");
printf("文件名在 dentry 中\n");
printf("一个 inode 可以有多个文件名(硬链接)\n");
printf("删除文件 = 减少 i_nlink\n");
printf("当 i_nlink = 0 时,inode 被释放\n\n");
printf("--- inode 操作 ---\n");
printf("create: 创建新文件\n");
printf("lookup: 在目录中查找文件\n");
printf("link: 创建硬链接\n");
printf("unlink: 删除文件\n");
printf("mkdir: 创建目录\n");
return 0;
}道藏笔记
内核启示
inode 是文件的元数据,但有一个反直觉的地方——它不包含文件名。
inode 身上刻着文件的所有关键信息: 是 inode 号,整个文件系统里的唯一标识; 管文件类型和权限,目录是 040755,普通文件是 0100644; 是大小; 是硬链接数——减到 0 的那天,inode 就该释放了。另外还有一张操作函数表 ,里面有 create、lookup、link、unlink 这些操作。
硬链接的本质是:多个 dentry 指向同一个 inode,不同的名字,同一棵树。删除一个链接只是减少 i_nlink,数据块还在,直到 i_nlink 归零才会真正释放。所以 inode 就是文件的"躯干",名字长在 dentry 上,数据存在磁盘上。
inode 之试
本章讲古树之躯时,保存文件权限、大小、时间和数据块指针的核心对象是什么?