Linux 之从硬件硬盘到文件系统的全面过渡

Linux 之从硬件硬盘到文件系统的全面过渡

前提引入

文件=内容+属性,这是从单个文件的角度。但是有很多文件,我们可以在宏观上把文件分为被打开的文件(在前面基础IO讲过)和没有被打开的文件;而被打开的文件在内存中方便管理,没有被打开的文件在磁盘里。

没有打来的文件肯定是很多的,那这么多的文件在磁盘中怎么被我们找到呢?从现阶段的认知,文件是一种目录结构,目录结构是树状的,需要路径(绝对、相对),文件存到磁盘上,最基本的诉求就是:就是被找到。而研究上面这些需求,要完成以特定的结构组织管理文件和帮我们找到文件就是文件系统做的事情!!!

1. 理解硬件

磁盘-服务器-机柜-机房

机械磁盘是计算机中唯一的一个机械设备磁盘--- 外设慢容量大,价格便宜

磁盘物理结构

磁盘存储结构

磁道是同心圆

扇区:是磁盘存储数据的基本单位,512字节,块设备

三片六面,六个磁头,磁头在传动臂的带动下,共进退!!!

磁盘写入的时候,是向柱面进行批量写入的!!!

如何定位一个扇区呢?可以先定位磁头(header)确定磁头要访问哪一个柱面(磁道)(cylinder)定位一个扇区(sector)文件 = 内容+属性 都是数据,无非就是占据那几个扇区的问题!能定位一个扇区了,能不能定位多个扇区呢?能

