理解ext系列文件系统

Last Updated: 2023-07-04 09:40:20 Tuesday

-- TOC --

Linux下第一个商用级别文件系统,ext2文件系统。后来升级到ext3/ext4,提供新功能,兼容性也很好。ext系列文件系统在Linux世界应用广泛,是Linux原生的文件系统。它支持标准的Unix文件属性和权限管理方式,这一点是与Windows系文件系统很显著的区别。

Linux最开始参考minix实现文件系统,minix是用于教学的,其很多参数都不能适用于商用环境。1992年,Remy Card设计并实现了Ext文件系统,其寓意也是对minix文件系统的扩展,同时实现了VFS,在0.96c版本中集成发布。1993年,Remy Card开发了Ext2,这是Linux第一个商用级别的文件系统,应用时间很长,但Ext2没有日志功能,无法解决系统崩溃导致文件系统数据不一致的问题。2001年Stephen Tweedie主导开发了Ext3文件系统,主要就是增加日志功能(Journal)。2008年,Ext4版本出现,引入了很多新特性,如Extent,预分配,延迟分配,加密等。

Partition Layout

ext系列文件系统将整个分区划分成特定size的Block,然后将一定数量的连续block合并在一起,成为大小相同的块组,即Block Group,最后的那个group所包含的block数量可能偏少。SuperBlock,GDT以及其他Metadata存储在第一个块组,即Group0,以及其它几个特定的Group中(备份)。每个group包含此group中所有block的bitmap,以及inode的bitmap和inode table。通过GDT可以定位inode table的位置。ext系列文件系统通过Block Group管理硬盘资源。

When block size is 4KiB:

Block Group 0 | Block Group 1 | Block Group 2 | ...

In Block Group 0:

1024 Bytes  |  SuperBlock  |  Block Group Desc Table  | ... | ... | ...

Super Block

super block应该可以对应FAT系统的boot sector。block可以对应cluster。

superblock的位置,固定在距离分区开始偏移1024个字节的地方。如果block的size是1KiB,superblock就是1号block,如果block的size大于1KiB,superblock在0号block内。superblock也是一个block,ext2文件系统就是将分区划分成固定大小的block(从分区开始的地方),然后把block组合成block group。

下面是Linux内核中ext2 super block的定义:(fs/ext2/ext2.h)

关于__bitwise申明,参考kernel中的bitwise

block和block group都从0开始编号!

// usr/inlcude/linux/types.h
typedef __u32 __bitwise __le32;

/*
 * Structure of the super block
 */
