细说jbd(journal-block-device)& 源码分析

ext4 用的日志文件系统变成了 jbd2,本次分析以ext3为主,分析jbd文件系统。

jbd 要解决什么问题

  • 或者说ext2的缺点在哪里,因为ext3与ext2的主要差别就在于ext3在ext2的基础上增加了日志功能。
  • 假设你正在运行一个Linux系统,运行一个程序,在一个ext2分区上不断地读写磁盘文件。突然断电了,或者系统崩溃了,你的心里肯定会咯噔一下:“磁盘分区没坏吧?文件还完整么?”告诉你一个不幸的消息,文件可能不完整了,文件可能已经损坏了,甚至该分区不能再被挂载了。也就是说,意外的系统崩溃,可能会使ext2文件系统处于一个不一致的状态。
  • 假设你的运气好一点,分区仍能被识别,但是重新挂载时,如果发现分区处于不一致状态,那么系统会自动调用fsck程序,尝试将文件系统恢复到一致的状态。那将是一个非常漫长的过程,并且随着磁盘容量的增大,花费的时间也越长,有时需要长达几个小时。这样会极大地影响系统的可用性。
  • 总之,jbd的主要目的不是减少系统崩溃的概率,而是系统正常运行时,尽量使文件系统处于一个一致的状态,以及系统崩溃后,尽可能减少使文件系统重新处于一致性状态的时间。通过减少维护时间,增加系统的可用性。

jbd是如何解决问题

  • 提到一致性,大家会想到数据库里面的事务的概念,事务有四个基本属性
  1. 原子性
    事务必须是原子工作单元;对于其数据修改,要么全部执行,要么全都不执行。
  2. 一致性
    事务在完成时,必须使所有的数据都保持一致状态。
  3. 隔离性
    由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务识别数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是第二个事务修改它之后的状态,事务不会识别中间状态的数据。
  4. 持久性
    事务完成之后,他对于系统的影响是永久性的。该修改即使出现系统故障也将一直保持。
  • 文件系统的开发者借用了数据库中事务的思想,将其应用于文件系统上,以期保证对文件系统操作的原子性、隔离性,尽量使文件系统处于一致性。
文件系统某些操作抽象成原子操作
  • 所谓原子操作,就是内部不再分割的操作,该操作要么完全完成,要么根本没有执行,不存在部分完成的状态。
  • 那么,什么样的操作可以看成对文件系统的原子操作呢?往一个磁盘文件中追加写入1MB字节可以看成一个原子操作么?这个操作其实比较大,因为要写1MB的数据,要为文件分配1024个磁盘块,同时还要分配若干个索引块,也会涉及到很多的磁盘块位图、块组块的读写,非常复杂,时间也会比较长,中间出问题的机会就比较多,所以不适宜看做一个原子操作。
  • 那么,什么样的操作可以看成对文件系统的原子操作呢?比如说为文件分配一个磁盘块,就看成一个原子操作就比较合适。分配一个磁盘块,可能需要修改一个inode块、一个磁盘块位图、最多三个间接索引块、块组块、超级块,一共最多7个磁盘块。将分配一个磁盘块看成一个原子操作,意味着上述修改7个磁盘块的操作要么都成功,要么都失败,不可能有第三种状态。
若干个原子操作组成一个事务
  • 实现日志文件系统时,可以将一个原子操作就作为一个事务来处理,但是这样实现的效率比较低。于是ext3将若干个原子操作组合成一个事务,对磁盘日志以事务为单位进行管理,以提高读写日志的效率。
在磁盘上单独划分一个日志空间
  • 日志,在这里指的是磁盘上存储事务数据的那个地方,即若干磁盘块。它可以以一个单独的文件形式存在,也可以由文件系统预留一个inode和一些磁盘块,也可以是单独的磁盘分区。总之就是磁盘上存储事务数据的那个地方。

  • 提到日志时,可能还有另外一种含义,就是它是一种机制,用于管理内存中的缓存区、事务、磁盘日志数据读写等等所有这一切,统称为日志。读者注意根据上下文进行区分。

