聊聊磁盤文件系統(tǒng)(二)
數(shù)據(jù)的存放
- 在 ext2 和 ext3 中,其中前 12 項(xiàng)直接保存了塊的位置,也就是說,我們可以通過 i_block[0-11],直接得到保存文件內(nèi)容的塊。
但是,如果一個(gè)文件比較大,inode的塊號不足以標(biāo)識所有的數(shù)據(jù)塊,就會使用間接塊。文件系統(tǒng)會在硬盤上分配一個(gè)數(shù)據(jù)塊,不存儲文件數(shù)據(jù),專門用來存儲塊號。該塊被稱為間接塊。inode的長度是固定的。間接塊占用的空間對于大文件來說是必要的。但是對于小文件不會帶來額外的開銷。當(dāng)我們用到 i_block[12]的時(shí)候,就不能直接放數(shù)據(jù)塊的位置了,要不然 i_block 很快就會用完了。這該怎么辦呢?我們需要想個(gè)辦法。我們可以讓 i_block[12]指向間接塊。也就是說,我們在 i_block[12]里面放間接塊的位置,通過 i_block[12]找到間接塊后,間接塊里面放數(shù)據(jù)塊的位置,通過間接塊可以找到數(shù)據(jù)塊。如果文件再大一些,i_block[13]會指向一個(gè)塊,我們可以用二次間接塊。二次間接塊里面存放了間接塊的位置,間接塊里面存放了數(shù)據(jù)塊的位置,數(shù)據(jù)塊里面存放的是真正的數(shù)據(jù)。如果文件再大一些,i_block[14]會指向三次間接塊。
- ext4文件系統(tǒng)的Extents一棵樹:
解釋一下 Extents。比方說,一個(gè)文件大小為 128M,如果使用 4k 大小的塊進(jìn)行存儲,需要 32k 個(gè)塊。Extents 可以用于存放連續(xù)的塊,也就是說,我們可以把 128M 放在一個(gè) Extents 里面。這樣的話,對大文件的讀寫性能提高了,文件碎片也減少了。如下圖所示:
索引節(jié)點(diǎn)區(qū),用來存儲索引節(jié)點(diǎn)。Inode存儲了文件系統(tǒng)對象的一些元信息,如所有者、訪問權(quán)限(讀、寫、執(zhí)行)、類型(是文件還是目錄)、內(nèi)容修改時(shí)間、inode修改時(shí)間、上次訪問時(shí)間、對應(yīng)的文件系統(tǒng)存儲塊的地址,等等。知道了1個(gè)文件的inode號碼,就可以在inode元數(shù)據(jù)中查出文件內(nèi)容數(shù)據(jù)的存儲地址。對于EXT4的默認(rèn)情況,一個(gè)inode的大小是256字節(jié),inode是EXT4最重要的元數(shù)據(jù)信息。注意Inode沒有文件名稱,將在下文中講述。
- struct ext4_inode {
- __le16 i_mode; /* File mode */
- __le16 i_uid; /* Low 16 bits of Owner Uid */
- __le32 i_size_lo; /* Size in bytes */
- __le32 i_atime; /* Access time */
- __le32 i_ctime; /* Inode Change 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_lo; /* Blocks count */
- __le32 i_flags; /* File flags */
- union {
- struct {
- __le32 l_i_version;
- } linux1;
- struct {
- __u32 h_i_translator;
- } hurd1;
- struct {
- __u32 m_i_reserved1;
- } masix1;
- } osd1; /* OS dependent 1 */
- __le32 i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
- __le32 i_generation; /* File version (for NFS) */
- __le32 i_file_acl_lo; /* File ACL */
- __le32 i_size_high;
- __le32 i_obso_faddr; /* Obsoleted fragment address */
- union {
- struct {
- __le16 l_i_blocks_high; /* were l_i_reserved1 */
- __le16 l_i_file_acl_high;
- __le16 l_i_uid_high; /* these 2 fields */
- __le16 l_i_gid_high; /* were reserved2[0] */
- __le16 l_i_checksum_lo;/* crc32c(uuid+inum+inode) LE */
- __le16 l_i_reserved;
- } linux2;
- struct {
- __le16 h_i_reserved1; /* Obsoleted fragment number/size which are removed in ext4 */
- __u16 h_i_mode_high;
- __u16 h_i_uid_high;
- __u16 h_i_gid_high;
- __u32 h_i_author;
- } hurd2;
- struct {
- __le16 h_i_reserved1; /* Obsoleted fragment number/size which are removed in ext4 */
- __le16 m_i_file_acl_high;
- __u32 m_i_reserved2[2];
- } masix2;
- } osd2; /* OS dependent 2 */
- __le16 i_extra_isize;
- __le16 i_checksum_hi; /* crc32c(uuid+inum+inode) BE */
- __le32 i_ctime_extra; /* extra Change time (nsec << 2 | epoch) */
- __le32 i_mtime_extra; /* extra Modification time(nsec << 2 | epoch) */
- __le32 i_atime_extra; /* extra Access time (nsec << 2 | epoch) */
- __le32 i_crtime; /* File Creation time */
- __le32 i_crtime_extra; /* extra FileCreationtime (nsec << 2 | epoch) */
- __le32 i_version_hi; /* high 32 bits for 64-bit version */
- __le32 i_projid; /* Project ID */
- };
普通文件的存儲格式
數(shù)據(jù)塊區(qū),則用來存儲文件數(shù)據(jù)。i_block,我們來看看EXT4_N_BLOCKS的具體定義:
- #define EXT4_NDIR_BLOCKS 12
- #define EXT4_IND_BLOCK EXT4_NDIR_BLOCKS
- #define EXT4_DIND_BLOCK (EXT4_IND_BLOCK + 1)
- #define EXT4_TIND_BLOCK (EXT4_DIND_BLOCK + 1)
- #define EXT4_N_BLOCKS (EXT4_TIND_BLOCK + 1)
inode 里面的 i_block 中,可以放得下一個(gè) ext4_extent_header 和 4 項(xiàng) ext4_extent。
- struct ext4_extent_header {
- __le16 eh_magic; /* probably will support different formats */
- __le16 eh_entries; /* number of valid entries */
- __le16 eh_max; /* capacity of store in entries */
- __le16 eh_depth; /* has tree real underlying blocks? */
- __le32 eh_generation; /* generation of the tree */
- };
- /*
- * This is the extent on-disk structure.
- * It's used at the bottom of the tree.
- */
- struct ext4_extent {
- __le32 ee_block; /* first logical block extent covers */
- __le16 ee_len; /* number of blocks covered by extent */
- __le16 ee_start_hi; /* high 16 bits of physical block */
- __le32 ee_start_lo; /* low 32 bits of physical block */
- };
- /*
- * This is index on-disk structure.
- * It's used at all the levels except the bottom.
- */
- struct ext4_extent_idx {
- __le32 ei_block; /* index covers logical blocks from 'block' */
- __le32 ei_leaf_lo; /* pointer to the physical block of the next *
- * level. leaf or next index could be there */
- __le16 ei_leaf_hi; /* high 16 bits of physical block */
- __u16 ei_unused;
- };
如果文件不大,inode 里面的 i_block 中,可以放得下一個(gè) ext4_extent_header 和 4 項(xiàng) ext4_extent。所以這個(gè)時(shí)候,eh_depth 為 0,也即 inode 里面的就是葉子節(jié)點(diǎn),樹高度為 0。如果文件比較大,4 個(gè) extent 放不下,就要分裂成為一棵樹,eh_depth>0 的節(jié)點(diǎn)就是索引節(jié)點(diǎn),其中根節(jié)點(diǎn)深度最大,在 inode 中。最底層 eh_depth=0 的是葉子節(jié)點(diǎn)。
目錄與文件名的存儲格式
目錄下文件比較少的情況下:目錄本身也是個(gè)文件,也有 inode。inode 里面也是指向一些塊。和普通文件不同的是,普通文件的塊里面保存的是文件數(shù)據(jù),而目錄文件的塊里面保存的是目錄里面一項(xiàng)一項(xiàng)的文件信息。這些信息我們稱為 ext4_dir_entry。從代碼來看,有兩個(gè)版本,在成員來講幾乎沒有差別,只不過第二個(gè)版本 ext4_dir_entry_2 是將一個(gè) 16 位的 name_len,變成了一個(gè) 8 位的 name_len 和 8 位的 file_type。即該目錄項(xiàng)的數(shù)據(jù)所在inode編號、文件名長度與類型、文件名字三部分組成。
- struct ext4_dir_entry {
- __le32 inode; /* Inode number */
- __le16 rec_len; /* Directory entry length */
- __le16 name_len; /* Name length */
- char name[EXT4_NAME_LEN]; /* File name */
- };
- struct ext4_dir_entry_2 {
- __le32 inode; /* Inode number */
- __le16 rec_len; /* Directory entry length */
- __u8 name_len; /* Name length */
- __u8 file_type; /* File type */
- char name[EXT4_NAME_LEN]; /* File name */
- };
file_type指定了目錄項(xiàng)的類型。改變量的可能值,由以下枚舉類型定義:
- enum{
- EXT4_FT_UNKNOWN,
- EXT4_FT_REG_FILE,
- EXT4_FT_DIR,
- EXT4_FT_CHRDEV,
- EXT4_FT_BLKDEV,
- EXT4_FT_FIFO,
- EXT4_FT_SOCK,
- EXT4_FT_SYMLINK,
- EXT4_FT_MAX
- }
ls列出的目錄內(nèi)容如下:
- [root@localhost ~]# ls -la
- 總用量 37536
- dr-xr-x---. 7 root root 4096 5月 26 16:54 .
- dr-xr-xr-x. 19 root root 288 6月 10 14:51 ..
- -rw-------. 1 root root 1260 1月 11 2014 anaconda-ks.cfg
每一項(xiàng)都會保存這個(gè)目錄的下一級的文件的文件名和對應(yīng)的 inode,通過這個(gè) inode,就能找到真正的文件。第一項(xiàng)是“.”,表示當(dāng)前目錄,第二項(xiàng)是“..”,表示上一級目錄,接下來就是一項(xiàng)一項(xiàng)的文件名和 inode。**目錄下文件比較多的情況下:如果一個(gè)目錄下有幾萬幾十萬個(gè)條目,這個(gè)方法就比較慢了。原因在于線性掃描,而且,1個(gè)block(4096字節(jié)),基本只能放下幾十~200個(gè)條目,一旦需要幾十幾百個(gè)block,那么為了獲取子文件的inode,這個(gè)DISK IO的消耗是不能忍受的。因此開發(fā)了dir_index的功能。dir_index使用dx_entry來對目錄文件的block進(jìn)行管理,一個(gè)dx_entry對象對應(yīng)一個(gè)block。dx_entry.hash記錄的是其對應(yīng)block內(nèi)所有目錄項(xiàng)的最小hash值,dx_entry.block記錄的是目錄文件的邏輯塊號。從/etc/mke2fs.conf中也可以看出,這個(gè)是格式化文件系統(tǒng)的默認(rèn)選項(xiàng):
- [defaults]
- base_features = sparse_super,filetype,resize_inode,dir_index,ext_attr
- default_mntopts = acl,user_xattr
- enable_periodic_fsck = 0
- blocksize = 4096
- inode_size = 256
- inode_ratio = 16384
- [fs_types]
- ext3 = {
- features = has_journal
- }
- ext4 = {
- features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize
- auto_64-bit_support = 1
- inode_size = 256
- }
如果在 inode 中設(shè)置 dir_index 標(biāo)志,則目錄文件的塊的組織形式將發(fā)生變化,變成了下面定義的這個(gè)樣子:
- struct dx_root
- {
- struct fake_dirent dot;
- char dot_name[4];
- struct fake_dirent dotdot;
- char dotdot_name[4];
- struct dx_root_info
- {
- __le32 reserved_zero;
- u8 hash_version;
- u8 info_length; /* 8 */
- u8 indirect_levels;
- u8 unused_flags;
- }
- info;
- struct dx_entry entries[0];
- };
當(dāng)然,首先出現(xiàn)的還是差不多的,第一項(xiàng)是“.”,表示當(dāng)前目錄;第二項(xiàng)是“..”,表示上一級目錄,這兩個(gè)不變。接下來就開始發(fā)生改變了。是一個(gè) dx_root_info 的結(jié)構(gòu),其中最重要的成員變量是 indirect_levels,表示間接索引的層數(shù)。接下來我們來看索引項(xiàng) dx_entry。這個(gè)也很簡單,其實(shí)就是文件名的哈希值和數(shù)據(jù)塊的一個(gè)映射關(guān)系。
- struct dx_entry
- {
- __le32 hash;
- __le32 block;
- };
那么,找到一個(gè)子文件需要如下步驟。1)根據(jù)待查找子文件名計(jì)算出hash值 2)在當(dāng)前的全部dx_entry中采用二分查找的方式找到對應(yīng)的dx_entry 3)根據(jù)dx_entry.block記錄值讀取目錄文件對應(yīng)的邏輯塊內(nèi)容 4)在讀取到的block內(nèi)容中遍歷查找匹配的子文件目錄項(xiàng) 不難發(fā)現(xiàn),之前的需要讀取N + 1個(gè)block的困境被簡化為只需要讀取一個(gè)block的內(nèi)容即可,問題迎刃而解
為了表示圖中上半部分的那個(gè)簡單的樹形結(jié)構(gòu),在文件系統(tǒng)上的布局就像圖的下半部分一樣。無論是文件夾還是文件,都有一個(gè) inode。inode 里面會指向數(shù)據(jù)塊,對于文件夾的數(shù)據(jù)塊,里面是一個(gè)表,是下一層的文件名和 inode 的對應(yīng)關(guān)系,文件的數(shù)據(jù)塊里面存放的才是真正的數(shù)據(jù)。
ext類文件系統(tǒng)的缺點(diǎn)
最大的缺點(diǎn)是它在創(chuàng)建文件系統(tǒng)的時(shí)候就劃分好一切需要?jiǎng)澐值臇|西,以后用到的時(shí)候可以直接進(jìn)行分配,也就是說它不支持動態(tài)劃分和動態(tài)分配。對于較小的分區(qū)來說速度還好,但是對于一個(gè)超大的磁盤,速度是極慢極慢的。例如將一個(gè)幾十T的磁盤陣列格式化為ext4文件系統(tǒng),可能你會因此而失去一切耐心。除了格式化速度超慢以外,ext4文件系統(tǒng)還是非??扇〉摹.?dāng)然,不同公司開發(fā)的文件系統(tǒng)都各有特色,最主要的還是根據(jù)需求選擇合適的文件系統(tǒng)類型。
本文轉(zhuǎn)載自微信公眾號「運(yùn)維開發(fā)故事」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系運(yùn)維開發(fā)故事公眾號。