Linux本地文件存储

Linux 本地文件存储原理

在LINUX系统中有一个重要的概念:一切都是文件。UNIX系统把每个硬件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。

Linux文件系统分为多层:


Linux文件系统
1. 系统调用

应用程序通过系统调用(System call:应用程序使用OS提供的接口调用内核功能)访问文件。

常用的与存储相关的系统调用函数包括:open, close, write, read, mmap(mmap函数可以将一个文件的内容映射到内存,这样就可以直接对该内存进行操作,从而省去IO操作。)等。

2. 虚拟文件系统

VFS是不同的文件系统的一个抽象,提供统一的API访问接口,这样,用户空间就不用关心不同文件系统中不一样的API了。并且通过系统调用层,可以在不同的文件系统之间复制和移动数据。

虚拟文件系统提供了一个通用的文件系统模型,采用了面向对象的设计思路,主要有以下4个对象类型:

  • 超级块(Super Block)

级块代表一个已安装的文件系统,用于存储文件系统的有关信息,如文件系统的类型、大小、状态等。对基于磁盘的文件系统,它存放在磁盘的特定扇区上。对非基于磁盘的文件系统,会现场创建保存在内存中。

  • 索引节点(Inode)

代表存储设备上一个实际的物理文件,用于存储该文件的有关信息。Linux将文件的相关信息(又称文件的元数据),包括:

1)文件的字节数
2)文件拥有者
3)文件的Group ID
4)文件的读、写、执行权限
5)文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
6)链接数,即有多少文件名指向这个inode
7)文件数据block的位置

与文件本身分开。

  • 目录项(Dentry)

描述了文件系统的层次结构。对于虚拟文件系统,目录和文件都是普通的文件。所以目录和文件都是目录项对象。文件目录项主要是存储文件名至文件inode的映射关系

  • 文件

文件对象代表已经被进程打开的文件,主要用于建立进程和文件之间的对应关系。由open()创建,close()销毁,当且仅当进程访问文件期间存在于内存之中。

3. Page Cache

Page Cache是以物理页为单位对磁盘文件进行缓存,以减少磁盘IO次数,提高对文件的访问速度。

  • 扇区、块、物理页

扇区是块设备传输数据的基本单元,也就是说它是块设备中最小的寻址单位。

是扇区之上的一个抽象。一个块通常对应一个或多个相邻的扇区。内核将块作为对磁盘操作的最小单位,因此VFS将其看作是单一的数据单元。块的大小是扇区的整数倍,一般是512Byte、1KB或4KB。内核只能基于块来访问物理文件系统,所以块也被称为文件系统的最小寻址单元。

一个磁盘块被调入内存时,它需要存储在一个缓冲区,每个块在内存中都与一个缓冲区相对应。一个物理页框可能包含一个或多个块缓冲区。

即:扇区是磁盘的最小存储单位;磁盘块是文件系统读写数据的最小单位;页是内存的最小存储单位;

  • 读缓存

我们读取数据的时候首先先检查页缓存中数据是否存在,如果存在直接读取,如果不存在就从磁盘中读取,然后将数据放到页缓存中。

  • 写缓存

一般而言,写缓存有三种实现方式:

1) 不缓存:直接写到磁盘,并且标记页缓存中的数据过期

2) 写透缓存:写缓存同时写磁盘,保持了良好的一致性

3) 回写:只写到缓存中,标记页面为脏表示磁盘中的数据过期,过段时间再刷新到磁盘。

  • 缓存的回收

1)LRU:最近最少使用

2)双链策略:有两个链表,分别为热链表,冷链表。热链表上的页面不会被换出,冷链表上的页面可以被换出。页面首先加入冷链表中,如果再次被访问就加入热链表,当热链表过长,需要将溢出的页面重新加入冷链表。

  • Page Cache 和 Buffer Cache

早期Page Cache 和 Buffer Cache是两个独立的缓存。内核2.4.10版本开始,Buffer Cache被包含在了Page Cache中,通过Page Cache来实现。

  • Direct I/O 与 Buffer IO

使用Page Cache的IO称为Buffer IO。而对于一些特殊应用程序,比如说自缓存应用程序,具有自己的用户空间的缓存机制,不需要利用内核中的缓存,如数据库,就可以在进程打开文件的时候将文件的访问模式设置为O_DIRECT,使用Direct I/O方式读写文件。

Direct I/O 优点是通过减少内核缓冲区和用户空间的数据复制次数,降低文件读/写时带来的CPU负载能力及内存带宽的占用率。

4. 文件系统

文件系统提供了存储相关API的具体具体实现。文件系统把文件读/写命令转换成对逻辑区块地址(LBA)的读写,并最终翻译成每个设备对应的可识别的地址。

linux下主流的文件系统包括:EXT2/3/4, xfs, btfs等。 目前大多Linux发行版本默认使用的文件系统一般是ext4。

  • 目录