将内存事务的数据写到日志中
  • 文件系统可以选择定期(每隔5秒,或用户指定的时间间隔)或者立即将内存中的事务数据写到磁盘日志上,以备发生系统崩溃后可以利用日志中的数据恢复,重新使文件系统保持一致的状态。
  • 这个间隔时间的选取,要注意性能的平衡。时间间隔越短,文件系统丢失数据的可能性就越少,一致性的时间点就越新,但是IO负担就越重,很可能就会影响系统的性能。反过来,时间间隔越大,文件系统丢失的数据可能就越多,一致性的时间点就越旧。但是IO负担就比较轻,不太会影响系统的性能。
从日志恢复数据
  • jbd的思想就是原来内核读写磁盘的逻辑保持不变,但是对于影响文件系统一致性的数据块(即元数据块,第四章会详细解释),及时地写到磁盘上的日志空间中去。这样,即使系统崩溃了,也能从日志中恢复数据,确保文件系统的一致性。如错误!未找到引用源。,其中绿色的箭头表示正常的磁盘读写,紫色的箭头表示由jbd将元数据块额外写一份到磁盘日志中,红色箭头表示恢复时,由jbd将日志中的数据写回磁盘的原始位置。
    在这里插入图片描述

概念介绍

buffer_head
  • buffer_head 是内核一个用于管理磁盘缓冲区的数据结构。根据局部性原理,磁盘上的数据进入内存后一般都是存放在磁盘缓冲区中,以备将来重复读写。所以说,一个buffer_head就会对应一个文件系统块,即对应一个磁盘块。(512字节大小块)
元数据块
  • 笼统地,可以将一个文件系统内的块分为两种,一种是对文件系统的一致性有重要影响的、用于文件系统管理的磁盘块,称之为元数据块,包括超级块、磁盘位图块、inode位图块、索引块、块组描述符块等等;另一种是存放文件数据的,称之为数据块。
  • 因为元数据块对文件系统的一致性有至关重要的影响,故jbd主要处理元数据块。当然,ext3的日志可以设置为三种模式,不同的模式中jbd处理的数据也是不一样的,这个下文会详述。ext3磁盘物理布局参考图表 2 ext3磁盘物理布局

在这里插入图片描述


handle
  • 提到的原子操作,jbd中用handle来表示。一个handle代表针对文件系统的一次原子操作。这个原子操作要么成功,要么失败,不会出现中间状态。在一个handle中,可能会修改若干个缓冲区,即buffer_head.
transaction
  • jbd为了提高效率,将若干个handle组成一个事务,用transaction来表示。对日志读写来说,都是以transaction为单位的。在处理日志数据时,transaction具有原子性,即恢复时,如果一个transaction是完整的,其中包含的数据就可用于文件系统的恢复,否则,忽略不完整的transaction。
journal
  • journal 在英文中有“日志”之意,在jbd中journal既是磁盘上日志空间的代表,又起到管理内存中为日志机制而创建的handle、transaction等数据结构的作用,可以说是整个体脂机制的代表。
commit
  • 所谓提交,就是把内存中transaction中的磁盘缓冲区中的数据写到磁盘的日志空间上。注意,jbd是将缓冲区中的数据另外写一份,写到日志上,原来的kernel将缓冲区写回磁盘的过程并没有改变。
  • 在内存中,transaction是可以有若干个的,而不是只有一个。transaction可分为三种,一种是已经commit到磁盘日志中的,它们正在进行checkpoint操作;第二种是正在将数据提交到日志的transaction;第三种是正在运行的transaction。正在运行的transaction管理随后发生的handle,并在适当时间commit到磁盘日志中。注意正在运行的transaction最多只可能有一个,也可能没有,如果没有,则handle提出请求时,则会按需要创建一个正在运行的transaction。
