歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux管理 >> Linux維護 >> Ext2 文件系統的硬盤布局

Ext2 文件系統的硬盤布局

日期:2017/3/2 10:46:09   编辑:Linux維護

  本文主要講述 Linux 上比較流行的 ext2 文件系統在硬盤分區上的詳細布局情況。Ext2 文件系統加上日志支持的下一個版本是 ext3 文件系統,它和 ext2 文件系統在硬盤布局上是一樣的,其差別僅僅是 ext3 文件系統在硬盤上多出了一個特殊的 ino de(可以理解為一個特殊文件),用來記錄文件系統的日志,也即所謂的 journal。由於本文並不討論日志文件,所以本文的內容對於 ext2 和 ext3 都是適用的。

  1、前言

  本文的資料來源是 Linux 內核中 ext3 文件系統的源代碼。為了便於讀者查閱源代碼,本文中一些關鍵的技術詞匯都使用了內核源代碼中所使用的英語單詞,而沒有使用相應的中文翻譯。(這種方法是否恰當,還請讀者朋友們指教。)

  2、粗略的描述

  對於 ext2 文件系統來說,硬盤分區首先被劃分為一個個的 block,一個 ext2 文件系統上的每個 block 都是一樣大小的,但是對於不同的 ext2 文件系統,block 的大小可以有區別。典型的 block 大小是 1024 bytes 或者 4096 bytes。這個大小在創建 ext2 文件系統的時候被決定,它可以由系統管理員指定,也可以由文件系統的創建程序根據硬盤分區的大小,自動選擇一個較合理的值。這些 blocks 被聚在一起分成幾個大的 block group。每個 block group 中有多少個 block 是固定的。

  每個 block group 都相對應一個 group descriptor,這些 group descriptor 被聚在一起放在硬盤分區的開頭部分,跟在 super block 的後面。所謂 super block,我們下面還要講到。在這個 descriptor 當中有幾個重要的 block 指針。我們這裡所說的 block 指針,就是指硬盤分區上的 block 號數,比如,指針的值為 0,我們就說它是指向硬盤分區上的 block 0;指針的值為 1023,我們就說它是指向硬盤分區上的 block 1023。我們注意到,一個硬盤分區上的 block 計數是從 0 開始的,並且這個計數對於這個硬盤分區來說是全局性質的。

  在 block group 的 group descriptor 中,其中有一個 block 指針指向這個 block group 的 block bitmap,block bitmap 中的每個 bit 表示一個 block,如果該 bit 為 0,表示該 block 中有數據,如果 bit 為 1,則表示該 block 是空閒的。注意,這個 block bitmap 本身也正好只有一個 block 那麼大小。假設 block 大小為 S bytes,那麼 block bitmap 當中只能記載 8*S 個 block 的情況(因為一個 byte 等於 8 個 bits,而一個 bit 對應一個 block)。這也就是說,一個 block group 最多只能有 8*S*S bytes 這麼大。

  在 block group 的 group descriptor 中另有一個 block 指針指向 inode bitmap,這個 bitmap 同樣也是正好有一個 block 那麼大,裡面的每一個 bit 相對應一個 inode。硬盤上的一個 inode 大體上相對應於文件系統上的一個文件或者目錄。關於 inode,我們下面還要進一步講到。

  在 block group 的 descriptor 中另一個重要的 block 指針,是指向所謂的 inode table。這個 inode table 就不止一個 block 那麼大了。這個 inode table 就是這個 block group 中所聚集到的全部 inode 放在一起形成的。

  一個 inode 當中記載的最關鍵的信息,是這個 inode 中的用戶數據存放在什麼地方。我們在前面提到,一個 inode 大體上相對應於文件系統中的一個文件,那麼用戶文件的內容存放在什麼地方,這就是一個 inode 要回答的問題。一個 inode 通過提供一系列的 block 指針,來回答這個問題。這些 block 指針指向的 block,裡面就存放了用戶文件的內容。

  2.1 回顧

  現在我們回顧一下。硬盤分區首先被分為好多個 block。這些 block 聚在一起,被分成幾組,也就是 block group。每個 block group 都有一個 group descriptor。所有這些 descriptor 被聚在一起,放在硬盤分區的開頭部分,跟在 super block 的後面。從 group descriptor 我們可以通過 block 指針,找到這個 block group 的 inode table 和 block bitmap 等等。從 inode table 裡面,我們就可以看到一個個的 inode 了。從一個 inode,我們通過它裡面的 block 指針,就可以進而找到存放用戶數據的那些 block。我們還要提一下,block 指針不是可以到處亂指的。一個 block group 的 block bitmap 和 inode bitmap 以及 inode table,都依次存放在這個 block group 的開頭部分,而那些存放用戶數據的 block 就緊跟在它們的後面。一個 block group 結束後,另一個 block group 又跟著開始。

  3、詳細的布局情況

  3.1 Super Block

  所謂 ext2 文件系統的 super block,就是硬盤分區開頭(開頭的第一個 byte 是 byte 0)從 byte 1024 開始往後的一部分數據。由於 block size 最小是 1024 bytes,所以 super block 可能是在 block 1 中(此時 block 的大小正好是 1024 bytes),也可能是在 block 0 中。

  硬盤分區上 ext3 文件系統的 super block 的詳細情況如下。其中 __u32 是表示 unsigned 不帶符號的 32 bits 的數據類型,其余類推。這是 Linux 內核中所用到的數據類型,如果是開發用戶空間(user-space)的程序,可以根據具體計算機平台的情況,用 unsigned long 等等來代替。下面列表中關於 fragments 的部分可以忽略,Linux 上的 ext3 文件系統並沒有實現 fragments 這個特性。另外要注意,ext3 文件系統在硬盤分區上的數據是按照 Intel 的 Little-endian 格式存放的,如果是在 PC 以外的平台上開發 ext3 相關的程序,要特別注意這一點。如果只是在 PC 上做開發,倒不用特別注意。

  

