文件系统
对于运行的进程来说,内存就像一个纸箱子,仅仅是一个暂存数据的地方,而且空间有限。如果我们想要进程结束之后,数据依然能够保存下来,就不能只保存在内存里,而是应该保存在外部存储中。
我们最常用的外部存储就是硬盘,数据是以文件的形式保存在硬盘上的。为了管理这些文件,我们在规划文件系统的时候,需要考虑到以下几点。
第一点,文件系统要有严格的组织形式,使得文件能够以块为单位进行存储。
硬盘分成相同大小的单元,我们称为块(Block)。一块的大小是扇区大小的整数倍,默认是 4K。在格式化的时候,这个值是可以设定的。一大块硬盘被分成了一个个小的块,用来存放文件的数据部分。
这样一来,如果我们像存放一个文件,就不用给他分配一块连续的空间了。我们可以分散成一个个小块进行存放。这样就灵活得多,也比较容易添加、删除和插入数据。
第二点,文件系统中也要有索引区,用来方便查找一个文件分成的多个块都存放在了什么位置
inode
元数据:例如名字、权限等,这就需要一个结构 inode「index」 来存放)
i_block
文件块:后文会有详细介绍
第三点,如果文件系统中有的文件是热点文件,近期经常被读取和写入,文件系统应该有缓存层。
为了能够更快读取文件,内存里会分配一块空间作为缓存,让一些数据块放在缓存里面。
说白了,就是要在内存里。
第四点,文件应该用文件夹的形式组织起来,方便管理和查询。
第五点,Linux 内核要在自己的内存里面维护一套数据结构,来保存哪些文件被哪些进程打开和使用。
lsof
查看进程打开那些文件 或者 查看文件给那个进程使用
fd
文件描述符 「File Derisible」
小实验
文件系统相关命令行:
默认情况下,超级块有副本保存在每一个块组里面。如果开启了 sparse_super 特性,超级块和块组描述符表的副本只会保存在块组索引为 0、3、5、7 的整数幂里。除了块组 0 中存在一个超级块外,在块组 1(30=1)的第一个块中存在一个副本;在块组 3(31=3)、块组 5(51=5)、块组 7(71=7)、块组 9(32=9)、块组 25(52=25)、块组 27(33=27)的第一个 block 处也存在一个副本。
系统调用相关
在用户态,每个打开的文件都有一个文件描述符,可以通过各种文件相关的系统调用,操作这个文件描述符。
#/usr/bin/env python3
# -*- coding: utf-8 -*-
# 打开文件
"""
在 Open 函数中,有一些参数:
O_CREAT 表示当文件不存在,创建一个新文件;
O_RDWR 表示以读写方式打开;
O_TRUNC 表示打开文件后,将文件的长度截断为 0。
"""
fd = open("test.txt", "at+")
"""
文件描述符,就是用来区分一个进程打开的多个文件的。
它的作用域就是当前进程,出了当前进程这个文件描述符就没有意义了。
open 返回的 fd 必须记录好,我们对这个文件的所有操作都要靠这个 fd,包括最后关闭文件。
"""
print("文件名: ", fd.name)
str = "acm.sandau.edu.cn\n"
# 在文件末尾写入一行
fd.seek(0, 2)
"""
lseek 用于重新定位读写的位置
第一个参数是文件描述符,第二个参数是重新定位的位置
第三个参数是 SEEK_SET,表示起始位置为文件头,第二个参数和第三个参数合起来表示将读写位置设置为从文件头开始 0 的位置,也即从头开始读写。
"""
line = fd.write(str)
"""
接下来,write 要用于写入数据。
第一个参数就是文件描述符,第二个参数表示要写入的数据存放位置
第三个参数表示希望写入的字节数,返回值表示成功写入到文件的字节数。
"""
# 刷新缓冲区
fd.flush()
# 读取文件所有内容
fd.seek(0, 0)
lines = fd.readlines() #读取全部内容 ,并以列表方式返回
for line in lines:
print(line)
"""
read 用于读取数据。
第一个参数是文件描述符,第二个参数是读取来的数据存到指向的空间
第三个参数是希望读取的字节数,返回值表示成功读取的字节数。
"""
# 关闭文件
fd.close()
"""
最终,close 将关闭一个文件。
"""