Linux 日誌 API

概述

詳情

日誌層易於使用。您首先需要建立一個 journal_t 資料結構。根據您決定如何分配日誌所在的物理介質,有兩種呼叫方式。如果日誌儲存在檔案系統 inode 中,則使用 jbd2_journal_init_inode() 呼叫;如果日誌儲存在原始裝置上(在一個連續的塊範圍內),則可以使用 jbd2_journal_init_dev() 呼叫。journal_t 是一個結構指標的 typedef,所以當您最終完成時,請務必對其呼叫 jbd2_journal_destroy() 以釋放任何已使用的核心記憶體。

一旦您獲得了 journal_t 物件,您需要“掛載”或載入日誌檔案。日誌層期望日誌空間已經由使用者空間工具正確分配和初始化。載入日誌時,您必須呼叫 jbd2_journal_load() 來處理日誌內容。如果客戶端檔案系統檢測到日誌內容不需要處理(甚至不需要有效內容),它可以在呼叫 jbd2_journal_load() 之前呼叫 jbd2_journal_wipe() 來清除日誌內容。

請注意,如果 jbd2_journal_wipe(..,0) 檢測到日誌中有任何未完成的事務,它會為您呼叫 jbd2_journal_skip_recovery();同樣,jbd2_journal_load() 在必要時會呼叫 jbd2_journal_recover()。我建議閱讀 fs/ext4/super.c 中的 ext4_load_journal() 以瞭解此階段的示例。

現在您可以繼續修改底層檔案系統了。差不多是這樣。

您仍然需要實際地記錄檔案系統更改,這是透過將它們包裝到事務中來完成的。此外,您還需要將每個緩衝區的修改包裝到對日誌層的呼叫中,以便日誌層知道您實際正在進行的修改。為此,請使用 jbd2_journal_start(),它會返回一個事務控制代碼。

jbd2_journal_start() 及其對應的 jbd2_journal_stop()(表示事務結束)是可巢狀的呼叫,因此如有必要,您可以重新進入一個事務,但請記住,您必須呼叫 jbd2_journal_stop() 的次數與呼叫 jbd2_journal_start() 的次數相同,然後事務才能完成(或者更準確地說,離開更新階段)。Ext4/VFS 利用此特性簡化了 inode 髒化、配額支援等的處理。

在每個事務內部,您需要包裝對單個緩衝區(塊)的修改。在您開始修改緩衝區之前,您需要酌情呼叫 jbd2_journal_get_create_access() / jbd2_journal_get_write_access() / jbd2_journal_get_undo_access(),這允許日誌層在需要時複製未修改的資料。畢竟,緩衝區可能是先前未提交事務的一部分。此時,您終於可以修改緩衝區了,一旦完成,您需要呼叫 jbd2_journal_dirty_metadata()。或者,如果您已請求訪問某個緩衝區,現在您知道不再需要將其推回裝置,您也可以呼叫 jbd2_journal_forget(),就像您過去可能使用 bforget() 一樣。

可以隨時呼叫 jbd2_journal_flush() 來提交併檢查所有事務。

然後在解除安裝時,在您的 put_super() 中,您可以呼叫 jbd2_journal_destroy() 來清理您的記憶體中日誌物件。

不幸的是,日誌層有幾種方式可能導致死鎖。首先要注意的是,每個任務在任何給定時間只能有一個未完成的事務,請記住,直到最外層的 jbd2_journal_stop(),沒有任何東西會被提交。這意味著您必須在執行的每個檔案/inode/地址等操作結束時完成事務,以便日誌系統不會在另一個日誌上重新進入。因為事務不能跨不同日誌巢狀/批處理,並且在稍後的系統呼叫中可能會修改除您之外的另一個檔案系統(例如 ext4)。

需要記住的第二種情況是,如果日誌中沒有足夠的空間用於您的事務(基於傳入的 nblocks 引數),jbd2_journal_start() 可能會阻塞——當它阻塞時,它只是(!)需要等待其他任務完成並提交事務,所以本質上我們是在等待 jbd2_journal_stop()。因此,為避免死鎖,您必須將 jbd2_journal_start() / jbd2_journal_stop() 視為訊號量,並將其包含在您的訊號量排序規則中以防止死鎖。請注意,jbd2_journal_extend() 具有與 jbd2_journal_start() 類似的阻塞行為,因此您在這裡也很容易死鎖,就像在 jbd2_journal_start() 上一樣。

第一次嘗試預留正確數量的塊。 ;-)。這將是您在此事務中將要接觸的最大塊數。我建議檢視 ext4_jbd.h 以瞭解 ext4 做出這些決定的基礎。

另一個需要注意的複雜之處是您的磁碟塊分配策略。為什麼?因為,如果您執行刪除操作,您需要確保在釋放這些塊的事務提交之前,您沒有重用任何已釋放的塊。如果您重用了這些塊並且發生了崩潰,那麼在最後一個完全提交的事務結束時,無法恢復重新分配塊的內容。一個簡單的方法是,只有在釋放它們的事務提交之後,才在內部記憶體塊分配結構中將塊標記為自由。Ext4 為此目的使用日誌提交回調。