目录在ext2/3是一张线性表。如果在一个目录下按文件名查找一个文件,需要进行线性遍历。ext3中引入了目录索引,即如果一个目录下的文件容量超过2KB,索引节点中的i_data域就会指向一个特殊的block,按照B-Tree结构对文件进行存储。而Btfs直接使用了B-Tree结构进行存储。

通过Linux的设备驱动对物理设备,如硬盘驱动器或固态硬盘进行相关的读写。

  • Linux文件系统的实现都是在内核进行的。但用户态也有一些管理机制可以对块设备文件进行相应的管理。如mkfs, LVM等。

  • 基于extend的文件存储

inode中保存了指向block位置的指针。在Ext2文件系统中,数据块都是被单独管理的,索引节点里指针数量至少是文件占用的数据块的数量。该指针包括了一级(指针直接指向磁盘的block位置)和多级(先指向一个指针列表,最后指向block位置,Ext2最多有3级)。

ext2_inode

而ext4, Btfs所支持的extend,是由一些连续的block组成,由起始的block加上长度定义的。inode里可以指向一个extend,这样能够大大减少指针的数量和层级,提高操作大文件的性能。

  • 写时复制

写时复制是指在对数据进行修改的时候,不会直接在原来的数据位置上进行操作,而是找一个新的位置修改,一旦系统突然断电,重启之后将不需要fsck。

例如Btfs中,每次数据写入磁盘时,先将更新的数据写入一个新的Block,写入成功后再更新指针。

5. 块层 (Block layer)

块层负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。

在通用块层中,使用bio结构体来描述一个I/O请求。而Linux驱动,则是使用request结构体来描述向块设备发出的I/0请求。一个request中包含了一个或多个bio。request存在的目的就是为了进行io的调度。通过request这个辅助结构,我们来给bio进行某种调度方法的排序,从而最大化地提高磁盘访问速度。

而对于慢速的磁盘设备,请求的处理速度很慢,这时内核就提供一种request_queue,把request添加到队列中

  • bio 与 request

每个bio对应磁盘里面一块连续的位置。bio在块层会被转化为request,多个连续的bio可以合并到一个request中,生成的request会继续合并,排序,并最终调用块设备驱动的接口将request从块层的request_queue中移到驱动层进行处理,

  • I/O合并

I/O合并会把符合条件的多个I/O合并成单个I/O请求进行一并处理。能够进行合并的位置主要有Page Cache, Plug List 和 I/O调度器。

每个进程有一个私有的Plug队列,进程在向通用块层派发I/O请求之前如果开启了蓄流功能,make request会尝试把bio合并到一个进程本地Plug队列里的request,如果无法合并则创造一个新的request。

泄流的时候,进程本地Plug队列的request会被加入调度算法队列中。这个调度算法主要目的是进一步合并request, 把request对硬盘的访问顺序化,以及执行一定的QoS (Quality of Service)

  • I/O调度

Linux支持多个I/O调度器,可以给每个磁盘定制不同的调度算法。常见的调度算法包括:

1)noop

不调度的算法(no operation, noop),即逐个执行队列中的I/O请求。该算法适合那些不希望调度器重新组织I/O请求顺序的应用。可以分为两种类型:
1> I/O调度器下方有更智能的I/O调度设备,不需要额外的调度工作。如磁盘阵列、SAN、NAS等 2>上层应用程序已对I/O请求进行优化

2)deadline

基于经典的电梯算法,即请求的处理按照块设备的偏移(一般是扇区号)朝一个方向移动,直到到达最远的对方,然后再按照反方向移动,就像电梯的运行过程一样。deadline算法在该算法基础上增加了deadline的逻辑,即deadline时间到了(都请求为500ms, 写请求为5s),就会先处理这些请求。从而保证了每个I/O请求在一定时间内一定会被服务到,避免了“饥饿”的请求。

3)CFQ (Completely Fair Queuing)

cfq算法,给每个进程一个IO队列,然后轮询各个队列,达到公平的效果。适用于传统硬盘,也是长久以来的默认算法。为减少寻址,该算法尝试给IO排序,极端情况可导致IO饥饿,即如果一个程序有大量顺序读写,那么它就会插队,导致其他程序IO饥饿。CentOS 6默认使用cfq算法。

Linux 存储结构与磁盘划分

1. Linux常见的目录名称及对应的内容
/boot  开机所需文件—内核、开机菜单以及所需配置文件等 
/dev  以文件形式存放任何设备与接口 
/etc  配置文件 
/home  用户家目录 
/bin  存放单用户模式下还可以操作的命令 
/lib  开机时用到的函数库,以及/bin 与/sbin 下面的命令要调用的函数 
/sbin  开机过程中需要的命令 
/media  用于挂载设备文件的目录 
/opt  放置第三方的软件 
/root  系统管理员的家目录 
/srv  一些网络服务的数据文件目录 
/tmp  任何人均可使用的“共享”临时目录 
/proc  虚拟文件系统,例如系统内核、进程、外部设备及网络状态等 
/usr/local  用户自行安装的软件 
/usr/sbin  Linux 系统开机时不会使用到的软件/命令/脚本 
/usr/share  帮助与说明文件,也可放置共享文件 
/var  主要存放经常变化的文件,如日志 
/lost+found  当文件系统发生错误时,将一些丢失的文件片段存放在这里
2. 物理设备的命名规则

