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 博士