Linux 内核pdflush实现的变迁

本文介绍了Linux内核中pdflush线程的三次版本变化,从早期的初始化方式,到2.6.32之后的版本实现,再到4.9.99版本的更新。pdflush线程负责将脏页刷新到磁盘,其功能在后续内核中逐渐被工作队列取代。同时,文章提到了与pdflush相关的kswapd线程及其作用,以及数据写入过程涉及的page cache机制。

内核使用pdflush线程刷新脏页到磁盘,pdflush线程是内核在初始化的时候创建的,不过,pdflush在内核中的实现并不是一直是这样的,它经历了几次变化,我们来回顾一下:

1.第一次,linux-2.6.32之前的版本实现:

以linux-2.6.30版本的内核为例,pdflush线程的初始化在pdflush.c文件中,完整路径是linux-stable/mm/pdflush.c。我们截取它的实现:

可见,在2.6.32之前的版本中,pdflush线程是在内核启动阶段,执行init构造函数的时候调用的,并且线程数目由nr_pdflush_threads变量控制,它的函数体如下:

当需要回写脏页时,唤醒pdflush线程的操作在这里执行,唤醒过程中,丢一个处理任务background_writeout进去。

处理任务将会挂接到一个任务链表中,择机由__pdflush 执行。

__pdflush在执行回写任务:

 2.第二个版本,linux-2.6.32之后的版本实现:

以v2.6.34为例,它的初始化过程变成了:

 2.第三个版本,linux-4.9.99的版本实现:

加入调试打印信息:

[   30.756556] set_worker_desc line 4586, desc = flush-8:16.
[   30.756558] wb_workfn line 2073, comm kworker/u8:2.
[   30.756562] CPU: 2 PID: 137 Comm: kworker/u8:2 Not tainted 5.4.129+ #28
[   30.756563] Hardware name: Dell Inc. Vostro 3268/0TJYKK, BIOS 1.11.1 12/11/2018
[   30.756571] Workqueue: writeback wb_workfn (flush-8:16)
[   30.756573] Call Trace:
[   30.756581]  dump_stack+0x6d/0x8b
[   30.756585]  wb_workfn+0x90/0x4e0
[   30.756589]  ? __switch_to_asm+0x40/0x70
[   30.756591]  ? __switch_to_asm+0x34/0x70
[   30.756593]  ? __switch_to_asm+0x40/0x70
[   30.756595]  ? __switch_to_asm+0x34/0x70
[   30.756597]  ? __switch_to_asm+0x40/0x70
[   30.756599]  ? __switch_to_asm+0x34/0x70
[   30.756601]  ? __switch_to_asm+0x40/0x70
[   30.756603]  ? __switch_to_asm+0x34/0x70
[   30.756605]  ? __switch_to_asm+0x40/0x70
[   30.756607]  ? __switch_to_asm+0x34/0x70
[   30.756611]  ? __switch_to+0x85/0x490
[   30.756613]  ? __switch_to_asm+0x40/0x70
[   30.756615]  ? __switch_to_asm+0x34/0x70
[   30.756620]  process_one_work+0x20f/0x400
[   30.756624]  ? inode_wait_for_writeback+0x40/0x40
[   30.756628]  ? process_one_work+0x20f/0x400
[   30.756631]  worker_thread+0x34/0x410
[   30.756635]  kthread+0x121/0x140
[   30.756638]  ? process_one_work+0x400/0x400
[   30.756641]  ? kthread_park+0x90/0x90
[   30.756643]  ret_from_fork+0x35/0x40

可以看到,最新的内核里面,pdflush的功能被工作队列取代,不再有独立的pdflush线程了。

会刷的关键数据结构

和flush线程相关的线程还有一个kswapd,它在当前的Linux中都有存在:

比如 Tina:

root@(none):/# ps
  PID USER       VSZ STAT COMMAND
    1 root       912 S    /sbin/init
    2 root         0 SW   [kthreadd]
    3 root         0 SW   [kworker/0:0]
    4 root         0 SW<  [kworker/0:0H]
    5 root         0 SW   [kworker/u2:0]
    6 root         0 SW   [ksoftirqd/0]
    7 root         0 SW   [rcu_preempt]
    8 root         0 SW   [rcu_sched]
    9 root         0 SW   [rcu_bh]
   10 root         0 SW<  [lru-add-drain]
   11 root         0 SW   [kdevtmpfs]
   12 root         0 SW   [kworker/u2:1]
  256 root         0 SW   [oom_reaper]
  257 root         0 SW<  [writeback]
  259 root         0 SW<  [crypto]
  260 root         0 SW<  [bioset]
  262 root         0 SW<  [kblockd]
  298 root         0 SW   [kworker/0:1]
  302 root         0 SW   [irq/329-axp2101]
  349 root         0 SW   [sys_user]
  358 root         0 SW<  [cfg80211]
  364 root         0 SW<  [watchdogd]
  456 root         0 SW   [spi0]
  465 root         0 SW   [kswapd0]
  523 root         0 SW<  [SquashFS read w]
  543 root         0 SW   [vsync proc 0]
  568 root         0 SW<  [bioset]
  573 root         0 SW<  [bioset]
  578 root         0 SW<  [bioset]
  583 root         0 SW<  [bioset]
  588 root         0 SW<  [bioset]
  593 root         0 SW<  [bioset]
  598 root         0 SW<  [bioset]
  603 root         0 SW<  [bioset]
  643 root         0 SW   [kworker/u2:2]
  644 root         0 SW   [kworker/u2:3]
  645 root         0 SW   [kworker/u2:4]
  649 root         0 SW   [cfinteractive]
  655 root         0 SW   [kworker/0:2]
  656 root         0 SW   [kworker/0:3]
  657 root         0 SW   [kworker/0:4]
  661 root         0 SW   [irq/304-sunxi-m]
  664 root         0 SW   [irq/165-sdc0 cd]
  705 root         0 SW<  [bioset]
  706 root         0 SW   [mmcqd/0]
  710 root         0 SW<  [kworker/0:1H]
  734 root         0 SWN  [jffs2_gcd_mtd4]
  763 root         0 SWN  [jffs2_gcd_mtd7]
  872 root      1048 S    adbd
  875 root         0 SW   [file-storage]
  882 root       676 S    /sbin/swupdate-progress -w
  889 root       916 S    -/bin/sh
  907 root       912 R    ps
