2021-2022-1-diocs-EXT2文件系统第六周学习笔记

20191205 2021-2022-1-diocs-EXT2文件系统(第六周学习笔记)

一、任务详情

自学教材第11章,提交学习笔记(10分)

知识点归纳以及自己最有收获的内容 (3分)

问题与解决思路(2分)

实践内容与截图,代码链接(3分)

...(知识的结构化,知识的完整性等,提交markdown文档,使用openeuler系统等)(2分)

二、教材内容归纳整理

本章讨论了 EXT2文件系统。本章首先描述了EXT2文件系统在Linux 中的历史地位以及 EXT3/EXT4文件系统的当前状况;用编程示例展示了各种EXT2数据结构以及如何遍历 EXT2文件系统树;介绍了如何实现支持Linux 内核中所有文件操作的 EXT2文件系统;展示了如何通过虚拟磁盘的mount root来构建基本文件系统;将文件系统的实现划分为3个级别,级别1扩展了基本文件系统,以实现文件系统树,级别2实现了文件内容的读/写操作,级别3实现了文件系统的挂载/装载和文件保护;描述了各个级别文件系统函数的算法,并通过编程示例演示了它们的实现过程;将所有级别融合到一个编程项目中;最后,将所有编程示例和练习整合到一个完全有效的文件系统中。

思维导图

 

 

一、知识点总结

1.通过mkfs创建虚拟磁盘

Linux下,命令

mke2fs [-b blksize -N ninodes] device nblocks

在设备上创建一个带有nblocks个块(每个块大小为blksize字节)和 ninodes 个索引节点的EXT2文件系统。设备可以是真实设备,也可以是虚拟磁盘文件。如果未指定blksize.则默认块大小为1KB。如果未指定ninoidesmke2fs将根据 nblocks计算一个默认的ninodes数。得到的 EXT2文件系统可在 Linux 中使用。举个具体的例子,下面的命令

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

mke2fs vdisk 1440

可在一个名为vdisk的虚拟磁盘文件上创建一个EXT2文件系统,有1440个大小为1KB 的块。

 2.超级块

Block#1超级块(在硬盘分区中字节偏移量为1024B1是超级块,用于容纳整个文件系统的信息。

struct ext2_guper_block {

u32 s_inodes_count;           /* Inodes count */

u32 s_blocks_count;           /* Blocks count*/

u32 s_r_blocks_count;        /* Reserved blocks count */

u32 s_free_blocks_count;       / * Free blocks count */

u32 s_free_inodes_count;     /* Free inodes count */

u32 s_first__data_blook;       /* First Data Block */

u32 s_log_block_size;            /* Block size */

u32 s_log_cluster_size;           /* Allocation cluster size */

u32 s_blocks_per_group;          /* # Blocks per group*/

u32 s_clusters_per_group;             /*# Fragments per group */

u32 s_inodes_per_group;           /* # Inodes per group*/

u32 s_mtime;u32 s_wtime;        /* Mount time */

u16 s_mnt_count;             /* Write time */

s16 s_max_mnt_count;          /*_Mount_count*/L

u16 8_magic;           /* Magic signature */

// more non-essential fields

u16 s_inode_size;          /* size of inode structure*/

}

 3.块组描述符

Block#2∶块组描述符块(硬盘上的s first data block+1EXT2将磁盘块分成几个组。每个组有8192个块(硬盘上的大小为32K)。每组用一个块组描述符结构体来描述。

struct ext2_group_desc {

u32 bg_block_bitmap;           // Bmap block number

u32 bg_inode_bitmap;          // Imap block number

u32 bg_inode_table;        // Inodes begin block number

u16 bg_free_blocks_count;      // THESE are OBVIOUS

u16 bg_free_inodes_count;     

ul6 bg_used_dirs_count;

u16 bg_pad;                // ignore these

u32 bg_reserved[3];

};

4.块和索引节点位图

Block#8∶块位图(Bmapbg block bitmap)位图是用来表示某种项的位序列,例如磁盘块或索引节点。位图用于分配和回收项。在位图中,0位表示对应项处于FREE状态,1位表示对应项处于IN USE状态。一个软盘有1440个块,但是 Block#0未被文件系统使用。所以,位图只有1439个有效位。无效位被视作INUSE,设置为1

Block#9∶索引节点位图Imap)(bg inode bitmap)一个索引节点就是用来代表一个文件的数据结构。EXT2文件系统是使用有限数量的索引节点创建的。各索引节点的状态用B9Imap 中的一个位表示。在EXT2 FS中,前10个索引节点是预留的。所以,空 EXT2 FSImap 101开头,然后是0。无效位再次设置为1

 5.索引节点

Block#10∶索引(开始)节点bg inode table)每个文件都用一个128字节(EXT4中是256字节)的唯一索引节点结构体表示。