透過日誌提交回調,您可以要求日誌層在事務最終提交到磁碟時呼叫回撥函式,以便您可以進行自己的管理。您只需設定 journal->j_commit_callback 函式指標即可請求日誌層呼叫回撥,該函式在每次事務提交後被呼叫。

JBD2 還提供了一種透過 jbd2_journal_lock_updates() / jbd2_journal_unlock_updates() 來阻塞所有事務更新的方法。Ext4 在需要一個乾淨穩定的檔案系統視窗時使用此功能。例如:

jbd2_journal_lock_updates() //stop new stuff happening..
jbd2_journal_flush()        // checkpoint everything.
..do stuff on stable fs
jbd2_journal_unlock_updates() // carry on with filesystem use.

如果您允許非特權使用者空間觸發包含這些呼叫的程式碼路徑,那麼濫用和 DOS 攻擊的機會應該是顯而易見的。

快速提交

JBD2 還允許您執行檔案系統特定的增量提交,稱為快速提交。為了使用快速提交,您需要設定以下執行相應工作的回撥:

journal->j_fc_cleanup_cb:在每次完全提交和快速提交後呼叫的清理函式。

journal->j_fc_replay_cb:用於重放快速提交塊的重放函式。

檔案系統可以隨時自由地執行快速提交,只要它透過呼叫函式 jbd2_fc_begin_commit() 獲得 JBD2 的許可。一旦快速提交完成,客戶端檔案系統應該透過呼叫 jbd2_fc_end_commit() 來通知 JBD2。如果檔案系統希望 JBD2 在停止快速提交後立即執行完全提交,可以透過呼叫 jbd2_fc_end_commit_fallback() 來實現。這在快速提交操作因某種原因失敗時很有用,此時保證一致性的唯一方法是 JBD2 執行完全的傳統提交。

JBD2 輔助函式用於管理快速提交緩衝區。檔案系統可以使用 jbd2_fc_get_buf()jbd2_fc_wait_bufs() 來分配快速提交緩衝區並等待其 I/O 完成。

目前,只有 Ext4 實現了快速提交。有關其快速提交實現的詳細資訊,請參閱 fs/ext4/fast_commit.c 中的頂級註釋。

總結

使用日誌就是將不同的上下文更改(即每次掛載、每次修改(事務)和每個更改的緩衝區)進行包裝,以告知日誌層。

資料型別

日誌層使用 typedefs 來“隱藏”所用結構的具體定義。作為 JBD2 層的客戶端,您可以依賴將指標用作某種“魔法 cookie”。顯然,這種隱藏在“C”語言中並未強制執行。

結構體

型別 handle_t

handle_t 型別表示某個程序正在執行的單個原子更新。

描述

程序所做的所有檔案系統修改都透過此控制代碼進行。遞迴操作(如配額操作)被收集到單個更新中。

緩衝區信用欄位用於記錄正在執行的程序修改的日誌緩衝區。為確保所有未完成操作都有足夠的日誌空間,我們需要限制任何時候可能未完成的緩衝區數量。操作完成後,任何未使用的緩衝區信用都會返還給事務,這樣我們就能始終知道事務上未完成的更新可能涉及多少緩衝區。

這是一個不透明的資料型別。

型別 journal_t

journal_t 維護單個檔案系統的所有日誌狀態資訊。

描述

journal_t 從檔案系統超級塊結構中連結。

我們使用 journal_t 來跟蹤檔案系統上所有未完成的事務活動,並管理日誌寫入過程的狀態。

這是一個不透明的資料型別。

結構體 jbd2_inode

jbd_inode 型別是連結到事務中有序模式下 inodes 的結構,以便我們可以在提交時同步它們。

定義:

struct jbd2_inode {
    transaction_t *i_transaction;
    transaction_t *i_next_transaction;
    struct list_head i_list;
    struct inode *i_vfs_inode;
    unsigned long i_flags;
    loff_t i_dirty_start;
    loff_t i_dirty_end;
};

成員

i_transaction

此 inode 屬於哪個事務?是正在執行的事務還是正在提交的事務。[j_list_lock]

i_next_transaction

指向修改 inode 資料的正在執行事務的指標,以防已有提交事務正在接觸它。[j_list_lock]

i_list

i_transaction 中的 inode 列表 [j_list_lock]

i_vfs_inode

此 inode 所屬的 VFS inode [結構體生命週期內保持不變]

i_flags

inode 標誌 [j_list_lock]

i_dirty_start

此 inode 髒範圍開始的位元組偏移量。[j_list_lock]

i_dirty_end

此 inode 髒範圍結束(含)的位元組偏移量。[j_list_lock]

結構體 jbd2_journal_handle

jbd2_journal_handle 型別是與 handle_t 關聯的具體型別。

定義:

struct jbd2_journal_handle {
    union {
        transaction_t *h_transaction;
        journal_t *h_journal;
    };
    handle_t *h_rsv_handle;
    int h_total_credits;
    int h_revoke_credits;
    int h_revoke_credits_requested;
    int h_ref;
    int h_err;
    unsigned int    h_sync:         1;
    unsigned int    h_reserved:     1;
    unsigned int    h_aborted:      1;
    unsigned int    h_type:         8;
    unsigned int    h_line_no:      16;
    unsigned long           h_start_jiffies;
    unsigned int            h_requested_credits;
    unsigned int            saved_alloc_context;
};

成員

{unnamed_union}