checkpoint
  • 当一个transaction已经commit,那么,是不是在内存中它就没有用了呢?好像是这样,因为其中的数据已经写到磁盘日志中了。但是实际上不是这样的。主要原因是磁盘日志是个有限的空间,比如说100MB,如果一直提交transaction,很快就会占满,所以日志空间必须复用。
  • 其实与日志提交的同时,kernel也在按照自己以前的方式将数据写回磁盘。试想,如果一个transaction中包含的所有磁盘缓冲区的数据都已写回到磁盘的原来的位置上(不是日志中,而是在磁盘的原来的物理块上),那么,该transaction就没有用了,可以被删除了,该transaction在磁盘日志中的空间就可以被回收,进而重复利用了。
revoke
  • 假设有一个缓冲区,对应着一个磁盘块,内核多次修改该缓冲区,于是磁盘日志中就会有该缓冲区的若干个版本的数据。假设此时要从文件中删除该磁盘块,那么,一旦包含该删除操作的transaction提交,那么,再恢复时,已经存放在磁盘日志中的该磁盘块的若干个版本的数据就不必再恢复了,因为到头来还是要删除的。revoke就是这样一种加速恢复速度的方法。当本transaction包含删除磁盘块操作时,就会在磁盘日志中写一个revoke块,该块中包含<被revoked的块号blocknr,提交的transaction的ID>,表示恢复时,凡是transaction ID小于等于ID的所有写磁盘块blocknr的操作都可以取消了,不必进行了。
recover
  • 加入日志机制后,一旦系统崩溃,重新挂载分区时,就会检查该分区上的日志是否需要恢复。如果需要,则依次将日志空间的数据写回磁盘原始位置,则文件系统又重新处于一致状态了。
kjournald
  • 日志的提交操作是由一个内核线程实现的,该线程称为kjournald。该内核线程平时一直在睡眠,直到有进程主动唤醒它,或者是定时器时间到了(一般为每隔5秒)。被唤醒后它就进行事务的提交操作。

数据结构介绍

  • 以下数据结构的定义在include/linux/jbd.h和include/linux/journal_head.h中。
handle_t 表示一个原子操作
struct handle_s
{
	transaction_t	*h_transaction;	// 本原子操作属于哪个transaction
	int			h_buffer_credits;	// 本原子操作的额度,即可以包含的磁盘块数
	int			h_ref;			// 引用计数
	int			h_err;

	unsigned int	h_sync:		1;	/* sync-on-close */	
	unsigned int	h_jdata:	1;	/* force data journaling */
	unsigned int	h_aborted:	1;	/* fatal error on handle */
	// 以上是三个标志
// h_sync表示同步,意思是处理完该原子操作后,立即将所属的transaction提交。
// 其余两个好像没有用到。
};
typedef struct handle_s handle_t;	/* Atomic operation type */

  • 注意jbd中数据结构定义一般都采用这种方式,即定义结构时用XXX_s,然后用typedef定义XXX_t。下面不再特别指出了。
  • 乍看这个表示原子操作的结构有些奇怪,它怎么不包含缓冲区呢?其实handle_t的主要目的是顺着它能找到对应的transaction。如果你想把一些缓冲区纳入日志管理,需要另外的步骤。
transaction_t 表示一个事务
struct transaction_s
{
	journal_t		*t_journal;	// 指向所属的jounal
	tid_t			t_tid;		// 本事务的序号

	/*
	 * Transaction's current state
	 * [no locking - only kjournald alters this]
	 * [j_list_lock] guards transition of a transaction into T_FINISHED
	 * state and subsequent call of __journal_drop_transaction()
	 * FIXME: needs barriers
	 * KLUDGE: [use j_state_lock]
	 */
	enum {
		T_RUNNING,
		T_LOCKED,
		T_FLUSH,
		T_COMMIT,
		T_COMMIT_RECORD,
		T_FINISHED
	} t_state;  // 事务的状态

	unsigned int		t_log_start;
	// log中本transaction_t从日志中哪个块开始
	int			t_nr_buffers;
	// 本transaction_t中缓冲区的个数
	
