chapter 11 EXT2文件系统

chapter 11 EXT2文件系统

摘要

本章介绍了Linux中的EXT2文件系统。重点是实现一个完整的,与Linux兼容的EXT2文件系统。本章首先解释了EXT2的历史意义,以及其与EXT3 / EXT4文件系统的当前状态。使用编程示例来说明各种EXT2数据结构以及如何遍历文件系统树。本章还涵盖了实现EXT2文件系统,包括支持在Linux内核中找到的所有文件操作,通过从虚拟磁盘安装根目录来构建基本文件系统,并将文件系统实现分为三个级别:级别1扩展基本文件系统以实现文件系统树,级别2实现文件内容的读写操作,级别3实现文件系统的挂载/卸载和文件保护。描述了每个级别的文件系统功能的算法,并使用编程示例演示了它们的实现。最后,本章将所有编程示例和练习集成到一个完全功能的文件系统中。

11.1 EXT2文件系统

EXT2是Linux多年来默认使用的文件系统。EXT3是EXT2的扩展,包括记录对文件系统所做更改的日志文件,以便在文件系统崩溃时更快地恢复错误。EXT4是EXT3的最新扩展,其重大变化在于分配磁盘块的方式,例如分配连续的磁盘块范围,称为扩展。尽管进行了这些更改,但文件系统结构和操作大部分保持不变。本章强调使用EXT2作为文件系统教授文件系统设计和实现的原则,并注重兼容性和简单性。深入理解EXT2可以使适应其他文件系统相对容易。

11.2 EXT2文件系统数据结构

11.2.1 使用mke2fs在虚拟磁盘上创建EXT2文件系统

在Linux中,可以使用以下命令来创建EXT2文件系统:

mke2fs [-b blksize -N ninodes] device nblocks
  • device:要创建EXT2文件系统的目标设备,可以是实际设备或虚拟磁盘文件。
  • nblocks:文件系统中的块数。
  • blksize(可选):每个块的大小(以字节为单位)。如果未指定,将使用默认块大小1 KB。
  • ninodes(可选):文件系统中的i节点数。如果未指定,它将基于nblocks计算出默认的i节点数。

例如:

dd if=/dev/zero of=vdisk bs=1024 count=1440
mke2fs vdisk 1440

这些命令将在名为vdisk的虚拟磁盘文件上创建一个包含1440个1KB块的EXT2文件系统。

11.2.2 虚拟磁盘布局

EXT2文件系统的虚拟磁盘布局如图11.1所示。我们假设基本文件系统布局包括以下元素。各种磁盘块的解释如下:

  • 块#0:引导块(Boot Block):这个块不被文件系统使用,通常包含用于从磁盘引导操作系统的引导程序。

图11.1

11.2.3 超级块

块#1:超级块(位于硬盘分区中的字节偏移量1024处)包含关于整个文件系统的重要信息。超级块结构包括字段如下:

struct ext2_super_block {
    u32 s_inodes_count;      /* i节点总数 */
    u32 s_blocks_count;      /* 块总数 */
    u32 s_r_blocks_count;    /* 保留块总数 */
    u32 s_free_blocks_count; /* 空闲块总数 */
    u32 s_free_inodes_count; /* 空闲i节点总数 */
    u32 s_first_data_block;  /* 第一个数据块 */
    u32 s_log_block_size;    /* 块大小 */
    u32 s_log_cluster_size;  /* 分配簇大小 */
    u32 s_blocks_per_group;  /* 每个组的块数 */
    u32 s_clusters_per_group; /* 每个组的片数 */
    u32 s_inodes_per_group;  /* 每个组的i节点数 */
    u32 s_mtime;             /* 挂载时间 */
    u32 s_wtime;             /* 写入时间 */
    u16 s_mnt_count;         /* 挂载计数 */
    s16 s_max_mnt_count;     /* 最大挂载计数 */
    u16 s_magic;             /* 魔数标识 */
    // 更多非必要字段
    u16 s_inode_size;        /* i节点结构大小 */
}