扇区是从磁盘读出和写入信息的最小单位,通常大小为 512 字节。磁头(head)数:每个盘片一般有上下两面,分别对应1个磁头,共2个磁头磁道(track)数:磁道是从盘片外圈往内圈编号0磁道,1磁道...,靠近主轴的同心圆用于停靠磁头,不存储数据柱面(cylinder)数:磁道构成柱面,数量上等同于磁道个数扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同圆盘(platter)数:就是盘片的数量磁盘容量=磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数细节:传动臂上的磁头是共进退的(柱面(cylinder),磁头(head),扇区(sector),显然可以定位数据了,这就是数据定位(寻址)方式之一,CHS寻址方式。📌 CHS寻址对早期的磁盘非常有效,知道用哪个磁头,读取哪个柱面上的第几扇区就可以读到数据了。但是CHS模式支持的硬盘容量有限,因为系统用8bit来存储磁头地址,用10bit来存储柱面地址,用6bit来存储扇区地址,而一个扇区共有512Byte,这样使用CHS寻址一块硬盘最大容量为256 * 1024 * 63 * 512B = 8064 MB(1MB = 1048576B)(若按1MB=1000000B来算就是8.4GB)

磁盘的逻辑结构

理解过程

磁带上面可以存储数据,我们可以把磁带“拉直”,形成线性结构。卷起来,就是同心圆,拉出来,就是一个线性结构!!!

那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在一起的磁带,那么磁盘的逻辑存储结构我们也可以类似于:

这样每一个扇区,就有了一个线性地址(其实就是数组下标),这种地址叫做LBA。

真实过程

一个细节:传动臂上的磁头是共进退的

柱面是一个逻辑上的概念,其实就是每一面上,相同半径的磁道逻辑上构成柱面。所以,磁盘物理上分了很多面,但是在我们看来,逻辑上,磁盘整体是由“柱面”卷起来的。

磁盘的真实情况是:磁道:某一盘面的某一个磁道展开:

即:一维数组柱面:整个磁盘所有盘面的同一个磁道,即柱面展开:

柱面上的每个磁道,扇区个数是一样的,可以理解成二维数组。整个盘:

整个磁盘不就是多张二维的扇区数组表(三维数组?)所有,寻址一个扇区:先找到哪一个柱面(Cylinder) ,在确定柱面内哪一个磁道(其实就是磁头位置, Head),在确定扇区(Sector),所以就有了CHS。之前学过C/C++的数组,在我们看来,其实全部都是一维数组:

所以,每一个扇区都有一个下标,我们叫做LBA(Logical Block Address)地址,其实就是线性地址。所以怎么计算得到这个LBA地址呢?LBA,1000,CHS 必须要! LBA地址转成CHS地址,CHS如何转换成为LBA地址。OS只需要使用LBA就可以了!!LBA地址转成CHS地址,CHS如何转换成为LBA地址。谁做啊??磁盘自己来做!固件(硬件电路,伺服系统)

CHS && LBA地址

CHS转成LBA:磁头数*每磁道扇区数 = 单个柱面的扇区总数LBA = 柱面号C*单个柱面的扇区总数 + 磁头号H*每磁道扇区数 + 扇区号S - 1即:LBA = 柱面号C*(磁头数*每磁道扇区数) + 磁头号H*每磁道扇区数 + 扇区号S - 1扇区号通常是从1开始的,而在LBA中,地址是从0开始的柱面和磁道都是从0开始编号的总柱面,磁道个数,扇区总数等信息,在磁盘内部会自动维护,上层开机的时候,会获取到这些参数。LBA转成CHS:柱面号C = LBA // (磁头数*每磁道扇区数)【就是单个柱面的扇区总数】磁头号H = (LBA % (磁头数*每磁道扇区数)) // 每磁道扇区数扇区号S = (LBA % 每磁道扇区数) + 1"//": 表示除取整所以:从此往后,在磁盘使用者看来,根本就不关心CHS地址,而是直接使用LBA地址,磁盘内部自己转换。所以:从现在开始,磁盘就是一个 元素为扇区 的一维数组,数组的下标就是每一个扇区的LBA地址。OS使用磁盘,就可以用一个数字访问磁盘扇区了。

2. 引入文件系统

引入"块概念"

其实硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,其实是不会一个个扇区地读取,这样 效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。 硬盘的每个分区是被划分为一个个的”块”。一个”块”的大小是由格式化的时候确定的,并且不可 以更改,最常见的是4KB,即连续八个扇区组成一个”块”。”块”是文件存取的最小单位。

注意:

• 磁盘就是一个三维数组,我们把它看待成为一个"一维数组",数组下标就是LBA,每个元素都是扇 区

• 每个扇区都有LBA,那么8个扇区一个块,每一个块的地址我们也能算出来。

• 知道LBA:块号=LBA/8 • 知道块号:LAB=块号*8+n.(n是块内第几个扇区)

引入"分区"概念

其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有一块磁盘并且将 它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的一种格式化。但是Linux的设备 都是以文件形式存在,那是怎么分区的呢?柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。 此时我们可以将硬盘上的柱面(分区)进行平铺,将其想象成一个大的平面,如下图所示:

注意:柱面大小一致,扇区个位一致,那么其实只要知道每个分区的起始和结束柱面号,知道每一个柱面多少个扇区,那么该分区多大,其实和解释LBA是多少也就清楚了.

引入"inode"概念

之前我们说过 文件=数据+属性 ,我们使用ls -l 的时候看到的除了看到文件名,还能看到文件元数据(属性)。

每行包含7列:模式硬链接数文件所有者组大小最后修改时间文件名ls -l读取存储在磁盘上的文件信息,然后显示出来

其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息

hu@hcss-ecs-6579:~/test$ stat code.c File: code.c Size: 1744 Blocks: 8 IO Block: 4096 regular file Device: fc01h/64513d Inode: 547045 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1002/ hu) Gid: ( 1002/ hu) Access: 2025-08-13 16:25:18.011548344 +0800 Modify: 2025-08-13 16:25:18.011548344 +0800 Change: 2025-08-13 16:25:18.015548373 +0800 Birth: 2025-08-13 16:25:18.011548344 +0800 hu@hcss-ecs-6579:~/test$ 