Linux中的硬盘设备一般以“/dev/sd”开头。例如设备名称“/dev/sda5”,其中“a”是硬盘的顺序号(a,b,c......表示),表示系统中同类接口中第一个被识别到的设备。“5”是分区的顺序号(以数字1,2,3.....表示)

  • 分区

硬盘设备是由大量的扇区组成的,每个扇区容量为512 byte。其中第一个扇区保存了主引导记录(Master Boot Record, MBR)和分区表信息。其中分区表为64 byte,每记录一个分区就需要16字节。因此,只能创建4个主分区。为了解决分区数不够的问题,可以将一个分区表的16字节空间指向另一个分区,从而产生一个扩展分区,在这个扩展分区中创建数个逻辑分区。通常情况下,用户会选择3个主分区加一个扩展分区的方法。

3. 硬盘的管理

通过虚拟机设置为linux虚拟机设备添加一块硬盘后,在/dev目录下将会按照命名规则生成一个硬盘设备文件。可以使用fdisk命令来对磁盘进行管理。命令参数包括:

m  查看全部可用的参数
n  添加新的分区 
d  删除某个分区信息 
l  列出所有可用的分区
t  改变某个分区的类型
p  查看分区信息 
w  保存并退出 
q  不保存直接退出  

使用fdisk 命令来管理/dev/sdb 硬盘设备。

[root@192 dev]# fdisk /dev/sdb
欢迎使用 fdisk (util-linux 2.23.2)。

更改将停留在内存中,直到您决定将更改写入磁盘。
使用写入命令前请三思。

Device does not contain a recognized partition table
使用磁盘标识符 0x473b5500 创建新的 DOS 磁盘标签。

命令(输入 m 获取帮助):p

磁盘 /dev/sdb:10.7 GB, 10737418240 字节,20971520 个扇区
Units = 扇区 of 1 * 512 = 512 bytes
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
磁盘标签类型:dos
磁盘标识符:0x473b5500

   设备 Boot      Start         End      Blocks   Id  System

命令(输入 m 获取帮助):n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
分区号 (1-4,默认 1):1
起始 扇区 (2048-20971519,默认为 2048):
将使用默认值 2048
Last 扇区, +扇区 or +size{K,M,G} (2048-20971519,默认为 20971519):+2G
分区 1 已设置为 Linux 类型,大小设为 2 GiB

命令(输入 m 获取帮助):w
The partition table has been altered!

Calling ioctl() to re-read partition table.
正在同步磁盘。

在上述步骤执行完毕之后,Linux 系统会自动把这个硬盘主分区抽象成/dev/sdb1 设备文件。我们可以使用 file 命令查看该文件的属性。

[root@192 dev]# file /dev/sdb1
/dev/sdb1: block special

接着我们使用mkfs命令在分区上创建指定的文件系统。例如我们创建xfs文件系统:

[root@192 dev]# mkfs.xfs /dev/sdb1
meta-data=/dev/sdb1              isize=512    agcount=4, agsize=131072 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=0, sparse=0
data     =                       bsize=4096   blocks=524288, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=1
log      =internal log           bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0

然后我们就可以挂载并使用存储设备了。mount命令可以把硬盘设备或分区与一个目录文件进行关联,然后就能在目录中看到硬件设备中的数据了。

[root@192 ~]# mkdir /newFS
[root@192 ~]# mount /dev/sdb1 /newFS/

需注意的是,使用mount 命令挂载的设备文件会在系统下一次重启的时候失效。如果想让这个设备文件的挂载永久有效,则需要把挂载的信息写入到配置文件中:

[root@192 ~]# vim /etc/fstab
...
/dev/sdb1                                  /newFS       xfs       defaults   0 0 

最后使用df -h 命令来查看挂载状态和硬盘使用量信息

[root@192 ~]# df -h
文件系统                 容量  已用  可用 已用% 挂载点
...
/dev/sdb1                2.0G   33M  2.0G    2% /newFS
  • LVM

在硬盘分好区后,再想修改硬盘分区大小就不容易了。而LVM 技术是在硬盘分区和文件系统之间添加了一个逻辑层,它提供了一个抽象的卷组,可以把多块硬盘进行卷组合并。这样一来,用户不必关心物理硬盘设备的低层架构和布局,就可以实现对硬盘分区的动态调整。

物理卷(Physical Volume)处于LVM中的最底层,可以将其理解为物理硬盘、硬盘分区或者RAID 磁盘阵列。卷组(Volume Group)建立在物理卷之上,一个卷组可以包含多个物理卷,而且在卷组创建之后也可以继续向其中添加新的物理卷。逻辑卷(Logical Volume)是用卷组中空闲的资源建立的,并且逻辑卷在建立后可以动态地扩展或缩小空间。这就是LVM 的核心理念。

你可能感兴趣的:(Linux本地文件存储)