20211102尹子扬第五次学习笔记
第十一章 EXT2文件系统
1.EXT2文件系统
多年来,Linux 一直使用EXT2(Card等1995)作为默认文件系统。EXT3(EXT3,2014)是EXT2的扩展。EXT3中增加的主要内容是一个日志文件,它将文件系统的变更 记录在日志中°日志可在文件系统崩溃时更快地从错误中恢复。没有错误的EXT3文件系 统与EXT2文件系统相同。EXT3的最新扩展是EXT4 ( Cao等2007)。EXT4的主要变化是磁盘块的分配。在EXT4中,块编号为48位。EXT4不是分配不连续的磁盘块,而是分配连续的磁盘块区,称为区段。除了这些细微的更改之外,文件系统结构和文件操作保持不变。本书的目的是讲授文件系统的原理°主要目标并非实现大的文件存储容量,而是重点论述文件系统设计和实现的原则,强调简单性以及与Linux的兼容性。为此,我们以ETX2作为文件系统。
2.EXT2文件系统数据结构
(1)通过mkfs创建虚拟磁盘
点击查看代码
mke2fs [-b blksize -N ninodes] device nblocks
点击查看代码
dd if=/dev/zero of=vdisk bs«1024 count«1440
mke2fs vdiek 1440
(2)虚拟磁盘布局
一开始,我们先使用这个基本文件系统布局进行假设。在适当的时候,我们会指出一些变化,包括硬盘上大型EXT2/3文件系统中的变化。下面来简要解释一下磁盘块的内容。
Block#0:引导块:B0是引导块,文件系统不会使用它。它用来容纳一个引导程序,从磁盘引导操作系统。
(3)超级块
Block#1:超级块:(在硬盘分区中字节偏移量为1024)B1是超级块,用于容纳整个文件系统的信息。下文说明了超级块结构中的一些重要字段。
点击查看代码
struct ext2_auper_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_block; /*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; /*Mount time */
u32 s_wtime; /*Write time */
U16 s_jnnt_count; /*Mount count */
S16 s_max_mnt_count; /*Maximal mount count */
ul6 s_magic; /*Magic signature */
// more non-essential fields
ul6 s_inode_size; /* size of inode structure */
}
(4)快组描述符
Block#2:块组描述符块(硬盘上的s_first_data_block+1) EXT2将磁盘块分成几个组。 每个组有8192个块(硬盘上的大小为32K)。每组用一个块组描述符结构体来描述。
点击查看代码
struct ext2_group_desc{
u32 bg_block_bitmap; //Bmap block number
u32 bg_inode_bicmap; //Imap block number
u32 bg_inode_table; //Inodes begin block number
ul6 bg_free_blocks_count; //THESE are OBVIOUS
U16 bg_free_inodes_count;
U16 bg_used_dirs_count;
ul6 bg_pad; //ignore these
u32 bg_reserved(3);
};
(5)块和索引点位图
Block#8:块位图(Bmap)(bg_block_bitmap)位图是用来表示某种项的位序列,例如磁盘块或索引节点。位图用于分配和回收项。在位图中,0位表示对应项处于FREE状态,1位表示对应项处于IN_USE状态。一个软盘有1440个块,但是Block#。未被文件系统使用。所以,位图只有1439个有效位。无效位被视作IN_USE,设置为1。
Block#9:索引节点位图(Imap)(bg_inode_bitmap)一个索引节点就是用来代表一个文件的数据结构。EXT2文件系统是使用有限数量的索引节点创建的。各索引节点的状态用B9的Imap中的一个位表示。在EXT2 FS中,前10个索引节点是预留的。所以,空EXT2 FS的Imap以10个1开头,然后是0。无效位再次设置为1。
(6)索引节点
*Block#10:索引(开始)节点:(bg_inode_table)每个文件都用一个128字节(EXT4中是256字节)的唯一索引节点结构体表示。下面列出了主要索引节点字段。
点击查看代码
struct ext2_inode {
ul6 i_mode; //16 bits = |tttt|ugs|rwx|rwx|rwx|
ul6 i_uid; //owner uid
u32 i_size; //file size in bytes
u32 i_atime; //time fields in seconds
u32 i_ctime; //since 00:00:00,1-1-1970
u32 i_mtime;
u32 i__dtime;
U16 i_gid; //group ID
u16 i_links_count; //hard-link count
u32 i_blocks; //number of 512-byte sectors
u32 i_flags; //IGNORE
u32 i_reserved1; //IGNORE
u32 i_block[15]; //See details below
u32 i_pad[7];; //for inode size = 128 bytes
}
点击查看代码
char *ctime (&time_field)
点击查看代码
printf("%s", ctime(&inode.i_atime); // note: pass & of time field prints i_atime in calendar form.
直接块:i_block[0] Mi_block[ll],指向直接磁盘块。
间接块:i_block[12]指向一个包含256个块编号(对于1KB BLKSIZE)的磁盘块,每个块编号指向一个磁盘块。
双重间接块:i_block[13]指向一个指向256个块的块,每个块指向256个磁盘块。
三重间接块: **i_block[14]是三重间接块。对于“小型” EXT2文件系统,可以忽略它。
索引节点大小(128或256 )用于平均分割块大小(1KB或4KB),所以,每个索引节点块都包含整数个索引节点在简単的EXT2文件系统中,索引节点的数量是184个(Linux默认值)。索引节点块数等于184/8-23个。因此,索引节点块为B10至B32。每个索引节点都有一个唯一的索引节点编号,即索引节点在索引节点块上的位置+1。注意,索引节点位置从0开始计数,而索引节点编号从1开始计数。0索引节点编号表示没有索引节点一根目录的索引节点编号为2。同样,磁盘块编号也从1开始计数,因为文件系统从未使用块0。块编号0表示没有磁盘块。
(7)数据块
紧跟在索引节点块后面的是文件存储数据块,假设有184个索引节点,第一个实际数据块是B33,它就是根目录/的i_block[0]。
(8)目录条目
目录包含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
3.邮差算法
在计算机系统中,经常出现下面这个问题。一个城市有M个街区,编号从0到M-1。每个街区有N座房子,编号从0到N-1。每座房子有一个唯一的街区地址。用(街区,房子)表示,其中0≤街区<M, 0≤房子<N。来自外太空的外星人可能不熟悉地球上的街区寻址方案,倾向于采用线性方法将这些房子地址编为0, 1, N-l, N, N+1等。已知某个街区地址BA=(街区,房子),怎么把它转换为线性地址LA,反过来,已知线性地址, 怎么把它转换为街区地址?如果都从0开始计数,转换就会非常简单。
点击查看代码
Linear_address LA = N*block + house;
Block_address BA = (LA / N, LA % N);
(1)C语言中的Test-Set-Clear位
在标准C语言程序中,最小的可寻址单元是一个字符或字节。在一系列位组成的位图中,通常需要对位进行操作。考虑字符bufI1024],它有1024个字节,用buf[i]表示,其中
i = 0,1,…,1023。它还有8192个位,编号为0,1,2,…,8191=已知一个位号BIT,
例如1234,那么哪个字节i包含这个位,以及哪个位j在该字节中呢?
点击查看代码
i = BIT/8; j = BIT%8; //8 = number of bits in a byte.
点击查看代码
.TST a bit for 1 or 0 : if (buf[i] & (1 « j))
.SET a bit to 1 : buf[i] |= (1 « j);
.CLR a bit to 0 : buffi] &= ~(1 « j);
点击查看代码
struct bits{
unsigned int bit0 : 1; // bit0 field is a single bit
unsigned int bit123 : 3; // bitl23 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;
(2)将索引节点号转换为磁盘上的索引节点
在EXT2文件系统中,每个文件都有一个唯一的索引节点结构。在文件系统磁盘上,索 引节点从inodejable块开始。每个磁盘块包含
点击查看代码
INODES_PER_BLOCK = BLOCK_SIZE/sizeof (INODE)
点击查看代码
block = (ino - 1) / INODES_PER_BLOCK + inode_table;
inode - (ino - 1) % INODES_PER_BLOCK;
5.遍历EXT2 文件系统树
遍历算法
(1)读取超级块。检查幻数 s_magic(0xEF53),验证它确实是 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。用name[0],name[1],…,name[n-1]来表示组件。
(5)从根索引节点开始,在其数据块中搜索 name[0]。为简单起见,我们可以假设某个目录中的条目数量很
少,因此一个目录索引节点只有12个直接数据块。有了这个假设,就可以在12个(非零)直接块中搜索
name[0]。目录索引节点的每个数据块都包含以下形式的 dir_entry 结构体;
点击查看代码
[ino rec_len name_len NAME] [ino rec_len name_len NAME]....
点击查看代码
dp =(dir_entry*)((char *)dp + dp->rec_len);
使用索引节点号ino来定位相应的索引节点。回想前面的内容,ino 从1开始计数。使用邮差算法计算包含索引节点的磁盘块及其在该块中的偏移量。
点击查看代码
blk =(ino - 1) / INODE8_PER_BLOCK + InodesBeginBlock;
offset = (ino - 1) % INODES_PER_BLOCK;
由于(5)~(6)步将会重复n次,所以最好编写一个搜索函数∶
点击查看代码
u32 search (INODE *inodePtr, char *name)
{
// search for name in the data blocks of current DIR inode
// if found, return its ino; else return 0 )
}
点击查看代码
Assume:n,name[0],....,name[n-1] are globals
INODE *ip points at INODE of /
for(i=0; i<n; i++)
{
ino = search(ip, name[4])
if(!ino){ // can't find name[i], exit;}
use ino to zead in INODE and let ip point to INODE
}
6.基本文件系统
(1)type.h 文件
这类文件包含EXT2文件系统的数据结构类型,比如超块、组描述符、索引节点和目录条目结构。此外,它还包含打开文件表、挂载表、PROC结构体和文件系统常数。
(2)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
(1)1级文件系统函数及其原理
1级文件系统函数的功能是文件和路径、创建和删除等较为直接和底层的相关的操作。
mkdir:mkdir创建一个带路径名的新目录。同时将目录的权限设置为 0755 ,即 rwxr_xr_x ,所有人可以访问和读写,其他人可以访问但是只能读取。
creat:creat创建一个空的普通文件。
rmdir:删除一个路径。
link:链接文件。
unlink:解除链接文件。
(2)2级文件系统函数及其原理
2级文件系统函数的功能常常是打开、读取文件、写入文件等对文件进行的操作。
open:打开文件。
lseek:将打开的文件描述符在OFT中的偏移量从文件开头或当前位置开始的字节位置。
close:关闭文件描述符。
(2)3级文件系统函数及其原理
3级文件系统函数进行文件系统的挂载、卸载和文件保护等。
mount:挂载。使用方法:
点击查看代码
mount filesys mount_point
一.苏格拉底挑战截图
二.问题解决与思路
下面链接可以对没有创建root用户的同学有所帮助
点击查看代码
https://blog.csdn.net/ganfanren00001/article/details/122275543
三.实验过程截图
创建虚拟磁盘
显示超级块
代码为:
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ext2fs/ext2_fs.h>
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef struct ext2_super_block SUPER;
SUPER* sp;
char buf[1024];
int fd, blksize, inodesize;
int print(char *s, u32 x)
{
printf("%-30s = %8d\n",s,x);
}
int super(char *device)
{
fd = open(device, O_RDONLY);
if (fd < 0)
{
printf("open %sfailed\n",device);
exit(1);
}
lseek(fd ,(long)1024*1,0);
read(fd, buf, 1024);
sp = (SUPER *)buf;
printf("%-30s = %8x ", "s_magic", sp->s_magic);
if(sp->s_magic != 0xEF53)
{
printf("NOT an EXT2 FS\n");
exit(2);
}
printf("EXT2 FS OK\n");
print("s_inodes_count",sp->s_inodes_count);
print("s_blocks_count",sp->s_blocks_count);
print("s_r_blocks_count",sp->s_r_blocks_count);
print("s_free_inodes_count",sp->s_free_inodes_count);
print("s_free_blocks_count",sp->s_free_blocks_count);
print("s_first_data_block",sp->s_first_data_block);
print("s_log_block_size",sp->s_log_block_size);
print("s_blocks_per_group",sp->s_blocks_per_group);
print("s_innodes_per_group",sp->s_inodes_per_group);
print("s_mnt_count",sp->s_mnt_count);
print("s_max_mnt_count",sp->s_max_mnt_count);
printf("%-30s = %8x\n","s_magic",sp->s_magic);
printf("s_mtime = %s",ctime(&sp->s_mtime));
printf("s_mtime = %s",ctime(&sp->s_wtime));
blksize = 1024 * (1 << sp->s_log_block_size);
printf("block size = %d\n" ,blksize);
printf("inode size = %d\n" ,sp->s_inode_size);
}
char *device = "vdisk";
int main(int argc, char *argv[])
{
if(argc < 1)
{
device = argv[1];
}
super(device);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)