	struct journal_head	*t_reserved_list;
// 被本transaction保留,但是并未修改的缓冲区组成的双向循环队列。
	struct journal_head	*t_locked_list;
	// 由提交时所有正在被写出的、被锁住的数据缓冲区组成的双向循环链表。
	struct journal_head	*t_buffers;
	// 元数据块缓冲区链表
	// 这里面可都是宝贵的元数据啊,对文件系统的一致性至关重要!
	struct journal_head	*t_sync_datalist;
	// 本transaction_t被提交之前,
	// 需要被刷新到磁盘上的数据块(非元数据块)组成的双向链表。
	// 因为在ordered模式,我们要保证先刷新数据块,再刷新元数据块。
	struct journal_head	*t_forget;
	// 被遗忘的缓冲区的链表。
	// 当本transaction提交后,可以un-checkpointed的缓冲区。
	// 这种情况是这样:
	// 一个缓冲区正在被checkpointed,但是后来又调用journal_forget(),
	// 此时以前的checkpointed项就没有用了。
// 此时需要在这里记录下来这个缓冲区,
	// 然后un-checkpointed这个缓冲区。
	struct journal_head	*t_checkpoint_list;
	// 本transaction_t可被checkpointed之前,
	// 需要被刷新到磁盘上的所有缓冲区组成的双向链表。
	// 这里面应该只包括元数据缓冲区。
	struct journal_head	*t_checkpoint_io_list;
	// checkpointing时,已提交进行IO操作的所有缓冲区组成的链表。
	struct journal_head	*t_iobuf_list;
	// 进行临时性IO的元数据缓冲区的双向链表。
	struct journal_head	*t_shadow_list;
	// 被日志IO复制(拷贝)过的元数据缓冲区组成的双向循环链表。
	// t_iobuf_list 上的缓冲区始终与t_shadow_list上的缓冲区一一对应。
	// 实际上,当一个元数据块缓冲区要被写到日志中时,数据会被复制一份,
	// 放到新的缓冲区中。
	// 新缓冲区会进入t_iobuf_list队列,
	// 而原来的缓冲区会进入t_shadow_list队列。
	struct journal_head	*t_log_list;
	// 正在写入log的起控制作用的缓冲区组成的链表。
	spinlock_t		t_handle_lock;
	// 保护handle的锁
	int			t_updates;
	// 与本transaction相关联的外部更新的次数
	// 实际上是正在使用本transaction的handle的数量
	// 当journal_start时,t_updates++
	// 当journal_stop时,t_updates--
	// t_updates == 0,表示没有handle正在使用该transaction,
	// 此时transaction处于一种可提交状态!
	int			t_outstanding_credits;
	// 本事务预留的额度
	transaction_t		*t_cpnext, *t_cpprev;	
	// 用于在checkpoint队列上组成链表
	unsigned long		t_expires;
	ktime_t			t_start_time;
	int t_handle_count;	
	// 本transaction_t有多少个handle_t
	unsigned int t_synchronous_commit:1;
	// 本transaction已被逼迫了,有进程在等待它的完成。
};

journal_t
  • journal_t是整个日志机制的代表,既管理者内存中的各种日志相关的数据结构,又管理着磁盘上的日志空间。
struct journal_s
{
	unsigned long		j_flags;	// journal的状态
	int			j_errno;			
	struct buffer_head	*j_sb_buffer;	// 指向日志超级块缓冲区
	journal_superblock_t	*j_superblock;
	int			j_format_version;
	spinlock_t		j_state_lock;
	int			j_barrier_count;
	// 有多少个进程正在等待创建一个barrier lock
	// 这个变量是由j_state_lock来保护的。
	struct mutex		j_barrier;
	// 互斥锁
	transaction_t		*j_running_transaction;
	// 指向正在运行的transaction
	transaction_t		*j_committing_transaction;
	// 指向正在提交的transaction
	transaction_t		*j_checkpoint_transactions;
	// 仍在等待进行checkpoint操作的所有事务组成的循环队列
	// 一旦一个transaction执行checkpoint完成,则从此队列删除。
	// 第一项是最旧的transaction,以此类推。
	wait_queue_head_t	j_wait_transaction_locked;
	// 等待一个已上锁的transaction_t开始提交,
	// 或者一个barrier 锁被释放。
	wait_queue_head_t	j_wait_logspace;
	// 等待checkpointing完成以释放日志空间的等待队列。
	wait_queue_head_t	j_wait_done_commit;
	//等待提交完成的等待队列
	wait_queue_head_t	j_wait_checkpoint;