一些重要字段包括:

  • s_first_data_block:表示第一个数据块。对于4KB块大小,值为0,对于1KB块大小,值为1。用于确定组描述符的起始块,即s_first_data_block + 1
  • s_log_block_size:确定文件块大小,例如,0表示1KB块大小,1表示2KB块大小,2表示4KB块大小,以此类推。通常用于小型文件系统的块大小为1KB,而大型文件系统通常使用4KB块大小。
  • s_mnt_count:表示文件系统被挂载的次数。
  • s_magic:魔法数字,用于识别文件系统类型(EXT2/3/4文件系统的魔法数字为0xEF53)。

11.2.4 组描述符

块#2:组描述符块(位于硬盘上的s_first_data_block+1位置)将硬盘块分成组。每个组包含8192个块,并由组描述符结构描述,如下所示:

struct ext2_group_desc {
    u32 bg_block_bitmap; // 块位图块编号
    u32 bg_inode_bitmap; // i节点位图块编号
    u32 bg_inode_table; // i节点起始块编号
    u16 bg_free_blocks_count; // 空闲块数
    u16 bg_free_inodes_count; // 空闲i节点数
    u16 bg_used_dirs_count; // 已使用目录数
    u16 bg_pad; // 忽略这些字段
    u32 bg_reserved[3];
}

组描述符中一些关键字段包括bg_block_bitmapbg_inode_bitmapbg_inode_table,它们分别指向组的块位图、i节点位图和i节点起始块。块编号从1开始计数,因为块0从不被文件系统使用。

11.2.5 块位图和i节点位图

块#8:块位图(Bmap):块位图用于分配和释放文件系统中的块。0位表示空闲块,1位表示已使用块。

块#9:i节点位图(Imap):i节点位图表示文件系统中每个i节点的状态。0位表示空闲i节点,1位表示已分配i节点。

11.2.6 i节点

块#10:i节点(开始)块(bg_inode_table):EXT2文件系统中的每个文件都由一个独特的i节点结构表示。i节点结构中的重要字段包

括:

struct ext2_inode {
    u16 i_mode; // 文件模式
    u16 i_uid; // 所有者用户ID
    u32 i_size; // 文件大小(字节)
    u32 i_atime; // 上次访问时间(秒)
    u32 i_ctime; // 创建时间(秒)
    u32 i_mtime; // 修改时间(秒)
    u32 i_dtime; // 删除时间(秒)
    u16 i_gid; // 所属组ID
    u16 i_links_count; // 硬链接计数
    u32 i_blocks; // 512字节扇区数
    u32 i_flags; // 忽略
    u32 i_reserved1; // 忽略
    u32 i_block[15]; // 指向文件的磁盘块
    u32 i_pad[7]; // 用于i节点大小为128字节
}
  • i_mode:表示文件类型和权限。
  • i_size:文件大小(以字节为单位)。
  • 时间字段(i_atimei_ctimei_mtimei_dtime):表示时间戳。
  • i_blocks:512字节扇区的数量。
  • i_block[15]:包含指向文件的磁盘块的指针,包括直接块、间接块、双间接块和三重间接块。

11.2.7 数据块

在i节点块之后,是用于存储文件数据的数据块。对于包含184个i节点的文件系统,第一个实际数据块是B33,通常用于根目录(/)。

11.2.8 目录条目

目录条目:目录包含dir_entry结构,其中包括inoderec_lenname_lenfile_typename等字段。name字段表示条目的名称,rec_len根据名称长度和内容变化。

11.3 Mailman's算法

Mailman's算法是一种用于在计算机系统中处理和操作数据的技术,特别是在数据以非线性方式组织的情况下。

11.3.1 问题描述

Mailman's算法所解决的问题是,当你有一个城市,拥有M个街区,每个街区包含N座房屋,而你想以线性方式寻址各个房屋时。换句话说,你需要一种方法来在基于街区的寻址系统和线性房屋编号系统之间进行转换,以及反过来。这种转换在一切从0开始时是直接的。

例如,每座房屋由唯一的(街区,房屋)地址标识,其中0 <= 街区 < M,0 <= 房屋 < N。 Mailman's算法提供了一种将该地址转换为线性地址及其逆转换的方法。如果一切都从0开始,则转换很简单:

  • 线性地址(LA)= N * 街区 + 房屋
  • 街区地址(BA)=(LA / N, LA % N)