root@(none):/#

以及ubuntu:

czl@czl-VirtualBox:~$ ps -ax
  PID TTY      STAT   TIME COMMAND
    1 ?        Ss     0:02 /sbin/init splash
    2 ?        S      0:00 [kthreadd]
    3 ?        I<     0:00 [rcu_gp]
    4 ?        I<     0:00 [rcu_par_gp]
    6 ?        I<     0:00 [kworker/0:0H-kb]
    7 ?        I      0:00 [kworker/0:1-eve]
    9 ?        I<     0:00 [mm_percpu_wq]
   10 ?        S      0:00 [ksoftirqd/0]
   11 ?        I      0:00 [rcu_sched]
   12 ?        S      0:00 [migration/0]
   13 ?        S      0:00 [idle_inject/0]
   14 ?        S      0:00 [cpuhp/0]
   15 ?        S      0:00 [kdevtmpfs]
   16 ?        I<     0:00 [netns]
   17 ?        S      0:00 [rcu_tasks_kthre]
   18 ?        S      0:00 [kauditd]
   19 ?        S      0:00 [khungtaskd]
   20 ?        S      0:00 [oom_reaper]
   21 ?        I<     0:00 [writeback]
   22 ?        S      0:00 [kcompactd0]
   23 ?        SN     0:00 [ksmd]
   24 ?        SN     0:00 [khugepaged]
   70 ?        I<     0:00 [kintegrityd]
   71 ?        I<     0:00 [kblockd]
   72 ?        I<     0:00 [blkcg_punt_bio]
   73 ?        I<     0:00 [tpm_dev_wq]
   74 ?        I<     0:00 [ata_sff]
   75 ?        I<     0:00 [md]
   76 ?        I<     0:00 [edac-poller]
   77 ?        I<     0:00 [devfreq_wq]
   78 ?        S      0:00 [watchdogd]
   79 ?        I      0:00 [kworker/u2:1-ev]
   81 ?        S      0:02 [kswapd0]

应用场景

以向磁盘文件按写内容为例,我们调用write这个函数,往这个句柄里面写数据的时候,那么在内核空间里面它首先会通过打开的句柄fd,在内核里面找到file的这样一个数据结构,在file数据结构里面它找到它的page cache,如果没有找到page cache,他就会新创建一个page cache,你要把这些page cache加到内核管理page cache这种基数树里面

它会根据buffer的大小,去分配buffer_head,通常是4个buffer_head,指向一个page。

通过文件系统的get_block(每个文件系统有自己的封装实现,比如ext2_get_block/fat_get_block),这个api为page cache去查找在磁盘中对应的这个block的号。通常文件系统会管理这个数据它要存放在磁盘哪个block的号里面,接下来将数据copy到page cache中,这里直接调用copy_from_user拷贝就可以了。

接下来,把buffer_head和page cache都要设置成dirty,即这个page cache它是脏的页面,而且要把这个文件所对应的inode也要添加到系统里面的一个脏的队列里面。这一步完成后,从用户空间的角度看,这个write函数已经返回了,但是我们要注意到,这个步骤完成之后,其实数据并没有真正写入到eMMC里面,而是写入到系统的缓存或者页缓存,叫做page cache里面。

第二步,linux内核里面有一个叫做flush的内核线程,这个内核线程的主要工作是定期把一些脏的page cache写回到磁盘里面,这是flush内核线程的主要工作。

这个flush线程会从inode的dirty队列里面去处理一个inode,即脏的inode,它会调用文件系统的write pages这种接口去处理这个page cache。

唤醒flush线程的地方:

用流程图表示如下:

for non-direct read, we can find it also operate through PAGE CACHE,generic_file_read_iter

所以,实际上写操作并不等数据落盘就会返回,真正的数据落盘要等到PDFLUSH回刷的时候。

page cache的建立路径是:

page_cache_alloc()->__page_cache_alloc() 最终调用到__add_to_page_cache_locked

理论分析请参考这篇文章:Linux中的Page Cache [二] - 知乎

kswapd线程的前世今生_papaofdoudou的博客-优快云博客

结束!

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papaofdoudou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值