文件系统简介
一.Linux下的文件系统
文件系统狭义的概念是一 种对存储设备上的数据进行组织和控制的机制。在Linux 下(当然包含UNIX), 文件的含义比较广泛,文件的概念不仅仅包含通常意义的保存在磁盘的各种格式的数据,还包含目录,甚至各种各样的设备,如键盘、鼠标、网卡、标准输出等,引用一 句经典的话 ''UNIX 下一 切皆文件”。
Linux下文件的内涵
Linux下的文件系统是对复杂系统进行合理抽象的一个经典的例子,它通过一 套统一的接口函数对不同的文件进行操作。例如 open()函数不仅可以打开ext2 类型的文件,还可以打开 fat32 类型的文件,并且包括如串口设备、显卡等,只不过打开设备的名称不同而已。L inux 下的文件主要分为如下几种。
-
普通文件:例如保存在磁盘上的 C 文件、可执行文件,以及目录等,这种文件的特性是数据在存储设备上存放,内核提供了对数据的抽象访问,此种文件为一 种字节流,访问接口完全独立千在磁盘上的存储数据。
-
字符设备文件:是一 种能够像文件一 样被访问的设备,例如控制台、串口等。
-
块设备文件:磁盘是此类设备文件的典型代表,与普通文件的区别是操作系统对数据的访问进行的重新的格式设计。
-
socket 文件:它是Linux 中通过网络进行通信的方式,对网络的访问可以通过文件描述符的抽象实现,访问网络和访问普通文件相似。
文件系统的创建
在Linux下对磁盘进行操作的工具是fdisk,与Windows下的fdisk功能有些类似,但是命令的格式完全不同。
1.系统分区情况
使用fdisk命令查看当前磁盘的情况:
2.建立分区
使用fdisk在没有使用的磁盘/dev/sdb上进行分区,先查看分区情况,然后建立一个100M大小的初级分区,将分区表写入磁盘并退出:
3.查看分区是否成功
4.格式化分区
磁盘多一个sdb1分区。仅进行分区,分区后的空间并不能使用,需要使用mkfs格式化分区sdb1:
5.挂载分区
建立一个test目录,将sdb1挂载上去:
mount /dev/sdb1 /test
6.查看分区挂载情况
用命令df查看当前文件系统的情况:
挂接文件系统
Linux系统下,要使用一个文件系统需要先将文件系统的分区挂载到系统上,mount命令用于挂载文件,它有很多选项。mount命令的使用格式为:
mount -t type mountpoint device -o options
-
将文件类型为type的设备device挂载到mountpoint上,挂载时要遵循options的设置。
-
进行分区挂载经常使用的是**-t选项。例如:“-t vfat**”表示挂载Windows下的fat32等文件类型:"-t proc"则表示挂载proc文件类型。
-
挂载命令的**-0**选项是 一 个重量级的设置,经常用于挂载比较特殊的文件属性。
-
在嵌入式Linux下,根文件系统经常是不可写的,要对其中的文件进行修改,需要使用**-0选项重新进行挂载。例如,"-o rewrite,rw**"将文件系统重新进行挂载,并将其属性改为可读写。
Linux也支持挂载网络文件系统,例如NFS文件系统等,挂载NFS文件系统的命令如下:
mount -t nfs 服务器地址:/目录 挂载点
例如:下面是一个挂载nfs文件系统的例子,例如在IP地址为192.168.1.150的机器上做了一个NFS服务器,提供192.168.1.x网段上的NFS服务。可以使用下面的命令:
索引节点indoe
-
在Linux下存储设备或存储设备的某个分区格式化为文件系统后,有两个主要的概念来描述它,一个是索引节点 (inode), 另一 个是块 (Block)。
-
块是用来存储数据的,索引节点则是用来存储数据的信息,这些信息包括文件大小、属主、归属的用户组、读写权限等。
-
索引节点为每个文件进行信息索引,所以就有了索引节点的数值。
-
通过查询索引节点,能够快速地找到对应的文件。这就像 一本书的整体,块是书的内容,而索引节点相当于一本书的目录,如果要查询某些方面的内容,可以通过查询前面的目录,快速地获得内容的信息,例如位置、大小等。
要查看索引节点的信息,可以使用命令 Is , 加上参数**-i** 。例如,使用ls 查看hello .c 的索引节点信息,可知索引节点的值为942227。
-
在 Linux 的文件系统中,索引节点值是文件的标识,并且这个值是唯一 的,两个不同文件的索引节点值是不同的,索引节点值相同的文件它的内容是相同的,仅仅文件名不同。
- 修改两个索引节点值相同的文件中的 一 个文件,另一 个文件的内容也跟着发生改变。
例如下面的一 个例子,使用命令In 为文件hello.c 创建一 个硬链接,命名其文件名为hello2.c ,并查看属性的变化情况。
-
可以看出,hello.c 在没有创建硬链接文件hello2 .c 的时候,其链接个数是 I ( 即-rw-rw-r–后的那个数值),创建了硬链接 hello2 .c 后,这个值变成了2 。也就是说,每次为hello .c 创建一 个新的硬链接文件后,其硬链接个数都会增加 1。
-
索引节点值相同的文件,二者的关系是互为硬链接。当修改其中 个文件的内容时,互为硬链接的文件内容也会跟着变化。如果删除互为硬链接关系的某个文件时,其他的文件并不受影响。
-
例如把 hello2 .c 删除后,还是一样能看到hello .c 的内容,并且hello .c 仍是存在的。
-
这是由于索引节点对于每一 个文件有一个引用计数,当创建硬链接的时候,引用计数会增加1, 删除文件的时候引用计数会减1, 当引用计数为0的时候,系统会删除此文件。
-
目录不能创建硬链接,只有文件才能创建硬链接。如果目录也可以创建硬链接,很容易在系统内部形成真实的环状文件系统,对文件系统的维护造成很大的困难。目录可以使用软链接的方式创建,可使用命令"ln-s"。
普通文件
普通文件是指在硬盘、CD、U盘等存储介质上的数据和文件结构。在本节中所指的文件系统是一 个狭义的概念,仅仅按照普通文件在磁盘中组织方式的不同来区分。
普通文件的概念与 Window s 下面文件的概念是相同的。可以对文件进行打开、读出数据、写入数据、关闭、删除等操作。
设备文件
Linux下用设备文件来表示所支待的设备,每个设备文件除了设备名,还有3个属性,即类型、主设备号、次设备号。例如,查看sdb1,可以获得磁盘分区sdb1的属性,属性的含义如下:
-
设备类型:设备属性的第 一 个字符是这个设备文件的类型。第一 个字符为 c, 表明这个设备是 一 个字符设备文件。第一 个字符为b,表明这个设备是 一 个块设备文件。 sdb1的第一个字符为b, 可知它是一 个块设备文件。
-
主设备号:每 一 个设备文件都有一 个 “主设备号” ,使用 ls -l命令输出的第5个字段即为主设备号。主设备号是表示系统存取这个设备的"内核驱动"。驱动程序是Linux内核中代码的 一 部分,其作用是用来控制一种特殊设备的输入输出。大多数的Linux操作系统都有多种设备驱动程序;每 一个设备文件名中的主设备号就代表这个设备使用的是那个设备驱动程序。lsdev命令可以列出当前内核中配置的驱动程序和这些驱动程序对应的主设备号。
-
次设备号:每 一 个设备文件都有一个次设备号。 "次设备号 ” 是一 个 24 位的十六进制数字,它定义了这个设备在系统中的物理位置。
-
设备文件名:设备文件名用于表示设备的名称,它遵循标准的命令方式,使得设备的分辨更容易。
1.字符设备于块设备
-
字符设备可以在 一 次数据读写过程中传送任意大小的数据,多个字符的访问是通过多次读写来完成的,通常用于访问连续的字符。例如,终端、打印机、moderm和绘图仪等设备是字符类型设备。
- 块设备文件可以在 一 次读写过程中访问固定大小的数据,通过块设备文件进行数据读写的时候,系统先从内存的缓冲区中读写数据,而不是直接与设备进行数据读写,这种访问方式可以大幅度地提高读写性能。块类型设备可以随机地访问数据,而数据的访问时间和数据位于设备中的位置无关。常用的块设备有硬盘、软盘和CD-ROM及RAM类型磁盘。
2.设备文件的创建
设备文件是通过mknod命令来创建的,如下:
-
其参数有设备文件名NAME、操作模式TYPE、主设备号MAJOR及次设备号MINOR。主设备号和次设备号两个参数合并成 一 个16 位的无符号短整数,高 8 位表示主设备号,低8位表示次设备号。可以在include/Linux/major.h文件中找到所支持的主设备号。
-
设备文件通常位于**/dev**目录下,下图显示了目录/dev下的 些设备文件的属性。注意同一 主设备号既可以标识字符设备,也可以标识块设备。
-
一个设备文件通常与 一 个硬件设备 (如硬盘,/dev/hda) 相关联,或者与硬件设备的某一物理或逻辑分区(如磁盘分区,/dev/hda2)相关联。但在某些情况下,设备文件不会和任何实际的硬件关联,而是表示 一 个虚拟的逻辑设备。例如,/dev/null就是对应于一 个 “黑洞” 的设备文件,所有写入这个文件的数据都被简单地丢弃。
3.设备文件的简单操作
设备描述符/dev/console是控制台的文件描述符,可以对其进行操作,例如下面的命令可能造成系统循环运行,设置死机:
cat /dev/console
上面的命令将控制台的输入打印出来,下面的命令向标准输出传入字符串test,系统将字符串test发给标准输出:
echo "test">/dev/stdout
嵌入式设备中常用的Framebuffer设备是 一 个字符设备,当系统打开Framebuffer设置的时候(通常可以在系统启动的时候,修改启动参数,例如在kernel 一 行增加 vga= 0x314启动一 个800X 600 分辨率的帧缓冲设备),运行如下命令,先将Framebuffer设备fb0 的数据写入文件test.txt中,然后利用cat命令将数据写入帧缓存设备fb0:
cat /dev/fb0 > test.txt(获取帧缓存设备的数据)
cat test.txt > /dev/fb0(将数据写入帧缓存设备)
虚拟文件系统VFS
-
Linux的文件系统是由虚拟文件系统作为媒介搭建起来的,虚拟文件系统VFS (Virtual File Systems)是Linux内核层实现的 一 种架构,为用户空间提供统一 的文件操作接口。它在内核内部为不同的真实文件系统提供 一 致的抽象接口。
-
如下图所示,用户应用程序通过系统调用,与内核中的虚拟文件系统交互,操作实际的文件系统和设备。
-
在图中可以看出,Linux文件系统支持多种类型的文件,对多种类型的文件系统进行了抽象。通过 一 组相同的系统调用接口,Linux 可以在各种设备上实现多种不同的文件系统。例如,write()函数可以向多种不同的文件系统上写入数据,调用write()函数的应用程序不用管文件的具体存储位置和文件系统的类型,但是当写入数据的时候,函数会正常返回。
-
VFS是文件系统的接口框架,这个组件导出一组接口,然后将它们抽象到各个文件系统,各个文件系统的具体实现方式差异很大。有两个针对文件系统对象的缓存(inode和dentry), 它们缓存的对象是最近使用过的文件系统。
-
每个文件系统实现(如ext4、vfat等)导出 一组通用接口,供 VFS 使用。缓冲区用于缓存文件系统和相关块设备二者之间的请求。例如,对底层设备驱动程序的读写请求会通过缓冲区缓存来传递。这就允许在其中缓存请求,减少访问物理设备的次数,加快访问速度。
-
以最近使用(LRU)列表的形式管理缓冲区缓存。注意,可以使用sync命令将缓冲区缓存中的请求发送到存储媒体(迫使所有未写的数据发送到设备驱动程序,进而发送到存储设备)。
1.文件系统类型
-
Linux的文件系统用 一 组通用对象来表示,这些对象是超级块 (superblock)、节点索引(inode)、目录结构(dentry)和文件(file)。
-
超级块是每种文件系统的根,用于描述和维护文件系统的状态。
-
文件系统中管理的每个对象(文件或目录)在Linux中表示为 一 个索引节点inode。inode包含管理文件系统中的对象所需的所有元数据(包括可以在对象上执行的操作)。
-
另一 组结构称为 dentry, 它们用来实现名称和inode之间的映射,有 一 个目录缓存用来保存最近使用的dentry。dentry还维护目录和文件之间的关系,支持目录和文件在文件系统中的移动。VFS文件表示一 个打开的文件 (保存打开的文件的状态,像文件的读偏移量和写偏移量等)。
struct fill_system_type{
const char *name; /*文件类型名称*/
int fs_flags; /*标志*/
struct super_block *(*read_super) (struct super_block *, void *,int); /*读超级块函数*/
struct module *owner;/*所有者*/
struct file_system_type * next;/*下一个文件类型*/
struct list_head fs_supers;/*头结构*/
};
-
可以使用 一 组注册函数在 Linux 中动态地添加或删除文件系统。
-
Linux 的内核中保存系统所支待的文件系统的列表,可以通过**/proc**文件系统在用户空间中查看这个列表。
-
虚拟文件系统还显示当前系统中与文件系统相关联的具体设备。
-
在Linux中添加新文件系统的方法是调用register_filesystem。这个函数的参数定义 一 个文件系统结构 (file_system_ type) 的引用,这个结构定义文件系统的名称、 一 组属性和两个超级块函数,也可以注销文件系统。
在注册新的文件系统时,会把这个文件系统和它的相关信息添加到file_ systems列表中。在命令行上输入cat/proc/filesystems, 就可以查看这个列表。例如:
2.超级块
超级块结构用来表示一个文件系统,结构如下:
struct super_block{
...
unsigned long long s_maxbytes; /*最大文件尺寸*/
struct file_system_type *s_type; /*文件类型*/
const struct super_operations *s_op; /*超级块的操作,主要是对inode的操作*/
...
char s_id[32];
}
由于篇幅的关系省略了很多信息,读者可以从linux/fs.h文件中获得全部的代码。这个结构包含一 个文件系统所需要的信息,例如文件系统名称、文件系统中最大文件的大小,以及对inode块的操作函数等。
在Linux系统中每种文件类型都有 一 个超级块,例如,如果系统中存在ext4和vfat, 则存在两个超级块,分别表示ext4文件系统和vfat文件系统。
struct super_operations{
struct inode *(*alloc_inode)(struct super_block *sb); /*申请节点*/
void (*destroy_inode)(struct inode *) /*销毁节点*/
void (*dirty_inode) (struct inode *);
int (*write_inode) (struct inode *,int);/*写节点*/
void (*drop_inode)(struct inode*);/*摘取节点*/
...
};
-
超级块中的一 个重要元素是超级块操作函数的定义。这个结构定义一 组用来管理这个文件系统中的节点索引 inode 的函数。
-
例如,可以使用函数 alloc_ inode() 来分配 inode, 用函数 destroy_inode()来删除inode。可以用read_inode()和write_inode()来读写 inode,用 **sync_fs()**执行文件系统同步。
-
在 Linux 的源代码树的文件 Linux/include/Linux/fs.b 中找到super_ operations 结构。Linux 文件系统中所支待的每个文件系统都实现一 套自己的 inode操作方法,这些方法实现超级块所定义的功能并向 V FS 层提供通用的抽象。
3.文件操作
在文件fs.h中定义了文件操作的结构,通常实际的文件系统都要实现对应的操作函数,例如打开文件open、关闭文件close、读取文件read和写入数据write等。
struct file_operations{
struct module *owner;
loff_t(*llseek) (struct file*, loff_t, int);
ssize_t(*read) (struct file*, char __user*, size_t, loff_t *);
ssize_t(*write) (struct file*, const char __user*, size t, loff t*);
ssize_t(*aio_read) (struct kiocb*, const struct iovec*, unsigned long, loff_t); /*异步读*/
ssize_t(*aio_write) (struct kiocb *, const struct iovec*, unsigned long, loff_t) ; /*异步写*/
int (*readdir) (struct file *, void *, filldir_t;) /*读目录*/
unsigned int (*poll) (struct file *, struct poll_table_struct *); /*poll操作*/
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); /*ioctl函数*/
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); /*非锁定ioctl*/
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);/*简装ioctl*/
int (*mmap) (struct file *, struct vm_area_struct *); /*内存映射*/
int (*open) (struct inode *, struct file *); /*打开*/
int (*flush) (struct file*,