struct ext2_super_block {
        __le32  s_inodes_count;         /* Inodes count */
        __le32  s_blocks_count;         /* Blocks count */
        __le32  s_r_blocks_count;       /* Reserved blocks count */
        __le32  s_free_blocks_count;    /* Free blocks count */
        __le32  s_free_inodes_count;    /* Free inodes count */
        __le32  s_first_data_block;     /* First Data Block */
        __le32  s_log_block_size;       /* Block size */
        __le32  s_log_frag_size;        /* Fragment size */
        __le32  s_blocks_per_group;     /* # Blocks per group */
        __le32  s_frags_per_group;      /* # Fragments per group */
        __le32  s_inodes_per_group;     /* # Inodes per group */
        __le32  s_mtime;                /* Mount time */
        __le32  s_wtime;                /* Write time */
        __le16  s_mnt_count;            /* Mount count */
        __le16  s_max_mnt_count;        /* Maximal mount count */
        __le16  s_magic;                /* Magic signature */
        __le16  s_state;                /* File system state */
        __le16  s_errors;               /* Behaviour when detecting errors */
        __le16  s_minor_rev_level;      /* minor revision level */
        __le32  s_lastcheck;            /* time of last check */
        __le32  s_checkinterval;        /* max. time between checks */
        __le32  s_creator_os;           /* OS */
        __le32  s_rev_level;            /* Revision level */
        __le16  s_def_resuid;           /* Default uid for reserved blocks */
        __le16  s_def_resgid;           /* Default gid for reserved blocks */
        /*
         * These fields are for EXT2_DYNAMIC_REV superblocks only.
         *
         * Note: the difference between the compatible feature set and
         * the incompatible feature set is that if there is a bit set
         * in the incompatible feature set that the kernel doesn't
         * know about, it should refuse to mount the filesystem.
         *
         * e2fsck's requirements are more strict; if it doesn't know
         * about a feature in either the compatible or incompatible
         * feature set, it must abort and not try to meddle with
         * things it doesn't understand...
         */
        __le32  s_first_ino;            /* First non-reserved inode */
        __le16   s_inode_size;          /* size of inode structure */
        __le16  s_block_group_nr;       /* block group # of this superblock */
        __le32  s_feature_compat;       /* compatible feature set */
        __le32  s_feature_incompat;     /* incompatible feature set */
        __le32  s_feature_ro_compat;    /* readonly-compatible feature set */
        __u8    s_uuid[16];             /* 128-bit uuid for volume */
        char    s_volume_name[16];      /* volume name */
        char    s_last_mounted[64];     /* directory where last mounted */
        __le32  s_algorithm_usage_bitmap; /* For compression */
        /*
         * Performance hints.  Directory preallocation should only
         * happen if the EXT2_COMPAT_PREALLOC flag is on.
         */
        __u8    s_prealloc_blocks;      /* Nr of blocks to try to preallocate*/
        __u8    s_prealloc_dir_blocks;  /* Nr to preallocate for dirs */
        __u16   s_padding1;
        /*
         * Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
         */
        __u8    s_journal_uuid[16];     /* uuid of journal superblock */
        __u32   s_journal_inum;         /* inode number of journal file */
        __u32   s_journal_dev;          /* device number of journal file */
        __u32   s_last_orphan;          /* start of list of inodes to delete */
        __u32   s_hash_seed[4];         /* HTREE hash seed */
        __u8    s_def_hash_version;     /* Default hash version to use */
        __u8    s_reserved_char_pad;
        __u16   s_reserved_word_pad;
        __le32  s_default_mount_opts;
        __le32  s_first_meta_bg;        /* First metablock block group */
        __u32   s_reserved[190];        /* Padding to the end of the block */
};

下面是一个新格式化的ext2分区superblock的内容:

$ sudo xxd -a -u -s1024 -l512 /dev/sdc2
00000400: 20B2 1B00 009A 6E00 B387 0500 11B1 6C00   .....n.......l.
00000410: 15B2 1B00 0000 0000 0200 0000 0200 0000  ................
00000420: 0080 0000 0080 0000 F01F 0000 0000 0000  ................
00000430: B8EA 8F64 0000 FFFF 53EF 0100 0100 0000  ...d....S.......
00000440: 89EA 8F64 0000 0000 0000 0000 0100 0000  ...d............
00000450: 0000 0000 0B00 0000 0001 0000 3800 0000  ............8...
00000460: 0200 0000 0300 0000 2820 B256 5651 47E6  ........( .VVQG.
00000470: 9F9B AEF7 99CD F9E7 0000 0000 0000 0000  ................
00000480: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
000004c0: 0000 0000 0000 0000 0000 0000 0000 FE03  ................
000004d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000004e0: 0000 0000 0000 0000 0000 0000 C959 D352  .............Y.R
000004f0: 7587 44C7 8C1A 382B C47C BC32 0100 0000  u.D...8+.|.2....
00000500: 0C00 0000 0000 0000 89EA 8F64 0000 0000  ...........d....
00000510: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
00000550: 0000 0000 0000 0000 0000 0000 2000 2000  ............ . .
00000560: 0100 0000 0000 0000 0000 0000 0000 0000  ................
00000570: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
000005f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

对照翻译:

做道数学题:

>>> import math
>>> math.ceil(7248384/32768)*8176
1815072
>>> import time
>>> time.ctime(1687153336)
'Mon Jun 19 13:42:16 2023'
# feature_compatibility
EXT2_FEATURE_COMPAT_DIR_PREALLOC 0x0001 Block pre-allocation for new directories
EXT2_FEATURE_COMPAT_IMAGIC_INODES 0x0002     
EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x0004  An Ext3 journal exists
EXT2_FEATURE_COMPAT_EXT_ATTR 0x0008 Extended inode attributes are present
EXT2_FEATURE_COMPAT_RESIZE_INO 0x0010   Non-standard inode size used
EXT2_FEATURE_COMPAT_DIR_INDEX 0x0020    Directory indexing (HTree)
# feature_incompat
EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001    Disk/File compression is used
EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002    
EXT3_FEATURE_INCOMPAT_RECOVER 0x0004     
EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008     
EXT2_FEATURE_INCOMPAT_META_BG 0x0010
EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001  Sparse Superblock
EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 Large file support, 64-bit file size
EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 Binary tree sorted directory files
EXT2_LZV1_ALG   0   Binary value of 0x00000001
EXT2_LZRW3A_ALG 1   Binary value of 0x00000002
EXT2_GZIP_ALG   2   Binary value of 0x00000004
EXT2_BZIP2_ALG  3   Binary value of 0x00000008
EXT2_LZO_ALG    4   Binary value of 0x00000010

这个字段在另一个项目的结构体中是reserved_gdt_blocks0x03FE,1022,gdt是Group Descriptor Table,GDT后面保留的block数量。

眼睛看花了,重新copy一下截止到这里的raw content:

000004d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000004e0: 0000 0000 0000 0000 0000 0000 C959 D352  .............Y.R
000004f0: 7587 44C7 8C1A 382B C47C BC32 0100 0000  u.D...8+.|.2....
00000500: 0C00 0000 0000 0000 89EA 8F64 0000 0000  ...........d....
00000510: 0000 0000 0000 0000 0000 0000 0000 0000  ................

在grub项目代码中,此处还有个这个字段mkfs_time0x648FEA89

>>> import time
>>> time.ctime(0x648FEA89)
'Mon Jun 19 13:41:29 2023'

显然,这个ext2的super block结构体,包含了ext3和ext4的扩展。

Block Group Desc Table (GDT)

Block Group Table在SuperBlock的后面一个block,它可能占用大于1个block的空间,由group的数量和block size决定。

/*
 * Structure of a blocks group descriptor
 */
struct ext2_group_desc
{
        __le32  bg_block_bitmap;                /* Blocks bitmap block */
        __le32  bg_inode_bitmap;                /* Inodes bitmap block */
        __le32  bg_inode_table;         /* Inodes table block */
        __le16  bg_free_blocks_count;   /* Free blocks count */
        __le16  bg_free_inodes_count;   /* Free inodes count */
        __le16  bg_used_dirs_count;     /* Directories count */
        __le16  bg_pad;
        __le32  bg_reserved[3];
};

每个GDT表项32字节!一个4KiB的block,可以存放128个GDT表项。

Block Bitmap

用每个bit来表示block group中的block是否available。

Each bit represent the current state of a block within that block group, where 1 means “used” and 0 “free/available”. The first block of this block group is represented by bit 0 of byte 0, the second by bit 1 of byte 0. The 8th block is represented by bit 7 (most significant bit) of byte 0 while the 9th block is represented by bit 0 (least significant bit) of byte 1.

按此例数据,每个block group含32768个block,共需要4096Byte来存储32768个bit,这刚好是一个block的大小。

如何确定Group数量

由于block bitmap固定使用一个block,因此,调整block size,就会得到不同的group数量!

Inode Bitmap

与block bitmap同理,但inode的编号从1开始,对应bit0。

GDT实例

下面是第1个GDT表项:

$ sudo xxd -a -u -s4096 -l32 /dev/sdc2
00001000: 0104 0000 0204 0000 0304 0000 F879 E51F  .............y..
00001010: 0200 0400 0000 0000 0000 0000 0000 0000  ................

第1项,对应第1个block group:

这个分区一共有7248384个block,每个block group包含32768个block,即共有222个block group。这第1个block group,格式化后啥都不干,就只剩下31224个block,用掉了1544个block。我们看一下block bitmap:

$ sudo xxd -a -u -s$((4096*1025)) -l 512 /dev/sdc2
00401000: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00401010: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00401020: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00401030: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00401040: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00401050: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00401060: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00401070: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00401080: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
00401090: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
004010a0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
004010b0: FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF  ................
004010c0: FF00 0000 0000 0000 0000 0000 0000 0000  ................
004010d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
004011f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

仔细数一下,刚好前1544个block显示被占用。

block bitmap位于1025号block,0号是superblock,1号和2号存放GDT,中间有1022个block保留,对应reserved_gdt_blocks字段。每个inode的size为256Bytes,每个block group包含8176个inode表项,每个block为4KiB大小,那么inode table占满511个block。1027+511=1538,这是root directory data block的编号。看一下block inode bitmap:

$ sudo xxd -a -u -s$((4096*1026)) -l 512 /dev/sdc2
00402000: FF07 0000 0000 0000 0000 0000 0000 0000  ................
00402010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
004021f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

刚好前11(8+3)个inode表项被占用,8165+11=8176,与其它数据能够对应起来。

为什么要把bitmap分散在不同的group中?

因为如果所有的bitmap都存放在同一个group中,当该group数据块被损坏时,整个文件系统的可用性都会受到影响。分散保存可以降低这种风险。此外,将bitmap分散在多个group中还能提高性能。

block group概念,并没有限制inode中记录的文件block属于其它group的情况!ext2文件系统甚至鼓励这种跨group存储文件。

将bitmap分散在group中存储,可以提高性能?

这一点对于一般普通Linux用户来说,是基本无感的。因为你的partition中,基本只包含一个物理硬盘,没有多个,无法并行IO。但是如果partition包含多个物理硬盘,就可以并行IO,此时就可以理解分散保存两个bitmap带来的安全性和高性能。从这个角度看,ext2文件系统的设计,已经考虑到了服务器运行环境的需求。

Inode Table

在Linux项目源码中(fs/ext2/ext2.h),找到inode的结构:

/*
 * Constants relative to the data blocks
 */
#define EXT2_NDIR_BLOCKS                12
#define EXT2_IND_BLOCK                  EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK                 (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK                 (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS                   (EXT2_TIND_BLOCK + 1)

/*
 * Structure of an inode on the disk
 */
struct ext2_inode {
        __le16  i_mode;         /* File mode */
        __le16  i_uid;          /* Low 16 bits of Owner Uid */
        __le32  i_size;         /* Size in bytes */
        __le32  i_atime;        /* Access time */
        __le32  i_ctime;        /* Creation time */
        __le32  i_mtime;        /* Modification time */
        __le32  i_dtime;        /* Deletion Time */
        __le16  i_gid;          /* Low 16 bits of Group Id */
        __le16  i_links_count;  /* Links count */
        __le32  i_blocks;       /* Blocks count of 512 bytes */
        __le32  i_flags;        /* File flags */
        union {
                struct {
                        __le32  l_i_reserved1;
                } linux1;
                struct {
                        __le32  h_i_translator;
                } hurd1;
                struct {
                        __le32  m_i_reserved1;
                } masix1;
        } osd1;                         /* OS dependent 1 */
        __le32  i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
        __le32  i_generation;   /* File version (for NFS) */
        __le32  i_file_acl;     /* File ACL */
        __le32  i_dir_acl;      /* Directory ACL */
        __le32  i_faddr;        /* Fragment address */
        union {
                struct {
                        __u8    l_i_frag;       /* Fragment number */
                        __u8    l_i_fsize;      /* Fragment size */
                        __u16   i_pad1;
                        __le16  l_i_uid_high;   /* these 2 fields    */
                        __le16  l_i_gid_high;   /* were reserved2[0] */
                        __u32   l_i_reserved2;
                } linux2;
                struct {
                        __u8    h_i_frag;       /* Fragment number */
                        __u8    h_i_fsize;      /* Fragment size */
                        __le16  h_i_mode_high;
                        __le16  h_i_uid_high;
                        __le16  h_i_gid_high;
                        __le32  h_i_author;
                } hurd2;
                struct {
                        __u8    m_i_frag;       /* Fragment number */
                        __u8    m_i_fsize;      /* Fragment size */
                        __u16   m_pad1;
                        __u32   m_i_reserved2[2];
                } masix2;
        } osd2;                         /* OS dependent 2 */
};

仔细计算,这个结构体是128字节的,为啥superblock中显示256字节呢?ext2只需要128字节,有些扩展功能,需要更多空间,inode size大一点,就可以支持了。

创建一个文本文件,用stat命令查看其stat编号为15。现在我们直接查看15号inode的内容:

$ sudo xxd -u -s$((4096*1027+256*14)) -l256 /dev/sdc2
00403e00: A481 0000 0B00 0000 D7B4 9264 D7B4 9264  ...........d...d
00403e10: D7B4 9264 0000 0000 0000 0100 0800 0000  ...d............
00403e20: 0000 0000 0300 0000 0108 0000 0000 0000  ................
00403e30: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00403e40: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00403e50: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00403e60: 0000 0000 219C CE93 0000 0000 0000 0000  ....!...........
00403e70: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00403e80: 2000 0000 343D FB14 309E ED10 309E ED10   ...4=..0...0...
00403e90: D7B4 9264 309E ED10 0000 0000 0000 0000  ...d0...........
00403ea0: 0000 02EA 0706 3400 0000 0000 2500 0000  ......4.....%...
00403eb0: 0000 0000 7365 6C69 6E75 7800 0000 0000  ....selinux.....
00403ec0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00403ed0: 0000 0000 0000 0000 756E 636F 6E66 696E  ........unconfin
00403ee0: 6564 5F75 3A6F 626A 6563 745F 723A 756E  ed_u:object_r:un
00403ef0: 6C61 6265 6C65 645F 743A 7330 0000 0000  labeled_t:s0....

i_block[EXT2_N_BLOCKS]

__le32 i_block[EXT2_N_BLOCKS]这个字段包含了文件所有data block。我们这个测试文件很小,只需要关注第1个block id,即0x00000801,2049号block,内容如下:

$ sudo xxd -u -s$((4096*2049)) -l32 /dev/sdc2
00801000: 3132 3334 3536 3738 3930 0A00 0000 0000  1234567890......
00801010: 0000 0000 0000 0000 0000 0000 0000 0000  ................

In total there are 15 pointers in the i_block[] array. The meaning of each of these pointers is explained below:

ext系列文件系统,不像FAT系列,采用cluster chain的方式,而是直接在一个数组中存放block id,还分级。数组的前12个entry指向12个direct block,后面3个entry是间接指向。

--------
|direct| --> data block
--------

----------
|single indirect | -->    | direct  0 | --> data block
----------    | direct  1 | --> data block
              | ...       | ...
              | direct  n | --> data block
              ----------
----------
|double indirect | -->    | single direct  0 | --> ...
----------    | single direct  1 | --> ...
              | ...              | ...
              | single direct  n | --> ...
              ----------

----------
|triple indirect | -->    | double direct  0 | --> ...
----------    | double direct  1 | --> ...
              | ...              | ...
              | double direct  n | --> ...
              ----------

都不用加起来,最后一项是其决定作用的,大小为4TiB。

最大文件Size

i_size字段只有32位,虽然i_block[]字段可以表达很大的空间,但ext2文件系统是如何计算出2TiB最大文件大小的?这是用到了i_dir_acl字段。

In revision 0, (signed) 32bit value indicating the size of the file in bytes. In revision 1 and later revisions, and only for regular files, this represents the lower 32-bit of the file size; the upper 32-bit is located in the i_dir_acl.

文件的acl有i_file_acl,因此使用i_dir_acl完全没问题。

i_mode

16bit value used to indicate the format of the described file and the access rights, which can be combined in various ways.

Constant    Value   Description
-- file format --
EXT2_S_IFSOCK   0xC000  socket
EXT2_S_IFLNK    0xA000  symbolic link
EXT2_S_IFREG    0x8000  regular file
EXT2_S_IFBLK    0x6000  block device
EXT2_S_IFDIR    0x4000  directory
EXT2_S_IFCHR    0x2000  character device
EXT2_S_IFIFO    0x1000  fifo
-- process execution user/group override --
EXT2_S_ISUID    0x0800  Set process User ID
EXT2_S_ISGID    0x0400  Set process Group ID
EXT2_S_ISVTX    0x0200  sticky bit
-- access rights --
EXT2_S_IRUSR    0x0100  user read
EXT2_S_IWUSR    0x0080  user write
EXT2_S_IXUSR    0x0040  user execute
EXT2_S_IRGRP    0x0020  group read
EXT2_S_IWGRP    0x0010  group write
EXT2_S_IXGRP    0x0008  group execute
EXT2_S_IROTH    0x0004  others read
EXT2_S_IWOTH    0x0002  others write
EXT2_S_IXOTH    0x0001  others execute

atime,ctime,mtime,dtime

这4个时间,都是unix time,32位,只能到秒,stat命令显示出来的小于秒的数据,来自哪里?

系统中的每份可见的文件,以及文件夹,都有三个时间属性,分别是atime,mtime,ctime,本节对这三个时间属性做点介绍。

atime,就是(last)access time,代表的是最后一次对文件的访问时间。当对文件进行read之类的系统调用的时候,这个时间会被更新。(很多时候为了提高系统性能,延长SSD硬盘寿命,会去掉atime的更新)

mtime,就是(last)modified time,代表的是文件最后一次被修改的时间。这个比较容易理解。

ctime,就是(last)changed time,这个属性与平台相关,在Linux系统中,ctime表示是最后一次文件的metadata被修改的时间,而在Win系统中,就是文件的初始创建的时间。

文件夹的xtime

文件夹的access time,atime,是在读取文件或者执行文件时更改的(如果我们只cd进入一个目录然后cd ..,不会引起atime的改变,但ls一下就不同了)。

文件夹的modified time,mtime,是在文件夹中有文件新建、删除才会改变(如果只是改变文件内容不会引起mtime的改变,换句话说,如果ls -f的结果发生改变mtime就会被刷新。这里可能有人要争论了:我进入dd这个文件夹vi了一个文件然后退出,前后ls -f的结果没有改变但是文件夹的mtime发生改变了……这点请主意vi命令在编辑文件时会在本文件夹下产生一个".file.swp"临时文件,该文件随着vi的退出而被删除……这就导致了mtime的改变。不信你可以用nano修改文件来试验)。

文件夹的change time,ctime,基本同文件的ctime,其体现的是inode的change time。

relatime

从kernel2.6.29开,还默认集成了一个relatime的属性。可能是因为在文件读操作很频繁的系统中,atime更新所带来的开销很大,所以很多SA都在挂装文件系统的时候使用noatime属性来停止更新atime。但是有些程序需要根据atime进行一些判断和操作,所以Linux就推出了一个relatime特性。使用这个特性来挂装文件系统后,只有当mtime比atime更新的时候,才会更新atime。事实上,这个时候atime和mtime已经是同一个东西了。所以这个选项就是为了实现对atime的兼容才推出的。并不是一个新的时间属性。使用方法就是通过mount -o relatime /dir来挂装目录.

这是所谓的硬链接的数量。

通过文件系统的inode链接来产生的新的文件名,而不是产生新的文件,称为硬链接。一般情况下,每个inode号码对应一个文件名,但是Linux允许多个文件名指向同一个inode号码。意味着可以使用不同的文件名访问相同的内容。

$ ln source-file taget-file  # hard link

运行该命令以后,源文件与目标文件的inode号码相同,都指向同一个inode,我们用ls命令,只是看起来多了个文件,但inode数并没有增加,多的只是一个可以被看到的文件名。硬链接会让inode信息中的链接数增加1。当一个文件拥有多个硬链接时,对文件内容修改,会影响到所有硬链接指向的文件名。但是删除一个文件名,不影响对另一个硬链接文件名的访问。删除一个文件名,只会使得inode信息中的链接数减1。

硬链接只能在同一文件系统中的文件之间进行链接,且不能对目录进行创建。如果删除硬链接对应的源文件,则硬链接文件仍然存在,而且保存了原有的内容,这样可以起到防止因为误操作而错误删除文件的效果。由于硬链接是有着相同inode号仅文件名不同的文件,因此,删除一个硬链接文件并不影响其他有相同inode号的文件。

软链接

软链接类似于Windows系统的快捷方式。软链接就是再创建一个独立的文件,而这个文件会让数据的读取指向它连接的那个文件的文件名。

$ ln -s source-file-dir target-file-dir  # soft link or symbolic link

软链接会生成新的文件,新文件有新的inode号码,但其内容依赖链接的另一个文件。

软链接主要应用于以下两个方面:

删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接就变成了死链接。软链接链接的是pathname,不是inode,目标文件删除后再重新创建,软链接依然有效。

inode特点

由于inode号码与文件名分离,导致一些Unix/Linux系统具备以下几种特有的现象:

  1. 文件名包含特殊字符,可能无法正常删除。这时可通过搜索inode来删除文件;(参考find命令删除inode)
  2. 移动文件或重命名文件,只是改变文件名,不影响inode号码;
  3. 打开一个文件以后,系统就以inode号码来识别这个文件,不再考虑文件名。

这种情况使得软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。因为系统通过inode号码,识别运行中的文件,不通过文件名。更新的时候,新版文件以同样的文件名,生成一个新的inode,不会影响到运行中的文件。等到下一次运行这个软件的时候,文件名就自动指向新版文件,旧版文件的inode则被回收。

有一种故障,叫做inode耗尽!明明硬盘还有很多空间,但确提示:No space left on device。如何解决inodes耗尽的问题

Root Directory

Linux中的文件系统采用层级目录树结构,因此任何文件都必须要位于某个目录中,每个文件系统都要有root,文件和子目录都从root开始。一块ext分区的文件系统,root在哪里?2号inode

The second entry of the Inode table contains the inode pointing to the data of the root directory; as defined by the EXT2_ROOT_INO constant.

In revision 0 directories could only be stored in a linked list. Revision 1 and later introduced indexed directories. The indexed directory is backward compatible with the linked list directory; this is achieved by inserting empty directory entry records to skip over the hash indexes.

2号inode一定在0号group中,通过0号group找到inode table所在的block,然后查看2号inode的内容,如下:

$ sudo xxd -a -u -s$((4096*1027+256*1)) -l256 /dev/sdc2
00403100: ED41 0000 0010 0000 0DF6 9864 0BF6 9864  .A.........d...d
00403110: 0BF6 9864 0000 0000 0000 0300 0800 0000  ...d............
00403120: 0000 0000 1000 0000 0206 0000 0000 0000  ................
00403130: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
00403180: 2000 0000 28D4 A86F 28D4 A86F 20B1 74EA   ...(..o(..o .t.
00403190: B8EA 8F64 0000 0000 0000 0000 0000 0000  ...d............
004031a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
004031f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

前16bit是i_mode,其内容表示这是个目录,只有一个data block,id为1538。

$ python -c 'print(bin(0x41ed)[2:].rjust(16,"0"))'
0100000111101101

查看1538号block:

$ sudo xxd -a -u -s$((4096*1538)) -l256 /dev/sdc2
00602000: 0200 0000 0C00 0102 2E00 0000 0200 0000  ................
00602010: 2000 0202 2E2E 0000 0000 0000 0000 0000   ...............
00602020: 0000 0000 0000 0000 0000 0000 0F00 0000  ................
00602030: 0C00 0301 6162 6300 0C00 0000 1400 0905  ....abc.........
00602040: 6E61 6D65 6470 6970 6500 0000 A1B7 1000  namedpipe.......
00602050: B40F 0602 6961 6D64 6972 0000 0000 0000  ....iamdir......
00602060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
*
006020f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

这就是root directory:

$ ll /mnt/u2
total 12K
drwxr-xr-x. 3 root root 4.0K Jun 26 10:20 .
drwxr-xr-x. 1 root root   20 Jun 12 15:54 ..
-rw-r--r--. 1 root root   11 Jun 21 16:29 abc
drwxr-xr-x. 2 root root 4.0K Jun 26 10:20 iamdir
prw-r--r--. 1 root root    0 Jun 26 10:20 namedpipe

目录内entry结构:

/*
 * The new version of the directory entry.  Since EXT2 structures are
 * stored in intel byte order, and the name_len field could never be
 * bigger than 255 chars, it's safe to reclaim the extra byte for the
 * file_type field.
 */
struct ext2_dir_entry_2 {
        __le32  inode;                  /* Inode number */
        __le16  rec_len;                /* Directory entry length */
        __u8    name_len;               /* Name length */
        __u8    file_type;
        char    name[];                 /* File name, up to EXT2_NAME_LEN */
};
0   Unknown
1   Regular File
2   Directory
3   Character Device
4   Block Device
5   Named pipe
6   Socket
7   Symbolic Link

现在开始解析上面打印出来的root directory:

这个..目录有点特殊,这里本就是分区的root,..表达的是上层路径,上层路径属于别的分区,即不同的device。这里的特殊处理,只看block data看不出来。我们继续:

Named Pipe这类特殊文件,不会占用data block,但会占用inode table。

1095585这个夸张的inode编号,说明Linux系统在使用ext文件系统的时候,并不会按照顺序,有一套自己的算法逻辑。这不正常的4020字节,是否是说明这个此data block的最后一项。同时,用0作padding是很正确的,还真不能是其它值,只有这个值表达了字符串的结束。

通过路径定位文件,就是一层层的寻找文件的inode,然后得到其data block列表,这要就得到了文件内容。

e2compr

在学习ext文件系统的过程中,了解到有这么个东西,实现普通文件的压缩和解压,on the fly...后来看到一个人在网上的回复,说这个东西已经死了,从没进入过mainline,他的回答还有一些其它信息:

As mentioned in the comments already, e2compr unfortunately never made it into the mainline kernel. It touched too many other parts of the kernel to ever really be clean enough to merge in. Also, the way it was written didn't allow it to support journals. This latter issue was really the kiss of death for it, as everything with the ext filesystem was moving towards journaling.

The actual old ext2 filesystem doesn't even exist in the kernel any more. It is all the ext4 code which mounts ext2 filesystems in a sort of compatibility mode. Compression would have to be written in from scratch at this point. Corporate support wasn't there for this kind of rewrite, instead it went to other filesystems.

Your options for on-the-fly compression are ZFS and Btrfs. Both of them are Copy-on-Write filesystems. This means when you copy a file, it doesn't take up any extra space until you write to it and change it from the original. This makes creating snapshots quite easy. It also can lead to file fragmentation, since when you change a portion of a large file, that portion doesn't overwrite the old part, it is written somewhere new and the old part is marked free. Large files which see lots of small changes, like database files, can suffer from bad fragmentation.

dumpe2fs命令

这个命令可以将ext文件系统的各种metadata一览无余,其显示的内容,正如本文的分析内容。

$ sudo dumpe2fs /dev/sdc2 | less

stat命令

stat命令显示的信息,与inode结构体前面部分,有比较好的对应关系。

$ stat <filename|dirname>
$ stat abc
  File: abc
  Size: 11              Blocks: 8          IO Block: 4096   regular file
Device: 8,34    Inode: 15          Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Context: unconfined_u:object_r:unlabeled_t:s0
Access: 2023-06-21 16:29:11.071001996 +0800
Modify: 2023-06-21 16:29:11.071001996 +0800
Change: 2023-06-21 16:29:11.088002381 +0800
 Birth: 2023-06-21 16:29:11.071001996 +0800

-f,显示文件系统的信息,但还是要跟一个文件名,用来定位文件系统的位置。

-t,以terse的方式显示。

本文链接:https://cs.pynote.net/hd/hdisk/202306132/

-- EOF --

-- MORE --