原文链接: Linux 内核进程详解之二: bdi-default


转载说明 (最后更新于: 2019-04-03, 12:26:50)

  1. 转载本文的原意仅是为了理解 “bdi” 所代表的含义以及其背后涉及到的初步的工作原理或机制, 避免原文因某些原因无法访问.
  2. 在原文的基础上, 博主根据自己的需要对文本格式进行了调整 (强迫症), 未对内容进行修改. 同时根据自己查阅的资料进行了注释或补充说明.
  3. 本文适用于 CentOS 6.x 版本. 但对于 CentOS 7.x, 从系统中已经看不到 bdi-defaultflush-x:y 这两个内核线程, 已经被合并新的到 “workqueues” 机制 (参见 Working on workqueues 和 backing-dev: replace private thread pool with workqueue).

bdi, 即是 “backing device info” 的缩写, 根据英文单词全称可知其通指备用存储设备相关描述信息, 这在内核代码里用一个结构体 backing_dev_info 来表示: http://lxr.linux.no/#linux+v2.6.38.8/include/linux/backing-dev.h#L62.

bdi, 备用存储设备, 简单点说就是能够用来存储数据的设备, 而这些设备存储的数据能够保证在计算机电源关闭时也不丢失. 这样说来, 软盘存储设备, 光驱存储设备, USB 存储设备, 硬盘存储设备都是所谓的备用存储设备 (后面都用 bdi 来指示), 而内存显然不是, 具体看下面这个链接: (链接已失效).

相对于内存来说, bdi 设备 (比如最常见的硬盘存储设备) 的读写速度是非常慢的, 因此为了提高系统整体性能, Linux 系统对 bdi 设备的读写内容进行了缓冲, 那些读写的数据会临时保存在内存里, 以避免每次都直接操作 bdi 设备, 但这就需要在一定的时机 (比如每隔 5 秒, 脏数据达到的一定的比率等) 把它们同步到 bdi 设备, 否则长久的呆在内存里容易丢失 (比如机器突然宕机, 重启), 而进行间隔性同步工作的进程之前名叫 pdflush, 但后来在 Kernel 2.6.2x/3x (没注意具体是哪个小版本的改动, 比如: Linux 2.6.35, kill unnecessary bdi wakeups + cleanups) 对此进行了优化改进, 产生有多个内核进程, bdi-default, flush-x:y 等, 这也是这两篇文章要介绍的内容.


注释于 2019-04-03, 详情请参见: Linux Page Cache Basics

  • Up to Version 2.6.31 of the Kernel: pdflush

    Up to and including the 2.6.31 version of the Linux kernel, the pdflush threads ensured that dirty pages were periodically written to the underlying storage device.

  • As of Version 2.6.32: per-backing-device based writeback

    Since pdflush had several performance disadvantages, Jens Axboe developed a new, more effective writeback mechanism for Linux Kernel version 2.6.32.


关于以前的 pdflush 不再多说, 我们这里只讨论 bdi-defaultflush-x:y, 这两个进程 (事实上, flush-x:y 为多个) 的关系与运行模式类似于 lighttpd 的那种标准的父子进程工作 demon 模型, 当然, 很多人不了解 lighttpd 的进程模型, 下面详解。

一般而言, 一个 Linux 系统会挂载很多 bdi 设备, 在 bdi 设备注册 (函数: bdi_register(…)) 时, 这些 bdi 设备会以链表的形式组织在全局变量 bdi_list 下, 除了一个比较特别的 bdi 设备以外, 它就是 default bdi 设备 (default_backing_dev_info), 它除了被加进到 bdi_list, 还会新建一个 bdi-default 内核进程, 即本文的主角. 具体代码如下, 我相信你一眼就能注意到 kthread_runlist_add_tail_rcu 这样的关键代码.

