56、磁盘基础全解析

磁盘基础全解析

1. 磁盘存储系统概述

磁盘存储系统在计算机中扮演着至关重要的角色,它涉及数据的物理分区、文件级别的数据访问以及文件名到物理存储的映射。在不同层面,磁盘存储有不同的描述方式:硬件层面用盘片、面、磁道、柱面和扇区描述;系统 BIOS 层面用簇和扇区描述;操作系统层面用目录和文件描述。

磁盘访问存在不同的层次:
- 磁盘控制器固件 :处于最底层,使用智能控制芯片为特定品牌和型号的磁盘驱动器规划磁盘几何结构(物理位置)。
- 系统 BIOS :提供一组低级功能,供操作系统执行扇区读取、扇区写入和磁道格式化等任务。
- 操作系统 API :提供一系列 API 函数,用于打开和关闭文件、设置文件属性、读取和写入文件等服务。

graph LR
    A[操作系统 API] --> B[系统 BIOS]
    B --> C[磁盘控制器固件]

用户级汇编语言程序在 MS - DOS、Windows 95、98 和 Millennium 系统下可直接访问系统 BIOS,可用于存储和检索非常规格式的数据、恢复丢失的数据或对磁盘硬件进行诊断。而在较新的 32 位 Windows 版本中,用户级程序只能使用 Win32 API 访问磁盘系统,以保障系统安全,只有最高权限级别的设备驱动程序才能绕过此规则。

2. 磁道、柱面和扇区

典型的硬盘由多个盘片安装在以恒定速度旋转的主轴上组成。每个盘片表面上方有一个读写头,用于记录磁脉冲。读写头作为一个整体,以小步长向中心和边缘移动。

  • 磁道 :磁盘表面被格式化为不可见的同心带,称为磁道,数据以磁性方式存储在磁道上。例如,一个典型的 3.5 英寸硬盘可能包含数千个磁道。将读写头从一个磁道移动到另一个磁道称为寻道,平均寻道时间是衡量磁盘速度的一种方式,另一种是每分钟转数(RPM),通常为 7200。磁盘的最外侧磁道是磁道 0,磁道编号向中心递增。
  • 柱面 :指从读写头的单个位置可访问的所有磁道。文件最初存储在相邻的柱面上,这样可以减少读写头的移动量。
  • 扇区 :是磁道的 512 字节部分,由制造商使用所谓的低级格式化在磁盘上以磁性方式(不可见)标记。无论安装何种操作系统,扇区大小都不会改变。一个硬盘每个磁道可能有 63 个或更多扇区。

物理磁盘几何结构由每个磁盘的柱面数、每个柱面的读写头数和每个磁道的扇区数组成,它们之间存在以下关系:
- 每个磁盘的柱面数等于每个表面的磁道数。
- 磁道总数等于柱面数乘以每个柱面的读写头数。

随着时间的推移,文件在磁盘上变得更加分散,就会出现碎片化。碎片化的文件其扇区不再位于磁盘的连续区域,这会导致读写头在读取文件数据时需要跳过磁道,从而减慢文件的读写速度。

硬盘控制器会将物理磁盘几何结构转换为操作系统能够理解的逻辑结构,即逻辑扇区号。逻辑扇区号从 0 开始顺序编号。

3. 磁盘分区(卷)

在 MS - Windows 系统中,单个物理硬盘可以划分为一个或多个逻辑单元,称为分区或卷。每个格式化的分区由一个单独的驱动器号(如 C、D 或 E)表示,并且可以使用多种文件系统之一进行格式化。硬盘可能包含两种类型的分区:
- 主分区 :通常可引导,并包含操作系统。
- 扩展分区 :可以划分为无限数量的逻辑分区,每个逻辑分区映射到一个驱动器号,但不能引导。

例如,一个 20GB 的硬盘可以分配一个 10GB 的主分区(驱动器 C)并安装操作系统,其扩展分区为 10GB。可以将扩展分区任意划分为两个逻辑分区,分别为 2GB 和 8GB,并使用 FAT16、FAT32 或 NTFS 等不同的文件系统进行格式化。

创建多个主分区很常见,每个主分区都可以引导不同的操作系统,这使得可以在不同环境中测试软件,并利用更高级系统的安全功能。逻辑分区主要用于存储数据,不同的操作系统可以共享存储在同一逻辑分区中的数据,例如,所有最新版本的 MS - Windows 和 Linux 都可以读取 FAT32 磁盘。