	wait_queue_head_t	j_wait_commit;
	// 等待进行提交的的等待队列
	wait_queue_head_t	j_wait_updates;
	// 等待handle完成的等待队列
	struct mutex		j_checkpoint_mutex;
	// 保护checkpoint队列的互斥锁。
	unsigned int		j_head;
	// journal中第一个未使用的块
	unsigned int		j_tail;
	// journal中仍在使用的最旧的块号
	// 这个值为0,则整个journal是空的。
	unsigned int		j_free;

	unsigned int		j_first;
	unsigned int		j_last;
	// 这两个是文件系统格式化以后就保存到超级块中的不变的量。
	// 日志块的范围[j_first, j_last)
	// 来自于journal_superblock_t

	struct block_device	*j_dev;
	int			j_blocksize;
	unsigned int		j_blk_offset;
	// 本journal相对与设备的块偏移量
	struct block_device	*j_fs_dev;

	unsigned int		j_maxlen;
	// 磁盘上journal的最大块数

	spinlock_t		j_list_lock;

	struct inode		*j_inode;

	tid_t			j_tail_sequence;
	// 日志中最旧的事务的序号

	tid_t			j_transaction_sequence;
	// 下一个授权的事务的顺序号

	tid_t			j_commit_sequence;
	// 最近提交的transaction的顺序号

	tid_t			j_commit_request;
	// 最近相申请提交的transaction的编号。
	// 如果一个transaction想提交,则把自己的编号赋值给j_commit_request,
	// 然后kjournald会择机进行处理。

	__u8			j_uuid[16];

	struct task_struct	*j_task;
	// 本journal指向的内核线程

	int			j_max_transaction_buffers;
	// 一次提交允许的最多的元数据缓冲区块数

	unsigned long		j_commit_interval;

	struct timer_list	j_commit_timer;	
	// 用于唤醒提交日志的内核线程的定时器

	spinlock_t		j_revoke_lock;
	// 保护revoke 哈希表
	struct jbd_revoke_table_s *j_revoke;
	// 指向journal正在使用的revoke hash table
	struct jbd_revoke_table_s *j_revoke_table[2];

	struct buffer_head	**j_wbuf;
	// 指向描述符块页面

	int			j_wbufsize;
	// 一个描述符块中可以记录的块数

	pid_t			j_last_sync_writer;

	u64			j_average_commit_time;

	void *j_private;	
	// 指向ext3的superblock
};

journal_superblock_t
  • 日志超级块在内存中的表现。
/*
 * The journal superblock.  All fields are in big-endian byte order.
 */
typedef struct journal_superblock_s
{
	journal_header_t s_header;	// 用于表示本块是一个超级块
	__be32	s_blocksize;		/* journal device blocksize */
							// journal所在设备的块大小
	__be32	s_maxlen;		/* total blocks in journal file */
							// 日志的长度,即包含多少个块
	__be32	s_first;		/* first block of log information */
							// 日志中的开始块号,
							// 注意日志相当于一个文件,
							// 这里提到的开始块号是文件中的逻辑块号,
							// 而不是磁盘的物理块号。
							// 初始化时置为1,因为超级块本身占用了逻辑块0。
							// 注意s_maxlen和s_first是在格式化时确定的,
							// 以后就不会改变了。
	__be32	s_sequence;		/* first commit ID expected in log */
							// 日志中第一个期待的commit ID
							// 就是指该值应该是日志中最旧的一个事务的ID
	__be32	s_start;		/* blocknr of start of log */
							// 日志开始的块号
							// s_start为0表示不需要恢复
							// 因为日志空间需要重复使用,相当于一个环形结构,
							// s_start表示本次有效日志块的起点
	__be32	s_errno;

// 注意:下列各域只有在superblock v2中才有效
	/* Remaining fields are only valid in a version-2 superblock */
	__be32	s_feature_compat;	/* compatible feature set */
	__be32	s_feature_incompat;	/* incompatible feature set */
	__be32	s_feature_ro_compat;	/* readonly-compatible feature set */
	__u8	s_uuid[16];		/* 128-bit uuid for journal */
	__be32	s_nr_users;		/* Nr of filesystems sharing log */
	__be32	s_dynsuper;		/* Blocknr of dynamic superblock copy*/
	__be32	s_max_transaction;	/* Limit of journal blocks per trans.*/
	__be32	s_max_trans_data;	/* Limit of data blocks per trans. */
	__u32	s_padding[44];
	__u8	s_users[16*48];		/* ids of all fs'es sharing the log */
} journal_superblock_t;

