1、理解硬件1.1 磁盘这里的磁盘指的是机械磁盘并不是笔记本上的SSD固态硬盘机械磁盘是计算机中唯一的一个机械设备磁盘属于外设速度慢磁盘的物理结构磁盘有两个面磁头一面一个磁头与高速旋转的盘面不接触磁盘存储数据的原理是磁盘表面分布数以亿计的微型磁铁每个磁畴都有固定的南北两极北极朝上代表二进制1南极朝上代表二进制0磁盘存储0、1的本质就是控制磁畴的磁极朝向。盘片的正反两面都可以存储数据磁头相当于读写笔负责读取盘片数据修改盘片数据工作时盘片高速旋转磁头悬浮摆动二者配合完成读写通过电磁感应改变盘片磁畴的磁极方向完成数据写入通过感应磁极状态完成数据读取1.2 磁盘的存储结构每一个磁盘由多个磁道构成每一个磁道有多个扇区组成扇区是磁盘读写的最小物理单位标准大小为512字节如果仅需修改扇区内少量数据需先将512字节扇区数据读取到内存修改完成后再整体写回磁盘。因此磁盘被称为块设备物理形态上磁盘内圈扇区弧长更短、外圈扇区弧长更长但存储容量完全一致均为512字节柱面柱面是磁盘多盘面堆叠形成的逻辑概念所有盘面中半径完全相同的磁道上下堆叠形成一个圆柱形空间这个整体就称为柱面磁头运动目的是定位磁道或柱面盘面旋转目的是定位扇区所有的磁头都连在一根机械臂杆上所有磁头共进退所有、同步摆动所有盘面同半径磁道可同时被精准定位2、磁盘的抽象逻辑如何定位一个扇区可以先定位磁头header确定磁头要访问哪⼀个柱面磁道cylinder定位⼀个扇区sectorCHS地址定位磁盘容量磁头数 × 磁道柱面数 × 每道扇区数 × 每扇区字节数磁盘的逻辑存储结构可以类似于这样每一个扇区就有了一个线性地址这种地址叫做LBALBA本质就是数组下标但真实情况是磁盘在进行访问时先确定我们要访问哪一个柱面由于磁盘整体是由“柱面”卷起来的所以磁盘真实的情况是磁道某一盘面的某一磁道展开即一维数组柱面整个磁盘所有盘面的同一个磁道即柱面展开整个磁盘所有盘面的同一个磁道柱面上的每个磁道扇区个数是一样的即二维数组整盘整张磁盘就是多张二维数组的扇区数组表组成的三维数组表所以寻址一个扇区先找到哪一个柱面Cylinder再确定柱面在哪一个磁道磁头位置Head再确定扇区Sector所以就有了CHSCHS LBA地址LBA转成CHS地址柱面号CLBA//磁头数*每磁道扇区数【就是单个柱面的扇区总数】磁头号H LBA % 磁头数*每磁道扇区数 // 每磁道扇区数扇区号S LBA % 每磁道扇区数 1“//”表示除取整eg磁盘参数四个磁头每个磁道四个扇区共两个柱面目标LBA地址18步骤1单柱面扇区总数 4 × 4 16步骤2柱面号 C 18 // 16 1目标扇区在1号柱面步骤3柱面内偏移 18 % 16 2步骤4磁头号 H 2 // 4 0目标扇区在0号磁头对应盘面步骤5扇区号 S 2 % 4 1 3目标扇区为3号扇区最终转换结果LBA18 → CHS(1,0,3)CHS转成LBA磁头数*每磁道扇区数 单个柱面的扇区总数LBA 柱面号C单个柱面的扇区总数 磁头号H每磁道扇区数 扇区号S - 1即LBA 柱面号C*(磁头数每磁道扇区数) 磁头号H每磁道扇区数 扇区号S - 1扇区号通常是从1开始的而在LBA中地址是从0开始的柱面和磁道都是从0开始编号的总柱面磁道个数扇区总数等信息在磁盘内部会自动维护上层开机的时候会获取到这些参数3、文件系统3.1 块硬盘是典型的“块”设备操作系统读取硬盘数据的时候其实是不会一个个扇区地读取这样效率太低而是一次性连续读取多个扇区即一次性读取一个”块”数据块是文件系统进行IO操作的最小逻辑单位标准大小为4KB即8个512字节的扇区块号和LBA地址可以相互转换知道LBA块号 LBA/8知道块号LAB块号*8 n.n是块内第几个扇区分区为了方便对磁盘进行管理操作系统首先将其划分成若干个逻辑分区如C盘、D盘类似于将一个大国划分为多个省份进行治理分组在Linux文件系统中每个分区会被进一步划分成多个块组Block Group这是一种分而治之的管理策略每个组块内部结构相同包含独立的元数据和数据区域只要掌握了一个块组的管理方法就可以通过复制CtrlC, CtrlV的方式管理所有块组进而管理整个分区和磁盘在完成分区和分组后向每个块组中写入文件系统所需的管理信息的过程叫格式化块组的结构Super Block存放文件系统File System全局的信息记录的信息主要有bolck 和 inode的总量未使用的block和inode的数量一个block和inode的大小最近一次挂载的时间最近一次写入数据的时间最近一次检验磁盘的时间等其他文件系统的相关信息注意不是每一个组都有Super Block为了防止Super Block被破坏Super Block被备份存入了不同分组中GDT块组描述符表描述块组属性信息整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储一个块组的描述信息如在这个块组中从哪里开始是inode Table从哪⾥开始是Data Blocks空闲的inode和数据块还有多少个等等Block Bitmap位图记录Data Block中哪个位置的数据块被占用值为1表示已分配0表示空闲Inode Bitmap位图用于跟踪Inode Table区域中每个inode的使用情况。比特位的位置对应inode编号值为1表示已分配0表示空闲。InodeInode是一个固定大小通常为128字节的结构体其中包含文件类型、权限、拥有者、大小、时间戳等有关文件属性的信息注意文件名不属于文件inode的属性Inode Tableinode表每一个inode存放单个文件的属性每个incode有全局唯一incode编号一个 4KB 块可以存放 32 个inode最前面的数字就是inode编号Data Block存放文件内容文件内容会根据其大小被分割并存储在一个或多个4KB的数据块中数据块占据了块组中绝大部分的物理空间每个块都有唯一的块号所以文件 文件内容 文件属性文件内容存放在Data Block中文件属性存放在Inode中3.2 文件的创建机制创建空文件时内核在内存中构建inode结构体填充属性不含文件名将inode bitmap载入内存遍历bit位查找bit0空闲的位置分配inode编号将内存中的inode写入inode table对应位置如果想文件中写入数据则在block bitmap中申请数据块bit值为1块号记录到inode映射表中将用户数据写入这个data block3.3 删除文件机制删除文件并非真正删除数据而是将文件对应的inode位Inode Bitmap和数据块位Block Bitmap从1置为0。这个过程非常快因为只是修改了少量数据3.4 查找文件和查看文件ls -l通过文件名找到inode编号 → 查inode table获取属性 → 打印cat通过文件名找到inode编号 → 读取inode中的块号列表 → 依次读取data block内容并输出3.5 目录上面的操作全部都基于inode编号但用户操作一般来讲全部写文件名不用inode号而且inode结构体不存储文件名原因是什么因为Linux下一切皆文件所以目录也是文件拥有自己的inode和data block文件 文件内容 文件属性目录文件的属性储存在inode中目录文件的data block中存储的是一张映射表以“文件名 ↔ inode编号”的映射对形式存在所以文件名保存在它的父目录的data block中不保存在自己的inode中利用代码查看文件名与inode映射关系#includestdio.h#includestring.h#includestdlib.h#includedirent.h#includesys/types.h#includeunistd.hintmain(intargc,char*argv[]){if(argc!2){fprintf(stderr,Usage: %s directory\n,argv[0]);exit(EXIT_FAILURE);}DIR*diropendir(argv[1]);// 系统调用自行查阅if(!dir){perror(opendir);exit(EXIT_FAILURE);}structdirent*entry;while((entryreaddir(dir))!NULL){// 系统调用自行查阅// Skip the . and .. directory entriesif(strcmp(entry-d_name,.)0||strcmp(entry-d_name,..)0){continue;}printf(Filename: %s, Inode: %lu\n,entry-d_name,(unsignedlong)entry-d_ino);}closedir(dir);return0;}4、重新理解inode编号和块号ext 文件系统磁盘会划分多个块组 (block group)包含super block、GDT、inode bitmap、block bitmap、inode table、data blockinode编号和块号不是组内有效而是整个分区全局有效且唯一但不能跨分区在一个分区内部一个文件系统内部有多少inode有多少数据块都是固定的都是提前设计好的已知inode编号可以找到数据块号找到对应的group在磁盘空间充足的条件下可能也无法创建新文件因为如果1个inode对应多个Data Block数据块1:200或1:300当inode还有空余时数据块可能已经被用完所以有时Inode Bitmap还有空余的位置但Data Block已经用完不能创建新文件5、路径解析和缓存机制5.1 路径解析如果我想访问/home/me/lesson11/code.ccode.c的文件名保存在lesson11中要拿到 test.c 的 inode必须先打开lesson11目录lesson11的目录要保存在父目录me的数据块中要拿到 lesson11 的 inode必须先打开me的目录要拿到 me 的 inode必须先打开 home 的目录home的目录保存在根目录/根目录会被Linux系统自动打开所以当我们要访问任何文件的时候Linux内核都要为我们做从根目录/开始的路径解析所以访问文件必须要有路径5.2 缓存机制但如果每次访问文件都路径解析、全部读磁盘 IO性能很差。内核引入dcachedentry cache 目录项缓存同时Linux要管理用户访问过的打开过的路径节点管理需要先描述再组织所以引入struct dentry管理为避免每次访问都进行磁盘 I/OLinux 内核维护一棵 dentry 多叉树缓存已访问过的路径节点struct dentry为内核缓存路径每一个被访问的文件/目录在内核中都会生成一个dentry对象dentry的成员d_name文件名d_inode指针指向内存中的 inode 对象d_parent指向父 dentry链表兄弟节点链表LRU 链表这棵多叉树会动态变化文件创建删除等操作会是dentry增加或删除节点不是只有目录才有dentry每一个访问的文件都要有dentry普通文件是 dentry 树的叶子节点搜索的时候第一次访问会比较慢第二次访问会比较快因为第一次打开文件时内核要从根开始逐级在磁盘查找每一级路径磁盘 IO 慢卡顿同时把每一级路径全部生成dentry 放到内存缓存第二次访问同一个路径会直接在内存dentry缓存树中查找不需要读磁盘注意dentry 缓存的目的是把路径名→inode 的解析结果缓存减少磁盘访问解析相对路径时操作系统会以进程当前工作目录的 dentry 作为查找起点再向下遍历 dentry 树5.3 用户调用open的过程调用open(/home/me/test.c, ...)系统调用完整流程从根目录或者相对路径的 cwd开始遍历 dcache缺失节点就读磁盘目录块生成 dentry加入 dcache 缓得到目标文件对应的struct dentry从 dentry 的d_inode拿到内存 inode内核创建struct file对象包含指向目标的dentry指针当前读写位置等在进程的文件描述符表分配一个空闲 fd 下标指向这个struct file返回 fd 给用户5.4 路径由谁提供访问任何文件Linux内核都是先做路径解析和 dentry 目录项缓存那么路径是由谁提供的访问文件都是指令/工具访问本质是进程访问进程有CWD为进程提供路径而CWD默认是由bash提供bash来自系统和环境变量的共同作用用户open文件提供了路径可是最开始的路径从哪里来所以Linux为什么要有根目录根目录下为什么要有那么多缺省目录用户为什么要有家目录用户可以新建目录上面所有行为本质就是在磁盘文件系统中新建目录文件。而用户新建的任何文件都在用户或者系统指定的目录下新建这就是天然路径系统用户共同构建Linux路径结构6、存储大文件问题场景分区200GB分为20个块组每组10GB如果想存50GB的大文件单个块组放不下怎么办在inode结构体中有数组一共15个元素有指针保存磁盘块号用于关联文件数据块前12个元素0~11直接指针直接指向数据块支持最多48KB文件第13个元素一级间接该位置保存一个索引块的块号这个索引块不存业务文件内容这个 4KB 块里面全部存放其他数据块的块号可以存储1024个块号额外容纳4MB文件第14个元素二级间接这个索引块里面存的是一级间接索引块的块号共可支持 1024 × 4MB ≈ 4GB 文件第15个元素三级间接三层索引嵌套可存储 1024 × 4GB ≈ 4TB 文件本质是inode的15项构成的一颗多叉树索引块类似根节点通过索引块间接扩展支持巨型文件7、硬链接创建命令ln原文件 硬链接文件名#示例 ln log.txt log_hard硬链接后两个文件的inode一样2表示硬链接数记录指向该inode的文件名数量硬链接的本质是在当前目录的数据块中增加一个“文件名 → inode”的映射关系不创建新的inode多个不同文件名可以指向同一个 inode、同一套磁盘数据块硬链接应用场景轻量备份不需要拷贝完整文件数据只新增文件名映射支撑Linux目录结构.和..由上图可以看出lesson12中.的indoe和code.c中..的inode相同所以.为当前目录的硬链接..为上级目录的硬链接限制用户不能给目录创建硬链接如果允许用户给目录创建硬链接会制造环形路径遍历目录会造成死循环系统保留的.和…由内核特殊处理8、软链接创建命令ln-s目标文件路径 软链接名#示例 ln -s test.c test_soft由上图可以看出软链接是独立文件拥有自己全新的 inode、自己的数据块其内容为目标文件的路径字符串。访问软链接时系统读取软链接内部保存的目标路径自动跳转打开目标文件软链接应用场景在~lesson13/test/dira/code中打印hello world并编写成可执行程序但每次访问时都要写入路径效率低所以使用软链接可以简化深层路径执行利用软链接此时只使用软链接将复杂的路径~lesson13/test/dira/code变成了code_soft提高了效率删除软硬链接可以用指令unlink code_soft