主引导记录(MBR)位于硬盘的第一个逻辑扇区,在创建第一个分区时生成。它包含:
- 磁盘分区表,描述磁盘上所有分区的大小和位置。
- 一个小程序,用于定位分区的引导扇区,并将控制权转移到该扇区中加载操作系统的程序。

4. 文件系统基础

每个操作系统都有某种类型的磁盘管理系统,在最低级别管理分区,在更高级别管理文件和目录。文件系统必须跟踪每个磁盘文件的位置、大小和属性。以最初为 IBM - PC 创建并在 Windows 中仍然可用的 FAT 类型文件系统为例,它具有以下结构:
- 将逻辑扇区映射到簇,簇是所有文件和目录的基本存储单元。
- 将文件和目录名映射到簇序列。

簇是文件使用的最小空间单位,由一个或多个相邻的磁盘扇区组成。文件系统将每个文件存储为一个链接的簇序列,簇的大小取决于所使用的文件系统类型和磁盘分区的大小。即使是小文件也至少需要一个簇的磁盘存储空间,这可能会导致磁盘空间的浪费。例如,一个 8200 字节的文件完全填满两个 4096 字节的簇,仅使用第三个簇的 8 字节,这会在第三个簇中留下 4088 字节的浪费空间。在有大量小文件的卷上,较小的簇大小是最佳选择。

以下是 Windows 2000 和 Windows XP 下硬盘的标准簇大小和文件系统类型示例(仅作说明,值会随操作系统版本更新而变化):
| 卷大小 | FAT16 簇 | FAT32 簇 | NTFS 簇 |
| ---- | ---- | ---- | ---- |
| 1.25GB - 2GB | 32KB | 4KB | 2KB |
| 2GB - 4GB | 64KB(仅 Windows 2000 和 XP 支持) | 4KB | 4KB |
| 4GB - 8GB | 不支持 | 4KB | 4KB |
| 8GB - 16GB | 不支持 | 8KB | 4KB |
| 16GB - 32GB | 不支持 | 16KB | 4KB |
| 32GB - 2TB | 不支持 | 需软件补丁(Windows 98) | 4KB |

5. 常见文件系统介绍
  • FAT12 :最初用于 IBM - PC 软盘,所有版本的 MS - Windows 和 Linux 都支持。簇大小仅为 512 字节,非常适合存储小文件。其文件分配表中的每个条目为 12 位长,FAT12 卷包含少于 4087 个簇。
  • FAT16 :是 MS - DOS 下格式化硬盘的唯一可用格式,所有版本的 MS - Windows 和 Linux 都支持。但存在一些缺点:在超过 1GB 的卷上存储效率低下,因为使用大簇大小;文件分配表中的每个条目为 16 位长,限制了簇的总数;卷可以容纳 4087 到 65526 个簇;引导扇区没有备份,单个扇区读取错误可能导致灾难性后果;没有内置的文件系统安全性或用户权限。
  • FAT32 :随 Windows 95 引入,并在 Windows 98 下得到改进。具有以下优点:单个文件最大可达 4GB 减 2 字节;文件分配表中的每个条目为 32 位长;卷可以容纳 65526 到 268435456 个簇;根文件夹可以位于磁盘的任何位置,并且几乎可以是任何大小;卷最大可容纳 32GB;在 1GB 到 8GB 的卷上使用比 FAT16 更小的簇大小,减少了浪费的空间;引导记录包含关键数据结构的备份副本,比 FAT16 驱动器更不容易受到单点故障的影响。
  • NTFS :所有最新版本的 Windows 都支持,相比 FAT32 有显著改进:能够处理大卷,可以在单个硬盘上或跨多个硬盘;对于超过 2GB 的磁盘,默认簇大小为 4KB;支持长达 255 个字符的 Unicode 文件名;允许为文件和文件夹设置权限,不同用户或用户组可以有不同的访问级别(如读取、写入、修改等);提供内置的数据加密和压缩功能;可以在更改日志中跟踪文件的个别更改;可以为单个用户或用户组设置磁盘配额;能够从数据错误中稳健恢复,通过保留事务日志自动修复错误;支持磁盘镜像,即相同的数据同时写入多个驱动器。

