ch11-EXT2文件系统

知识点归纳

EXT2即第二代扩展文件系统(英语:second extended filesystem,缩写为 EXT2)

Linux一直使用EXT2作为默认文件系统,EXT3是EXT2的扩展,增加的主要内容是日志文件

Block#0 是引导块,文件系统不会使用它,它用于容纳从磁盘引导操作系统的引导程序

Block#1 是超级块,用于容纳关于整个文件系统的信息

Block#2 是块组描述符,EXT2将磁盘块分成几个组,每组有8192个块,每组用一个块组描述符结构体描述

Block#8 是块位图,用于表示某种项的位序列,例如磁盘块或索引节点,位图用于分配和回收项。

Block#9 是索引节点位图,索引节点用于代表一个文件的数据结构

Block#10 是索引(开始)节点块,索引节点大小用于平均分割块大小,所以每个索引节点块都包含整数个索引节点。

数据块 是紧跟在索引节点块后面的文件存储块。

inode结构体

struct ext2_inode{
	u16 i_mode;	        //16 bits = |tttt | ugs |rwx | rwx | rwx |
	u16 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-1ink count
	u32 i_b1ocks;		//number of 512-byte sectors
	u32 i_f1ags;		//IGNORE
	u32 i_reserved1;	//IGNORE
	u32 i_block [15];	//See details below
	u32 i_pad[7];		//for inode size = 128 bytes
}

i_mode

i_block[15]数组存了15个指针
i_block[0-11]指向12个直接磁盘块
i_block[12]是间接块,指向一个磁盘块,该磁盘块包含256个块编号,即能映射256个磁盘块
i_block[13]是双重间接块,即双重映射,1 --> 256 * 256
i_block[14]是三重间接块,即三重映射,1 --> 256 * 256 * 256

dentry结构体

struct ext2_dir_entry_2{
	u32  inode;		 	//inode number; count from 1,NOT o
	u16  rec_len;		        //this entry's length in bytes
	u8	 name_len;	  	//name length in bytes
	u8	 fi1e_type;		//not used
	char name[EXT2_NAME_LEN] ;      // name:1-255 chars,no ending NULL
};

邮差算法:类似二维数组的存储和访问

遍历算法:

(1)读取超级块。检查幻数s_magic ( OxEF53),验证它确实是EXT2FS。

(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)

从(3)中的根索引节点开始,在其数据块中搜索name[0]。
为简单起见,我们可以假设某个目录中的条目数量很少,因此一个目录索引节点只有12个直接数据块。
有了这个假设,就可以在12个(非零)直接块中搜索name[0]。目录索引节点的每个数据块都包含以下形式的dir_entry结构体:
[ino rec_len name_len NANE] [ino rec_len name_len NAME]
其中NAME是一系列nlen字符,不含终止NULL。
对于每个数据块,将该块读入内存并使用dir_entry *dp指向加载的数据块。
然后使用name_len将NAME提取为字符串,并与name[0]进行比较。如果它们不匹配,则通过以下代码转到下一个dir_entry:
dp = (dir_entry *) ((char *)dp + dp->rec_len);
继续搜索。如果存在name[0],则可以找到它的dir_entry,从而找到它的索引节点号。

(6)

使用索引节点号ino来定位相应的索引节点。回想前面的内容,ino从1开始计数。使用邮差算法计算包含索引节点的磁盘块及其在该块中的偏移量。
blk=(ino - 1) / INODES_PER_BLOCK+ InodesBeginBlock;
offset = (ino - 1)% INODES_PER_BLOCK;
然后在索引节点中读取/a,从中确定它是否是一个目录(DIR)。
如果/a不是目录,则不能有/a/b,因此搜索失败。
如果它是目录,并且有更多需要搜索的组件,那么继续搜索下一个组件name[1]。
现在的问题是:在索引节点中搜索/a的name[1],与第(5)步完全相同。

(7)
由于(5)~(6)步将会重复n次,所以最好编写一个搜索函数:
u32 search ( INODE*inodePtr, char *name)
然后只需调用n次search函数
如果搜索循环成功结束,ip必须指向路径名的索引节点。

文件系统的结构

文件系统的级别

文件系统的实现分为三个级别。每个级别处理文件系统的不同部分。这使得实现过程模块化,更容易理解。
第1级别实现了基本文件系统树。用户命令程序有:mkdir,creat,mknod,rmdir,link,unlink,symlink,rm,ls,cd和pwd
第2级别实现了文件内容读写函数
第3级别实现了文件系统的挂载、卸载和文件保护