11.3.2 处理非零起始点

Mailman's算法非常简单,并且在一切从0开始时有效。然而,当不是一切都从0开始时,需要进行调整。这些调整需要考虑数据不是从索引0开始的情况。

这个算法通常在以下场景中使用:

  • 操纵位图中的各个位。
  • 将INODE号转换为磁盘上的INODE号,如EXT2文件系统。
  • 将逻辑磁盘块号转换为物理块号,也在EXT2文件系统中。
  • 在调用BIOS INT13时,将磁盘块号转换为CHS(柱面、磁头、扇区)格式。

11.4 编程示例

11.4.1 显示超级块

在EXT2文件系统中,超级块包含有关文件系统的重要信息,例如inode和块数、块大小等。这一部分提供了一个示例C程序,用于显示EXT2文件系统的超级块。

11.4.2 显示位图

示例程序'imap.c'用于显示EXT2文件系统的inode位图(imap)。它读取并以十六进制格式打印inode位图的每个字节。练习建议修改程序以以字符映射形式打印inode位图,其中对于0位打印'0',对于1位打印'1'。

11.4.3 显示根INODE

根inode是EXT2文件系统中的一个关键结构。提供的程序'inode.c'读取和显示根inode的信息,包括模式、用户ID、组ID、文件大小、时间戳、链接计数和数据块号。

11.4.4 显示目录项

Mailman's算法用于处理目录项,特别是在目录数据块的上下文中。它有助于遍历目录数据块中的目录项,并允许您提取有关每个目录项的信息,包括inode号、记录长度、名称长度和名称本身。

练习11.4

编写一个C程序,用于打印目录的目录项,假设一个目录inode最多有12个直接块,从i_block[0]到i_block[11]。该程序的输出应列出目录的目录项,并应考虑rec_len是4的倍数。

练习11.5

挂载EXT2文件系统并创建目录并复制文件。然后,卸载文件系统。再次运行'dir.c'程序以查看输出。输出应类似于图11.6。

练习11.6

给定一个指向DIRectory inode的INODE指针,编写一个函数:

int search(INODE *dir, char *name)

该函数搜索具有给定名称的目录项。如果找到该项,则返回其inode号,否则返回0。这些练习有助于深入了解Mailman's算法,并了解它如何在实际情境中使用,特别是在EXT2文件系统的背景下。

11.5 遍历EXT2文件系统树