不同操作系统对常见文件系统的支持情况如下表所示:
| 文件系统 | MS - DOS | Linux | Win 95/98 | Win NT 4 | Win 2000 及以后 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| FAT12 | X | X | X | X | X |
| FAT16 | X | X | X | X | X |
| FAT32 | X | X | X | | |
| NTFS | | X | | X | X |

6. 主要磁盘区域

FAT12 和 FAT16 卷有专门为引导记录、文件分配表和根目录保留的特定位置(FAT32 驱动器的根目录不存储在固定位置)。每个区域的大小在格式化卷时确定。以一个 3.5 英寸、1.44MB 的软盘为例,其扇区映射如下:
| 逻辑扇区 | 内容 |
| ---- | ---- |
| 0 | 引导记录 |
| 1 - 18 | 文件分配表(FAT) |
| 19 - 32 | 根目录 |
| 33 - 2879 | 数据区域 |

  • 引导记录 :包含一个保存卷信息的表和一个将 MS - DOS 加载到内存的简短引导程序。引导程序检查某些操作系统文件的存在并将它们加载到内存。
  • 文件分配表(FAT) :比较复杂,后续会详细讨论。
  • 根目录 :是磁盘卷的主目录,目录条目可以是其他目录名或文件引用。引用文件的目录条目包含文件名、大小、属性和文件使用的起始簇号。
  • 数据区域 :是存储文件和子目录的地方。
7. 相关复习问题解答

以下是对前面章节复习问题的解答:
1. (True):磁道被划分为多个称为扇区的单元。
2. (False):扇区是磁道的一部分,而不是由多个磁道组成。
3. 一个 柱面 由硬盘读写头的单个位置可访问的所有磁道组成。
4. (True):物理扇区始终为 512 字节,因为它们是由制造商在磁盘上标记的。
5. 在 FAT32 下,逻辑扇区使用 512 字节。
6. 文件最初存储在相邻的柱面上是为了减少读写头的移动量。
7. 当文件的存储变得碎片化时,意味着文件的扇区不再位于连续的柱面上,驱动器的读写头在读取文件数据时需要进行更多的寻道操作,从而减慢文件的读写速度。
8. 驱动器分区的另一个名称是驱动器
9. 驱动器的平均寻道时间衡量的是将读写头从一个磁道移动到另一个磁道所需的平均时间。
10. 低级格式化是制造商在磁盘上以磁性方式标记扇区的过程。
11. 主引导记录包含磁盘分区表和一个定位分区引导扇区并将控制权转移到加载操作系统程序的小程序。
12. 同一时间只能有一个主分区处于活动状态。
13. 当主分区处于活动状态时,它被称为 活动 分区。

在创建和管理磁盘分区时,可以使用 MS - DOS 和 Windows 98 下的 FDISK.EXE 程序,但该程序不会保留数据。更好的选择是使用较新版本 Windows 中的磁盘管理器实用程序,它可以在不破坏数据的情况下创建、删除和调整分区大小。

8. 磁盘目录详解
8.1 MS - DOS 目录结构

MS - DOS 的目录结构采用树状结构。根目录是整个文件系统的起始点,从根目录可以分支到各个子目录。每个子目录又可以包含文件和其他子目录。这种结构使得文件和目录的组织更加清晰,便于管理。在 MS - DOS 中,目录路径使用反斜杠(\)来分隔不同级别的目录。例如, C:\Program Files\MyApp 表示在 C 盘的 Program Files 目录下的 MyApp 子目录。

8.2 MS - Windows 长文件名

在早期的 MS - DOS 系统中,文件名遵循 8.3 规则,即文件名最多 8 个字符,扩展名最多 3 个字符。而在 MS - Windows 中,支持长文件名,文件名可以长达 255 个字符。这大大方便了用户对文件的命名,能够更准确地描述文件的内容。长文件名的实现是通过在文件系统中额外存储相关信息来支持的,同时为了兼容旧的 8.3 文件名规则,系统会自动生成一个对应的短文件名。

8.3 文件分配表(FAT)

文件分配表(FAT)是 FAT 类型文件系统中非常重要的一个数据结构。它记录了文件和目录所使用的簇的信息。每个文件或目录在 FAT 中都有一个对应的条目,该条目指向文件或目录所使用的第一个簇。如果文件或目录占用多个簇,FAT 中的条目会形成一个链,依次指向后续的簇。

