Linux内核sync流程

news/2025/3/4 17:27:52/

进程写文件时, 文件并没有真正写到存储设备, 而是写到了page cache中。 文件系统会定期把脏页写到存储设备, 进程也可以调用sync 这样的调用把脏页写回存储设设备。

数据结构

backing_dev_info

要理解这个结构体, 得从它需要解决什么问题说起。早期版本的Linux采用pdflush线程将“脏“页面回写到磁盘。 pdflush 线程时”通用“的, 也就是系统中所有的请求队列公用这些有限的资源, 这样如果系统中存在多个队列, 可能就会存在资源上的竞争, 导致IO速率的下降。

我们希望每个请求队列都有一个 flush 线程, 于是就有了backing_dev_info 。每个请求队列通过这个结构体管理自己的回写线程。 其主要数据结构如下:

include/linux/backing-dev-defs.h
struct backing_dev_info {
........struct list_head bdi_list;
.......struct bdi_writeback wb;  /* the root writeback info for this bdi */
......
};

bdi_list : 用来把所有backing_dev_info 链接到全局链表bdi_list

wb: 用于控制回写行为的核心成员, 后面详细介绍

bdi_writeback

bdi_writeback 的定义如下:

struct bdi_writeback {
...struct list_head b_dirty;   /* dirty inodes */struct list_head b_io;      /* parked for writeback */
...struct delayed_work dwork;  /* work item used for writeback */struct list_head work_list;
...
};

b_dirty: 用来存放文件系统中所有的脏页

b_io: 用来存放准备写回到存储设备的inode

dwork : 负责把脏页写回到存储设备。 对应的函数时wb_workfn

work_list:每一个回写任务为一个work, 会被链接到这个链表上来

mark inode dirty

当inode的属性或者数据改变时, 需要标记该inode dirty, 把其放到bdi_writeback 的b_dirty 链表中。 其主要函数是__mark_inode_dirty

fs/fs-writeback.c
void __mark_inode_dirty(struct inode *inode, int flags)
{struct super_block *sb = inode->i_sb;.../** Paired with smp_mb() in __writeback_single_inode() for the* following lockless i_state test.  See there for details.*/smp_mb();if (((inode->i_state & flags) == flags) ||(dirtytime && (inode->i_state & I_DIRTY_INODE)))   /*       1      */return;if ((inode->i_state & flags) != flags) {const int was_dirty = inode->i_state & I_DIRTY;inode_attach_wb(inode, NULL);....inode->i_state |= flags;                        /*       2      */...../** If the inode was already on b_dirty/b_io/b_more_io, don't* reposition it (that would break b_dirty time-ordering).*/if (!was_dirty) {struct bdi_writeback *wb;struct list_head *dirty_list;bool wakeup_bdi = false;wb = locked_inode_to_wb_and_lock_list(inode);WARN((wb->bdi->capabilities & BDI_CAP_WRITEBACK) &&!test_bit(WB_registered, &wb->state),"bdi-%s not registered\n", bdi_dev_name(wb->bdi));inode->dirtied_when = jiffies;if (dirtytime)inode->dirtied_time_when = jiffies;if (inode->i_state & I_DIRTY)dirty_list = &wb->b_dirty;elsedirty_list = &wb->b_dirty_time;wakeup_bdi = inode_io_list_move_locked(inode, wb,dirty_list);             /*       3      */spin_unlock(&wb->list_lock);trace_writeback_dirty_inode_enqueue(inode);/** If this is the first dirty inode for this bdi,* we have to wake-up the corresponding bdi thread* to make sure background write-back happens* later.*/if (wakeup_bdi &&(wb->bdi->capabilities & BDI_CAP_WRITEBACK))wb_wakeup_delayed(wb);               /*       4      */return;}}
out_unlock_inode:spin_unlock(&inode->i_lock);
}

(1) 判断flags 是否已经设置, 如果是, 直接放回。 这里的flags是I_DIRTY或者是I_DIRTY_SYNC

(2) 设置inode 的i_state

(3) 将inode 链接到bdi_writeback 对应的链表里面

(4) 如果是b_dirty 链表为空, 第三步wakeup_bdi 会返回true, 这时候会wakeup bdi_writeback 中的delay work, 开始执行回写操作。

周期回写

系统会定时去回写dirty page。这一节描述该具体流程

回写相关的入口都在 wb_workfn , mark inode dirty 或者sync触发之后, 都会调用到这里来。