struct backing_dev_info default_backing_dev_info = {.name       = "default",.ra_pages    = VM_MAX_READAHEAD * 1024 / PAGE_CACHE_SIZE,.state     = 0,.capabilities  = BDI_CAP_MAP_COPY,.unplug_io_fn   = default_unplug_io_fn,
};
EXPORT_SYMBOL_GPL(default_backing_dev_info);static inline bool bdi_cap_flush_forker(struct backing_dev_info *bdi)
{return bdi == &default_backing_dev_info;
}int bdi_register(struct backing_dev_info *bdi, struct device *parent,const char *fmt, ...)
{va_list args;struct device *dev;if (bdi->dev)   /* The driver needs to use separate queues per device */return 0;va_start(args, fmt);dev = device_create_vargs(bdi_class, parent, MKDEV(0, 0), bdi, fmt, args);va_end(args);if (IS_ERR(dev))return PTR_ERR(dev);bdi->dev = dev;/** Just start the forker thread for our default backing_dev_info,* and add other bdi's to the list. They will get a thread created* on-demand when they need it.*/if (bdi_cap_flush_forker(bdi)) {struct bdi_writeback *wb = &bdi->wb;wb->task = kthread_run(bdi_forker_thread, wb, "bdi-%s",dev_name(dev));if (IS_ERR(wb->task))return PTR_ERR(wb->task);}bdi_debug_register(bdi, dev_name(dev));set_bit(BDI_registered, &bdi->state);spin_lock_bh(&bdi_lock);list_add_tail_rcu(&bdi->bdi_list, &bdi_list);spin_unlock_bh(&bdi_lock);trace_writeback_bdi_register(bdi);return 0;
}
EXPORT_SYMBOL(bdi_register);

接着跟进函数 bdi_forker_thread, 它是 bdi-default 内核进程的主体:

static int bdi_forker_thread(void *ptr)
{struct bdi_writeback *me = ptr;current->flags |= PF_SWAPWRITE;set_freezable();/** Our parent may run at a different priority, just set us to normal*/set_user_nice(current, 0);for (;;) {struct task_struct *task = NULL;struct backing_dev_info *bdi;enum {NO_ACTION,   /* Nothing to do */FORK_THREAD, /* Fork bdi thread */KILL_THREAD, /* Kill inactive bdi thread */} action = NO_ACTION;/** Temporary measure, we want to make sure we don't see* dirty data on the default backing_dev_info*/if (wb_has_dirty_io(me) || !list_empty(&me->bdi->work_list)) {del_timer(&me->wakeup_timer);wb_do_writeback(me, 0);}spin_lock_bh(&bdi_lock);set_current_state(TASK_INTERRUPTIBLE);list_for_each_entry(bdi, &bdi_list, bdi_list) {bool have_dirty_io;if (!bdi_cap_writeback_dirty(bdi) ||bdi_cap_flush_forker(bdi))continue;WARN(!test_bit(BDI_registered, &bdi->state),"bdi %p/%s is not registered!\n", bdi, bdi->name);have_dirty_io = !list_empty(&bdi->work_list) ||wb_has_dirty_io(&bdi->wb);/** If the bdi has work to do, but the thread does not* exist - create it.*/if (!bdi->wb.task && have_dirty_io) {/** Set the pending bit - if someone will try to* unregister this bdi - it'll wait on this bit.*/set_bit(BDI_pending, &bdi->state);action = FORK_THREAD;break;}spin_lock(&bdi->wb_lock);/** If there is no work to do and the bdi thread was* inactive long enough - kill it. The wb_lock is taken* to make sure no-one adds more work to this bdi and* wakes the bdi thread up.*/if (bdi->wb.task && !have_dirty_io &&time_after(jiffies, bdi->wb.last_active +bdi_longest_inactive())) {task = bdi->wb.task;bdi->wb.task = NULL;spin_unlock(&bdi->wb_lock);set_bit(BDI_pending, &bdi->state);action = KILL_THREAD;break;}spin_unlock(&bdi->wb_lock);}spin_unlock_bh(&bdi_lock);/* Keep working if default bdi still has things to do */if (!list_empty(&me->bdi->work_list))__set_current_state(TASK_RUNNING);switch (action) {case FORK_THREAD:__set_current_state(TASK_RUNNING);task = kthread_create(bdi_writeback_thread, &bdi->wb,"flush-%s", dev_name(bdi->dev));if (IS_ERR(task)) {/** If thread creation fails, force writeout of* the bdi from the thread.*/bdi_flush_io(bdi);} else {/** The spinlock makes sure we do not lose* wake-ups when racing with 'bdi_queue_work()'.* And as soon as the bdi thread is visible, we* can start it.*/spin_lock_bh(&bdi->wb_lock);bdi->wb.task = task;spin_unlock_bh(&bdi->wb_lock);wake_up_process(task);}break;case KILL_THREAD:__set_current_state(TASK_RUNNING);kthread_stop(task);break;case NO_ACTION:if (!wb_has_dirty_io(me) || !dirty_writeback_interval)/** There are no dirty data. The only thing we* should now care about is checking for* inactive bdi threads and killing them. Thus,* let's sleep for longer time, save energy and* be friendly for battery-driven devices.*/schedule_timeout(bdi_longest_inactive());elseschedule_timeout(msecs_to_jiffies(dirty_writeback_interval * 10));try_to_freeze();/* Back to the main loop */continue;}/** Clear pending bit and wakeup anybody waiting to tear us down.*/clear_bit(BDI_pending, &bdi->state);smp_mb__after_clear_bit();wake_up_bit(&bdi->state, BDI_pending);}return 0;
}