struct ext2_inode {

u16 i_mode;       // 16 bits=|tttt |ugs|rwx|rwx|rwxl

ul6 i_uid;         // owner uid

u32 i_size;        // file size in bytes

u32 i_atime;      // time fields in seconds

u32 1_ctime;     // since 00:00:00,1-1-1970

u32 i_mtime;     

u32 i_dtime;      

i_gid;     // group ID u16

u16 i_links_count;     // hard-link count

u32 i_blocks;u32 i_flags;     // number of 512-byte sectors

u32 i_reservedl;     // IGNORE // IGNORE

u32 i_block[15];       // See details below

u32 i_pad[7];           // for inode size = 128 bytes

}

直接块: block[0]iblock[11],指向直接磁盘块。

间接块:iblock[12]指向一个包含256个块编号(对于1KB BLKSIZE)的磁盘块,每个块编号指向一个磁盘块。

双重间接块:i block[13]指向一个指向 256个块的块,每个块指向256个磁盘块。

三重间接块:iblock[14]是三重间接块。对于"小型"EXT2文件系统,可以忽略它。

 6.目录条目

目录包含dir_entry 结构,即

struct ext2_dir_entry_2{

u32 inode;         // inode number; count from 1,NOT 0

u16 rec_len;       // this entry's length in bytes

u8 name_len;      // name length in bytes

u8 file_type;       // not used

char name[EXT2_NAME_LEN];        // name:1-255 chars,no ending NULL ;

};

dir_entry 是一种可扩充结构。名称字段包含1255个字符,不含终止NULL。所以dir entry rec len也各不相同。

 7.邮差算法应用

1C语言中的Test-Set-Clear

注意,一些 C语言编译器允许在结构体中指定位,如

struct bits{

unsigned int bit0      : 1;     //bit0 field is a single bit

unsigned int bit123    : 3;     // bit123 field is a range of 3 bits

unsigned int otherbits  :27;    // other bits field has 27 bits

unsigned int bit31     :1;     // bit31 is the highest bit

}var;

该结构体将 var.定义为一个32位无符号整数,具有单独的位或位范围。那么,var.bit0=0;1赋值给第0位,则有var.bit123=5;101赋值给第1位到第3位等。但是,生成的代码仍然依赖于邮差算法和位屏蔽来访问各个位。我们可以用邮差算法直接操作位图中的位,无须定义复杂的C语言结构体。

2)将索引节点号转换为磁盘上的索引节点

EXT2文件系统中,每个文件都有一个唯一的索引节点结构。在文件系统磁盘上,索引节点从inode table块开始。每个磁盘块包含

INODES_PER_BLOCK  =  BLoCK_SIZE/sizeofINODE

个索引节点。每个索引节点都有一个唯一的索引节点号,ino=12,从1开始线性计数。已知一个ino,如1234,那么哪个磁盘块包含该索引节点,以及哪个索引节点在该块中呢?我们需要知道磁盘块号,因为需要通过块来读/写一个真正的磁盘。

 8.遍历EXT2文件系统树

1.遍历算法

1)读取超级块。检查幻数s magic0xEF53),验证它确实是 EXT2 FS

2)读取块组描述符块(1+s first data block),以访问组0描述符。从块组描述符的bg_ inode_table条目中找到索引节点的起始块编号,并将其称为InodesBeginBlock

3)读取InodeBeginBlock,获取/的索引节点,即INODE #2

4)将路径名标记为组件字符串,假设组件数量为 n。例如,如果路径名=/a/b/c,则组件字符串是"a""b""c",其中n=3。用name0】,name【【1】,…,namen-1来表示组件。

5)从(3)中的根索引节点开始,在其数据块中搜索 name0】。为简单起见,我们可以假设某个目录中的条目数量很少,因此一个目录索引节点只有12个直接数据块。有了这

个假设,就可以在12个(非零)直接块中搜索 name0】。目录索引节点的每个数据块都包含以下形式的 dir_entry结构体:

[ino rec_len name_len NAME]  [ino rec_len name_len NAME] . . . . .

6)使用索引节点号ino 来定位相应的索引节点。回想前面的内容,ino 1开始计数。使用邮差算法计算包含索引节点的磁盘块及其在该块中的偏移量。

 9.文件系统的结构

(1)是当前运行进程的PROC结构体;

(2)是文件系统的根指针;

(3)是一个openTable条目;

(4)是内存索引节点;

(5)是已挂载的文件系统表。

 10.基本文件系统

  1. type.h文件

这类文件包含EXT2文件系统的数据结构类型,比如超块、组描述符、索引节点和目录条目结构。此外,它还包含打开文件表、挂载表、PROC结构体和文件系统常数。

  1. global.c文件

这类文件包含文件系统的全局变量。全局变量的例子有

MINODE minode [NMINODE];       // in memory INODEs

MTABLE mtable [NMTABLE];        // mount tables

OFT    oft [NOFT];              // Opened file instance

PROC   proc[NPROC]PROC ]       // PROC structures

PROC      *running;            // current executing

 

 11.文件系统项目的扩展

1)多个组组描述符的大小为32字节。对于1KB大小的块,一个块可能包含1024/32=32组描述符。32个组的文件系统大小可以扩展为32*8=256MB

24KB大小的块对于4KB大小的块和一个组,文件系统大小应为4*8=32MB。对于一个组描述符块,文件系统可能有128个组,可将文件系统大小扩展到128*32=4GB。对于2个组描述符块,文件系统大小为8GB等。大多数扩展都很简单,适合用于编程项目。

