chapter 7 文件操作&chapter 8 使用系统调研进行文件操作

发布时间 2023-09-28 13:42:27作者: 20211108俞振阳

chapter 7 文件操作&chapter 8 使用系统调研进行文件操作

7.1 文件操作

文件操作由五个层次构成,从低到高,如下图所示。

7.1.1 硬件级别

硬件级别的文件操作包括以下程序:

  • fdisk:将硬盘、USB 或 SDC 驱动器分成分区。
  • mkfs:格式化磁盘分区以准备它们用于文件系统。
  • fsck:检查和修复文件系统。
  • 碎片整理:压缩文件系统中的文件。

7.1.2 操作系统内核中的文件系统函数

每个操作系统内核都提供基本的文件系统操作支持。这些函数往往是系统面向的工具程序。

kmount(), kumount() // 挂载/卸载文件系统
kmkdir(), krmdir() // 创建/删除目录
kchdir(), kgetcwd() // 更改目录,获取当前工作目录路径名
klink(), kunlink() // 硬链接/取消链接文件
kchmod(), kchown(), kutime() // 更改 r|w|x 权限、所有者、时间
kcreat(), kopen() // 创建/打开文件以进行读取、写入、读写、添加操作
kread(), kwrite() // 读取/写入已打开的文件
klseek(), kclose() // 定位、关闭文件描述符
ksymlink(), kreadlink() // 创建、读取符号链接文件
kstat(), kfstat(), klstat() // 获取文件状态/信息
kopendir(), kreaddir() // 打开、读取目录

7.1.3 系统调用

用户模式程序使用系统调用来访问内核函数。可以使用 C 库提供的一系列标准 I/O 函数来发出系统调用。

7.1.4 库 I/O 函数

标准 I/O 函数通过系统调用间接调用内核的文件系统函数。

7.1.5 Shell 命令

用户可以使用 Unix/Linux 命令而不是编写程序来进行文件操作。

7.1.6 sh脚本

sh脚本是用sh编程语言编写的程序,可通过命令解释程序sh来执行。

7.2 文件I/O操作

文件I/O操作是在计算机中对文件进行读写的过程。下面是文件I/O操作的流程:
image

  1. 用户进程执行fopen()函数来打开读/写文件流。
  2. fopen()函数在用户空间中创建一个文件结构体,同时向内核发出打开文件的系统调用。
  3. 内核根据系统调用创建一个OpenTable表示打开的文件实例,并将其与用户空间的文件结构体相关联。
  4. 用户进程使用fread()fwrite()函数读取或写入文件内容。
    • 如果文件结构体中有足够的数据,就直接返回。
    • 如果文件结构体中没有更多数据,则向内核发出读取块的系统调用,将文件块读取到文件结构体中,并将数据复制到用户空间中。
    • 如果文件结构体中还有空间,则将数据复制到文件结构体中。
    • 如果文件结构体已满,则向内核发出写入块的系统调用,将文件块写入内核空间。
  5. 文件系统函数操作中,如果是非特殊文件,则使用read()系统调用从内核中读取文件块。
  6. read()系统调用中,根据文件描述符找到相应的OpenTable,其中存储了文件的一些属性和控制信息。
  7. 根据OpenTable的偏移量,计算逻辑块号,并将其转换为物理块号。
  8. 根据物理块号读取磁盘块中的数据。
  9. 为了提高磁盘I/O效率,内核通常使用一组I/O缓冲区进行缓存。
    • 如果缓冲区的数据无效,则进行物理I/O操作,将数据从磁盘读取到缓冲区中。
    • 如果缓冲区的数据有效,则将数据从缓冲区复制到文件结构体中。
  10. 最终,设备驱动程序会执行物理I/O请求,将数据从缓冲区写入磁盘。

7.3 低级文件操作

7.3.1 分区

  • 块存储设备(如硬盘、USB驱动器、SD卡等)可以分为多个逻辑单元,称为分区。
  • 每个分区可以格式化为特定的文件系统,并安装不同的操作系统。
  • 大多数引导程序(如GRUB、LILO等)可以配置从各个分区启动不同的操作系统。
  • 分区表位于第一个扇区的字节偏移量0x1BE(即设备的主引导记录MBR)。该表有4个条目,每个条目由16个字节的分区结构定义。
struct partition {
    u8 drive;           // 0x80 - 活动分区
    u8 head;            // 起始磁头
    u8 sector;          // 起始扇区
    u8 cylinder;        // 起始柱面
    u8 sys_type;        // 分区类型
    u8 end_head;        // 结束磁头
    u8 end_sector;      // 结束扇区
    u8 end_cylinder;    // 结束柱面
    u32 start_sector;   // 从0开始计数的起始扇区
    u32 nr_sectors;     //分区中的扇区数
};
  • 如果分区是EXTEND类型(类型号=5),则可以分成更多分区。假设分区P4是EXTEND类型,分为扩展分区P5、P6、P7。扩展分区在扩展分区区域形成链接列表,如图所示。
    image

  • 每个扩展分区的第一个扇区是一个本地MBR。每个本地MBR也有一个分区表,位于0x1BE的字节偏移,仅包含两个条目。第一个条目定义扩展分区的起始扇区和大小。第二个条目指向下一个本地MBR。所有本地MBR的扇区号都是相对于P4的起始扇区的。通常,链接列表以最后一个本地MBR中的0结束。在分区表中,CHS值只对小于8GB的磁盘有效。对于大于8GB但小于4G扇区的磁盘,只有最后2个条目,即start_sector和nr_sectors有意义。

7.3.2 格式化分区

在 Linux 系统中,要将一个分区用于文件存储,必须先将它格式化为指定的文件系统类型。Linux 支持许多种不同的文件系统类型,可以使用 mkfs 命令来对存储设备进行格式化操作。

filetype和mkfs命令

mkfs 命令格式如下:

mkfs -t TYPE [-b bsize] device nblocks

其中,TYPE 表示要创建的文件系统类型;bsize 表示块大小,如果未指定,则默认为 1KB;device 表示要格式化的设备名称;nblocks 表示设备中块的总数。

例如,要将 vdisk 格式化为 EXT2 文件系统,可以使用以下命令:

mkfs –t ext2 vdisk 1440

还可以使用 mke2fs 命令:

mke2fs vdisk 1440

挂载和卸载存储设备

格式化后的存储设备不会立即存储文件,还需要将它挂载到已有的目录中,作为文件系统的一部分。通常使用 /mnt 目录来挂载其它文件系统设备。

使用 mount 命令可以挂载存储设备,例如:

sudo mount –o loop vdisk /mnt

此命令会将 vdisk 挂载到 /mnt 目录。

使用 umount 命令可以对挂载的设备进行卸载操作,例如:

sudo umount /mnt

卸载设备后,文件应该仍然保存在设备中。

7.3.3 挂载分区

mount 命令可以挂载实际设备的分区或整个虚拟磁盘,但不能挂载虚拟磁盘的分区。如果虚拟磁盘包含分区,必须先关联分区和回环设备。

使用 losetup 命令可以创建回环设备,例如:

losetup -o $(expr 2048 \* 512) --sizelimit $(expr 65535 \* 512) /dev/loop1 vdisk

此命令会创建一个回环设备 /dev/loop1 并关联到 vdisk 的第一个分区。

创建好回环设备后,可以使用 mkfs 命令对分区进行格式化:

mke2fs -b 4096 /dev/loop1 7936

然后,可以使用 mount 命令挂载回环设备:

mount /dev/loop1 /mnt

操作完成后,应该使用 umount 和 losetup 命令分别卸载和删除回环设备。