代码看上去很多, 但逻辑十分简单, 一个 for 死循序, 接着一个 list_for_each_entry 遍历 bdi_list 下的所有 bdi 设备对应的 flush-x:y 内核进程是否存在, 进程状态如何, 是否需要进行对应的操作 (kill 掉或 create).

绝大部分的 bdi 设备都会有对应的 flush-x:y 内核进程, 除了一些特殊的 bdi 设备, 比如 default bdi 设备或其它一些内存虚拟 bdi设 备, 这从第一个 if 判断代码可以看出:

if (!bdi_cap_writeback_dirty(bdi) ||bdi_cap_flush_forker(bdi))continue;

关于 flush-x:y 内核进程具体做什么, 待下一篇文章再讲, 但我们这里需要知道, 如果一个 bdi 设备当前有脏数据需要同步, 那么它对应的 flush-x:y 内核进程就会被创建 (当然, 这是在它本身不存在的情况下):

have_dirty_io = !list_empty(&bdi->work_list) ||wb_has_dirty_io(&bdi->wb);/** If the bdi has work to do, but the thread does not* exist - create it.*/
if (!bdi->wb.task && have_dirty_io) {/** Set the pending bit - if someone will try to* unregister this bdi - it'll wait on this bit.*/set_bit(BDI_pending, &bdi->state);action = FORK_THREAD;break;
}

标记 actionFORK_THREAD, 在接下来 (注意 if 语句里的 break 语句, 这个 break 语句会跳出 list_for_each_entry 循环) 的switch (action) 的语句体里进行具体的 flush-x:y 内核进程创建工作.

如果一个 bdi 设备当前没有脏数据需要同步, 并且它对应的 flush-x:y 内核进程已经有很久没有活动 (通过对比最后活动时间 last_active 与当前 jiffies) 了, 那么就把它 kill 掉:

            /** If there is no work to do and the bdi thread was* inactive long enough - kill it. The wb_lock is taken* to make sure no-one adds more work to this bdi and* wakes the bdi thread up.*/if (bdi->wb.task && !have_dirty_io &&time_after(jiffies, bdi->wb.last_active +bdi_longest_inactive())) {task = bdi->wb.task;bdi->wb.task = NULL;spin_unlock(&bdi->wb_lock);set_bit(BDI_pending, &bdi->state);action = KILL_THREAD;break;}/** Calculate the longest interval (jiffies) bdi threads are allowed to be* inactive.*/
static unsigned long bdi_longest_inactive(void)
{unsigned long interval;interval = msecs_to_jiffies(dirty_writeback_interval * 10);return max(5UL * 60 * HZ, interval);
}unsigned int dirty_writeback_interval = 5 * 100; /* centiseconds */

可以看到 “很久” 在默认情况下是 5 分钟, 此时标记 actionKILL_THREAD, 在接下来的 switch (action) 的语句体里进行具体的flush-x:y 内核进程 kill 工作.

如果所有 bdi 设备遍历操作结束, 此时 bdi-default 内核进程自身执行 switch (action) 的语句体里 NO_ACTION 语句进行睡眠, 直到超时后 continue 重复上面的工作.


原文链接: Linux 内核进程详解之二: bdi-default