匿名

h_transaction

此更新是哪個複合事務的一部分?

h_journal

此日誌控制代碼屬於哪個日誌——僅在 h_reserved 設定時使用。

h_rsv_handle

為完成邏輯操作而保留的控制代碼。

h_total_credits

允許新增到日誌的剩餘緩衝區數量。這些是髒緩衝區和撤銷描述符塊。

h_revoke_credits

控制代碼可用的剩餘撤銷記錄數量

h_revoke_credits_requested

控制代碼啟動後保留 h_revoke_credits

h_ref

此控制代碼的引用計數。

h_err

供呼叫者在大型檔案系統操作中跟蹤錯誤時使用。

h_sync

同步關閉標誌。

h_reserved

用於保留信用的控制代碼標誌。

h_aborted

指示控制代碼上發生致命錯誤的標誌。

h_type

用於控制代碼統計。

h_line_no

用於控制代碼統計。

h_start_jiffies

控制代碼開始時間。

h_requested_credits

控制代碼啟動後保留 h_total_credits

saved_alloc_context

事務開啟時儲存的上下文。

結構體 journal_s

journal_s 型別是與 journal_t 關聯的具體型別。

定義:

struct journal_s {
    unsigned long           j_flags;
    int j_errno;
    struct mutex            j_abort_mutex;
    struct buffer_head      *j_sb_buffer;
    journal_superblock_t *j_superblock;
    rwlock_t j_state_lock;
    int j_barrier_count;
    struct mutex            j_barrier;
    transaction_t *j_running_transaction;
    transaction_t *j_committing_transaction;
    transaction_t *j_checkpoint_transactions;
    wait_queue_head_t j_wait_transaction_locked;
    wait_queue_head_t j_wait_done_commit;
    wait_queue_head_t j_wait_commit;
    wait_queue_head_t j_wait_updates;
    wait_queue_head_t j_wait_reserved;
    wait_queue_head_t j_fc_wait;
    struct mutex            j_checkpoint_mutex;
    struct buffer_head      *j_chkpt_bhs[JBD2_NR_BATCH];
    struct shrinker         *j_shrinker;
    struct percpu_counter   j_checkpoint_jh_count;
    transaction_t *j_shrink_transaction;
    unsigned long           j_head;
    unsigned long           j_tail;
    unsigned long           j_free;
    unsigned long           j_first;
    unsigned long           j_last;
    unsigned long           j_fc_first;
    unsigned long           j_fc_off;
    unsigned long           j_fc_last;
    struct block_device     *j_dev;
    int j_blocksize;
    unsigned long long      j_blk_offset;
    char j_devname[BDEVNAME_SIZE+24];
    struct block_device     *j_fs_dev;
    errseq_t j_fs_dev_wb_err;
    unsigned int            j_total_len;
    atomic_t j_reserved_credits;
    spinlock_t j_list_lock;
    struct inode            *j_inode;
    tid_t j_tail_sequence;
    tid_t j_transaction_sequence;
    tid_t j_commit_sequence;
    tid_t j_commit_request;
    __u8 j_uuid[16];
    struct task_struct      *j_task;
    int j_max_transaction_buffers;
    int j_revoke_records_per_block;
    int j_transaction_overhead_buffers;
    unsigned long           j_commit_interval;
    struct timer_list       j_commit_timer;
    spinlock_t j_revoke_lock;
    struct jbd2_revoke_table_s *j_revoke;
    struct jbd2_revoke_table_s *j_revoke_table[2];
    struct buffer_head      **j_wbuf;
    struct buffer_head      **j_fc_wbuf;
    int j_wbufsize;
    int j_fc_wbufsize;
    pid_t j_last_sync_writer;
    u64 j_average_commit_time;
    u32 j_min_batch_time;
    u32 j_max_batch_time;
    void (*j_commit_callback)(journal_t *, transaction_t *);
    int (*j_submit_inode_data_buffers) (struct jbd2_inode *);
    int (*j_finish_inode_data_buffers) (struct jbd2_inode *);
    spinlock_t j_history_lock;
    struct proc_dir_entry   *j_proc_entry;
    struct transaction_stats_s j_stats;
    unsigned int            j_failed_commit;
    void *j_private;
    __u32 j_csum_seed;
#ifdef CONFIG_DEBUG_LOCK_ALLOC;
    struct lockdep_map      j_trans_commit_map;
#endif;
    void (*j_fc_cleanup_callback)(struct journal_s *journal, int full, tid_t tid);
    int (*j_fc_replay_callback)(struct journal_s *journal,struct buffer_head *bh,enum passtype pass, int off, tid_t expected_commit_id);
    int (*j_bmap)(struct journal_s *journal, sector_t *block);
};

成員

j_flags

通用日誌狀態標誌 [j_state_lock, 快速粗略檢查無鎖]

j_errno

日誌是否存在未清除的未決錯誤(來自先前的中止)?[j_state_lock]

j_abort_mutex

鎖定整個中止過程。

j_sb_buffer

超級塊緩衝區的第一部分。

j_superblock

超級塊緩衝區的第二部分。

j_state_lock

保護日誌中的各種標量。

j_barrier_count

等待建立 barrier 鎖的程序數量 [j_state_lock, 快速粗略檢查無鎖]

j_barrier