到这我们要思考一个问题,文件数据都储存在”块”中,那么很显然,我们还必须找到一个地方储存文件的元信息(属性信息),比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。

每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。为了能解释清楚inode,我们需要是深入了解一下文件系统。注意:Linux下文件的存储是属性和内容分离存储的Linux下,保存文件属性的集合叫做inode,一个文件,一个inode,inode内有一个唯一的标识符,叫做inode号所以一个文件的属性inode长什么样子呢?

struct ext2_inode { __le16 i_mode; /* File mode */ __le16 i_uid; /* Low 16 bits of Owner Uid */ __le32 i_size; /* Size in bytes */ __le32 i_atime; /* Access time */ __le32 i_ctime; /* Creation time */ __le32 i_mtime; /* Modification time */ __le32 i_dtime; /* Deletion Time */ __le16 i_gid; /* Low 16 bits of Group Id */ __le16 i_links_count; /* Links count */ __le32 i_blocks; /* Blocks count */ __le32 i_flags; /* File flags */ union { struct { __le32 l_i_reserved1; } linux1; struct { __le32 h_i_translator; } hurd1; struct { __le32 m_i_reserved1; } masix1; } osd1; /* OS dependent 1 */ __le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ __le32 i_generation; /* File version (for NFS) */ __le32 i_file_acl; /* File ACL */ __le32 i_dir_acl; /* Directory ACL */ __le32 i_faddr; /* Fragment address */ union { struct { __u8 l_i_frag; /* Fragment number */ __u8 l_i_fsize; /* Fragment size */ __u16 i_pad1; __le16 l_i_uid_high; /* these 2 fields */ __le16 l_i_gid_high; /* were reserved2[0] */ __u32 l_i_reserved2; } linux2; struct { __u8 h_i_frag; /* Fragment number */ __u8 h_i_fsize; /* Fragment size */ __le16 h_i_mode_high; __le16 h_i_uid_high; __le16 h_i_gid_high; __le32 h_i_author; } hurd2; struct { __u8 m_i_frag; /* Fragment number */ __u8 m_i_fsize; /* Fragment size */ __u16 m_pad1; __u32 m_i_reserved2[2]; } masix2; } osd2; /* OS dependent 2 */ }; /* * Constants relative to the data blocks */ #define EXT2_NDIR_BLOCKS 12 #define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS #define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1) #define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1) #define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1) 备注:EXT2_N_BLOCKS = 15

再次注意:文件名属性并未纳入到inode数据结构内部inode的大小一般是128字节或者256,我们后面统一128字节任何文件的内容大小可以不同,但是属性大小一定是相同的

结束语

 我们已经知道硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,读取的基本单位是”块”。“块”又是硬盘的每个分区下的结构,难道“块”是随意的在分区上排布的吗?那要怎么找到“块”呢?还有就是上面提到的存储文件属性的inode,又是如何放置的呢?⽂件系统就是为了组织管理这些的!!!下节小编将对文件系统进行讲解,欢迎大佬们评论区发表见解!!!

Read more

深入解析C/C++标量初始化警告:braces around scalar initializer的根源与修复

1. 什么是"braces around scalar initializer"警告? 当你用C或C++写代码时,可能会遇到这样的警告:"warning: braces around scalar initializer"。这个警告的意思是你在初始化一个标量(scalar)变量时,不必要地使用了花括号{}。 标量变量指的是那些简单的、不可再分的变量类型,比如: * 基本数据类型:int, float, double等 * 指针类型:int*, char*等 * 枚举类型 举个例子,下面这行代码就会触发这个警告: int x = {5}; // 警告:标量初始化使用了不必要的花括号 而正确的写法应该是: int x = 5; // 正确:直接使用值初始化标量 这个警告通常出现在GCC和Clang编译器中,特别是当你开启了-Wall或-Wextra警告选项时。

By Ne0inhk

C++中std::string的弱点:你可能未曾注意到的缺点