struct ext3_super_block {
    /*00*/ __u32 s_inodes_count;  /* inodes 計數 */
    __u32 s_blocks_count;   /* blocks 計數 */
    __u32 s_r_blocks_count;  /* 保留的 blocks 計數 */
    __u32 s_free_blocks_count; /* 空閒的 blocks 計數 */
/*10*/ __u32 s_free_inodes_count; /* 空閒的 inodes 計數 */
    __u32 s_first_data_block; /* 第一個數據 block */
    __u32 s_log_block_size;  /* block 的大小 */
    __s32 s_log_frag_size;   /* 可以忽略 */
/*20*/ __u32 s_blocks_per_group;  /* 每 block group 的 block 數量 */
    __u32 s_frags_per_group;    /* 可以忽略 */
    __u32 s_inodes_per_group;   /* 每 block group 的 inode 數量 */
    __u32 s_mtime;         /* Mount time */
/*30*/ __u32 s_wtime;       /* Write time */
    __u16 s_mnt_count;     /* Mount count */
    __s16 s_max_mnt_count;   /* Maximal mount count */
    __u16 s_magic;       /* Magic 簽名 */
    __u16 s_state;       /* File system state */
    __u16 s_errors;      /* Behaviour when detecting errors */
    __u16 s_minor_rev_level;  /* minor revision level */
/*40*/ __u32 s_lastcheck;     /* time of last check */
    __u32 s_checkinterval;   /* max. time between checks */
    __u32 s_creator_os;    /* 可以忽略 */
    __u32 s_rev_level;     /* Revision level */
/*50*/ __u16 s_def_resuid;     /* Default uid for reserved blocks */
    __u16 s_def_resgid;    /* Default gid for reserved blocks */
    __u32 s_first_ino;     /* First non-reserved inode */
    __u16 s_inode_size;    /* size of inode structure */
    __u16 s_block_group_nr;  /* block group # of this superblock */
    __u32 s_feature_compat;  /* compatible feature set */
/*60*/ __u32 s_feature_incompat;  /* incompatible feature set */
    __u32 s_feature_ro_compat; /* readonly-compatible feature set */
/*68*/ __u8 s_uuid[16];      /* 128-bit uuid for volume */
/*78*/ char s_volume_name[16];   /* volume name */
/*88*/ char s_last_mounted[64];  /* directory where last mounted */
/*C8*/ __u32 s_algorithm_usage_bitmap; /* 可以忽略 */
    __u8 s_prealloc_blocks;  /* 可以忽略 */
    __u8 s_prealloc_dir_blocks;/* 可以忽略 */
    __u16 s_padding1;     /* 可以忽略 */
/*D0*/ __u8 s_journal_uuid[16]; /* uuid of journal superblock */
/*E0*/ __u32 s_journal_inum;   /* 日志文件的 inode 號數 */
    __u32 s_journal_dev;   /* 日志文件的設備號 */
    __u32 s_last_orphan;   /* start of list of inodes to delete */
/*EC*/ __u32 s_reserved[197];   /* 可以忽略 */
};

  我們可以看到,super block 一共有 1024 bytes 那麼大。在 super block 中,我們第一個要關心的字段是 magic 簽名,對於 ext2 和 ext3 文件系統來說,這個字段的值應該正好等於 0xEF53。如果不等的話,那麼這個硬盤分區上肯定不是一個正常的 ext2 或 ext3 文件系統。從這裡,我們也可以估計到,ext2 和 ext3 的兼容性一定是很強的,不然的話,Linux 內核的開發者應該會為 ext3 文件系統另選一個 magic 簽名才對。

  在 super block 中另一個重要的字段是 s_log_block_size。從這個字段,我們可以得出真正的 block 的大小。我們把真正 block 的大小記作 B,B = 1 << (s_log_block_size + 10),單位是 bytes。舉例來說,如果這個字段是 0,那麼 block 的大小就是 1024 bytes,這正好就是最小的 block 大小;如果這個字段是 2,那麼 block 大小就是 4096 bytes。從這裡我們就得到了 block 的大小這一非常重要的數據。

  3.2 Group Descriptors

  我們繼續往下,看跟在 super block 後面的一堆 group descriptors。首先注意到 super block 是從 byte 1024 開始,一共有 1024 bytes 那麼大。而 group descriptors 是從 super block 後面的第一個 block 開始。也就是說,如果 super block 是在 block 0,那麼 group descriptors 就是從 block 1 開始;如果 super block 是在 block 1,那麼 group descriptors 就是從 block 2 開始。因為 super block 一共只有 1024 bytes 那麼大,所以不會超出一個 block 的邊界。如果一個 block 正好是 1024 bytes 那麼大的話,我們看到 group descriptors 就是緊跟在 super block 後面的了,沒有留一點空隙。而如果一個 block 是 4096 bytes 那麼大的話,那麼在 group descriptors(從 byte 4096 開始)和 super block 的結尾之間,就有一定的空隙(4096 - 2048 bytes)。

  那麼硬盤分區上一共有多少個 block group,或者說一共有多少個 group descriptors,這我們要在 super block 中找答案。super block 中的 s_blocks_count 記錄了硬盤分區上的 block 的總數,而 s_blocks_per_group 記錄了每個 group 中有多少個 block。顯然,文件系統上的 block groups 數量,我們把它記作 G,G = (s_blocks_count - s_first_data_block - 1) / s_blocks_per_group + 1。為什麼要減去 s_first_data_block,因為 s_blocks_count 是硬盤分區上全部的 block 的數量,而在 s_first_data_block 之前的 block 是不歸 block group 管的,所以當然要減去。最後為什麼又要加一,這是因為尾巴上可能多出來一些 block,這些 block 我們要把它劃在一個相對較小的 group 裡面。

  注意,硬盤分區上的所有這些 group descriptors 要能塞在一個 block 裡面。也就是說 groups_count * descriptor_size 必須小於等於 block_size。

  知道了硬盤分區上一共有多少個 block group,我們就可以把這麼多個 group descriptors 讀出來了。先來看看 group descriptor 是什麼樣子的。

  