barrier 鎖本身。

j_running_transaction

事務:當前正在執行的事務……[j_state_lock, 快速粗略檢查無鎖][呼叫者持有開啟的控制代碼]

j_committing_transaction

我們正在推送到磁碟的事務 [j_state_lock] [呼叫者持有開啟的控制代碼]

j_checkpoint_transactions

……以及所有等待檢查點的事務的鏈式迴圈列表。[j_list_lock]

j_wait_transaction_locked

等待佇列,用於等待鎖定的事務開始提交,或等待 barrier 鎖釋放。

j_wait_done_commit

等待佇列,用於等待提交完成。

j_wait_commit

等待佇列,用於觸發提交。

j_wait_updates

等待佇列,用於等待更新完成。

j_wait_reserved

等待佇列,用於等待保留的緩衝區信用減少。

j_fc_wait

等待佇列,用於等待非同步快速提交完成。

j_checkpoint_mutex

用於防止併發檢查點的訊號量。

j_chkpt_bhs

檢查點例程使用的緩衝區頭列表。此列表已從 jbd2_log_do_checkpoint() 移出,以減少堆疊使用。對該陣列的訪問由 j_checkpoint_mutex 控制。[j_checkpoint_mutex]

j_shrinker

日誌頭縮小器,回收已寫回的緩衝區日誌頭。

j_checkpoint_jh_count

檢查點列表上的日誌緩衝區數量。[j_list_lock]

j_shrink_transaction

記錄將在檢查點列表上縮小的下一個事務。[j_list_lock]

j_head

日誌頭:標識日誌中第一個未使用的塊。[j_state_lock]

j_tail

日誌尾:標識日誌中最舊的仍在使用中的塊。[j_state_lock]

j_free

日誌空閒:日誌中有多少空閒塊?[j_state_lock]

j_first

日誌中第一個可用塊的塊號 [j_state_lock]。

j_last

日誌中最後一個可用塊之後的一個塊號 [j_state_lock]。

j_fc_first

日誌中第一個快速提交塊的塊號 [j_state_lock]。

j_fc_off

當前已分配的快速提交塊數量。僅在快速提交期間訪問。目前只有程序可以執行快速提交,因此此欄位不受任何鎖的保護。

j_fc_last

日誌中最後一個快速提交塊之後的一個塊號 [j_state_lock]。

j_dev

我們儲存日誌的裝置。

j_blocksize

我們儲存日誌的位置的塊大小。

j_blk_offset

我們儲存日誌的裝置中的起始塊偏移量。

j_devname

日誌裝置名稱。

j_fs_dev

持有此日誌的檔案系統客戶端裝置。對於內部日誌,這將等於 j_dev。

j_fs_dev_wb_err

記錄客戶端檔案系統支援塊裝置的錯誤序列。

j_total_len

日誌區域在磁碟上的總最大容量。

j_reserved_credits

從正在執行的事務中保留的緩衝區數量。

j_list_lock

保護緩衝區列表和內部緩衝區狀態。

j_inode

可選的 inode,我們可以在其中儲存日誌。如果存在,所有日誌塊號都透過 bmap() 對映到此 inode。

j_tail_sequence

日誌中最舊事務的序列號 [j_state_lock]

j_transaction_sequence

下一個要授予的事務的序列號 [j_state_lock]

j_commit_sequence

最近提交事務的序列號 [j_state_lock, 快速粗略檢查無鎖]

j_commit_request

最近需要提交的事務的序列號 [j_state_lock, 快速粗略檢查無鎖]

j_uuid

日誌 uuid:標識此日誌支援的物件(檔案系統、LVM 卷等)。這最終將被一個 uuid 陣列取代,允許我們在單個日誌中索引多個裝置並在它們之間執行原子更新。

j_task

指向此日誌當前提交執行緒的指標。

j_max_transaction_buffers

單個複合提交事務中允許的最大元資料緩衝區數量。

j_revoke_records_per_block

一個描述符塊中可容納的撤銷記錄數量。

j_transaction_overhead_buffers

每個事務用於自身簿記所需的塊數

j_commit_interval

在開始提交之前,最大事務生命週期是多少?

j_commit_timer

用於喚醒提交執行緒的定時器。

j_revoke_lock

保護撤銷表。

j_revoke

撤銷表——維護當前事務中已撤銷塊的列表。

j_revoke_table

j_revoke 的備用撤銷表。

j_wbuf

jbd2_journal_commit_transaction 的 bhs 陣列。

j_fc_wbuf

用於快速提交的快速提交 bhs 陣列。僅在快速提交期間訪問。目前只有程序可以執行快速提交,因此此欄位不受任何鎖的保護。

j_wbufsize

j_wbuf 陣列的大小。

j_fc_wbufsize

j_fc_wbuf 陣列的大小。

j_last_sync_writer

最後透過日誌運行同步操作的人的 pid。

j_average_commit_time

將事務提交到磁碟所需的平均時間(納秒)。[j_state_lock]

j_min_batch_time

我們應該等待額外檔案系統操作批處理到同步控制代碼中的最短時間(微秒)。

j_max_batch_time

我們應該等待額外檔案系統操作批處理到同步控制代碼中的最長時間(微秒)。

j_commit_callback

事務關閉時呼叫此函式。