journal_head
  • 一个buffer_head对应一个磁盘块,而一个journal_head对应一个buffer_head。日志通过journal_head对缓冲区进行管理。
struct journal_head {
	struct buffer_head *b_bh;
	int b_jcount;
	unsigned b_jlist;
	// 本journal_head在transaction_t的哪个链表上
	unsigned b_modified;
	// 标志该缓冲区是否以被当前正在运行的transaction修改过
	char *b_frozen_data;
    // 当jbd遇到需要转义的块时,
	// 将buffer_head指向的缓冲区数据拷贝出来,冻结起来,供写入日志使用。
	char *b_committed_data;
	// 目的是防止重新写未提交的删除操作
	// 含有未提交的删除信息的元数据块(磁盘块位图)的一份拷贝,
	// 因此随后的分配操作可以避免覆盖未提交的删除信息。 
	// 也就是说随后的分配操作使用的时b_committed_data中的数据,
	// 因此不会影响到写入日志中的数据。
	transaction_t *b_transaction;
	// 指向所属的transaction
	transaction_t *b_next_transaction;
	// 当有一个transaction正在提交本缓冲区,
	// 但是另一个transaction要修改本元数据缓冲区的数据,
	// 该指针就指向第二个缓冲区。

	/*
	 * Doubly-linked list of buffers on a transaction's data, metadata or
	 * forget queue. [t_list_lock] [jbd_lock_bh_state()]
	 */
	struct journal_head *b_tnext, *b_tprev;

	transaction_t *b_cp_transaction;
	// 指向checkpoint本缓冲区的transaction。
	// 只有脏的缓冲区可以被checkpointed。

	struct journal_head *b_cpnext, *b_cpprev;
	// 在旧的transaction_t被checkpointed之前必须被刷新的缓冲区双向链表。

	/* Trigger type */
	struct jbd2_buffer_trigger_type *b_triggers;
	struct jbd2_buffer_trigger_type *b_frozen_triggers;
};

journal_head_t
  • 每个块的开头,都有一个起描述作用的结构,定义如下:
/*
 * Standard header for all descriptor blocks:
 */
typedef struct journal_header_s
{
	__be32		h_magic;
	__be32		h_blocktype;
	__be32		h_sequence;
} journal_header_t;
  • 其中,h_magic是一个幻数,如果是一个日志块的描述块,则为JFS_MAGIC_NUMBER,
    #define JFS_MAGIC_NUMBER 0xc03b3998U,否则该块就不是一个日志描述块。
    h_blocktype表示该块的类型,即上述五种块之一。
    h_sequence表示本描述块对应的transaction的序号。

