一、 机械硬盘
1.1 工作原理
写入时,磁头线圈加电,在周围产生磁场,磁化其下的磁性材料;电流的方向不同,所以磁场的方向也不同,可以表示 0 和 1 的区别。——“电生磁”。
读取时,磁头线圈(不加电)切割磁场线产生感应电流,磁性材料的磁场方向不同,所以产生的感应电流方向也不同。可以表示出 0 和 1 的区别。——“磁生电”。
硬盘保存数据是有时间限制的,当硬盘消磁后,存储在硬盘里的数据就读不出来了。
1.2 扇区
硬盘的
基本存储单位 为
扇区 (
sector ),每个扇区一般为512byte。一个硬盘往往会有多个
盘片 ,每个盘片分成两面,每面按照同心圆划分为若干个
磁道 ,每个磁道分为若干个
扇区 。
图1 柱面、磁头 图2-1 磁道、扇区(合理) 图2-2 磁道、扇区(不合理) 如图1 所示:具有相同磁道编号的同心圆组成
柱面 。那么需要知道某个扇区的具体位置,需要知道
柱面号 、
磁头号 、
扇区号 。每个盘面同心圆的周长不一样。如图2-2 所示,如果按照每个磁道都拥有相同数量的扇区,那么外围的磁道密度肯定比内圈更加稀疏。但是如果不同的磁道扇区数量不同,计算起来就十分麻烦。产商们最终选择了图2-1 所示的方案。为了屏蔽这些复杂的硬件细节,现代的硬盘普遍使用一种叫做
LBA (
logical block address )的方式,即整个硬盘中的所有扇区从0开始编号,一直到最后一个扇区。这个扇区编号叫做逻辑扇区号。
在最外圈从0开始编号 ,缘由将在1.3 节给出。
逻辑扇区号抛弃了所有复杂的磁道、盘面之类的概念。当我们给出一个逻辑的扇区号时,硬盘的
电子设备 会将其转换成实际的盘面、磁道等这些位置。
在Linux操作系统中,要读取这个文件的前4096个字节时,会使用一个read系统调用来实现。文件系统收到 read 请求之后,判断出文件的前 4096 个字节位于磁盘的 1000 号逻辑扇区到 1007号逻辑扇区。然后文件系统就向磁盘驱动发出一个读取逻辑扇区为 1000 号开始的 8 个扇区的请求,磁盘驱动程序收到这个请求以后就向硬盘发出硬件命令。向硬盘发送 I/O 命令的方式有很多种,
最为常见的一种 就是通过读写 I/O 寄存器来实现。在 x86 平台上,共有65 536 (
12位 )个硬件端口寄存器,不同的硬件被分配到不同的 I/O 端口地址。CPU 提供了两条专门的指令 “in” 和 “out” 来实现对硬件端口的读写。
对 IDE 接口来说,它有两个通道,分别为 IDE0 和 IDE1 ,每个通道上可以连接两个设备,分别为 Master 和 Slave ,一个 PC 中最多可以有 4 个 IDE 设备。假设我们的文件位于 IDE0 的 Master 硬盘上,这也是正常情况下硬盘所在的位置。在 PC 中,IDE0 通道的 I/O 端口地址是 0x1F0~0x1F7 及 0x376~0x377。通过读写这些端口地址就能和 IDE 硬盘进行通信。这些端口的作用和操作方式十分复杂,我们就以实现读取 1000 号逻辑扇区开始的 8 个扇区为例:
第0x1F3~0x1F6 4个字节的端口地址是用来写入 LBA 地址的,那么 1000 号的逻辑扇区的 LBA 地址为 0x0000 03E8,所以我们需要往0x1F3、0x1F4 写入 0x00,往 0x1F5 写入0x03,往 0x1F6 写入0xE8。
0x1F2 这个地址是用来写入命令所需要读写的扇区数。比如读取 8 个扇区即写入 8。
0x1F7 这个地址是用来写入要执行的操作的命令码,对于读取操作来说,命令字为 0x20。所以我们要执行的命令为:
out 0x1F3, 0x00
out 0x1F4, 0x00
out 0x1F5, 0x03
out 0x1F6, 0xE8
out 0x1F2, 0x08
out 0x1F7, 0x20
硬盘收到这个命令以后,它就会执行相应的操作,并且将数据读取到事先设置好的内存地址中(这个地址也是通过类似的命令方式设置的)。
1.3 操作系统
硬盘的磁道定义是从外圈往内圈排顺序,最外圈为零磁道,用于存放引导信息。操作系统也是从最外圈开始安装的。外圈磁道相对内圈磁道
更长 ,
读写数据更稳定 。
1.4 块
文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(
Sector )。每个扇区储存512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个一个扇区进行读取——效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(
block )。问题就来了!需要在效率和磁盘利用率之间做一个折中。
由多个扇区组成的"块",是文件存取的最小 单位。
文件数据都储存在"块"中,还必须找到一个地方储存文件的
元信息 (metadata, 就是文件的"属性, 描述信息"),如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode(identifier),中文译名为"索引节点"。
图3 磁盘有无可用空间的因素
磁盘格式化创建
文件系统 时会生成一定数量的inode和block。磁盘空间是否满了,是由两项参数决定的:inode满或是block满,任何一个满了都不能存放数据。
二、 深入理解inode
2.1 概述
disk(磁盘) memeory(内存)
Linux把inode分为两种方式保存,一种是在硬盘中的inode(d_inode),一种是在内存中的inode(m_inode)。m_inode除了
完全包含 d_inode中的字段之外还有一些专门的字段。
图4 目录项结构 如图4 所示:每个文件的目录项存储在该文件
所属目录 中。inode是文件的
唯一 标识,文件名和inode的对应关系存放在
上一级 目录的block中;inode所含字段包含指向文件block的指针和文件的属性等,通过block获得文件数据。
图5 文件存储结构 如图5 所展示的那样,通过inode访问步骤如下:
在目录文件中找到文件名对应的inode编号;如图3 所示;
利用inode号,获取inode信息;
根据inode信息,找到文件数据所在的block,读出数据。
图6 访问流程
2.2 inode所含字段
图7 inode结构示意图 如图 7所示:
字节数。
UID、GID。
读、写、执行权限(w-r-x)。
ctime:inode最后修改时间;mtime:文件内容最后变动时间;atime:文件最后打开时间。
链接数:有多少文件名指向这个inode。
块(block)的位置(指向文件block的指针)。
2.3 inode大小
inode会消耗硬盘空间。硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是
数据区 (存放文件数据);另一个是
inode区 (inode table)(存放inode)。
每个inode节点的大小,一般是128byte或256byte。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。
2.4 d_inode
struct d_inode
{
unsigned short i_mode;//I结点的模式——如读、写、执行权限。
//i_mode一共10位,第一位表明结点文件类型,后9位依次为:I结点所有者、所属组成员、其他成员的权限(权限有读写执行三种)。
unsigned short i_uid; //I结点的user id。
unsigned long i_size; //I结点文件的大小。
unsigned long i_time; //I结点创建时间。
unsigned char i_gid; //I结点的组id。
unsigned char i_nlinks; //表明有多少进程链接到此结点。
unsigned short i_zone[9]; //文件数据对应在磁盘上的位置。
}
这里要说明一点:由于在Linux0.01内核中,数据最终是存放在磁盘上的。而磁盘存放数据的单位是块(block),大小为1KB。对于小于 7K 的文件,文件信息可直接反映在inode中。(I结点字段 i_zone 的前7个元素来存放前7块数据在磁盘上的地址)。对于大文件,inode 通过利用间接块的方式来支持。i_zone[7] 存放的是一级间接块的地址(一级间接块指向512个数据块),i_zone[8]存放的是二级间接块的地址(二级间接块指向512个一级间接块)。
图8 理解进程PCB
正如图8 所展示的那般:Linux最大可以支持(7+512+512×512)KB ≈ 256.51MB 大小的文件。
2.5 m_inode
m_inode 是存放在内存中的I结点,会比 d_inode 多一些字段,具体字段如下:
struct m_inode
{
unsigned short i_mode;
unsigned short i_uid;
unsigned long i_size;
unsigned long i_mtime;
unsigned char i_gid;
unsigned char i_nlinks;
unsigned short i_zone[9];
//以下是m_inode特有的字段
struct task_struct* i_wait; //task_struct是进程pCB结构。I结点的等待队列。
unsigned long i_atime; //文件上次访问时间。
unsigned long i_ctime; //I结点修改时间,注意和i_mtime区别。
unsigned short i_dev; //表明此结点所属的设备号。
unsigned short i_num; //I结点的结点号。
unsigned short i_count; //I结点被打开次数,主要用于判断文件是否共享!
unsigned char i_lock; //判断I结点是否被锁住。
unsigned char i_dirt; //判断I结点是否需要写回磁盘。
unsigned char i_pipe; //判断I结点是否为管道文件。
unsigned char i_mount; //如果有文件系统安装在此结点上,则置此位。
//以下字段Linux0.01版本没有用到。
unsigned char i_seek; //在lseek调用时置此位。
unsigned char i_update;
};
2.6 软链接、硬链接
正如图1 所展示的,通过文件名(带路径,不带路径极可能重复)在该文件的目录文件中找到其对应的目录项,文件名和inode号是唯一对应关系。凭着inode号在inode table找出对应的inode结点。
图9 软硬链接示意图 正如图9 所展示的,硬链接hardlink.c 和原文件helloa.c 指向都inode1,不管硬链接hardlink.c还是原文件helloa.c,都可以根据inode1访问block块;软链接softlink.c 指向inode2,通过inode2访问链接文件(该文件主要内容是原文件文件名),通过链接文件获得文件名,通过文件名获得inode1;根据inode1的信息,访问block块。链接文件和原文件是
依存 的关系。
文件是在磁盘上存放的,删除文件并不是真正删除磁盘上的文件,而是将block块对应的inode链接数减一,同时在目录文件中删除目录项。inode引用计数减到0时也不会真正去删除文件,而是等待新文件内容将其覆盖。
软、硬链接不同点:
硬链接原文件和新文件的inode编号一致 。而软链接不一样。
对原文件删除,会导致软链接不可用,而硬链接不受影响。这就是第一条性质导致的。
软、硬链接相同点:
对原文件的修改,软、硬链接文件内容也一样的修改,因为都是指向同一个文件内容的。