j_submit_inode_data_buffers

在我們將事務寫入日誌之前,為與提交事務關聯並標記為 JI_WRITE_DATA 標誌的所有 inode 呼叫此函式。

j_finish_inode_data_buffers

在我們將事務寫入日誌之後但在寫入提交塊之前,為與提交事務關聯並標記為 JI_WAIT_DATA 標誌的所有 inode 呼叫此函式。

j_history_lock

保護事務統計歷史。

j_proc_entry

jbd 統計目錄的 procfs 條目。

j_stats

總體統計資訊。

j_failed_commit

失敗的日誌提交 ID。

j_private

指向檔案系統私有資訊的不透明指標。ext3 將其超級塊指標放在這裡。

j_csum_seed

預計算的日誌 UUID 校驗和,用於播種其他校驗和。

j_trans_commit_map

Lockdep 實體,用於跟蹤事務提交依賴項。控制代碼以讀模式持有此“鎖”,當我們等待提交時,以寫模式獲取此“鎖”。這與 jbd2 日誌的特性相匹配,其中正在執行的事務必須等待所有控制代碼都已釋放才能提交該事務,並且獲取控制代碼可能需要事務提交完成。

j_fc_cleanup_callback

快速提交或完全提交後的清理。JBD2 在每次提交操作後呼叫此函式。

j_fc_replay_callback

檔案系統特定函式,用於執行快速提交的重放。JBD2 為日誌中找到的每個快速提交塊呼叫此函式。此函式應返回 JBD2_FC_REPLAY_CONTINUE,表示塊已正確處理,並且應繼續進行更多快速提交重放。返回 JBD2_FC_REPLAY_STOP 表示重放結束(沒有更多塊剩餘)。負返回值表示錯誤。

j_bmap

應使用的 Bmap 函式,而不是通用的 VFS bmap 函式。

函式

這裡的函式分為兩組:影響整個日誌的函式,以及用於管理事務的函式。

日誌級別

int jbd2_journal_force_commit_nested(journal_t *journal)

如果呼叫程序不在事務中,則強制提交併等待。

引數

journal_t *journal

要強制提交的日誌。如果取得進展則返回 true。

描述

這用於強制輸出包含點陣圖的撤銷保護資料,當檔案系統空間不足時。

int jbd2_journal_force_commit(journal_t *journal)

強制提交任何未提交的事務

引數

journal_t *journal

要強制提交的日誌

描述

呼叫者需要無條件提交。只有當我們沒有活動控制代碼時,才能強制執行正在執行的事務,否則會發生死鎖。

journal_t *jbd2_journal_init_dev(struct block_device *bdev, struct block_device *fs_dev, unsigned long long start, int len, int blocksize)

建立並初始化一個日誌結構

引數

struct block_device *bdev

要在其上建立日誌的塊裝置

struct block_device *fs_dev

持有此日誌的日誌檔案系統的裝置。

unsigned long long start

日誌起始塊號。

int len

日誌的塊長度。

int blocksize

日誌裝置的塊大小

返回

新建立的 journal_t *

jbd2_journal_init_dev 建立一個日誌,它將任意塊裝置上的固定連續塊範圍對映為日誌。

journal_t *jbd2_journal_init_inode(struct inode *inode)

建立一個對映到 inode 的日誌。

引數

struct inode *inode

要在其中建立日誌的 inode

描述

jbd2_journal_init_inode 建立一個日誌,它將一個磁碟 inode 對映為日誌。該 inode 必須已經存在,必須支援 bmap() 並且必須預分配所有資料塊。

void jbd2_journal_update_sb_errno(journal_t *journal)

更新日誌中的錯誤。

引數

journal_t *journal

要更新的日誌。

描述

更新日誌的 errno。將更新後的超級塊寫入磁碟,等待 I/O 完成。

int jbd2_journal_load(journal_t *journal)

從磁碟讀取日誌。

引數

journal_t *journal

要操作的日誌。

描述

給定一個 journal_t 結構,該結構告訴我們哪些磁碟塊包含日誌,從磁碟讀取日誌以初始化記憶體中的結構。

int jbd2_journal_destroy(journal_t *journal)

釋放 journal_t 結構。

引數

journal_t *journal

要操作的日誌。

描述

一旦日誌物件不再使用 journal_t 結構,則釋放它。如果我們無法清理日誌,則返回 <0。

int jbd2_journal_check_used_features(journal_t *journal, unsigned long compat, unsigned long ro, unsigned long incompat)

檢查指定功能是否已使用。

引數

journal_t *journal

要檢查的日誌。

unsigned long compat

相容功能的位掩碼

unsigned long ro

強制只讀掛載的功能位掩碼

unsigned long incompat

不相容功能的位掩碼

描述

檢查日誌是否使用了給定功能集中的所有功能。如果是,則返回 true (非零)。

int jbd2_journal_check_available_features(journal_t *journal, unsigned long compat, unsigned long ro, unsigned long incompat)

檢查日誌層中的功能集

引數

journal_t *journal

要檢查的日誌。

unsigned long compat

相容功能的位掩碼

unsigned long ro

強制只讀掛載的功能位掩碼

unsigned long incompat

不相容功能的位掩碼

描述

檢查日誌程式碼是否支援在此日誌上使用給定功能集中的所有功能。返回 true。