基本文件系统

  • type.h文件
    这类文件包含EXT2文件系统的数据结构类型,比如超块、组描述符、索引节点和目录条目结构。
    此外,它还包含打开文件表、挂载表、PROC结构体和文件系统常数。
  • global.c文件
    这类文件包含文件系统的全局变量。全局变量的例子有:
    MINODE minode [NMINODE]; //in memory INODEs
    MTABLE mtable[NMTABLE]; //mount tables
    OFT oft[NOFT]; //Opened file instance
    PROC proc [NPROC] //PRoC structures
    PROC *running; //current executing PROC
  • util.c文件
    该文件包含文件系统常用的实用程序函数。最重要的实用程序函数是读/写磁盘块函数iget()、iput()和 getino()。
  • mount-root.c文件
    该文件包含mount_root()函数,在系统初始化期间调用该函数来挂载根文件系统。它读取根设备的超级块,以验证该设备是否为有效的EXT2文件系统。
    然后,它将根设备的根 INODE ( ino = 2)加载到minode中,并将根指针设置为根minode。它还将所有进程的当前工作目录设置为根minode。
    分配一个挂载表条目来记录挂载的根文件系统。根设备的一些关键信息,如 inode和块的数量、位图的起始块和inode,表,也记录在挂载表中,以便快速访问。

问题与解决思路

时间戳转换为任意可读的日期格式

unix时间戳的数据类型,是32位(4字节)的无符号数,可以表示的大致时间范围是1970-2038

为了避免"时间回归"bug,有的地方已经开始用64位(8字节)的无符号数,来表示系统时间

<time.h>

time_t

通过sizeof,在本人电脑上是8字节,long是4字节,long long 是8字节。

获取unix时间戳
time_t time(time_t *seconds)

time_t curtime;
curtime = time(NULL);
or
time(curtime)
printf("%ld
",curtime);

char *ctime(const time_t *timer)
该函数返回一个字符串,包含了可读格式的日期和时间信息。
注意传入参数是指针。
格式固定,英文输出,星期,月份,日期,时:分:秒,年份
字符串结尾自带 ' ' ,不够灵活


struct tm *localtime(const time_t *timer)
传入指针,返回结构体指针

struct tm {
   int tm_sec;         /* 秒,范围从 0 到 59                */
   int tm_min;         /* 分,范围从 0 到 59                */
   int tm_hour;        /* 小时,范围从 0 到 23                */
   int tm_mday;        /* 一月中的第几天,范围从 1 到 31                    */
   int tm_mon;         /* 月份,范围从 0 到 11                */
   int tm_year;        /* 自 1900 起的年数                */
   int tm_wday;        /* 一周中的第几天,范围从 0 到 6                */
   int tm_yday;        /* 一年中的第几天,范围从 0 到 365                    */
   int tm_isdst;       /* 夏令时                        */    
};

虽然可以自行输出结构体里的成员,达到灵活输出日期的效果,但是time.h还有个格式化输出神器。


size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)
format如下:它们是区分大小写的
%a 星期几的简写
%A 星期几的全称
%b 月分的简写
%B 月份的全称
%c 标准的日期的时间串
%C 年份的后两位数字
%d 十进制表示的每月的第几天
%D 月/天/年
%e 在两字符域中,十进制表示的每月的第几天
%F 年-月-日
%g 年份的后两位数字,使用基于周的年
%G 年分,使用基于周的年
%h 简写的月份名
%H 24小时制的小时
%I 12小时制的小时
%j 十进制表示的每年的第几天
%m 十进制表示的月份
%M 十时制表示的分钟数
%n 新行符
%p 本地的AM或PM的等价显示
%r 12小时的时间
%R 显示小时和分钟:hh:mm
%S 十进制的秒数
%t 水平制表符
%T 显示时分秒:hh:mm:ss
%u 每周的第几天,星期一为第一天 (值从0到6,星期一为0)
%U 第年的第几周,把星期日做为第一天(值从0到53)
%V 每年的第几周,使用基于周的年
%w 十进制表示的星期几(值从0到6,星期天为0)
%W 每年的第几周,把星期一做为第一天(值从0到53)
%x 标准的日期串
%X 标准的时间串
%y 不带世纪的十进制年份(值从0到99)
%Y 带世纪部分的十进制年份
%z,%Z 时区名称,如果不能得到时区名称则返回空字符。
%% 百分号

