searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Linux writeback刷脏数据主要参数探究

2024-07-24 09:43:51
157
0

Linux常用的回写脏数据的参数可通过sysctl -a | grep dirty来查看:

1.png

其中比较重要的参数有dirty_background_ratio, dirty_expire_centisecs, dirty_ratio, dirty_writeback_centisecs, dirtytime_expire_seconds。这些参数是如何影响回写脏数据的,下面我们来深入探寻一下。在此之前需要回顾一下writeback的刷盘流程。

每个块设备在初始化的时候都会分配一个request_queue, request_queue里有一个成员backing_dev_info(struct backing_dev_info类型)用于管理该块设备的刷盘回写。backing_dev_info里有个重要的变量wb(struct bdi_writeback类型),负责将页缓存的脏页异步刷新到对应的块设备上。

块设备格式化文件系统执行挂载时,会将文件系统superblock的s_bdi指向磁盘的bdi,以ext4文件系统为例,具体流程如下:

ext4_mount
    mount_bdev
        sget(fs_type, test_bdev_super, set_bdev_super, flags | SB_NOSEC, bdev);
            sget_userns
                err = set(s, data);		// 调用set_bdev_super设置文件系统超级块的superblock

当ext4文件系统有脏数据时,会调用__mark_inode_dirty对文件inode置脏,调用过程参见如下堆栈:

2.png

__mark_inode_dirty 关键流程:

  1. 调用具体文件系统的dirty_inode函数置脏inode
sb->s_op->dirty_inode(inode, flags);
  1. 根据dirty类型将inode添加到wb的b_dirty或b_dirty_time链表里。
  2. 以delay timeout方式唤醒刷盘回写线程。
queue_delayed_work(bdi_wq, &wb->dwork, timeout);

wb_workfn 回写线程:

if (likely(!current_is_workqueue_rescuer() ||
	   !test_bit(WB_registered, &wb->state))) {
	// 通用路径
	do {
		pages_written = wb_do_writeback(wb);
		trace_writeback_pages_written(pages_written);
	} while (!list_empty(&wb->work_list));
} else {
	// bdi_wq没有足够的worker
	pages_written = writeback_inodes_wb(wb, 1024,
						WB_REASON_FORKER_THREAD);
	trace_writeback_pages_written(pages_written);
}

if (!list_empty(&wb->work_list))
	wb_wakeup(wb);	// 如果在回写过程中,加入了新的worker, 立即唤醒
else if (wb_has_dirty_io(wb) && dirty_writeback_interval)
	wb_wakeup_delayed(wb);	// 延迟唤醒

wb_do_writeback 回写流程:

  1. 依次遍历wb中所有的work执行回写;
while ((work = get_next_work_item(wb)) != NULL) {
	wrote += wb_writeback(wb, work);
}
  1. 周期性回写历史数据;
// 如果设置了dirty_writeback_interval参数,当前时间超过了上次记录的回写时间+dirty_writeback_interval阈值,则会启动回写。
wrote += wb_check_old_data_flush(wb);
// 如果系统当前脏页数量超过后台回写阈值,则启动脏页回写。
wrote += wb_check_background_flush(wb);

wb_writeback 流程参见下图:

3.png

wb_writeback传入参数work(struct wb_writeback_work类型)的nr_pages控制了本次回写的脏页数,脏页数会根据写入的数据量实时修改。流程里会根据work的类型计算本次回写的时间阈值,即在时间阈值之内的inode才会启动回写。wb_writeback里的queue_io会遍历wb->b_dirty/wb->b_dirty_time中记录的置脏inode链表,挑选时间阈值以内的inode放入wb->b_io链表。wb->b_io链表为本次回写的inode链表,里面是按照文件系统来排序(注:一个块设备可能存在多个分区,每个分区可格式化为一个文件系统)。接下来会对每个文件系统启动回写。

每个文件系统回写核心流程writeback_sb_inodes:

writeback_sb_inodes

	while (!list_empty(&wb->b_io)) {

		wbc_attach_and_unlock_inode

		__writeback_single_inode

			do_writepages(mapping, wbc);

				mapping->a_ops->writepages(mapping, wbc);

					...

		wbc_detach_inode

		requeue_inode	// 这里b_io出链

			/* The inode is clean. Remove from writeback lists. */

			inode_io_list_del_locked(inode, wb);

	}

接下来我们来分析刷写脏数据的主要参数:

vm.dirty_expire_centisecs​:关联内核的dirty_expire_interval变量(单位:10ms)

wb_writeback:

// for_kupdate是周期性回写类型
if (work->for_kupdate) {
	dirtied_before = jiffies -
		msecs_to_jiffies(dirty_expire_interval * 10);
}

可见该参数是周期性回写时,每个inode的过期时间,默认是30s。即每个inode置脏后,最多30s就会写入到磁盘。

