文件系统初识
一、什么是文件系统?
文件系统是操作系统中负责管理持久数据的子系统,简言之就是负责把用户的文件存到磁盘硬件中。
文件系统的基本数据单位是文件,它的目的是对磁盘上的文件进行组织管理,那组织的方式不同,就会形成不同的文件系统。
linux中一切皆文件,不仅是普通的文件和目录,包括块设备、管道、socket等。
二、文件系统的组成
一个文件包含两个数据结构:索引节点(index node,inode)、目录项(directory entry)。
索引节点,inode
记录文件的元信息,比如inode编号、文件大小、访问权限、创建和修改时间、数据在磁盘的位置等。并不会包含文件名。一个文件对应唯一一个inode。此外,inode也会存储在磁盘中,同样占用磁盘空间。
Linux 通过 inode 节点表将文件的逻辑结构和物理结构进行转换。
linux下查看一个文件的详细信息:stat命令
stat filename
目录项,dentry
记录文件的名字,索引节点指针以及与其他目录项的层级关联关系。多个目录项关联起来,就会形成目录结构,但它与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存。
目录也是文件,也是用索引节点唯一标识,和普通文件不同的是,普通文件在磁盘里面保存的是文件数据,而目录文件在磁盘里面保存子目录或文件。
注意目录项和目录不是一个东西,目录是一个文件,目录项是内核的一个数据结构,所以目录包含目录项。内核会把已经读过的目录用目录项这个数据结构缓存在内存,下次再次读到相同的目录时,只需从内存读就可以,大大提高了文件系统的效率。
关于目录的目录项和索引节点,目录的的索引节点指向的数据块会保存该目录下的文件的目录项以及它的类型(可能是普通文件,也可能是一个目录)。如下图:
为了加快查找效率,保存目录的格式改成哈希表,对文件名进行哈希计算,把哈希值保存起来,如果我们要查找一个目录下面的文件名,可以通过名称取哈希。如果哈希能够匹配上,就说明这个文件的信息在相应的块里面。
两者的关系:
索引节点唯一标识一个文件,而目录项记录着文件的名字,所以目录项和索引节点的关系是多对一,也就是说,一个文件可以有多个别名,即一个索引节点可以有多个目录项。比如,硬链接的实现就是多个目录项中的索引节点指向同一个文件。
关于硬链接和软链接:
- 硬链接就是只有一个索引节点,但有多个目录项,多个文件指向同一个索引节点。不可以对目录创建硬链接。创建硬链接文件的链接数会增加。硬链接是不可用于跨文件系统的。由于多个目录项都是指向一个 inode,那么只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。
- 软链接的本质是一个链接文件,其中存储的了对另一个文件的指针。所以对一个文件创建软链接,inode号不相同,创建软链接文件的链接数不会增加。可以对目录创建软链接。软链接相当于重新创建一个文件,这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候,实际上相当于访问到了另外一个文件,所以软链接是可以跨文件系统的,甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。
三、文件数据如何存储在磁盘的
磁盘的读写单位是扇区(sector),扇区的大小通常是512B,如果每次读写的单位是这么小的扇区,那么效率较低。所以文件系统会把多个扇区组成一个逻辑块(logical block),每次读写的最小单位就是逻辑块。一般以4kB为一个逻辑块。
关于索引节点、目录项、文件数据的关系:
- 超级块,用来存储文件系统的详细信息,比如块个数、块大小、空闲块等等。
- 索引节点区,用来存储索引节点;
- 数据块区,用来存储文件或目录数据;
加载到内存的时机:
- 超级块:当文件系统挂载时进入内存;
- 索引节点区:当文件被访问时进入内存;
四、虚拟文件系统
为了屏蔽文件系统的差异,操作系统对用户提供了一个统一的接口,在用户层与文件系统层引入了中间层,这个中间层就称为虚拟文件系统(Virtual File System,VFS)。
Linux 文件系统中,用户空间、系统调用、虚拟文件系统、缓存、文件系统以及存储之间的关系如下图:
文件系统的分类:
- 磁盘的文件系统,它是直接把数据存储在磁盘中,比如 Ext 2/3/4、XFS 等都是这类文件系统。
- 内存的文件系统,这类文件系统的数据不是存储在硬盘的,而是占用内存空间,我们经常用到的 /proc 和 /sys 文件系统都属于这一类,读写这类文件,实际上是读写内核中相关的数据。
- 网络的文件系统,用来访问其他计算机主机数据的文件系统,比如 NFS、SMB 等等。
五、文件的使用
比如调用write()函数从指定内存写数据到文件:
首先需要获取文件描述符(file descriptor,通过open函数),然后利用fd进行后续操作,在最后需要关闭文件,避免资源泄露。
关于文件描述符:
fd
文件描述符(file descriptor)是内核为了高效管理已经被打开的文件所创建的索引,是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符来实现。同时还规定系统刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。这意味着如果此时去打开一个新的文件,它的文件描述符会是3,再打开一个文件文件描述符就是4…
关于文件描述符0,1,2,0、1、2fd
在程序刚开始运行时,有三个文件被自动打开了,占用了 0 1 2 这三个描述符:
依次打开的三个文件分别是/dev/stdin,/dev/stdout,/dev/stderr
函数原型:
ssize_t write(int fd, const void *buf, size_t count);fd为需要写入的目标文件的描述符,buf为需要写入的字符串,第三个参数是需要写入的长度。
read函数:ssize_t read(int fd, void *buf, size_t count);fd为文件描述符;buf表示读出数据缓冲区地址;count表示读出的字节数。
以上两个函数需要包含头文件 :#include <unistd.h>
关于这两个函数的使用参考:读写示例
linux下实验write和read函数:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd;
char *buf="woSAFUAIUFASFJ";
char rdbuf[21];
fd=open("./1.txt",O_RDWR);
if(fd==-1){
printf("open file failed\n");
fd=open("./1.txt",O_RDWR|O_CREAT,0600);
if(fd > 0){
printf("creat file success\n");
}
}
printf("open successs fd=%d\n",fd);
//函数原型 off_t lseek(int filde,off_t offset ,int whence)
lseek(fd, 0, SEEK_END); //从文件末尾开始
//函数原型 ssize_t write(int fd, const void *buf, size_t count);
write(fd, buf, strlen(buf));//将buf中的数据写入文件末尾
//lseek(fd, -1*strlen(buf), SEEK_CUR); //将光标移到写的个数前的位置
//read(fd, rdbuf, 20); //从头开始读取20个字节
//for (int i =0; i<20; i++) { //打印输出
// printf("%c", rdbuf[i]);
// }
close(fd);
return 0;
}
目前好像只能从三种位置开始读写数据,即lseek函数中的whence会指定当前的光标位置,然后再指定偏移offset。不指定默认从头开始。