三种日志模式

  • 日志机制的基本原理就是选择与文件系统一致性相关的缓冲区,在适当的时机“偷偷地”写入日志。但是选择什么样的缓冲区,以及在什么时间写入日志,就是所谓的日志模式。日志模式与系统性能息息相关,是个值得仔细研究、平衡的大问题。
  • ext3支持三种日志模式,划分的依据是选择元数据块还是数据块写入日志,以及何时写入日志。
  1. 日志(journal)
    文件系统所有数据块和元数据块的改变都记入日志。 这种模式减少了丢失每个文件所作修改的机会,但是它需要很多额外的磁盘访问。例如,当一个新文件被创建时,它的所有数据块都必须复制一份作为日志记录。这是最安全和最慢的ext3日志模式。
  2. 预定(ordered)
    只对文件系统元数据块的改变才记入日志,这样可以确保文件系统的一致性,但是不能保证文件内容的一致性。然而,ext3文件系统把元数据块和相关的数据块进行分组,以便在元数据块写入日志之前写入数据块。这样,就可以减少文件内数据损坏的机会;例如,确保增大文件的任何写访问都完全受日志的保护。这是缺省的ext3 日志模式。
  3. 写回(writeback)
    只有对文件系统元数据的改变才记入日志,不对数据块进行任何特殊处理。这是在其他日志文件系统发现的方法,也是最快的模式。
    在挂载ext3文件系统时,可通过data=journal等修改日志模式。如果不做特殊说明,下文中将默认介绍预定(ordered)模式,这是ext3默认的日志模式,也是实现最复杂的一种模式。

jbd基本操作

  • 日志机制的核心是处理磁盘块缓冲区。它是在不影响系统原有的处理缓冲区的逻辑的基础上进行了。
  • 日志的基本操作包括对原子操作的操作,对缓冲区的操作,对日志空间的操作,对日志提交的操作等等。
journal_start
  • journal_start 的主要作用是取得一个原子操作描述符handle_t,如果当前进程已经有一个,则直接返回,否则,需要新创建一个。
  • 同样,该handle_t也必须与一个正在运行的transaction相关联,如果没有正在运行的transaction,则创建一个新的transaction,并将其设置为正在运行的。
  • 参数nblocks是向日志申请nblocks个缓冲区的空间,也表明该原子操作预期将修改 nblocks个缓冲区。
271 handle_t *journal_start(journal_t *journal, int nblocks)
 272 {
 273     handle_t *handle = journal_current_handle();
 274     int err;
279     if (handle) {
			 // 如果当前进程已经有handle_t,则直接返回。
 280         J_ASSERT(handle->h_transaction->t_journal == journal);
 281         handle->h_ref++;
 282         return handle;
 283     }
 284 
		 // 否则,创建一个新的handle_t
 285     handle = new_handle(nblocks);
289     current->journal_info = handle;
          // start_this_handle的主要作用是将该handle与当前正在运行的transaction相关联,
         // 如果没有正在运行的transaction,则创建一个新的,并将其设置为正在运行的。
291     err = start_this_handle(journal, handle);
299     return handle;
 300 }

journal_stop
  • 该函数的主要作用是将该handle与transaction断开链接,调整所属transaction的额度。如果该原子操作时同步的,则设置事务的t_synchronous_commit标志。在事务提交时,会根据该标志决定缓冲区的写方式。
1363 int journal_stop(handle_t *handle)
1364 {
1365     transaction_t *transaction = handle->h_transaction;
1366     journal_t *journal = transaction->t_journal;
		 
// 如果该原子操作时同步的,则设置相应transaction的t_synchronous_commit标志,
// 则在提交事务时同步写出缓冲区。
1436     if (handle->h_sync)
1437         transaction->t_synchronous_commit = 1;

1438     current->journal_info = NULL;
1441     transaction->t_outstanding_credits -= handle->h_buffer_credits;
1442     transaction->t_updates--;
1455     if (handle->h_sync ||
1456             transaction->t_outstanding_credits >
1457                 journal->j_max_transaction_buffers ||
1458             time_after_eq(jiffies, transaction->t_expires)) {
1462         tid_t tid = transaction->t_tid;
			 
// 这里的提交,只是设置journal中的j_commit_request,
// 并唤醒等待的内核线程。
1468         __log_start_commit(journal, transaction->t_tid);

// 如果该原子操作时是同步的,则我们等待事务提交完成
1475         if (handle->h_
  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值