fs/fs-writeback.c
void wb_workfn(struct work_struct *work)
{struct bdi_writeback *wb = container_of(to_delayed_work(work),struct bdi_writeback, dwork);long pages_written;set_worker_desc("flush-%s", bdi_dev_name(wb->bdi));current->flags |= PF_SWAPWRITE;if (likely(!current_is_workqueue_rescuer() ||!test_bit(WB_registered, &wb->state))) {/** The normal path.  Keep writing back @wb until its* work_list is empty.  Note that this path is also taken* if @wb is shutting down even when we're running off the* rescuer as work_list needs to be drained.*/do {pages_written = wb_do_writeback(wb);            /*          1         */trace_writeback_pages_written(pages_written);} while (!list_empty(&wb->work_list));} else {/** bdi_wq can't get enough workers and we're running off* the emergency worker.  Don't hog it.  Hopefully, 1024 is* enough for efficient IO.*/pages_written = writeback_inodes_wb(wb, 1024,WB_REASON_FORKER_THREAD);trace_writeback_pages_written(pages_written);}if (!list_empty(&wb->work_list))                /*          2         */wb_wakeup(wb);else if (wb_has_dirty_io(wb) && dirty_writeback_interval)wb_wakeup_delayed(wb);current->flags &= ~PF_SWAPWRITE;
}

(1) 调用wb_do_writeback 执行后续的操作

(2) 如果还有新的work(wb->work_list) 或者有新的io(wb_has_dirty_io(wb)) , 继续调度自己。

wb_do_writeback 是处理回写的关键函数, 其定义如下

static long wb_do_writeback(struct bdi_writeback *wb)
{struct wb_writeback_work *work;long wrote = 0;set_bit(WB_writeback_running, &wb->state);while ((work = get_next_work_item(wb)) != NULL) {    /*           1          */trace_writeback_exec(wb, work);wrote += wb_writeback(wb, work);finish_writeback_work(wb, work);}/** Check for a flush-everything request*/wrote += wb_check_start_all(wb);/** Check for periodic writeback, kupdated() style*/wrote += wb_check_old_data_flush(wb);               /*           2          */wrote += wb_check_background_flush(wb);             /*           3          */clear_bit(WB_writeback_running, &wb->state);return wrote;
}

(1) 处理当前的work。sync等同步调用就会给wb添加一个wb_writeback_work , 然后在这里之行

(2) 周期回写的入口, 检查是否有dirty page到期

(3) 后台回写的入口, 如果此时的dity page 超过了系统的限制, 会进行回写。

下面我们主要看第二步, 周期回写的逻辑。

fs/fs-writeback.c
wb_workfn->wb_do_writeback->wb_check_old_data_flush
static long wb_check_old_data_flush(struct bdi_writeback *wb)
{unsigned long expired;long nr_pages;/** When set to zero, disable periodic writeback*/if (!dirty_writeback_interval)return 0;expired = wb->last_old_flush +msecs_to_jiffies(dirty_writeback_interval * 10);if (time_before(jiffies, expired))             /*             1           */return 0;                     wb->last_old_flush = jiffies;nr_pages = get_nr_dirty_pages();if (nr_pages) {struct wb_writeback_work work = {.nr_pages   = nr_pages,.sync_mode  = WB_SYNC_NONE,.for_kupdate    = 1,.range_cyclic   = 1,.reason     = WB_REASON_PERIODIC,};return wb_writeback(wb, &work);        /*                2              */}return 0;
}

(1) 检查回写周期是否到期。这个是一个可配的参数dirty_writeback_interval的配置在 /proc/sys/vm/dirty_writeback_centisecs。 单位是10ms, 默认配置是500, 即超时时间是5s。

(2) 构建wb_writeback_work, 调用wb_writeback 执行后续的操作。

wb_writeback

无论是周期回写, 后台回写,还是sync调用,其本质上都是构建一个wb_writeback_work ,然后调用wb_writeback去执行。

我们首先看下wb_writeback_work 的定义

