linux系统编程——文件IO——文件系统和描述符

1. 从磁盘说起

1.1 读写磁盘数据

众所周知,磁盘读写是通过 盘面,磁道,扇区 3D寻址,用磁极 NS 表示 01 ,且通过接口指令(如SATA)完成共工作。当然这些都不重要,对用户层系统编程而言,只需要记住 磁盘读写是以 数据块为单位,且读写速度慢。

1.2 读写磁盘上文件

文件由两部分组成:

  • 文件元数据 : 描述文件自身的数据

在磁盘的位置

创建时间,修改时间

大小

读写执行权限

所有者,所属组

  • 文件里的数据

数据有多有少,但磁盘空间最小单位为一个 数据块,所以 1字节文件也要占用一个数据块,大文件占用连续数据块

1.2.1 磁盘利用率

由于文件至少使用 一个块的空间,所以 若 磁盘最小块 越小,能存放文件数量越多,但寻址时间增加。

1.2.2 逻辑块

不同厂家的磁盘,最小数据块 可能不同,为了方便 OS 统一操作,虚拟文件系统提供了统一大小的逻辑块,逻辑块大小为实际数据块大小的倍数。

2. 从文件到逻辑块

到此我们有了浩如烟海的逻辑块,也知道如何读写逻辑块,但是我们操作的单位是文件,那么如何通过 一个文件 找到 相关的逻辑块呢?

linux用 inode 解决 从 文件到逻辑块的 映射。

2.1 inode

inode就是linux对文件元数据的称呼。

inode在磁盘格式化时生成,在文件系统挂载时,磁盘的inode加载到内存中,创建文件时找到空闲的inode,录入文件信息,删除文件时 将对应inode改为空闲状态。内存中的inode会适时写入磁盘 以保持同步。

注意inode没有文件名信息

2.2 super block

inode的数量也太多了,于是linux使用名为 super block的结构维护 inode的使用情况。

实际上 super block 记录整个文件系统的信息:单个inode, block的大小,inode,block使用情况等。

super block 和 inode表 一样,在文件系统格式化 完成构造,当文件系统挂载时,载入内存,并常驻内存。

2.3 如何创建 super block 和 inode

磁盘在挂载前,需要分区和格式化。

分区可以视为初始格式化,分区会在磁盘首部构造一个 MBR 结构,用于记录整个磁盘信息和 各个分区信息,同时给主分区首部构造一个 boot 扇区。

格式化在某个分区安装文件系统,不同分区可以安装不同文件系统。格式化就是 构造 super block 和 inode 表,data block表。

3. 从路径名到inode

可以说 inode 就是一个文件,拿到inode就能读写文件数据,但是我们只有pathname,怎么通过 pathname 得到 inode呢?

3.1 目录和目录项

文件名 和 inode编号 是 key-value 关系,所以可以将 一个目录下的 所有文件 的 key-value(文件名和 inode 编号) 存放在一张表中,这张表作为数据存放在磁盘 的 data block,那么这些 data block 也就有一个 相关 的 inode ,或者说是文件,这个文件就是目录。而key-value对就是目录项。

3.2 从目录到pathname

我们有了目录这张表,就能找到该目录下所有文件的inode,但是如何获得目录的数据呢(也就是获得目录的inode)。

目录也是文件,所以他的inode被记录在其父目录的 data block中,所以需要找到其父目录的 inode,如此往复,如何是个头。

于是我们规定 最顶层目录,也叫根目录的inode为固定值 2。

所以以pathname为线索,从根目录开始就可以找到对应文件的inode

注意上面的pathname指绝对路径。

3.3 从相对路径到绝对路径

我们知道获得文件inode实际是使用绝对路径,但是我们更喜欢相对路径,怎么办呢?

首先进程知道自己的当前目录(目录的inode号),而每个目录都有两个特殊目录项, . 和 .. ,表示当前目录和上级目录的的 文件名和inode对,所以可以回溯到上级目录,如此往复,根目录的 . 和 .. 相关,都是2,所以当.和..的inode相等,就说明到顶层了,同时也 得到了pwd,并接上 相对路径,就得到目标文件的绝对路径。