3)管道文件管道可实现为普通文件,这些文件遵循管道的读/写协议。此方案的优点是;它统一了管道和文件索引节点,并允许可被不相关进程使用的命名管道。为支持快速读/写操作,管道内容应在内存中,比如在 RAMdisk中。必要时,读者可将命名管道实现为FIFO文件。

4I/O缓冲在编程项目中,每个磁盘块都是直接读写的。这会产生过多的物理磁盘I/O操作。为提高效率,实际文件系统通常使用一系列I/O缓冲区作为磁盘块的缓存内存。文件系统的I/O缓冲将会在第12章中讨论,但是可以把它合并到文件系统项目中。

 

二、最有收获的内容

编程项目:1级文件系统的实现&&2级文件系统的实现

1.1级函数包括

mkdir算法、creat算法、mkdir-creat算法、rmdir算法、link算法、unlink算法、symlink算法、readlink算法,其他1级函数(访问、chmodchown、更改文件的时间字段等)

所有这些函数的操作方式均相同。

操作示例:

(1)chmod oct文件名:将文件名的权限位更改为八进制值;

(2)utime文件名:将文件的访问时间更改为当前时间。

2.2级函数包括

Open算法、lseekclose算法、读/写普通文件、opendir-readdir

操作示例:

3.3级函数包括

挂载算法、卸载算法、交叉挂载点、文件保护、文件锁定、实际uid和有效uid

操作示例:

 

 

三、问题与解决思路

通过学习本章知识,深刻地感知到了LinuxEXT2文件系统的操作复杂,所以一直在思考一个问题:Linux系统是如何读取一个文件的?

解决思路:下面分别针对目录和文件来说明:

1.目录

ext2文件系统建立一个目录时,会给该目录分配一个indoe和至少一个块。inode记录该目录的相关属性,并指向分配到的那个块。这个块记录了这个目录下的相关文件或目录的关联性。

2.文件

建立普通文件时,会给该文件分配一个inode与相对于该文件大小的块数量。如一个块大小为4KB,建立一个100KB的文件,linux将分配一个inode25个块来存储该文件。

其中inode本省不记录文件名,而是记录文件的相关属性,文件名是记录在目录所属的块区域中的。

因此要读取一个文件的内容时,Linux会从/开始,一直获取到该文件的上层目录所在的inode,再由该目录的块区域中的文件名对应的inode号来找到对应的文件,最后根据inode中的指针找到最终的文件内容。

如上图,读取/etc/crontab的流程为:

1.根据/根目录的块中找到/etc对应的inode

2.根据/etc 目录的块中的inode数据,查找到crontabinode

3.根据查到的inode号来获取该文件的属性,并且前往该inode所指的块,顺利获取crontab 的文件内容。

3.具体操作:编译代码并运行

代码链接:

https://gitee.com/two_thousand_and_thirteen/codes/1i07cq8ag6po4zswjdmth16

运行截图:

 

 

 

四、实践内容(截图、代码链接)本次实践是基于OpenEuler系统下实现的

通过编写C语言实现mkdir功能

一、背景

 linux 中的mkdir命令是用来创建一个目录的,相应的就需要使用到linux中的系统调用函数mkdir来实现目录创建的功能。单单只是创建目录的话一个系统调用足以,本文是使用mkdir函数来实现mkdir -p这个选项的功能,对其不存在的父目录实现创建。

二、思路

对于一个a/b/c这样的一个多级目录,要想实现父目录的创建方法和思路有很多,可以进行字符串处理分出一级一级目录来,但是这样实现很是繁琐,以至于我想到了递归实现。

思路如下:

    1.先判断a/b/c是否存在,不存在获取其父目录判断。若存在直接退出

    2.判断a/b是否存在,不存在就获取其父目录,若存在退出

    3.判断a/是否存在,不存在就获取其父目录,若存在退出

    4.如果其父目录为./时退出

为了实现以上过程,需要一个可以获得一个目录的父目录的函数。通过man可以清楚这个函数要求

最终定位到dirname函数非常符合我的要求。函数声明如下:

#include <libgen.h>

char *dirname(char *path);

三、代码实现

代码链接:

https://gitee.com/two_thousand_and_thirteen/codes/a9qhy5pizk6grsdfcvot856

功能实现截图:

 

 

四、总结

在写mkdir -p这个功能的时候,思路很明确,代码也基本上早就写好了,但是调试花了很长时间。究其原因是在于dirname这个函数,看其声明很明显就是给一个目录的path字符串指针,返回一个指向其目录的字符串指针,但是其实不然。dirname不仅返回一个指向其父目录的字符串指针还可能修改传入的参数path的值为父目录字符串。man文档中说明如下:

The  dirname()  function  may  modify  the string pointed to by path, and may return a

 pointer to static storage that may then be overwritten by subsequent calls to dirname().

最终还是通过printf打印调试的,没有借助gdb,主要还是认为gdb用起来不太方便

 

posted @ 2021-10-16 23:01  张灯结彩,潇潇暮雨  阅读(118)  评论(0编辑  收藏  举报