int jbd2_journal_set_features(journal_t *journal, unsigned long compat, unsigned long ro, unsigned long incompat)

在超級塊中標記給定日誌功能

引數

journal_t *journal

要操作的日誌。

unsigned long compat

相容功能的位掩碼

unsigned long ro

強制只讀掛載的功能位掩碼

unsigned long incompat

不相容功能的位掩碼

描述

將給定日誌功能標記為在超級塊上存在。如果請求的功能可以設定,則返回 true。

int jbd2_journal_flush(journal_t *journal, unsigned int flags)

重新整理日誌

引數

journal_t *journal

要操作的日誌。

unsigned int flags

重新整理後日志塊上的可選操作(見下文)

描述

將給定日誌的所有資料重新整理到磁碟並清空日誌。檔案系統在重新掛載只讀模式時可以使用此功能,以確保在重新掛載時不需要進行恢復。可選地,可以在重新整理後對日誌塊發出丟棄或清零操作。

標誌

JBD2_JOURNAL_FLUSH_DISCARD:對日誌塊發出丟棄操作 JBD2_JOURNAL_FLUSH_ZEROOUT:對日誌塊發出清零操作

int jbd2_journal_wipe(journal_t *journal, int write)

清除日誌內容

引數

journal_t *journal

要操作的日誌。

int write

標誌(見下文)

描述

安全地清除日誌的所有內容。如果日誌包含任何有效的恢復資訊,這將產生警告。必須在 journal_init_*()jbd2_journal_load() 之間呼叫。

如果“write”非零,則我們將磁碟上的日誌清除;否則我們只抑制恢復。

void jbd2_journal_abort(journal_t *journal, int errno)

立即關閉日誌。

引數

journal_t *journal

要關閉的日誌。

int errno

要記錄在日誌中的錯誤號,指示關閉原因。

描述

執行日誌的完整、立即關閉(而非單個事務的關閉)。此操作無法在不關閉和重新開啟日誌的情況下撤消。

jbd2_journal_abort 函式旨在支援更高層次的錯誤恢復機制,例如 ext2/ext3 的只讀重新掛載錯誤模式。

日誌中止具有非常具體的語義。主檔案系統中任何現有的髒的、未日誌化的緩衝區仍將由 bdflush 寫入磁碟,但日誌機制將立即暫停,並且不會再執行進一步的事務提交。

任何髒的、已日誌化的緩衝區都將直接寫回磁碟,而不會進入日誌。在已中止的檔案系統上無法保證原子性,但我們確實嘗試留下儘可能多的資料供 fsck 用於清理。

在處於 ABORT 狀態的日誌上獲取新事務控制代碼的任何嘗試都將導致返回 -EROFS 錯誤。如果在更新期間進入中止狀態,對現有控制代碼執行 jbd2_journal_stop 將返回 -EIO。

遞迴事務不會受到日誌中止的干擾,直到最終的 jbd2_journal_stop,屆時將收到 -EIO 錯誤。

最後,jbd2_journal_abort 呼叫允許呼叫者提供一個 errno,該 errno 將(如果可能)記錄在日誌超級塊中。這允許客戶端在事務中間記錄故障情況,而無需完成事務以將故障記錄到磁碟。例如,ext3_error 現在使用此功能。

int jbd2_journal_errno(journal_t *journal)

返回日誌的錯誤狀態。

引數

journal_t *journal

要檢查的日誌。

描述

這是使用 jbd2_journal_abort() 設定的 errno 號,是日誌上次掛載時設定的——如果日誌在未呼叫中止的情況下停止,則此值為 0。

如果日誌在此次掛載時已中止,則返回 -EROFS。

int jbd2_journal_clear_err(journal_t *journal)

清除日誌的錯誤狀態

引數

journal_t *journal

要操作的日誌。

描述

必須清除或確認錯誤才能使檔案系統脫離只讀模式。

void jbd2_journal_ack_err(journal_t *journal)

確認日誌錯誤。

引數

journal_t *journal

要操作的日誌。

描述

必須清除或確認錯誤才能使檔案系統脫離只讀模式。

int jbd2_journal_recover(journal_t *journal)

恢復磁碟日誌

引數

journal_t *journal

要恢復的日誌

描述

掛載日誌裝置時恢復日誌內容的主要功能。

恢復分三步完成。第一步,我們查詢日誌的末尾。第二步,我們組裝撤銷塊列表。第三步也是最後一步,我們重放日誌中所有未撤銷的塊。

int jbd2_journal_skip_recovery(journal_t *journal)

啟動日誌並清除現有記錄

引數

journal_t *journal

要啟動的日誌

描述

從日誌中找到任何有效的恢復資訊,並設定記憶體中的日誌結構以忽略它(大概是因為呼叫者有證據表明它已過期)。此函式似乎未匯出。

我們對日誌進行一次遍歷,以便告訴使用者正在清除多少恢復資訊,並初始化日誌事務序列號為下一個未使用的 ID。

事務級別

handle_t *jbd2_journal_start(journal_t *journal, int nblocks)

獲取新控制代碼。

引數

journal_t *journal

開始事務的日誌。

int nblocks

我們可能修改的塊緩衝區數量

描述