struct ext3_group_desc
    {
    __u32 bg_block_bitmap;   /* block 指針指向 block bitmap */
    __u32 bg_inode_bitmap;   /* block 指針指向 inode bitmap */
    __u32 bg_inode_table;    /* block 指針指向 inodes table */
    __u16 bg_free_blocks_count; /* 空閒的 blocks 計數 */
    __u16 bg_free_inodes_count; /* 空閒的 inodes 計數 */
    __u16 bg_used_dirs_count;  /* 目錄計數 */
    __u16 bg_pad;     /* 可以忽略 */
    __u32 bg_reserved[3];  /* 可以忽略 */
    };

  每個 group descriptor 是 32 bytes 那麼大。從上面,我們看到了三個關鍵的 block 指針,這三個關鍵的 block 指針,我們已經在前面都提到過了。

  3.3 Inode

  前面都准備好了以後,我們現在終於可以開始讀取文件了。首先要讀的,當然是文件系統的根目錄。注意,這裡所謂的根目錄,是相對於這一個文件系統或者說硬盤分區而言的,它並不一定是整個 Linux 操作系統上的根目錄。這裡的這個 root 目錄存放在一個固定的 inode 中,這就是文件系統上的 inode 2。需要提到 inode 計數同 block 計數一樣,也是全局性質的。這裡需要特別注意的是,inode 計數是從 1 開始的,而前面我們提到過 block 計數是從 0 開始,這個不同在開發程序的時候要特別留心。(這一奇怪的 inode 計數方法,曾經讓本文作者大傷腦筋。)

  那麼,我們先來看一下得到一個 inode 號數以後,怎樣讀取這個 inode 中的用戶數據。在 super block 中有一個字段 s_inodes_per_group 記載了每個 block group 中有多少個 inode。用我們得到的 inode 號數除以 s_inodes_per_group,我們就知道了我們要的這個 inode 是在哪一個 block group 裡面,這個除法的余數也告訴我們,我們要的這個 inode 是這個 block group 裡面的第幾個 inode;然後,我們可以先找到這個 block group 的 group descriptor,從這個 descriptor,我們找到這個 group 的 inode table,再從 inode table 找到我們要的第幾個 inode,再以後,我們就可以開始讀取 inode 中的用戶數據了。

Copyright © Linux教程網 All Rights Reserved