11.5.1 遍历算法

  1. 从读取EXT2文件系统的超级块开始,验证其真实性,使用魔术数字(0xEF53)。

  2. 读取组描述符块,以访问第0组的描述符。找到inode开始的块号(InodesBeginBlock)。

  3. 读取InodeBeginBlock,获取根inode(INODE #2)。

  4. 将给定的路径名(例如,/a/b/c)分割为组件,并计算它们的数量(n)。

  5. 从根inode开始,搜索第一个组件(name[0])的数据块,假设每个目录的条目数量很少。每个数据块包含dir_entry结构,需要将组件名称与name[0]进行比较。

  6. 使用找到的inode编号(ino)来定位相应的inode,并使用Mailman算法来找到它的磁盘块和偏移量。

  7. 如果/a是一个目录并且还有更多的组件,则对下一个组件(name[1])重复搜索过程。

  8. 可以编写一个搜索函数来封装这个过程,以便进行多次搜索。

  9. 最后,对所有组件重复搜索过程,如果成功,最终的inode指向所需的文件。

11.5.2 将路径名转换为INODE

创建一个名为 path2inode 的C函数,它接受文件描述符和路径名作为输入,并返回文件的inode指针,如果文件无法访问,则返回0。

11.5.3 显示INODE磁盘块

开发一个C程序,名为 'showblock',它打印文件的所有磁盘块号。

11.6 实现EXT2文件系统

11.6.1 文件系统组织

EXT2文件系统的组织如下:

  1. 每个运行的进程都有一个PROC结构,包括当前工作目录(CWD)和指向打开文件实例的文件描述符数组(fd[])。

  2. 文件系统的根指针指向内存中的根INODE。

  3. 当进程打开文件时,PROC的fd数组中的一个条目指向openTable,该表引用了打开文件的内存中INODE。

  4. 内存中的INODE保存有关文件的信息,每个唯一的INODE只能一次加载到内存中。

  5. 挂载文件系统表格包含已挂载文件系统的信息。

11.6.2 文件系统级别

EXT2文件系统的实现分为三个级别,以实现模块化和清晰:

第一级:基本文件系统树功能

  • 实现用于创建目录、常规文件、列出目录、更改目录、删除目录、硬链接、解除链接、符号链接、文件统计和各种文件操作的函数。

使用第一级功能的用户命令程序 包括 mkdircreatmknodrmdirlinkunlinksymlinkrmlscdpwd 等。

第二级:读取/写入文件内容

  • 实现用于打开、关闭文件以及在打开常规文件中读取和写入的函数。

第三级:挂载、卸载和文件保护

  • 实现用于挂载和卸载文件系统、访问权限检查、文件锁定和解锁的函数。

这些级别共同构成了完整的EXT2文件系统,允许在系统中创建、管理和操作文件和目录。

11.7 基本系统文件

11.7.1 type.h文件

  • type.h文件包含了EXT2文件系统的关键数据结构类型,如超级块、组描述符、inode和目录项结构。
  • 该文件还包含了文件系统中的一些常量和定义,如文件和目录的权限、文件系统魔数、超级用户ID等。
  • 还定义了一些数据结构,如Open File Table(OFT)、PROC结构、MINODE结构和Mount Table(MTABLE)。

11.7.2 global.c文件

  • global.c文件包含了EXT2文件系统的全局变量,这些变量在整个文件系统中使用。
  • 这些全局变量包括minode数组、mtable数组、oft数组、proc数组和running指针,这些变量维护了文件系统的状态和进程信息。
  • 初始化文件系统时,fs_init函数用于初始化这些全局数据结构。

11.7.3 实用工具函数

  • util.c文件包含了一些常用的工具函数,如get_blockput_block函数,用于读写磁盘块。
  • iget函数用于获取内存中的inode,而iput函数用于释放inode,将其写回磁盘。
  • getino函数实现了文件系统树遍历的算法,以获取指定路径名的inode编号。
  • 这些工具函数是构建文件系统操作的基础,确保正确加载和释放inode。

11.7.4 mount_root.c文件

  • mount_root.c文件包含mount_root函数,该函数用于挂载根文件系统。
  • 该函数首先打开根设备,读取超级块信息以验证文件系统是否为EXT2。
  • 接着将根inode加载到内存中,并设置root指针指向该inode。
  • 同时,它设置了所有进程的当前工作目录(CWD)为根inode。
  • 还为根文件系统分配一个Mount Table表项,记录了根设备的关键信息,如inode数、块数、位图起始块等。
  • mount_root函数在系统初始化时调用,确保文件系统的挂载和初始化。

11.7.5 文件系统操作

  • 文件系统操作,如lscdpwd等,都在main函数中实现。
  • ls函数列出文件或目录的信息,它遍历目录的目录项并使用iget加载相关inode,然后使用ls_file打印文件信息。
  • chdir函数实现了切换当前工作目录功能,它使用iget加载新目录的inode并将其设置为当前工作目录。
  • pwd函数实现了打印当前工作目录路径的功能,它使用递归遍历目录的父目录并打印路径。

11.8 文件系统一级功能概要

在这个概要中,我们将涵盖与文件系统一级操作相关的关键功能和算法,包括mkdircreatrmdirlinkunlinksymlink和其他相关功能。

1. mkdir - 创建新目录

  • 命令:mkdir pathname
  • 创建一个具有指定pathname的新目录。
  • 设置新目录的权限位为默认值0755(拥有者有读、写、执行权限,其他用户只有读权限)。

mkdir的算法

  1. pathname分成dirnamebasename
  2. 检查dirname是否存在且为目录。
  3. 验证basename在父目录中不存在。
  4. 分配一个inode和一个磁盘块。
  5. 创建一个包含...条目的新目录数据块。
  6. 将新目录条目输入到父目录中。
  7. 增加父目录的links_count计数。

2. creat - 创建空的普通文件

  • 命令:creat pathname
  • 创建一个指定pathname的空普通文件。

creat的算法

  • mkdir相似,但将INODE.i_mode字段设置为0x41ED(等同于0644,拥有者有读和写权限,其他用户只有读权限)。
  • 不分配数据块,因此文件大小为0。
  • links_count设置为1。

3. rmdir - 移除目录

  • 命令:rmdir dirname
  • 移除一个目录,但该目录必须为空才能成功执行。

rmdir的算法

  1. 获取目录的内存中的INODE。
  2. 验证它是一个目录且不繁忙。
  3. 检查目录是否为空。
  4. 获取父目录的inode和目录名。
  5. 从父目录中删除目录条目。
  6. 减少父目录的links_count
  7. 释放数据块和目录的inode。
  • 命令:link old_file new_file
  • new_fileold_file创建一个硬链接。硬链接只能应用于普通文件。

link的算法

  1. 验证old_file是否存在且不是目录。
  2. 确保new_file不存在。
  3. 在新的父目录中创建一个与old_file相同inode的条目。
  4. old_filelinks_count增加1。
  • 命令:unlink filename
  • 将文件的links_count减1并从其父目录中删除文件名。如果links_count达到0,文件将被删除。

unlink的算法

  1. 获取文件的minode并确保它不是目录。
  2. 从父目录中删除文件名。
  3. 减少文件的links_count
  4. 如果links_count为0,释放数据块和inode。
  • 命令:symlink old_file new_file
  • new_fileold_file创建一个符号链接。符号链接可以链接到任何文件或目录。

symlink的算法

  1. 确保old_file存在且new_file不存在。
  2. 创建new_file,将其类型更改为LNK。
  3. old_file的名称存储在new_file的INODE中。
  4. 设置文件大小为old_file名称的长度。
  • 函数:int readlink(file, buffer)
  • 读取符号链接的目标文件名并返回其长度。

readlink的算法

  1. 获取文件的内存中的INODE并验证它是LNK文件。
  2. 将目标文件名从INODE.i_block[]复制到buffer
  3. 返回文件大小。

8. 其他一级功能

  • 其他功能如accesschmodchown和时间字段的修改遵循相似的模式:获取文件的内存中的INODE,获取信息或修改INODE,如果INODE已修改,则设置dirty,并调用iput释放minode。

这些文件系统一级功能是在类似UNIX的文件系统中处理目录、文件和符号链接的基本操作。rmdirunlink命令确保文件系统的完整性,通过在正确处理目录删除和取消文件链接的情况下维护系统的健康状态。

11.9 文件系统二级功能

在本部分中,我们将总结“文件系统二级功能”的关键信息。文件系统的二级功能主要用于实现文件内容的读取和写入操作,包括以下功能:open、close、lseek、read、write、opendir 和 readdir。

1. 打开操作

打开操作允许打开文件以进行读取或写入。它接受文件名和标志作为参数。标志是在“fcntl.h”中定义的符号常量,例如“O_RDONLY”、“O_WRONLY”或它们的组合,如“O_RDWR”。打开的算法如下:

打开的算法

  1. 通过调用“getino(filename)”获得文件的minode。
  2. 如果文件不存在,则使用“creat(filename)”创建它。
  3. 检索文件的minode。
  4. 在打开表(OFT)中分配一个条目,并用适当的值进行初始化。
  5. 在“PROC”结构中搜索第一个空闲文件描述符条目。
  6. 将文件描述符与OFT条目关联。
  7. 将索引作为文件描述符返回。

2. lseek 操作

“lseek”操作将打开文件描述符的偏移量设置为文件的偏移量。它允许您在文件内移动当前字节位置,从开头或相对于当前位置。实现假设新位置总是相对于文件开头。

3. 关闭操作

“close”操作用于关闭文件描述符。关闭的算法如下:

关闭的算法

  1. 检查文件描述符是否有效。
  2. 在OFT中减少refCount。如果refCount变为0,则释放相关的minode。
  3. 清除“PROC”中的文件描述符条目。

4. 读取常规文件

“read”操作从打开的文件描述符中读取数据到用户空间缓冲区。常规文件的读取算法如下:

读取的算法

  1. 初始化用于读取的字节数和当前偏移量的变量。
  2. 计算文件中可用的字节数。
  3. 循环读取数据,直到没有字节可读和可用的字节。
  4. 确定逻辑块和物理块的位置。
  5. 从物理块复制字节到缓冲区。
  6. 更新偏移和计数。

5. 写入常规文件

“write”操作从用户空间缓冲区将数据写入打开的文件描述符。常规文件的写入算法如下:

写入的算法

  1. 初始化用于写入的字节数的变量。
  2. 计算逻辑块、起始字节和剩余字节数。
  3. 将逻辑块转换为物理块。
  4. 从磁盘读取物理块到缓冲区。
  5. 从用户缓冲区传输数据到缓冲区。
  6. 更新偏移和计数。
  7. 将minode标记为已更改。

6. Opendir 和 Readdir

在Unix中,目录被视为文件。Opendir 和 readdir 函数允许您打开和读取目录,就像处理常规文件一样。Opendir 实现为具有特定标志的“open”系统调用的变体,而readdir 读取目录条目。标准的“readdir”函数返回指向“dirent”结构的指针。

7. 编程项目 #2:实现文件系统二级功能

编程项目 #2 包括完成文件系统二级功能的实现并演示读取、写入、cat、cp 和mv 操作。Cat显示文件内容,cp 将源文件复制到目标文件,而mv 则移动或重命名文件。

文件系统三级功能

1. 挂载操作

挂载命令

mount filesys mount_point

挂载命令将文件系统挂载到一个挂载点目录。这允许文件系统将其他文件系统作为现有文件系统的一部分包含其中。挂载操作使用MOUNT表和挂载点目录的内存minode。挂载的算法如下:

挂载算法

  1. 如果没有参数,则显示当前已挂载的文件系统。
  2. 检查文件系统是否已经挂载:MOUNT表包含已挂载文件系统(设备)的名称和它们的挂载点。如果设备已经挂载,则拒绝挂载。否则,分配一个空闲的MOUNT表项。
  3. 打开文件系统虚拟磁盘(在Linux下)以供读写;使用(Linux)文件描述符作为新设备。读取文件系统的超级块以验证它是一个EXT2文件系统。
  4. 查找挂载点的ino,然后查找minode:
    • ino = getino(pathname); // 获取ino:
    • mip = iget(dev, ino); // 将其inode加载到内存中;
  5. 检查挂载点是否是一个目录且没有被占用,例如不是某个进程的当前工作目录。
  6. 记录新设备和文件系统名称在MOUNT表项中,并存储ninodes、nblocks、bmap、imap以及inodes的起始块等以供快速访问。
  7. 将挂载点minode标记为已挂载(mounted flag = 1),并使其指向MOUNT表项,MOUNT表项又指回挂载点minode。

2. 卸载操作

卸载命令

umount filesys

卸载操作将一个已挂载的文件系统从挂载点卸载。它将已挂载的文件系统从挂载点分离出来,其中filesys可以是虚拟磁盘名称或挂载点目录名称。卸载的算法如下:

卸载算法

  1. 搜索MOUNT表以检查filesys是否已挂载。
  2. 检查是否在已挂载文件系统中存在任何活动的文件;如果有,拒绝卸载。
  3. 查找在内存中的挂载点minode,它在挂载时应该在内存中。将minode的mounted标志重置为0,然后使用iput()函数将minode释放。

3. 挂载点穿越

挂载点穿越是在跨越挂载点时会发生的现象。在挂载点穿越时,我们需要修改getino(pathname)函数来支持这种情况。假设文件系统newfs已经挂载到目录/a/b/c/。在遍历路径名时,可能会发生挂载点穿越,可以是向下遍历或向上遍历。

向下遍历

当遍历路径名/a/b/c/x时,一旦到达/a/b/c的minode,我们应该看到该minode已经挂载(mounted flag = 1)。因此,不再在/a/b/c的INODE中搜索x,而是要按照minode的mntPtr指针来查找挂载表项。然后,根据挂载表的dev编号,获取其根(ino=2)INODE,然后在已挂载设备的根INODE下继续搜索x

向上遍历

假设我们位于目录/a/b/c/x/并向上遍历,比如cd ../../,这将跨越挂载点/a/b/c。当到达已挂载文件系统的根INODE时,我们应该看到它是根目录(ino=2),但其dev编号与实际根目录的不同,因此它不是真正的根目录。使用其dev编号,可以定位挂载表项,该表项指向/a/b/c的已挂载minode。然后,切换到/a/b/c/的minode,继续向上遍历。这样,挂载点穿越就像猴子或松鼠从一棵树跳到另一棵树再跳回来。

因为挂载点穿越会更改设备号,一个全局的设备号将不再足够。我们需要修改getino()函数,使其返回设备号(dev)和ino的元组。修改getino()函数,将dev参数传递给函数,并将调用getino()的函数中的dev参数设置为返回的设备号。这样,修改后的getino()函数实际上返回了带有最终设备号的路径名。

4. 文件保护

在Unix/Linux中,文件保护是通过文件的INODE中的权限位来实现的。每个文件的INODE具有一个i_mode字段,其中的低9位表示权限。这9个权限位分为3组:

  • 拥有者(owner)
  • 同组用户(group)
  • 其他用户(other)

权限位

  • 拥有者:r(读取)、w(写入)、x(执行)
  • 同组用户:r(读取)、w(写入)、x(执行)
  • 其他用户:r(读取)、w(写入)、x(执行)

每个进程都

有一个用户ID(uid)和一个组ID(gid)。当进程尝试访问文件时,文件系统会将进程的uid和gid与文件的权限位进行比较,以确定是否允许以所期望的操作模式访问文件。如果进程没有适当的权限,访问将被拒绝。为了简化起见,我们可以忽略进程的gid,只使用进程的uid来检查访问权限。

5. 实际和有效uid

在Unix/Linux中,每个进程都有一个实际uid和一个有效uid。文件系统通过进程的有效uid来检查进程的访问权限。在正常情况下,进程的有效uid和实际uid是相同的。当进程执行一个设置了setuid位的程序(该位在文件的i_mode字段中打开)时,进程的有效uid变为该程序的uid。在执行设置了setuid位的程序时,进程实际上变成了该程序文件的所有者。例如,当进程执行mail程序时,这是一个由超级用户拥有的setuid程序,它可以写入另一个用户的邮件文件。当进程执行完setuid程序后,它会恢复到实际uid。为了简化起见,我们将忽略有效uid。

6. 文件锁定

文件锁定是一种机制,允许进程在更新文件时设置文件或文件部分的锁定,以防止竞争条件。文件锁定可以是共享的,允许并发读取,也可以是排他的,强制独占写入。文件锁定也可以是强制的或建议的。例如,Linux支持共享和独占的文件锁,但文件锁只是建议性的。在Linux中,可以使用fcntl()系统调用设置文件锁,也可以使用flock()系统调用来操作文件锁。为了简化起见,我们将假设只有一种非常简单的文件锁定方式。当进程尝试打开文件时,会检查所期望的操作模式是否与已有锁定兼容。唯一兼容的模式是读取(READ)。如果文件已经以更新模式打开,即WR|RW|APPEND,那么它不能再次打开。但是,这不会阻止相关进程(例如,父进程和子进程)修改已由父进程打开的相同文件。在这种情况下,文件系统只能保证每个写入操作是原子的,但不能保证进程的写入顺序,这取决于进程调度。

7. 编程项目 #3:完整文件系统的实现

编程项目 #3 是通过包括文件系统三级功能来完成文件系统的实现。书本的网站上提供了文件系统的示例解决方案以供下载。完整文件系统的源代码可以根据需要向教师索取。

这些笔记提供了关于文件系统的挂载、卸载、挂载点穿越、文件保护、实际和有效uid以及文件锁定的信息。您可以将其作为了解文件系统功能的参考。

苏格拉底挑战

问答过程 学EXT2文件系统知识

posted @ 2023-10-13 23:12  20211108俞振阳  阅读(169)  评论(0编辑  收藏  举报