我們確保事務在日誌中至少能保證 nblocks 的修改緩衝區空間。我們阻塞直到日誌能保證足夠的空間。此外,如果 rsv_blocks > 0,我們還會建立另一個在日誌中保留 rsv_blocks 塊的控制代碼。此控制代碼儲存在 h_rsv_handle 中。它不附加到任何特定事務,因此不會阻塞事務提交。如果呼叫者使用此保留控制代碼,則必須將 h_rsv_handle 設定為 NULL,否則父控制代碼上的 jbd2_journal_stop() 將釋放保留的控制代碼。保留的控制代碼在使用之前必須使用 jbd2_journal_start_reserved() 轉換為正常控制代碼。

返回指向新分配控制代碼的指標,或失敗時返回 ERR_PTR() 值。

int jbd2_journal_start_reserved(handle_t *handle, unsigned int type, unsigned int line_no)

啟動保留控制代碼

引數

handle_t *handle

要啟動的控制代碼

unsigned int type

用於控制代碼統計

unsigned int line_no

用於控制代碼統計

描述

使用 jbd2_journal_reserve() 預先保留的啟動控制代碼。這會將 handle 附加到正在執行的事務(如果當前沒有執行事務,則建立一個)。與 jbd2_journal_start() 不同,此函式不會因日誌提交、檢查點或類似操作而阻塞。它可能會因記憶體分配或凍結的日誌而阻塞。

成功返回 0,錯誤返回非零——在這種情況下控制代碼會被釋放。

int jbd2_journal_extend(handle_t *handle, int nblocks, int revoke_records)

延長緩衝區信用。

引數

handle_t *handle

要“延長”的控制代碼

int nblocks

嘗試延長多少塊。

int revoke_records

要嘗試延長撤銷記錄的數量。

描述

一些事務,例如大型擴充套件和截斷,可以一次性或分幾個階段原子地完成。操作會預先請求一定數量的緩衝區修改的信用,但如果需要更多,可以延長其信用。

jbd2_journal_extend 嘗試為正在執行的控制代碼提供更多緩衝區信用。它不保證分配——這只是盡力而為。呼叫過程必須能夠乾淨地處理此處的擴充套件失敗。

成功返回 0,失敗返回非零。

返回程式碼 < 0 表示錯誤,返回程式碼 > 0 表示正常事務滿狀態。

int jbd2__journal_restart(handle_t *handle, int nblocks, int revoke_records, gfp_t gfp_mask)

重新啟動控制代碼。

引數

handle_t *handle

要重新啟動的控制代碼

int nblocks

請求的信用數量

int revoke_records

請求的撤銷記錄信用數量

gfp_t gfp_mask

記憶體分配標誌(用於 start_this_handle)

描述

為多事務檔案系統操作重新啟動控制代碼。

如果上述 jbd2_journal_extend() 呼叫未能授予正在執行的控制代碼新的緩衝區信用,則呼叫 jbd2_journal_restart 將提交控制代碼迄今為止的事務,並將控制代碼重新附加到能夠保證所需信用數量的新事務。如果傳遞的控制代碼附加了任何保留控制代碼,我們將保留它。

void jbd2_journal_lock_updates(journal_t *journal)

建立事務屏障。

引數

journal_t *journal

要建立屏障的日誌。

描述

這會阻止任何進一步的更新啟動,並阻塞直到所有現有更新完成,只有當日志處於沒有更新執行的靜止狀態時才返回。

進入時日誌鎖不應被持有。

void jbd2_journal_unlock_updates(journal_t *journal)

釋放屏障

引數

journal_t *journal

要釋放屏障的日誌。

描述

釋放透過 jbd2_journal_lock_updates() 獲取的事務屏障。

呼叫時應不持有日誌鎖。

int jbd2_journal_get_write_access(handle_t *handle, struct buffer_head *bh)

通知意圖修改元資料的緩衝區(非資料)更新。

引數

handle_t *handle

要新增緩衝區修改的事務

struct buffer_head *bh

用於元資料寫入的 bh

返回

錯誤程式碼,成功時為 0。

描述

在完整資料日誌模式下,緩衝區可能是 BJ_AsyncData 型別,因為我們正在 write() 一個也是共享對映一部分的緩衝區。

int jbd2_journal_get_create_access(handle_t *handle, struct buffer_head *bh)

通知意圖使用新建立的 bh

引數

handle_t *handle

新緩衝區所在的事務

struct buffer_head *bh

新緩衝區。

描述

如果您建立了一個新的 bh,請呼叫此函式。

int jbd2_journal_get_undo_access(handle_t *handle, struct buffer_head *bh)

通知意圖修改具有不可撤銷後果的元資料

引數

handle_t *handle

事務

struct buffer_head *bh

要撤銷的緩衝區

描述

有時需要區分已提交到磁碟的元資料和未提交的元資料。ext3fs 程式碼使用它來釋放和分配空間,我們必須確保在去分配提交之前不重用已釋放的空間,因為如果我們覆蓋該空間,在發生崩潰時,我們將使刪除操作無法回滾。

為了解決這個問題,jbd2_journal_get_undo_access 請求對點陣圖上刪除操作等不可撤銷操作的緩衝區進行寫訪問。日誌程式碼必須在呼叫 undo_access 之前保留緩衝區的原始內容副本,直到我們確定緩衝區已明確提交到磁碟。