4. 挂载

磁盘经过分区格式化后,还要挂载才能访问,挂载时需要指定挂载点,作为挂载点的目录将被屏蔽。这是为什么呢?

4.1 为什么能访问挂载的磁盘

VFS有一个 VFSMOUNT hash table,里面装 vfsmount 对象。
vfsmount 对象描述一个文件系统的所有挂载信息

父文件系统的挂载点:vfsmount->mnt_mountpoint = /mnt
子文件系统的根目录: vfsmount->mnt_root = superblock->s_root

上面的 superblock 是 该磁盘挂载到虚拟文件系统时,从磁盘载入的super block信息,而构造的superblock对象。
我们知道通过superblock对象可以访问磁盘的inode(也就是文件)。

注意:根文件系统同样也是挂载的,挂载到虚拟文件系统的 根目录

4.2 为什么挂载点原有文件不能访问

挂载成功后,挂载点的目录项 dentry标记为 DCACHE_MOUNTED。

用户输入pathname后,递归查找目录项,走到 挂载点目录项,发现 被标记为 DCACHE_MOUNTED,所以不使用其inode,而计算目录hash code,去 VFSMOUNT hash table查找对应的 vfsmount对象。

根据vfsmount->mnt_root找到子文件系统的根目录,继续在子文件系统查找目录项。

5. 虚拟文件系统

5.1 各种各样的文件系统

通常我们会同时使用多种文件系统,如磁盘文件系统ext,网络文件系统nfs等。

不同文件系统有不同的特性,这很好,但是文件系统读写方法差异很大,如何解决呢?

5.2 统一接口

虚拟文件系统采用面向对象思想解决 上述问题,通过回调函数满足不同文件系统有不同操作接口的需求,而对上层统一接口。

6. 进程读写文件

由上面可以知道,给内核一个pathname,内核能返回 对应的 inode,但我们知道进程不是直接使用inode,而是用一个 名为 文件描述符的东西,文件描述符和inode怎么关联呢?

6.1 file_struct

task_struct下有个 file_struct,这个结构记录此进程所有打开的文件。

struct file_struct {
	...
	struct file *fd_array[NR_OPEN_DEFAULT];
};

file_struct 下有个 fd_array的 指针数组,明显 文件描述符就是 这个数组的索引,所以 struct file就表示 打开的文件

6.2 struct file

struct file {
	union {
		struct llist_node		fu_llist;
		struct rcu_head			fu_rcuhead;
	} f_u;
	struct inode 				*f_inode;
	const struct file_operations 		*f_op;
	spinlock_t				f_lock;
	atomic_long_t				f_count;
	fmode_t					f_mode;
	struct mutex				f_pos_lock;
	loff_t					f_pos;
	struct fown_struct			f_owner;
	struct address_space			*f_mapping;
	...
}

f_u 说明所有 打开的文件会构成一个 内核级表,这也为进程间传递文件描述符埋下基础

f_inode:磁盘的文件,多个进程可以打开同一个文件,所以多个struct file的 f_indoe 可以指向相同inode

f_op : 文件的读写等方法

f_count : 应该和 close()相关,表示被引用数,当引用数为0就真正释放本struct file

f_mode : 打开的模式

f_pos : 文件当前操作位置

f_mapping : 这和文件映射相关

7. 链接

  • 硬链接: 多个 目录项 的inode相同,文件名不同,则为硬连接。当删除硬链文件,也就是删除目录项,当inode没有任何目录引用,则会被转为空闲

只能对存在文件创建硬链接,不能对目录创建硬链接,创建硬链接不能跨越文件系统分区

  • 软链接:软链接本身为文件,data block的内容为 目标文件的 路径,可以用相对路径,也可以用绝对路径,删除软链接不会影响目标文件本身

可以对不存在的文件或目录创建软链接,可以对目录创建软链接,可以跨文件系统创建软连接

posted on 2021-08-18 09:16  开心种树  阅读(125)  评论(0编辑  收藏  举报