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 函数。
函数¶
这里的函数分为两组:影响整个日志的函数,以及用于管理事务的函数。
日志级别¶
参数
journal_t *journal
要强制提交的日志。如果取得进展则返回 true。
描述
这用于强制输出包含位图的撤销保护数据,当文件系统空间不足时。
参数
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
创建一个日志,它将任意块设备上的固定连续块范围映射为日志。
参数
struct inode *inode
要在其中创建日志的 inode
描述
jbd2_journal_init_inode
创建一个日志,它将一个磁盘 inode 映射为日志。该 inode 必须已经存在,必须支持 bmap()
并且必须预分配所有数据块。
参数
journal_t *journal
要更新的日志。
描述
更新日志的 errno。将更新后的超级块写入磁盘,等待 I/O 完成。
参数
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。
参数
journal_t *journal
要操作的日志。
unsigned int flags
刷新后日志块上的可选操作(见下文)
描述
将给定日志的所有数据刷新到磁盘并清空日志。文件系统在重新挂载只读模式时可以使用此功能,以确保在重新挂载时不需要进行恢复。可选地,可以在刷新后对日志块发出丢弃或清零操作。
- 标志
JBD2_JOURNAL_FLUSH_DISCARD:对日志块发出丢弃操作 JBD2_JOURNAL_FLUSH_ZEROOUT:对日志块发出清零操作
参数
journal_t *journal
要操作的日志。
int write
标志(见下文)
描述
安全地清除日志的所有内容。如果日志包含任何有效的恢复信息,这将产生警告。必须在 journal_init_*()
和 jbd2_journal_load()
之间调用。
如果“write”非零,则我们将磁盘上的日志清除;否则我们只抑制恢复。
参数
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 现在使用此功能。
参数
journal_t *journal
要检查的日志。
描述
这是使用 jbd2_journal_abort()
设置的 errno 号,是日志上次挂载时设置的——如果日志在未调用中止的情况下停止,则此值为 0。
如果日志在此次挂载时已中止,则返回 -EROFS。
参数
journal_t *journal
要操作的日志。
描述
必须清除或确认错误才能使文件系统脱离只读模式。
参数
journal_t *journal
要操作的日志。
描述
必须清除或确认错误才能使文件系统脱离只读模式。
参数
journal_t *journal
要恢复的日志
描述
挂载日志设备时恢复日志内容的主要功能。
恢复分三步完成。第一步,我们查找日志的末尾。第二步,我们组装撤销块列表。第三步也是最后一步,我们重放日志中所有未撤销的块。
参数
journal_t *journal
要启动的日志
描述
从日志中找到任何有效的恢复信息,并设置内存中的日志结构以忽略它(大概是因为调用者有证据表明它已过期)。此函数似乎未导出。
我们对日志进行一次遍历,以便告诉用户正在清除多少恢复信息,并初始化日志事务序列号为下一个未使用的 ID。
事务级别¶
参数
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()
值。
参数
handle_t *handle
要启动的句柄
unsigned int type
用于句柄统计
unsigned int line_no
用于句柄统计
描述
使用 jbd2_journal_reserve()
预先保留的启动句柄。这会将 handle
附加到正在运行的事务(如果当前没有运行事务,则创建一个)。与 jbd2_journal_start()
不同,此函数不会因日志提交、检查点或类似操作而阻塞。它可能会因内存分配或冻结的日志而阻塞。
成功返回 0,错误返回非零——在这种情况下句柄会被释放。
参数
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
将提交句柄迄今为止的事务,并将句柄重新附加到能够保证所需信用数量的新事务。如果传递的句柄附加了任何保留句柄,我们将保留它。
参数
journal_t *journal
要建立屏障的日志。
描述
这会阻止任何进一步的更新启动,并阻塞直到所有现有更新完成,只有当日志处于没有更新运行的静止状态时才返回。
进入时日志锁不应被持有。
参数
handle_t *handle
要添加缓冲区修改的事务
struct buffer_head *bh
用于元数据写入的 bh
返回
错误代码,成功时为 0。
描述
在完整数据日志模式下,缓冲区可能是 BJ_AsyncData 类型,因为我们正在 write()
一个也是共享映射一部分的缓冲区。
参数
handle_t *handle
新缓冲区所在的事务
struct buffer_head *bh
新缓冲区。
描述
如果您创建了一个新的 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 可清除触发器。
参数
handle_t *handle
要将缓冲区添加到的事务。
struct buffer_head *bh
要标记的缓冲区
描述
标记需要作为当前事务一部分进行日志记录的脏元数据。
缓冲区必须事先调用 jbd2_journal_get_write_access()
,以便其缓冲区头附加了有效的 journal_head。
缓冲区被放置在事务的元数据列表上,并被标记为属于该事务。
成功返回错误号或 0。
如果缓冲区已经属于当前正在提交的事务(在这种情况下,我们应该有该提交的冻结数据),则需要特别注意。在这种情况下,我们不重新链接缓冲区:只有当旧事务最终完成提交时,才会执行此操作。
参数
handle_t *handle
事务句柄
struct buffer_head *bh
要“忘记”的 bh
描述
只有当没有针对缓冲区挂起的提交时,我们才能执行 bforget。如果缓冲区在当前正在运行的事务中是脏的,我们可以安全地解除其链接。
bh 可能根本不是日志缓冲区——它可能是从哈希表出来的非 JBD 缓冲区。检查这一点。
将 bh->b_count 减一。
即使句柄已中止,也允许此调用——它可能是调用者在中止后清理的一部分。
参数
handle_t *handle
要完成的事务。
描述
特定句柄的所有操作都已完成。
这里不需要太多操作。我们只是将任何剩余的缓冲区信用返还给事务并移除句柄。唯一的复杂之处在于,如果文件系统标记为同步更新,我们需要启动提交操作。
jbd2_journal_stop
本身通常不会返回错误,但在特殊情况下可能会返回错误。特别是,如果事务开始后执行了 jbd2_journal_abort
,则预期它会返回 -EIO。
参数
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。调用者此时必须等待当前提交完成并重试。