在上一篇文章中我们简单讨论了struct super_block
的作用, 我们知道在struct super_block
中可以找到我们文件系统的根目录, 有了这个根目录我们就可以在下面新建文件了, 但是我们没有具体讨论这个根目录是如何生成的, 因为要用到dentry
和inode
的概念, 今天我们将继续这个讨论
目录和文件
我们知道, 在Linux系统中几乎一切都是文件
inode
的作用就是在文件系统上代表一个文件, 不论这个文件有多大, 也不论这个文件是什么类型
但是inode
中并没有存放文件的名字, 实际上文件的名字是存放在一个特殊的文件里的, 这个特殊的文件就是目录
(没错, 目录也是一个文件), 目录
中的每一个条目都指向一个文件, 这个条目我们称之为目录项(dentry)
dentry
和struct dentry
在Linux内核中我们使用struct dentry
来代表一个目录项
struct dentry {
struct inode *d_inode; /* 目录项对应的 inode */
struct dentry *d_parent; /* 父目录的目录项 */
struct qstr d_name; /* 目录项的名称 */
unsigned int d_count; /* 目录项的引用计数 */
// ... 其他成员变量
};
其中d_name
就是inode
的名字(也就是文件名), d_inode
就是该目录项
关联的inode
下图可以大概描述dentry
和inode
的关系
+---------------+ +-----------------+
| | | |
| inode |<--- | dentry |
| | | | |
|---------------| | |-----------------|
| i_mode | | | d_name --------|> inode指向的文件的名字
| i_uid | |--<|-d_inode |
| i_gid | | d_parent -------|> 父目录的Dentry对象
| i_size | | ... |
| ... | | |
+---------------+ +-----------------+
可以简单理解为, inode
指向一个具体的文件, 而dentry
又指向这个inode
, 所以从dentry
出发就可以找到任何一个文件, 实际上内核对dentry
做了一系列优化, 使得查找文件的过程非常快
inode
和struct inode
在Linux内核中我们使用struct inode
来代表文件系统上的inode
struct inode {
umode_t i_mode; /* 文件类型和访问权限 */
uid_t i_uid; /* 用户ID */
gid_t i_gid; /* 组ID */
dev_t i_rdev; /* 设备号 */
loff_t i_size; /* 文件大小 */
struct timespec i_atime; /* 最后访问时间 */
struct timespec i_mtime; /* 最后修改时间 */
struct timespec i_ctime; /* 创建时间 */
unsigned long i_ino; /* 索引节点号 */
unsigned int i_count; /* 索引节点的引用计数 */
// ... 其他成员变量
};
正如上面所说, inode
用来代表一个文件/目录, 每个文件或目录都有一个唯一的inode
,它保存了有关该文件或目录的所有元数据信息,例如创建时间、修改时间、大小、所属用户和组等。此外,inode
还存储有关文件或目录的物理位置、文件类型和权限等信息。当我们读取或写入文件时,内核会使用inode
来定位并操作实际的文件内容。
文件系统根目录的是如何生成的
我们知道在struct super_block
中存放了该文件系统的根目录(s_root
字段), 现在我们可以讨论这个目录是如何生成的了
简单起见我们使用ramfs
为例看看这个根目录是如何生成的
ramfs
也叫内存文件系统, 应该是Linux内核中最简单的文件系统了, 这个文件系统中的所有文件都保存在内存里, 无需写入磁盘, 所以实现非常简单, 用这个文件系统作为例子, 来理解文件系统的基本逻辑非常合适
在ramfs
中, 根目录的生成是在ramfs_fill_super()函数中, 代码如下, 为了方便理解我删除了无关紧要的代码
int ramfs_fill_super(struct super_block *sb, void *data, int silent)
{
// ...其他代码
// 1. 获取根目录的inode
inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
// 2. 生成根目录的dentry
sb->s_root = d_make_root(inode);
// ...其他代码
return 0;
}
下面我们分别看看这2个关键步骤
ramfs_get_inode
ramfs_get_inode
代码如下, 为了方便理解我删除了无关紧要的代码
struct inode *ramfs_get_inode(struct super_block *sb,
const struct inode *dir, umode_t mode, dev_t dev)
{
// new一个inode
struct inode * inode = new_inode(sb);
// ...其他代码
// 填充操作函数
inode->i_op = &ramfs_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
// ...其他代码
return inode;
}
由于ramfs是内存文件系统, 不需要从磁盘读取inode
信息, 这里直接new_inode()
在内存里分配了一个inode
, 然后设置了inode
的操作函数, 就完成了
d_make_root
d_make_root
代码如下, 为了方便理解我删除了无关紧要的代码
struct dentry *d_make_root(struct inode *root_inode)
{
// new一个dentry
struct dentry *res = __d_alloc(root_inode->i_sb, NULL);
// 把root_inode关联到我们new的dentry
d_instantiate(res, root_inode);
return res;
}
这里的逻辑也很简, 先new一个dentry
, 然后把根目录的inode
和这个dentry
关联起来就完成了, 当然具体是如何关联的还有一些细节问题, 但是这里我们不讨论, 有兴趣的读者可以在参考资料中看到这些细节
至此, 根目录就创建完成了, 有了根目录我们就也在这个文件系统上创建文件了, 我们将在后续的文章中讨论创建文件的过程
参考资料
struct dentry 结构体
struct inode 结构体
https://elixir.bootlin.com/linux/v4.8/source/fs/ramfs
https://elixir.bootlin.com/linux/v4.8/source/fs/ext2/super.c
https://blog.51cto.com/liangchaoxi/5422262
https://elixir.bootlin.com/linux/v4.8/source/fs/dcache.c#L1846