性能方面的局限 由于std::string是动态大小的字符串,它需要在运行时动态分配内存来存储字符串的内容。在字符串长度变化时,要频繁地进行内存分配和释放操作,导致一定的性能开销。 1. 频繁的内存分配和释放操作可能导致内存碎片的产生,内存空间的利用率降低。 2. 内存分配的成本比较高,特别是在频繁进行小块内存分配时,会增加系统开销。 3. 频繁地进行内存分配和释放操作会导致性能下降,尤其是在大规模数据处理时。 当字符串长度超过当前分配的内存空间时,std::string需要进行动态内存重分配,这会带来一定的性能开销。当字符串长度超过当前分配的内存空间时,std::string需要进行内存重分配,涉及到申请新的内存空间、拷贝数据、释放旧内存等操作,导致性能开销。 std::string 的性能局限之一是字符串拼接的效率问题。当对多个字符串进行拼接操作时,使用加法操作符或者append()方法在每次拼接时都需要进行内存重新分配和复制,这会导致较高的性能开销。特别是在频繁拼接大量字符串时,这种操作会导致大量的内存重分配和数据复制,从而影响程序的性能表现。 三、可变性带来的

By Ne0inhk

JavaQuestPlayer终极指南:简单快速的QSP游戏完整解决方案

JavaQuestPlayer终极指南:简单快速的QSP游戏完整解决方案 【免费下载链接】JavaQuestPlayer 项目地址: https://gitcode.com/gh_mirrors/ja/JavaQuestPlayer 想要轻松畅玩各类QSP游戏却苦于复杂的配置过程?JavaQuestPlayer为你提供了最简单快捷的解决方案,这款基于Java开发的智能游戏运行器让新手也能快速上手,享受流畅的游戏体验。无论你是游戏爱好者还是开发者,都能在这里找到适合你的运行方式。 🎯 从零开始:快速入门指南 环境准备与项目获取 首先确保你的系统已安装Java运行环境,支持Oracle JDK1.8或OpenJDK JDK 11及以上版本。通过以下命令获取项目: git clone https://gitcode.com/gh_mirrors/ja/JavaQuestPlayer 项目结构清晰,主要源码位于src/main/java/com/baijiacms/qsp目录下,包含游戏控制器、资源管理、核心逻辑等模块。 两种运行模式详解 桌面应用模式提供原生的游戏体验

By Ne0inhk
计算机毕设java共享单车租借网络平台设计 基于SpringBoot的城市公共自行车智能租赁与调度系统 Java Web共享出行工具在线预约与运营管理平台

计算机毕设java共享单车租借网络平台设计 基于SpringBoot的城市公共自行车智能租赁与调度系统 Java Web共享出行工具在线预约与运营管理平台

计算机毕设java共享单车租借网络平台设计g4j999(配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。 随着城市化进程的加速和绿色出行理念的普及,共享单车作为解决"最后一公里"出行的重要方式,已成为现代城市交通体系的重要组成部分。然而,传统的单车租赁管理存在车辆分布不均、租借流程繁琐、费用结算不透明、车辆维护滞后等问题,难以满足用户便捷出行和运营商高效管理的需求。在智慧城市建设和共享经济发展的背景下,构建一套智能化、网络化的单车租借平台,能够实现车辆的精准调度、租借流程的自动化处理以及运营数据的实时分析,从而提升用户体验,优化资源配置,推动城市绿色交通的可持续发展。 本系统采用Java作为开发语言,基于SpringBoot框架构建,结合MySQL数据库和B/S架构设计,旨在打造一个功能完善、操作便捷、高效稳定的共享单车租借网络平台。系统核心功能模块涵盖以下方面: 用户管理模块:实现用户账号注册、登录、个人信息维护,包含用户账号、密码、用户姓名、头像、性别、手机号码、身份证号等基础信息管理,支持密码修改与账户

By Ne0inhk