Linux核心中的檔案管理

本文件描述了檔案 ( struct file) 和檔案描述符表 (struct files) 的鎖是如何工作的。

在 2.6.12 版本之前,檔案描述符表受到鎖 (files->file_lock) 和引用計數 (files->count) 的保護。 ->file_lock 保護對該表所有與檔案相關的欄位的訪問。 ->count 用於在使用 CLONE_FILES 標誌克隆的任務之間共享檔案描述符表。 通常,這適用於 posix 執行緒。 與核心中常見的引用計數模型一樣,最後一個執行 put_files_struct() 的任務會釋放檔案描述符 (fd) 表。 檔案 (struct file) 本身使用引用計數 (->f_count) 保護。

在新的無鎖檔案描述符管理模型中,引用計數類似,但鎖基於 RCU。 檔案描述符表包含多個元素 - fd 集合(open_fds 和 close_on_exec、檔案指標陣列、集合和陣列的大小等)。 為了使更新對無鎖讀取器呈現原子性,檔案描述符表的所有元素都位於一個單獨的結構中 - struct fdtable。 files_struct 包含一個指向 struct fdtable 的指標,透過該指標訪問實際的 fd 表。 最初,fdtable 嵌入在 files_struct 本身中。 在後續的 fdtable 擴充套件時,會分配一個新的 fdtable 結構,並且 files->fdtab 指向新的結構。 fdtable 結構使用 RCU 釋放,無鎖讀取器要麼看到舊的 fdtable,要麼看到新的 fdtable,從而使更新呈現原子性。 以下是 fdtable 結構的鎖定規則 -

  1. 對 fdtable 的所有引用都必須透過 files_fdtable() 宏完成

    struct fdtable *fdt;
    
    rcu_read_lock();
    
    fdt = files_fdtable(files);
    ....
    if (n <= fdt->max_fds)
            ....
    ...
    rcu_read_unlock();
    

    files_fdtable() 使用 rcu_dereference() 宏,該宏負責無鎖解引用所需的記憶體屏障。 fdtable 指標必須在讀取端臨界區內讀取。

  2. 如上所述的 fdtable 讀取必須受到 rcu_read_lock()/rcu_read_unlock() 的保護。

  3. 對於 fd 表的任何更新,都必須持有 files->file_lock。

  4. 要查詢給定 fd 的檔案結構,讀取器必須使用 lookup_fdget_rcu() 或 files_lookup_fdget_rcu() API。 這些 API 負責由於無鎖查詢而產生的屏障要求。

    一個例子

    struct file *file;
    
    rcu_read_lock();
    file = lookup_fdget_rcu(fd);
    rcu_read_unlock();
    if (file) {
            ...
            fput(file);
    }
    ....
    
  5. 由於 fdtable 和 file 結構都可以無鎖查詢,因此必須使用 rcu_assign_pointer() API 安裝它們。 如果它們是無鎖查詢的,則必須使用 rcu_dereference()。 但是,建議使用 files_fdtable() 和 lookup_fdget_rcu()/files_lookup_fdget_rcu(),它們可以解決這些問題。

  6. 更新時,必須在持有 files->file_lock 的情況下查詢 fdtable 指標。 如果 ->file_lock 被丟棄,則另一個執行緒會擴充套件檔案,從而建立一個新的 fdtable 並使之前的 fdtable 指標過時。

    例如

    spin_lock(&files->file_lock);
    fd = locate_fd(files, file, start);
    if (fd >= 0) {
            /* locate_fd() may have expanded fdtable, load the ptr */
            fdt = files_fdtable(files);
            __set_open_fd(fd, fdt);
            __clear_close_on_exec(fd, fdt);
            spin_unlock(&files->file_lock);
    .....
    

    由於 locate_fd() 可以丟棄 ->file_lock(並重新獲取 ->file_lock),因此 fdtable 指標 (fdt) 必須在 locate_fd() 之後載入。

在較新的核心中,基於 rcu 的檔案查詢已切換為依賴 SLAB_TYPESAFE_BY_RCU 而不是 call_rcu()。 僅使用 atomic_long_inc_not_zero() 在 rcu 下獲取對相關檔案的引用已不再足夠,因為該檔案可能已被回收,並且其他人可能已增加了引用計數。 換句話說,呼叫者可能會看到來自較新使用者的引用計數增加。 因此,有必要在引用計數增加前後驗證指標是否相同。 可以在 get_file_rcu() 和 __files_get_rcu() 中看到此模式。

此外,如果不首先在 rcu 查詢下獲取引用,則無法訪問或檢查 struct file 中的欄位。 不這樣做總是非常可疑的,並且它僅適用於 struct file 中的非指標資料。 使用 SLAB_TYPESAFE_BY_RCU,呼叫者要麼首先獲取引用,要麼必須持有 fdtable 的 files_lock。