例如,一个文件使用了簇 1、簇 2 和簇 3,那么在 FAT 中,文件对应的条目会先指向簇 1,簇 1 的条目会指向簇 2,簇 2 的条目会指向簇 3,簇 3 的条目会标记为文件的结束。通过 FAT,文件系统可以快速定位文件或目录所占用的簇,从而实现文件的读写操作。

9. 磁盘扇区的读写操作
9.1 扇区显示程序

可以编写扇区显示程序来读取磁盘扇区的内容并显示出来。在早期的操作系统(如 MS - DOS、Windows 95、98 和 Millennium)中,用户级的汇编语言程序可以直接访问系统 BIOS 来进行扇区的读写操作。以下是一个简单的扇区读取程序的示例思路:

1. 初始化磁盘参数,包括磁盘号、扇区号等。
2. 调用系统 BIOS 的磁盘读取功能(如 INT 13H),将指定扇区的内容读取到内存缓冲区。
3. 对内存缓冲区中的数据进行处理,例如显示到屏幕上。

在较新的 32 位 Windows 版本中,用户级程序只能使用 Win32 API 来访问磁盘系统。使用 Win32 API 进行扇区读取的步骤如下:

1. 打开磁盘设备句柄,使用 CreateFile 函数。
2. 设置文件指针到要读取的扇区位置,使用 SetFilePointer 函数。
3. 读取扇区数据到缓冲区,使用 ReadFile 函数。
4. 关闭磁盘设备句柄,使用 CloseHandle 函数。
10. 系统级文件函数
10.1 获取磁盘可用空间(7303h)

在早期的操作系统中,可以使用中断调用(如 7303h)来获取磁盘的可用空间。具体步骤如下:

1. 设置相关寄存器的值,指定要查询的磁盘。
2. 执行中断调用(如 INT 21H)。
3. 从寄存器中获取磁盘的可用空间信息。

在现代 Windows 系统中,可以使用 Win32 API 中的 GetDiskFreeSpaceEx 函数来获取磁盘的可用空间。示例代码如下:

#include <windows.h>
#include <iostream>

int main() {
    ULARGE_INTEGER freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes;
    if (GetDiskFreeSpaceEx(L"C:", &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes)) {
        std::cout << "Free bytes available: " << freeBytesAvailable.QuadPart << std::endl;
        std::cout << "Total number of bytes: " << totalNumberOfBytes.QuadPart << std::endl;
        std::cout << "Total number of free bytes: " << totalNumberOfFreeBytes.QuadPart << std::endl;
    } else {
        std::cout << "Failed to get disk free space." << std::endl;
    }
    return 0;
}
10.2 创建子目录(39h)

在早期的操作系统中,使用中断调用(如 39h)来创建子目录。步骤如下:

1. 设置相关寄存器的值,指定要创建的子目录的路径。
2. 执行中断调用(如 INT 21H)。
3. 根据返回的结果判断子目录是否创建成功。

在现代 Windows 系统中,可以使用 Win32 API 中的 CreateDirectory 函数来创建子目录。示例代码如下:

#include <windows.h>
#include <iostream>

int main() {
    if (CreateDirectory(L"C:\\MyNewDirectory", NULL)) {
        std::cout << "Directory created successfully." << std::endl;
    } else {
        std::cout << "Failed to create directory." << std::endl;
    }
    return 0;
}
10.3 删除子目录(3Ah)

在早期的操作系统中,使用中断调用(如 3Ah)来删除子目录。步骤如下:

1. 设置相关寄存器的值,指定要删除的子目录的路径。
2. 执行中断调用(如 INT 21H)。
3. 根据返回的结果判断子目录是否删除成功。

在现代 Windows 系统中,可以使用 Win32 API 中的 RemoveDirectory 函数来删除子目录。需要注意的是,被删除的子目录必须为空。示例代码如下:

#include <windows.h>
#include <iostream>

int main() {
    if (RemoveDirectory(L"C:\\MyNewDirectory")) {
        std::cout << "Directory removed successfully." << std::endl;
    } else {
        std::cout << "Failed to remove directory." << std::endl;
    }
    return 0;
}
10.4 设置当前目录(3Bh)

在早期的操作系统中,使用中断调用(如 3Bh)来设置当前目录。步骤如下:

1. 设置相关寄存器的值,指定要设置为当前目录的路径。
2. 执行中断调用(如 INT 21H)。
3. 根据返回的结果判断当前目录是否设置成功。

在现代 Windows 系统中,可以使用 Win32 API 中的 SetCurrentDirectory 函数来设置当前目录。示例代码如下:

#include <windows.h>
#include <iostream>

int main() {
    if (SetCurrentDirectory(L"C:\\MyNewDirectory")) {
        std::cout << "Current directory set successfully." << std::endl;
    } else {
        std::cout << "Failed to set current directory." << std::endl;
    }
    return 0;
}
10.5 获取当前目录(47h)

在早期的操作系统中,使用中断调用(如 47h)来获取当前目录。步骤如下:

1. 设置相关寄存器的值,指定用于存储当前目录路径的缓冲区。
2. 执行中断调用(如 INT 21H)。
3. 从缓冲区中获取当前目录的路径。

在现代 Windows 系统中,可以使用 Win32 API 中的 GetCurrentDirectory 函数来获取当前目录。示例代码如下:

#include <windows.h>
#include <iostream>

int main() {
    wchar_t buffer[MAX_PATH];
    if (GetCurrentDirectory(MAX_PATH, buffer)) {
        std::wcout << "Current directory: " << buffer << std::endl;
    } else {
        std::cout << "Failed to get current directory." << std::endl;
    }
    return 0;
}
10.6 获取和设置文件属性(7143h)

在早期的操作系统中,使用中断调用(如 7143h)来获取和设置文件属性。步骤如下:

获取文件属性:
1. 设置相关寄存器的值,指定要查询的文件路径。
2. 执行中断调用(如 INT 21H)。
3. 从寄存器中获取文件的属性信息。

设置文件属性:
1. 设置相关寄存器的值,指定要设置属性的文件路径和新的属性值。
2. 执行中断调用(如 INT 21H)。
3. 根据返回的结果判断文件属性是否设置成功。

在现代 Windows 系统中,可以使用 Win32 API 中的 GetFileAttributes 和 SetFileAttributes 函数来获取和设置文件属性。示例代码如下:

#include <windows.h>
#include <iostream>

int main() {
    // 获取文件属性
    DWORD attributes = GetFileAttributes(L"C:\\MyFile.txt");
    if (attributes != INVALID_FILE_ATTRIBUTES) {
        std::cout << "File attributes: " << attributes << std::endl;
    } else {
        std::cout << "Failed to get file attributes." << std::endl;
    }

    // 设置文件属性
    if (SetFileAttributes(L"C:\\MyFile.txt", FILE_ATTRIBUTE_READONLY)) {
        std::cout << "File attributes set successfully." << std::endl;
    } else {
        std::cout << "Failed to set file attributes." << std::endl;
    }

    return 0;
}
11. 总结

磁盘存储系统是计算机系统中不可或缺的一部分,它涉及到多个层面的知识,包括磁盘的物理结构(磁道、柱面、扇区等)、磁盘分区、文件系统、目录结构以及各种系统级文件函数等。不同的操作系统在这些方面有不同的实现方式,但总体的原理是相似的。

在选择文件系统时,需要根据实际需求来考虑。如果是小容量磁盘且主要存储小文件,FAT12 可能是一个不错的选择;如果需要兼容旧系统,FAT16 仍然可以使用;对于大多数现代应用,FAT32 和 NTFS 更为合适,其中 NTFS 具有更多的高级功能,如安全性、大文件支持等。

在进行磁盘管理和文件操作时,要根据操作系统的版本选择合适的方法。早期的操作系统可以通过系统 BIOS 直接进行操作,而现代 Windows 系统则主要使用 Win32 API 来保证系统的安全性和兼容性。

12. 编程练习建议

为了更好地掌握磁盘相关知识,可以进行以下编程练习:

  • 编写一个程序,使用 Win32 API 获取指定磁盘的详细信息,包括总容量、可用容量、文件系统类型等。
  • 实现一个简单的文件管理器程序,能够列出指定目录下的所有文件和子目录,并支持文件的复制、移动和删除操作。
  • 编写一个程序,模拟文件的碎片化过程,并分析不同文件系统下碎片化对文件读写性能的影响。

通过这些练习,可以加深对磁盘存储系统的理解,提高编程能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值