[转载] Linux 内核进程详解之二: bdi-default相关推荐

  1. Linux防火墙详解(二)

    1 2 3 4 5 6 一.iptables命令基本语法 二.iptables语法进阶 三.iptables显示扩展 四.iptables简单案例 五.iptables之forward 六.iptab ...

  2. Linux 命令详解(二)awk 命令

    AWK是一种处理文本文件的语言,是一个强大的文本分析工具.之所以叫AWK是因为其取了三位创始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的Fami ...

  3. 『Linux』第九讲:Linux多线程详解(三)_ 线程互斥 | 线程同步

    「前言」文章是关于Linux多线程方面的知识,上一篇是 Linux多线程详解(二),今天这篇是 Linux多线程详解(三),内容大致是线程互斥与线程同步,讲解下面开始! 「归属专栏」Linux系统编程 ...

  4. Linux中/proc目录下文件详解(二)

    Linux中/proc目录下文件详解(二) /proc/mdstat文件 这个文件包含了由md设备驱动程序控制的RAID设备信息. 示例: [root@localhost ~]# cat /proc/ ...

  5. Linux常用基本命令详解(二)-------磁盘分区和磁盘管理类命令

    Linux常用基本命令详解(一) Linux常用基本命令详解(二)-------磁盘分区和磁盘管理类命令 Linux常用基本命令详解(三) 1.磁盘分区 磁盘分区(系统分区)是使用分区编辑器(part ...

  6. 高通平台msm8953 Linux DTS(Device Tree Source)设备树详解之二(DTS设备树匹配过程)

    本系列导航: 高通平台8953  Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇) 高通平台8953 Linux DTS(Device Tree Source ...

  7. Linux内核Thermal框架详解十二、Thermal Governor(2)

    本文部分内容参考 万字长文 | Thermal框架源码剖析, Linux Thermal机制源码分析之框架概述_不捡风筝的玖伍贰柒的博客-CSDN博客, "热散由心静,凉生为室空" ...

  8. 【Linux驱动开发】设备树详解(二)设备树语法详解

    ​ 活动地址:CSDN21天学习挑战赛 [Linux驱动开发]设备树详解(一)设备树基础介绍 [Linux驱动开发]设备树详解(二)设备树语法详解 [Linux驱动开发]设备树详解(三)设备树Kern ...

  9. Linux进程详解(二)完结

    原创架构师之路2019-08-13 22:08 接Linux进程详解(一) 4. 进程运行 程序运行时大部分进程状态为运行或睡眠.调度算法解决可以跑的运行状态(就绪和运行),剩下的不可以跑的进程就是睡 ...

最新文章

  1. PHP服务器时间差8小时解决方案
  2. 数据库数据用Excel导出的3种方法
  3. IOS-React-Native:unable to find utility instruments, not a developer tool or in PATH
  4. 聚类效果评估指标总结
  5. python开发跟淘宝有关联微_Python_淘宝用户行为分析
  6. linux查进程内存问题,关于linux下内存问题排查的工具
  7. 三周第二次课(4月3日)
  8. 关于宏定义的一些用法
  9. 计算机考研408真题2021年,2021年408考研科目真题及答案_文得学习网
  10. 科技文献检索(六)——检索语言
  11. 数值分析(一) 牛顿插值法及matlab代码
  12. FullScreen
  13. 总结命令行05:Kafka
  14. java制作名片applet程序_【小程序 提取码:krua】壹佰智能名片小程序版本V1.1.45 – 持续更新 无后门...
  15. azw3电子书如何用MAC打开?
  16. 地理信息系统GIS的评价
  17. 本周开课 | 10年运营专家亲授,掌握9类运营的核心技能,强化运营实力
  18. linux v4l2架构分析之v4l2_ctrl_handler初始化及添加v4l2_ctrl的过程分析
  19. 网页代码扒ppt_在网页中在线浏览ppt文档
  20. Asp.Net Core WebApi 身份验证、注册、用户管理

热门文章

  1. 程序设计大赛试题及答案
  2. 【不为人知的Word文档分节符设置】
  3. 获取iWatch的UDID
  4. python编程视频剪辑_你知道吗?Python也可以剪辑视频!
  5. 14、列出所有员工及领导的姓名
  6. HDMI学习-2:HDMI传输原理
  7. 2022年安全员-A证上岗证题库及模拟考试
  8. 智能养殖控制系统如何养牛
  9. DS排序--快速排序
  10. EF登录+首页+列表