Linux内核之文件系统

如何描述一个文件

文件在内存和磁盘上是如何描述的:每个文件至少要有一个数据结构存放该文件的信息,包括uid、gid、flag、文件长度、文件内容存放位置的数据结构等。在Linux中这个数据结构被称为inode,本来inode中也应该包括文件名称等信息,但是由于符号链接的存在,导致一个文件可能存在多个文件名称,因此把和文件名称相关的信息从inode中提出,专门放到dentry 结构中。dentry通过其成员变量d_inode 指向对应的inode数据结构。如下图所示:



另外,inode结构中还包括了成员i_fop,其类型是struct file_operations,其中包括的针对该文件的一些操作接口。

根据路径名寻找目标文件

在Linux中目录也被作为文件看待,只是目录是一种比较特殊的文件。其 特殊之处在于文件的内容是该目录中文件和子目录的dentry的描述符,通过这些dentry的描述符可以找到文件或子目录的dentry,进而找到相应的inode。
下面我们看看如果根据绝对路径寻找一个文件/tmp/temp/abc的。

  1. 首先找到根文件系统的根目录文件的 dentry 和 inode
  2. 由这个 inode 提供的操作接口 i_op->lookup(),找到下一层节点 ‘tmp’ 的 dentry 和 inode
  3. 由 ‘tmp’ 的 inode 找到 ‘temp’ 的 dentry 和 inode
  4. 最后由 ‘temp’ 的 inode 找到 ‘abc’ 的 dentry 和 inode

我们再看看如何通过相对路径寻找文件/tmp/temp/abc,假如我们目前的工作目录为/tmp/temp/dir_a 中,比如我们通过拷贝命令拷贝该文件:cp ../abc ./
如何通过相对路径寻找文件呢?我们来看看dentry这个数据结构的成员,其中有一个是d_parent,数据结构定义如下

struct dentry { 删除了无关的成员
       struct dentry *d_parent; /* parent directory */
       struct inode *d_inode;           /* Where the name belongs to - NULL is      * negative */
       unsigned char d_iname[DNAME_INLINE_LEN];       /* small names */
}

d_parent指向了本目录的父目录的dentry,这样就在通过“..”时就是通过该指针找到的父目录dentry,找到父母inode,进而找到父目录下的所有文件的信息。

进程中打开的文件

一个文件可以被多次打开,并且多个进程对一个文件的访问权限可能不同,因此打开方式就会不同(只读、读写、可执行)。而dentry 和 inode 只能描述一个物理的文件,无法描述“打开”这个概念。因此有必要引入 file 结构,来描述一个“被打开的文件”。每打开一个文件,就创建一个 file 结构。
file 结构中包含以下信息:

  • 打开这个文件的uid,pid
  • 打开文件的方式
  • 读写的方式
  • 当前在文件中的位置

实际上,打开文件的过程正是建立file,dentry,inode之间的关联的过程。如下图:



在进程中如何和打开的文件相关联呢,下面来看一下进程的数据结构

struct task_struct { 只保留了相关信息
       struct files_struct *files; /* open file information */
}

每一个进程的结构体中包含"files"成员,其类型为files_struct。如下图:



进程中所有打开的文件的指针都存在了fd_array[]数组当中。

虚拟文件系统

Linux 通过虚拟文件系统 (VFS) 来支持不同的具体的文件系统,从程序员的角度看, VFS 就是一套代码框架(framework),它将用户与具体的文件系统隔离开来。每个要通过mount 命令挂接到Linux系统的存储设备,如磁盘、光盘等(它们各自对应具体的文件系统),每个设备对应的文件系统都要按照VFS的要求提供一套统一的接口。这样,用户就可以使用这些统一的接口在不同的文件系统中拷贝数据了。参考下图:



安装一个文件系统,除了需要“被安装设备”外,还要指定一个“安装点”。“安装点”是已经存在的一个目录节点。例如把 /dev/sda1 安装到 /mnt/win 下,那么 /mnt/win 就是“安装点”。
可是文件系统要先安装后使用。因此,要使用 /mnt/win 这个“安装点”,必然要求它所在文件系统已也经被安装。
也就是说,安装一个文件系统,需要另外一个文件系统已经被安装。
这是一个鸡生蛋,蛋生鸡的问题:最顶层的文件系统是如何被安装的?
答案是,最顶层文件系统的时候是被安装在“根安装点”上的,而根安装点不属于任何文件系统,它对应的 dentry 、inode 是由内核在初始化阶段凭空构造出来的。
最顶层的文件系统叫做“根文件系统”。Linux 在启动的时候,要求用户必须指定一个“根设备”,内核在初始化阶段,将“根设备”安装到“根安装点”上,从而有了根文件系统。这样,文件系统才算准备就绪。此后,用户就可以通过 mount 命令来安装新的设备。

mount设备(文件系统)

我们通过mount命令向Linux系统mount了一个设备。其实该命令触发了两个过程,一个是文件系统注册过程(当然,如果文件系统已注册过的话,就不需要该步骤了),另一个才是真正意义上的mount设备的过程。

文件系统注册过程

Linux内核是可加载的,许多模块是可选的,只有真正需要使用时才加载他们。文件系统注册过程就是把对应某类型文件系统相关的模块加载到内核,并创建相关的数据结构。每个文件系统模块都有一个初始化例程,它的作用就是在VFS中进行注册,即填写一个叫做file_system_type的数据结构。所有已注册的文件系统的file_system_type结构形成一个链表,我们把这个链表称为注册链表。



每个设备在mount时都要搜索该注册链表,选择适合自己设备文件系统的一项,并从中取出read_super()函数获取设备的超级块(存储在具体设备上,记录存储设备各种信息的一个存储块),并解析其内容。因为每种类型文件系统的超级块的格式不同,并且各自有特定的信息,每种文件系统必须使用对应的解析函数,否则内核就因为不认识该文件系统而无法完成安装。这就是注册文件系统的意义所在。
设备的真正的mount过程,总体的数据结构,参考下图:


  1. 创建一个设备的 vfsmount
  2. 为“被安装设备”创建一个 super_block,并由具体的文件系统来设置这个super_block。在super_block中包含了该类型设备操作的各种接口的结构成员s_op,类型为super_operations。
  3. 为被安装设备的根目录节点创建 dentry
  4. 为被安装设备的根目录节点创建 inode, 并由 super_block->s_op->read_inode() 来设置此 inode
  5. 将 super_block 与“被安装设备“根目录节点 dentry 关联起来
  6. 将 super_block中的s_root与“被安装设备”的根目录节点 dentry 关联起来如图6所示,在linux2.4.30中有三条链表,文件系统类型结构file_system_type的链表头为file_systems,超级块结构super_block的链表头为super_blocks,挂接点结构vfsmount的链表头为vfsmntlist。
    在Linux3.3.5中只有两条链表结构,文件系统类型结构file_system_type的链表头为file_systems,超级块结构super_block的链表头为super_blocks。数据结构vfsmount 的结构定义还存在,但已经没有了mnt_list成员了。

挂接设备中查找文件的过程

下面的流程参考了linux3.3.5中的数据结构。
例如要打开 /mnt/win/dir1/abc 这个文件,就是根据这个路径,找到目标节点 ‘abc’ 对应的 dentry ,进而得到 inode 的过程。
寻找过程大致如下:

  1. 首先找到根文件系统的根目录节点 dentry 和 inode
  2. 由这个 inode 提供的操作接口 i_op->lookup(),找到下一层节点 ‘mnt’ 的 dentry 和inode
  3. 由 ‘mnt’ 的 inode 找到 ‘win’ 的 dentry 和 inode
  4. 由于 ‘win’ 是个“安装点”,因此需要找到“被安装设备”/dev/sda1 根目录节点的 dentry 和 inode。“win”的dentry中有d_sb(超级块成员),d_sb中有“struct dentry *s_root;”,s_root就是指向“/dev/sda1”的dentry。
  5. 然后由 /dev/sda1 根目录节点的 inode 负责找到下一层节点 ‘dir1’ 的 dentry 和 inode
  6. 由于 dir1 是个“安装点”,因此需要借助dir1的dentry->d_sb->s_root找到 /dev/sda2 的根目录节点 dentry 和 inode
  7. 最后由这个 inode 负责找到 ‘abc’ 的 dentry 和 inode

可以看到,整个寻找过程是一个递归的过程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容