Linux 日志 API¶
概述¶
详情¶
日志层易于使用。首先,您需要创建一个 journal_t 数据结构。有两种调用方式,取决于您如何决定分配日志所在的物理介质。jbd2_journal_init_inode()
调用用于存储在文件系统 inode 中的日志,或者 jbd2_journal_init_dev()
调用可用于存储在原始设备上的日志(在连续的块范围内)。journal_t 是结构指针的 typedef,因此当您最终完成时,请确保调用 jbd2_journal_destroy()
来释放任何已使用的内核内存。
一旦您获得了 journal_t 对象,您需要“挂载”或加载日志文件。日志层期望日志的空间已由用户空间工具正确分配和初始化。加载日志时,您必须调用 jbd2_journal_load()
来处理日志内容。如果客户端文件系统检测到日志内容不需要处理(甚至不需要有有效内容),它可以调用 jbd2_journal_wipe()
来清除日志内容,然后再调用 jbd2_journal_load()
。
请注意,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
函数指针即可要求日志层调用回调,并且该函数在每次事务提交后都会被调用。您还可以使用 transaction->t_private_list
将需要事务提交时处理的条目附加到事务。
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()
来分配和等待快速提交缓冲区的 IO 完成。
目前,只有 Ext4 实现了快速提交。有关其快速提交实现的详细信息,请参考 fs/ext4/fast_commit.c 中的顶层注释。
摘要¶
使用日志是关于包装不同的上下文更改,包括每次挂载、每次修改(事务)和每个已更改的缓冲区,以告知日志层。
数据类型¶
日志层使用 typedef 来“隐藏”所用结构的具体定义。作为 JBD2 层的客户端,您可以只依赖于将指针用作某种魔术令牌。显然,这种隐藏并非强制性的,因为这是“C”。
结构体¶
-
type handle_t¶
handle_t 类型表示由某个进程执行的单个原子更新。
描述
进程所做的所有文件系统修改都通过此句柄进行。递归操作(例如配额操作)会汇总到单个更新中。
缓冲区 credits 字段用于核算正在运行的进程修改的已记录缓冲区。为确保所有未完成的操作都有足够的日志空间,我们需要限制任何时候可能存在的未完成缓冲区数量。当操作完成时,任何未使用的缓冲区 credits 将返还给事务,以便我们始终知道事务中未完成的更新可能触及多少缓冲区。
这是一个不透明的数据类型。
-
type journal_t¶
journal_t 维护单个文件系统的所有日志状态信息。
描述
journal_t 从 fs 超级块结构链接。
我们使用 journal_t 来跟踪文件系统上所有未完成的事务活动,并管理日志写入过程的状态。
这是一个不透明的数据类型。
-
struct jbd2_inode¶
jbd_inode 类型是在事务中存在的有序模式下链接 inode 的结构,以便我们可以在提交期间同步它们。
定义:
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 的数据,则该事务会修改 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]
-
struct 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_jdata: 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
供调用者使用,以跟踪大型 fs 操作中的错误。
h_sync
用于同步关闭的标志。
h_jdata
强制数据日志的标志。
h_reserved
用于保留 credits 的句柄的标志。
h_aborted
指示句柄上出现致命错误的标志。
h_type
用于句柄统计信息。
h_line_no
用于句柄统计信息。
h_start_jiffies
句柄启动时间。
h_requested_credits
在句柄启动后,持有 h_total_credits。
saved_alloc_context
事务打开时保存的上下文。
-
struct 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;
struct crypto_shash *j_chksum_driver;
__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
等待创建屏障锁的进程数量 [j_state_lock, 用于快速竞争性检查时不使用锁]
j_barrier
屏障锁本身。
j_running_transaction
事务:当前正在运行的事务... [j_state_lock, 用于快速竞争性检查时不使用锁] [调用方持有打开的句柄]
j_committing_transaction
我们正在推送到磁盘的事务 [j_state_lock] [调用方持有打开的句柄]
j_checkpoint_transactions
...以及所有等待检查点的事务的链接循环列表。[j_list_lock]
j_wait_transaction_locked
等待锁定的事务开始提交或屏障锁被释放的等待队列。
j_wait_done_commit
等待提交完成的等待队列。
j_wait_commit
触发提交的等待队列。
j_wait_updates
等待更新完成的等待队列。
j_wait_reserved
等待保留的缓冲区 credits 下降的等待队列。
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
保存客户端 fs 的设备。对于内部日志,这将等于 j_dev。
j_fs_dev_wb_err
记录客户端 fs 的后备块设备的 errseq。
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
Journal 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_chksum_driver
通过 cryptoapi 引用校验和算法驱动程序。
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。将更新的超级块写入磁盘,等待 IO 完成。
参数
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 之前,递归事务不受日志中止的干扰,最终的 jbd2_journal_stop 将收到 -EIO 错误。
最后,jbd2_journal_abort 调用允许调用者提供一个 errno,该 errno 将(如果可能)记录在日志超级块中。这允许客户端在事务中间记录失败情况,而无需完成事务以将失败记录到磁盘。例如,ext3_error 现在使用此功能。
参数
journal_t *journal
要检查的日志。
描述
这是使用 jbd2_journal_abort()
设置的 errno 号,即上次挂载日志时 - 如果日志在没有调用 abort 的情况下停止,则此值将为 0。
如果日志在此挂载时被中止,则将返回 -EROFS。
参数
journal_t *journal
要操作的日志。
描述
必须清除或确认错误,才能使 FS 脱离只读模式。
参数
journal_t *journal
要操作的日志。
描述
必须清除或确认错误,才能使 FS 脱离只读模式。
参数
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
在其上建立屏障的日志。
描述
这会阻止启动任何进一步的更新,并阻塞直到所有现有的更新都已完成,仅当日志处于静止状态且没有更新正在运行时才返回。
不应在入口处持有日志锁。
-
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()
一个也是共享映射一部分的缓冲区。
参数
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 减 1。
即使句柄已中止,也允许此调用 --- 这可能是调用者在中止后清理的一部分。
参数
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。 然后,调用者必须等待当前提交并重试。
另请参阅¶
Journaling the Linux ext2fs Filesystem, LinuxExpo 98, Stephen Tweedie