fs/fs-writeback.c
struct wb_writeback_work {long nr_pages;struct super_block *sb;enum writeback_sync_modes sync_mode;unsigned int tagged_writepages:1;unsigned int for_kupdate:1;unsigned int range_cyclic:1;unsigned int for_background:1;unsigned int for_sync:1;    /* sync(2) WB_SYNC_ALL writeback */unsigned int auto_free:1;   /* free on completion */enum wb_reason reason;      /* why was writeback initiated? */struct list_head list;      /* pending work list */st

nr_pages: 此次work需要回写的总页数

writeback_sync_modes:是否是同步模式

for_kupdate:是否是周期回写。周期回写时, 这里会置1

for_background:是否是后台回写。后台回写时, 这里会置1

for_sync : 是否是sync调用。sync调用时, 这里会置1

wb_writeback的具体流程如下:

fs/fs-writeback.c
wb_workfn->wb_do_writeback->wb_check_old_data_flush->wb_writeback
static long wb_writeback(struct bdi_writeback *wb,struct wb_writeback_work *work)
{unsigned long wb_start = jiffies;long nr_pages = work->nr_pages;unsigned long dirtied_before = jiffies;struct inode *inode;long progress;struct blk_plug plug;blk_start_plug(&plug);spin_lock(&wb->list_lock);for (;;) {/** Stop writeback when nr_pages has been consumed*/if (work->nr_pages <= 0)    /*         1        */break;/** Background writeout and kupdate-style writeback may* run forever. Stop them if there is other work to do* so that e.g. sync can proceed. They'll be restarted* after the other works are all done.*/if ((work->for_background || work->for_kupdate) &&!list_empty(&wb->work_list))         /*         2        */break;/** For background writeout, stop when we are below the* background dirty threshold*/if (work->for_background && !wb_over_bg_thresh(wb))       /*         3        */break;/** Kupdate and background works are special and we want to* include all inodes that need writing. Livelock avoidance is* handled by these works yielding to any other work so we are* safe.*/if (work->for_kupdate) {                     /*         4        */dirtied_before = jiffies -msecs_to_jiffies(dirty_expire_interval * 10);} else if (work->for_background)dirtied_before = jiffies;trace_writeback_start(wb, work);if (list_empty(&wb->b_io))queue_io(wb, work, dirtied_before);          /*         5        */if (work->sb)progress = writeback_sb_inodes(work->sb, wb, work);  /*         6        */elseprogress = __writeback_inodes_wb(wb, work);trace_writeback_written(wb, work);}spin_unlock(&wb->list_lock);blk_finish_plug(&plug);return nr_pages - work->nr_pages;
}

(1) 如果回写任务已经完成, 退出循环

(2) 如果当前是周期回写或者后台回写, 此时如果来了其他的work, 直接返回,避免其他更高优先级的work被阻塞。

(3) 如果是后台回写, 且当前没有达到后台回写的阈值,直接返回。

(4) 如果是周期回写, 设置超时时间。如果是其他情况, dirtied_before设置的是当前时间,也就是所有inode 肯定会超时。

(5) 把超时的inode转移到b_io链表中。

(6) 调用writeback_sb_inodes 或者__writeback_inodes_wb 执行后续的工作。

writeback_sb_inodes__writeback_inodes_wb 两者的逻辑类似, 都是遍历wb->b_io, 执行__writeback_single_inode

fs/fs-writeback.c
static int
__writeback_single_inode(struct inode *inode, struct writeback_control *wbc)
{struct address_space *mapping = inode->i_mapping;long nr_to_write = wbc->nr_to_write;unsigned dirty;int ret;WARN_ON(!(inode->i_state & I_SYNC));trace_writeback_single_inode_start(inode, wbc, nr_to_write);ret = do_writepages(mapping, wbc);                                                  /*        1       *//** Make sure to wait on the data before writing out the metadata.* This is important for filesystems that modify metadata on data* I/O completion. We don't do it for sync(2) writeback because it has a* separate, external IO completion path and ->sync_fs for guaranteeing* inode metadata is written back correctly.*/if (wbc->sync_mode == WB_SYNC_ALL && !wbc->for_sync) {           /*        2       */int err = filemap_fdatawait(mapping);if (ret == 0)ret = err;}.....int err = write_inode(inode, wbc);                                                  /*        3       */if (ret == 0)ret = err;trace_writeback_single_inode(inode, wbc, nr_to_write);return ret;
}

(1) 调用 do_writepages 回写文件的数据

(2) 等待数据写入完成

(3) 调用write_inode写入文件的元数据。 可以看到这里是保证数据是先于元数据写入的。这里会调用到s_op->write_inode

后台回写

当dirty page 的数量超过后台回写的阈值时, 系统开始执行后台回写。

这个阈值是 /proc/sys/vm/dirty_backgroud_ratio, 是脏页占整体可用内存的比例。或者是/proc/sys/vm/dirty_backgroud_bytes, 脏页最大的字节数, 这两个是互斥的关系。

wb_do_writeback 中, 会调用wb_check_background_flush 执行后台回写

static long wb_check_background_flush(struct bdi_writeback *wb)
{if (wb_over_bg_thresh(wb)) {struct wb_writeback_work work = {.nr_pages   = LONG_MAX,.sync_mode  = WB_SYNC_NONE,.for_background = 1,.range_cyclic   = 1,.reason     = WB_REASON_BACKGROUND,};return wb_writeback(wb, &work);}return 0;
}

这个逻辑和周期回写一样, 构建一个wb_writeback_work, 然后调用wb_writeback执行work。

这里通过wb_over_bg_thresh判断当前是否超过了后台回写阈值。

系统调用sync

用户层可以主调调用 sync, 将系统中所有的脏数据写回。 sync的入口如下。

fs/sync.c
SYSCALL_DEFINE0(sync)
{ksys_sync();return 0;
}void ksys_sync(void)
{int nowait = 0, wait = 1;wakeup_flusher_threads(WB_REASON_SYNC);             /*    1    */iterate_supers(sync_inodes_one_sb, NULL);               /*    2    */iterate_supers(sync_fs_one_sb, &nowait);              /*    3    */iterate_supers(sync_fs_one_sb, &wait);                /*    4    */iterate_bdevs(fdatawrite_one_bdev, NULL);             /*    5    */iterate_bdevs(fdatawait_one_bdev, NULL);            /*    6    */if (unlikely(laptop_mode))laptop_sync_completion();
}

(1) 唤醒所有的bdi 。这里的逻辑是如果bdi 里有dirty_page, 就唤醒它让它去执行回写。

(2) 遍历所有sb, 执行sync_inodes_one_sb,这里会构建wb_writeback_work,添加到对应wb的work_list中, 并等待其执行3完毕

(3) 遍历所有sb , 执行sync_fs, 不等待执行完。

(4) 遍历所有sb, 执行sync_fs, 等待执行完

(5)(6) 遍历所有block_device, 将其缓存写入


http://www.ppmy.cn/news/244100.html

相关文章

牛客网Linux错题六

1.有一个文件ip.txt&#xff0c;每行一条ip 记录&#xff0c;共若干行&#xff0c;已排好序&#xff0c;下面哪个命令可以实现“统计出现次数最多的前3个ip及其次数”&#xff1f;&#xff08;B&#xff09; A. uniq -c ip.txt B. uniq -c ip.txt | sort -nr | head -n 3 C.…

SpringBoot个人博客系统(含源码+数据库)

一、作品设计理念 个人博客系统是一个让个人可以通过互联网自由表达、交流和分享的平台&#xff0c;是个人展示自己思想、感受和经验的品牌。设计理念对于任何一个个人博客系统来说都非常重要&#xff0c;它直接影响到用户的使用体验和网站的整体感觉。 好的设计理念应该着眼于…

预见未来:超强元AI诞生,抓住这个机会,利用AI变现也变得更加容易

目录 一、引言 二、介绍 三、技术展现 四、元AI架构图展现 五、元AI变现技巧—商业版说明 六、后期规划 一、引言 如何利用AI变现已经成为了当今各个行业亟需解决的问题。随着人工智能技术的快速发展和普及&#xff0c;越来越多的企业开始将其应用于产品研发、销售流程优化、客…

SpringData整合ElasticSearch

一、环境搭建 1.Maven依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version><relativePath/></parent><dependencies&g…

小米9es更新MIUI 11.0.3.0稳定版本,解决耗电问题

等待以已久的小米MIUI11系统终于迎来更新。今天早上有不少米粉称已经更新小米MIUI11.0.3.0稳定版本&#xff0c;本次更新大小为700M。 更新主要内容&#xff1a; 全面成熟和完善的全面屏设计&#xff0c;去除多余的视觉符号&#xff0c;采用精心设计大屏触控互交控件&#xff…

ElasticSearch快速入门实战

全文检索 数据分类&#xff1a; 结构化数据&#xff1a; 固定格式&#xff0c;有限长度 比如mysql存的数据非结构化数据&#xff1a;不定长&#xff0c;无固定格式 比如邮件&#xff0c;word文档&#xff0c;日志半结构化数据&#xff1a; 前两者结合 比如xml&#xff0c;htm…

Elasticsearch HTTP查询

1、条件查询 匹配查询 {"query": {"match": {"category": "小米"}} } 查询全部 {"query": {"match_all": {}} } 2、分页查询 查询第二页的数据&#xff0c;并且只获取title字段。 {"query": {&q…

SpringData集成Elasticsearch

Spring Data 是一个用于简化数据库、非关系型数据库、索引库访问&#xff0c;并支持云服务的 开源框架。其主要目标是使得对数据的访问变得方便快捷&#xff0c;并支持 map-reduce 框架和云计 算数据服务。 Spring Data 可以极大的简化 JPA&#xff08;Elasticsearch„&#xf…