vm.dirty_writeback_centisecs​: 关联内核的dirty_writeback_interval参数(单位10ms)。

wb_wakeup_delayed
	timeout = msecs_to_jiffies(dirty_writeback_interval * 10);
	...
	queue_delayed_work(bdi_wq, &wb->dwork, timeout);

该参数启动回写线程delay的时间,默认为5s。即唤醒启动回写时,会开启一个定时器,在5s后调用回写线程。

vm.dirtytime_expire_seconds​: 关联内核的dirtytime_expire_interval变量(单位:s)

queue_io
	if (!work->for_sync)
		time_expire_jif = jiffies - dirtytime_expire_interval * HZ;
	moved += move_expired_inodes(&wb->b_dirty_time, &wb->b_io, EXPIRE_DIRTY_ATIME, time_expire_jif);

该参数为非透写(非direct io下),脏页过期时间。(注:inode涉及时间的更改,会放入wb->b_dirty_time链表里)

vm.dirty_ratio​: 关联内核的vm_dirty_ratio变量,指定系统中脏页占可用内存的比例,默认30%,与vm.dirty_bytes互斥。

global_dirty_limits
	domain_dirty_limits
		unsigned long bytes = vm_dirty_bytes;
		unsigned long ratio = (vm_dirty_ratio * PAGE_SIZE) / 100;
		...
		// 如果指定了dirty_bytes,会根据dirty_bytes算ratio
		if (bytes)
			ratio = min(DIV_ROUND_UP(bytes, global_avail), PAGE_SIZE);

vm.dirty_background_ratio​: 关联内核的dirty_background_ratio变量,指定后台回写进程认为脏页最多占可用内存的比例。达到该比例后,后台回写进程开始将脏页写回磁盘。默认10%,与vm.dirty_backgroup_bytes互斥。

global_dirty_limits
	domain_dirty_limits
		unsigned long bg_bytes = dirty_background_bytes;
		unsigned long bg_ratio = (dirty_background_ratio * PAGE_SIZE) / 100;
		...
		// 如果指定了dirty_backgroup_bytes, 会根据该值重算bg_ratio
		if (bg_bytes)
			bg_ratio = min(DIV_ROUND_UP(bg_bytes, global_avail), PAGE_SIZE);
0条评论
0 / 1000
c****9
2文章数
0粉丝数
c****9
2 文章 | 0 粉丝
c****9
2文章数
0粉丝数
c****9
2 文章 | 0 粉丝
原创

Linux writeback刷脏数据主要参数探究

2024-07-24 09:43:51
157
0

Linux常用的回写脏数据的参数可通过sysctl -a | grep dirty来查看:

1.png

其中比较重要的参数有dirty_background_ratio, dirty_expire_centisecs, dirty_ratio, dirty_writeback_centisecs, dirtytime_expire_seconds。这些参数是如何影响回写脏数据的,下面我们来深入探寻一下。在此之前需要回顾一下writeback的刷盘流程。

每个块设备在初始化的时候都会分配一个request_queue, request_queue里有一个成员backing_dev_info(struct backing_dev_info类型)用于管理该块设备的刷盘回写。backing_dev_info里有个重要的变量wb(struct bdi_writeback类型),负责将页缓存的脏页异步刷新到对应的块设备上。

块设备格式化文件系统执行挂载时,会将文件系统superblock的s_bdi指向磁盘的bdi,以ext4文件系统为例,具体流程如下:

ext4_mount
    mount_bdev
        sget(fs_type, test_bdev_super, set_bdev_super, flags | SB_NOSEC, bdev);
            sget_userns
                err = set(s, data);		// 调用set_bdev_super设置文件系统超级块的superblock

当ext4文件系统有脏数据时,会调用__mark_inode_dirty对文件inode置脏,调用过程参见如下堆栈:

2.png

__mark_inode_dirty 关键流程:

  1. 调用具体文件系统的dirty_inode函数置脏inode
sb->s_op->dirty_inode(inode, flags);
  1. 根据dirty类型将inode添加到wb的b_dirty或b_dirty_time链表里。
  2. 以delay timeout方式唤醒刷盘回写线程。
queue_delayed_work(bdi_wq, &wb->dwork, timeout);

wb_workfn 回写线程:

if (likely(!current_is_workqueue_rescuer() ||
	   !test_bit(WB_registered, &wb->state))) {
	// 通用路径
	do {
		pages_written = wb_do_writeback(wb);
		trace_writeback_pages_written(pages_written);
	} while (!list_empty(&wb->work_list));
} else {
	// bdi_wq没有足够的worker
	pages_written = writeback_inodes_wb(wb, 1024,
						WB_REASON_FORKER_THREAD);
	trace_writeback_pages_written(pages_written);
}

