跳轉到

7.3 空間配置與碎片整理#

概念說明#

檔案系統的空間配置策略決定碎片的命運,三大經典策略:

  1. 連續配置(contiguous):檔案占一整段連續區塊。讀取快(順序 I/O)、隨機也快,但外部碎片嚴重,檔案擴充困難(可能要整檔搬移)。
  2. 鏈結配置(linked):如 FAT,無外部碎片但隨機存取 O(k),且鏈跳來跳去。
  3. 索引配置(indexed):如 inode,隨機存取好,但小型檔案也要負擔指標開銷。

現代檔案系統(ext4、XFS)用折衷:延遲配置(delayed allocation)——先讓資料留在 page cache,累積到一定量後一次決定「往哪裡放」,爭取把相鄰寫入配到連續區塊,從源頭減少碎片。

碎片整理(defragmentation)是把分散的區塊搬動集中,使檔案盡量連續。HDD 上意義重大(每次跳磁軌 10ms),SSD 上則幾乎無意義甚至有害:SSD 沒有尋找時間,碎片不影響讀取,而搬移反而增加寫入、消耗壽命。F2FS 等為快閃設計的檔案系統根本不做傳統碎片整理。

碎片整理演算法(HDD 版):

  1. 掃描位圖,找出碎片最嚴重的檔案(區塊間跳轉率最高)。
  2. 為該檔案在位圖中找一段等長的連續空間。
  3. 逐區塊搬移(讀舊 → 寫新 → 更新映射 → 釋放舊區塊)。
  4. 重複直到碎片率低於門檻。

Python 範例:碎片測量與整理#

class BitmapFS:
    def __init__(self, total=64):
        self.total = total
        self.blocks = [None] * total     # None=空閒, 否則 (檔名, 邏輯序)
        self.io = 0

    def alloc(self, name, n, scattered=False):
        """scattered=True 模擬碎片化的配置"""
        import random
        free = [i for i, b in enumerate(self.blocks) if b is None]
        if len(free) < n: raise RuntimeError("空間不足")
        random.seed(7 if scattered else None)
        picks = random.sample(free, n) if scattered else free[:n]
        for i, blk in enumerate(picks):
            self.blocks[blk] = (name, i)

    def frag_ratio(self, name):
        locs = [i for i, b in enumerate(self.blocks) if b and b[0] == name]
        order = sorted(locs, key=lambda i: self.blocks[i][1])
        jumps = sum(1 for a, c in zip(order, order[1:]) if c != a + 1)
        return jumps / max(len(order) - 1, 1), order

    def defrag(self, name):
        order = [i for i, b in enumerate(self.blocks)
                 if b and b[0] == name]
        order.sort(key=lambda i: self.blocks[i][1])
        n = len(order)
        # 找連續空段
        run, start = 0, None
        for i in range(self.total):
            run = run + 1 if self.blocks[i] is None or self.blocks[i][0] == name else 0
            if run == n:
                start = i - n + 1; break
        if start is None:
            return False                  # 沒有連續空間,需先搬其他檔案
        for j, old in enumerate(order):
            tgt = start + j
            if tgt == old: continue
            self.io += 2                  # 讀舊 + 寫新
            self.blocks[tgt] = (name, j)
            if self.blocks[old] and self.blocks[old][0] == name:
                self.blocks[old] = None
        return True

fs = BitmapFS(total=64)
fs.alloc("A", 6); fs.alloc("B", 8); fs.alloc("C", 6)
# 製造碎片:刪掉 B 的一部分概念用散置配置模擬
fs2 = BitmapFS(total=64)
fs2.alloc("A", 6, scattered=True); fs2.alloc("C", 6, scattered=True)
r1, order1 = fs2.frag_ratio("A")
print(f"整理前碎片率: {r1:.0%},區塊位置 {order1}")
fs2.defrag("A")
r2, order2 = fs2.frag_ratio("A")
print(f"整理後碎片率: {r2:.0%},區塊位置 {order2},搬移 I/O: {fs2.io}")

複雜度與 I/O 成本分析#

  • 連續配置:讀取任意範圍 1 次順序 I/O;配置需找長度 ≥ n 的空段(first-fit O(表長))。
  • 鏈結/索引配置:隨機讀分別為 O(k) 與 O(1~3) 次 I/O。
  • 碎片整理:搬一個檔案 2n 次 I/O;全碟整理最壞 O(N) 次 I/O,且需先挪出連續空間(可能引發連鎖搬移)。
  • HDD 上碎片率 30% 的大檔案讀取可慢 2~3 倍;SSD 上幾乎無差——「碎片」是機械時代的演算法約束。

練習#

  1. 在 64 區塊的模擬中製造 40% 碎片率,統計讀整個檔案相比連續配置多付出多少 I/O。
  2. 實作 first-fit 與 best-fit 兩種連續配置策略,比較外部碎片的累積速度。
  3. 說明為何 SSD 上「定期碎片整理」反而縮短壽命,連結 1.3 節的寫入放大概念。