我們永遠不需要知道提交的資料是哪個事務的一部分,這裡涉及的緩衝區保證稍後會被髒化,因此會適時提交到一個新事務,此時我們可以丟棄舊的已提交資料指標。

成功返回錯誤號或 0。

void jbd2_journal_set_triggers(struct buffer_head *bh, struct jbd2_buffer_trigger_type *type)

新增提交寫入觸發器

引數

struct buffer_head *bh

要觸發的緩衝區

struct jbd2_buffer_trigger_type *type

包含觸發器(或多個觸發器)的 struct jbd2_buffer_trigger_type。

描述

在此 journal_head 上設定任何觸發器。這總是安全的,因為提交緩衝區的觸發器將被儲存,而正在執行的事務的觸發器將與該事務中的緩衝區匹配。

呼叫 NULL 可清除觸發器。

int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh)

標記緩衝區包含髒元資料

引數

handle_t *handle

要將緩衝區新增到的事務。

struct buffer_head *bh

要標記的緩衝區

描述

標記需要作為當前事務一部分進行日誌記錄的髒元資料。

緩衝區必須事先呼叫 jbd2_journal_get_write_access(),以便其緩衝區頭附加了有效的 journal_head。

緩衝區被放置在事務的元資料列表上,並被標記為屬於該事務。

成功返回錯誤號或 0。

如果緩衝區已經屬於當前正在提交的事務(在這種情況下,我們應該有該提交的凍結資料),則需要特別注意。在這種情況下,我們不重新連結緩衝區:只有當舊事務最終完成提交時,才會執行此操作。

int jbd2_journal_forget(handle_t *handle, struct buffer_head *bh)

針對可能已日誌化的緩衝區的 bforget()

引數

handle_t *handle

事務控制代碼

struct buffer_head *bh

要“忘記”的 bh

描述

只有當沒有針對緩衝區掛起的提交時,我們才能執行 bforget。如果緩衝區在當前正在執行的事務中是髒的,我們可以安全地解除其連結。

bh 可能根本不是日誌緩衝區——它可能是從雜湊表出來的非 JBD 緩衝區。檢查這一點。

將 bh->b_count 減一。

即使控制代碼已中止,也允許此呼叫——它可能是呼叫者在中止後清理的一部分。

int jbd2_journal_stop(handle_t *handle)

完成事務

引數

handle_t *handle

要完成的事務。

描述

特定控制代碼的所有操作都已完成。

這裡不需要太多操作。我們只是將任何剩餘的緩衝區信用返還給事務並移除控制代碼。唯一的複雜之處在於,如果檔案系統標記為同步更新,我們需要啟動提交操作。

jbd2_journal_stop 本身通常不會返回錯誤,但在特殊情況下可能會返回錯誤。特別是,如果事務開始後執行了 jbd2_journal_abort,則預期它會返回 -EIO。

bool jbd2_journal_try_to_free_buffers(journal_t *journal, struct folio *folio)

嘗試釋放頁面緩衝區。

引數

journal_t *journal

操作日誌

struct folio *folio

要分離資料的 Folio。

描述

對於此頁面上的所有緩衝區,如果它們是完全寫入的有序資料,則將它們移動到 BUF_CLEAN,以便 try_to_free_buffers() 可以回收它們。

如果希望呼叫 try_to_free_buffers(),此函式將返回非零。如果該頁面可由 try_to_free_buffers() 釋放,我們就會這樣做。如果該頁面有鎖定或髒緩衝區,並且呼叫者希望我們執行同步或非同步寫入,我們也會這樣做。

這在某種程度上使 JBD 鎖定複雜化。我們在這裡不受 BKL 保護。我們希望透過 __jbd2_journal_unfile_buffer 從其提交或正在執行的事務的 ->t_datalist 中移除緩衝區。

這可能會*改變* transaction_t->t_datalist 的值,因此任何檢視 t_datalist 的人都需要鎖定以防止此函式的影響。

更糟的是,有人可能正在對此緩衝區執行 jbd2_journal_dirty_data。因此我們需要鎖定以防止這種情況。 jbd2_journal_dirty_data() 將在解鎖後使緩衝區變髒,從而使其不適合在此處釋放。

還有誰受此影響?嗯……實際上唯一的競爭者是 do_get_write_access()——當 journal_try_to_free_buffer() 正在改變其狀態時,它可能正在檢視緩衝區。但這不可能發生,因為當資料是事務的一部分時,我們從不將已釋放的資料重新分配為元資料。是嗎?

失敗返回 false,成功返回 true

int jbd2_journal_invalidate_folio(journal_t *journal, struct folio *folio, size_t offset, size_t length)

引數

journal_t *journal

用於重新整理的日誌...

struct folio *folio

要重新整理的 folio

size_t offset

要無效的範圍起始點

size_t length

要無效的範圍長度

描述

回收頁面中指定範圍內包含資料的頁面緩衝區。如果緩衝區是正在提交的事務的一部分且頁面跨越 i_size,則可能返回 -EBUSY。呼叫者此時必須等待當前提交完成並重試。

另請參閱

《Linux ext2fs 檔案系統日誌》,LinuxExpo 98,Stephen Tweedie

《Ext3 日誌檔案系統》,OLS 2000,Stephen Tweedie 博士