void format_time(time_t arg){
    char buf[64];
    struct tm *ptm = localtime(&arg);
    strftime(buf,64,"%F %T",ptm);
    printf("%s
",buf);
  }
想要灵活输出,修改format即可
"%F %R" 2021-10-17 17:15
"%F %T" 2021-10-17 17:15:10
"%Y年%m月%e日 %H:%M:%S" 2021年10月17日 17:15:10

实践内容

显示超级块

首先创建一个包含简单EXT2文件系统的虚拟磁盘

dd if=/dev/zero of=mydisk bs=1024 count=1440
mke2fs -b 1024 mydisk 1440

superblock.c

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <fcntl.h>
  4 #include <sys/types.h>
  5 #include <unistd.h>
  6 #include <ext2fs/ext2_fs.h>
  7 #include <time.h>
  8 //typedef u8,u16,u32 SUPER for convenience
  9 typedef unsigned char  u8;
 10 typedef unsigned short u16;
 11 typedef unsigned int   u32 ;
 12 typedef struct ext2_super_block SUPER;
 13 
 14 SUPER *sp;
 15 char buf[1024];
 16 int fd,blksize,inodesize;
 17 int print (char *s,u32 x){
 18         printf ( "%-30s = %8d
" ,s,x);
 19 }
 20 
 21 int super (char *device)
 22 {
 23         fd = open(device, O_RDONLY);
 24         if(fd < 0 ){
 25                 printf ( " open %sfailed
" , device);
 26                 exit(1);
 27         }
 28         lseek(fd,(long)1024*1,0);               //block 1 on PD,offset 1024 on HD
 29         read (fd,buf,1024);
 30         sp = (SUPER*)buf;                               //as a super b1ock structure
 31         
 32         //check EXT2 Fs magic number :
 33         printf ("%-30s = %8x ","s_magic",sp->s_magic);
 34         if(sp->s_magic != 0xEF53){
 35                 printf ( "NOT an EXT2 FS
" );
 36                 exit(2);
 37         }
 38         printf( "EXT2 FS OK
" ) ;
 39         print( "s_inodes__count" ,sp->s_inodes_count);
 40         print( "s_blocks_count" ,sp->s_blocks_count) ;
 41         print ("s_r_blocks_count" ,sp->s_r_blocks_count);
 42         print("s_free_inodes_count" , sp->s_free_inodes_count);
 43         print ("s_free_blocks_count" , sp->s_free_blocks_count);
 44         print ( "s_first_data_block" ,sp->s_first_data_block);
 45         print ( "s_log_block_size" ,sp->s_log_block_size);
 46         print ( "s_blocks_per_group", sp->s_blocks_per_group);
 47         print ( "s_inodes _per_group",sp->s_inodes_per_group);
 48         print ( "s__mnt_count" ,sp->s_mnt_count) ;
 49         print ( "s_max_mnt_count" ,sp->s_max_mnt_count);
 50         printf ( "%-30s = %8x
" , "s_magic" , sp->s_magic);
 51         printf ( "s__mtime = %s" , ctime ( &sp->s_mtime) ) ;
 52         printf( "s_wtime =%s" , ctime ( &sp->s_wtime));
 53         blksize = 1024 * ( 1 <<sp->s_log_block_size);
 54         printf( "block size = %d
", blksize) ;
 55         printf ( "inode size = %d
" ,sp->s_inode_size);
 56 }
 57 
 58 char *device = "mydisk" ;               //default device name
 59 int main(int argc, char *argv[])
 60 {
 61         if(argc>1){
 62                 device = argv[1];
 63         }
 64         super(device);
 65 }
 66 

显示索引节点位图(imap)

imap.c

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <fcntl.h>
  4 #include <ext2fs/ext2_fs.h>
  5 #include <unistd.h>
  6 #include <sys/types.h>
  7 typedef unsigned char  u8;
  8 typedef unsigned short u16;
  9 typedef unsigned int   u32 ;
 10 typedef struct ext2_super_block SUPER;
 11 typedef struct ext2_group_desc GD;
 12 #define BLKSIZE 1024
 13 
 14 SUPER *sp;
 15 GD *gp;
 16 char buf[BLKSIZE];
 17 int fd;
 18 
 19 //get_block ( ) reads a disk block into a buf[ ]
 20 
 21 int get_block(int fd, int blk,char *buf){
 22     lseek (fd,(long)blk*BLKSIZE,SEEK_SET);
 23     return read(fd,buf,BLKSIZE);
 24 }
 25 
 26 int imap(char *device){
 27     int i, ninodes, blksize,imapblk;
 28     fd = open (device,O_RDONLY) ;
 29     if (fd < 0 ){
 30         printf ( " open %s failed
" , device);
 31         exit (1);
 32     }   
 33     get_block (fd,1,buf) ;              //get superblock
 34     sp = (SUPER*) buf;                  
 35     //check magic number to ensure it's an EXT2 FS
 36     ninodes = sp->s_inodes_count;       //get inodes_count
 37     printf ( "number of inodes = %d
" , ninodes) ;
 38     get_block(fd,2,buf);                //get group descriptor
 39     gp = (GD*)buf;
 40     imapblk = gp->bg_inode_bitmap;      //get imap block number
 41     printf ( "imap blk number = %d
" , imapblk) ;
 42     get_block(fd,imapblk,buf);          //get imap block into buf[ ]
 43     for (i=0; i<=ninodes/8; i++){       //print each byte in HEX
 44         printf ("%02x ", (u8)buf[i]);
 45     }
 46     printf ("
");
 47 }
 48 
 49 int main(int argc,char *argv[]){
 50     char *dev= "mydisk" ;               //default device
 51     if(argc>1){
 52         dev = argv[1];
 53     }
 54     imap(dev);
 55 }


原文地址:https://www.cnblogs.com/cfqlovem-521/p/15414979.html