if (!list_empty(&wb->work_list))
	wb_wakeup(wb);	// 如果在回写过程中,加入了新的worker, 立即唤醒
else if (wb_has_dirty_io(wb) && dirty_writeback_interval)
	wb_wakeup_delayed(wb);	// 延迟唤醒

wb_do_writeback 回写流程:

  1. 依次遍历wb中所有的work执行回写;
while ((work = get_next_work_item(wb)) != NULL) {
	wrote += wb_writeback(wb, work);
}
  1. 周期性回写历史数据;
// 如果设置了dirty_writeback_interval参数,当前时间超过了上次记录的回写时间+dirty_writeback_interval阈值,则会启动回写。
wrote += wb_check_old_data_flush(wb);
// 如果系统当前脏页数量超过后台回写阈值,则启动脏页回写。
wrote += wb_check_background_flush(wb);

wb_writeback 流程参见下图:

3.png

wb_writeback传入参数work(struct wb_writeback_work类型)的nr_pages控制了本次回写的脏页数,脏页数会根据写入的数据量实时修改。流程里会根据work的类型计算本次回写的时间阈值,即在时间阈值之内的inode才会启动回写。wb_writeback里的queue_io会遍历wb->b_dirty/wb->b_dirty_time中记录的置脏inode链表,挑选时间阈值以内的inode放入wb->b_io链表。wb->b_io链表为本次回写的inode链表,里面是按照文件系统来排序(注:一个块设备可能存在多个分区,每个分区可格式化为一个文件系统)。接下来会对每个文件系统启动回写。

每个文件系统回写核心流程writeback_sb_inodes:

writeback_sb_inodes

	while (!list_empty(&wb->b_io)) {

		wbc_attach_and_unlock_inode

		__writeback_single_inode

			do_writepages(mapping, wbc);

				mapping->a_ops->writepages(mapping, wbc);

					...

		wbc_detach_inode

		requeue_inode	// 这里b_io出链

			/* The inode is clean. Remove from writeback lists. */

			inode_io_list_del_locked(inode, wb);

	}

接下来我们来分析刷写脏数据的主要参数:

vm.dirty_expire_centisecs​:关联内核的dirty_expire_interval变量(单位:10ms)

wb_writeback:

// for_kupdate是周期性回写类型
if (work->for_kupdate) {
	dirtied_before = jiffies -
		msecs_to_jiffies(dirty_expire_interval * 10);
}

可见该参数是周期性回写时,每个inode的过期时间,默认是30s。即每个inode置脏后,最多30s就会写入到磁盘。

vm.dirty_writeback_centisecs​: 关联内核的dirty_writeback_interval参数(单位10ms)。

wb_wakeup_delayed
	timeout = msecs_to_jiffies(dirty_writeback_interval * 10);
	...
	queue_delayed_work(bdi_wq, &wb->dwork, timeout);

该参数启动回写线程delay的时间,默认为5s。即唤醒启动回写时,会开启一个定时器,在5s后调用回写线程。

vm.dirtytime_expire_seconds​: 关联内核的dirtytime_expire_interval变量(单位:s)

queue_io
	if (!work->for_sync)
		time_expire_jif = jiffies - dirtytime_expire_interval * HZ;
	moved += move_expired_inodes(&wb->b_dirty_time, &wb->b_io, EXPIRE_DIRTY_ATIME, time_expire_jif);

该参数为非透写(非direct io下),脏页过期时间。(注:inode涉及时间的更改,会放入wb->b_dirty_time链表里)

vm.dirty_ratio​: 关联内核的vm_dirty_ratio变量,指定系统中脏页占可用内存的比例,默认30%,与vm.dirty_bytes互斥。

global_dirty_limits
	domain_dirty_limits
		unsigned long bytes = vm_dirty_bytes;
		unsigned long ratio = (vm_dirty_ratio * PAGE_SIZE) / 100;
		...
		// 如果指定了dirty_bytes,会根据dirty_bytes算ratio
		if (bytes)
			ratio = min(DIV_ROUND_UP(bytes, global_avail), PAGE_SIZE);

vm.dirty_background_ratio​: 关联内核的dirty_background_ratio变量,指定后台回写进程认为脏页最多占可用内存的比例。达到该比例后,后台回写进程开始将脏页写回磁盘。默认10%,与vm.dirty_backgroup_bytes互斥。

global_dirty_limits
	domain_dirty_limits
		unsigned long bg_bytes = dirty_background_bytes;
		unsigned long bg_ratio = (dirty_background_ratio * PAGE_SIZE) / 100;
		...
		// 如果指定了dirty_backgroup_bytes, 会根据该值重算bg_ratio
		if (bg_bytes)
			bg_ratio = min(DIV_ROUND_UP(bg_bytes, global_avail), PAGE_SIZE);
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0