工作者队列原理解析(后台writeback)

时间:2022-08-30 22:28:30

每一个CPU都会有两个(或者一个?)kwoker线程.

kwoker线程,说白了就是尽量减少进程的数目,为了什么呢?因为线程数据太多的话,调度的成本比较高,占用太多的系统资源,所以这里是进程的一个简化的版本,一个线程,

做多项工作!

init_workqueues里面有这样的代码:

/* create the initial worker */
  for_each_online_cpu(cpu) {
    struct worker_pool *pool;
    for_each_cpu_worker_pool(pool, cpu) {
      pool->flags &= ~POOL_DISASSOCIATED;
      BUG_ON(!create_worker(pool));
    }
  }

这里会调用create_worker去创建属于每一个CPU的woker线程!

你的PC机上上是不是有这样的进程呢:

root 5053 0.0 0.0 0 0 ? S 12月08 0:00 [kworker/0:1]
root 11887 0.0 0.0 0 0 ? S 12月13 0:01 [kworker/1:2]
root 12300 0.0 0.0 0 0 ? S 12月13 0:02 [kworker/3:0]
root 12564 0.0 0.0 0 0 ? S 12月13 0:02 [kworker/2:0]
root 14059 0.0 0.0 0 0 ? S 12月13 0:00 [kworker/1:0]
root 14072 0.0 0.0 0 0 ? S 12月13 0:00 [kworker/2:2]
root 14464 0.0 0.0 0 0 ? S 12月13 0:04 [kworker/0:0]
root 15394 0.0 0.0 0 0 ? S 12月17 0:00 [kworker/3:2]

每个CPU有两个poll, 我的CPU上共有4个CPU, 这样我整个系统要提供8个poll出来, 对应的, 上面这8个进程就是为了守护这8个poll的.

原来每一个CPU都会有两个poll,需要为每个worker_poll创建一个守护的进程.

[这八个线程就是绑定在特定的CPU上了,比如kworker/0:1就会绑定在CPU0上了,只会处理挂在第1个worklist上的工作]

好了,上面说完了worker_poll和kworker之间的关系, 那么还有两个重要的概念是workqueue以及work.

以后台回写的队列来说,它的工作队列是:(mm/backing-dev.c)

32 /* bdi_wq serves all asynchronous writeback tasks */
33 struct workqueue_struct *bdi_wq;

当一个磁盘上第一次发生inode为脏时, 就要设置回写的定时器了, 我们发现回写定时器中queue_delayed_work中第一个参数都是

bdi_wq,也就是说把work和bdi_wq发生了关联,那么就很有意思了.这里突然感觉有点断层:发现kwoker和pool是一队的, workqueue和work是

一对的, 不可能啊,两个队伍之间肯定有某种内在的关联, 凭直觉,我们看workqueue和pool之间的关系.

wb_wakeup_delayed

-->queue_delayed_work

-->queue_delayed_work_on

-->__queue_delayed_work

-->add_timer

看bdi_wq:

248 bdi_wq = alloc_workqueue("writeback", WQ_MEM_RECLAIM | WQ_FREEZABLE |
249 WQ_UNBOUND | WQ_SYSFS, 0);

这个函数分配了一个workqueue就是bdi_wq, 深入alloc_workqueue看下这个函数在下面是如何把一个workqueue链到了一个work_pool中去:

3856 if (alloc_and_link_pwqs(wq) < 0)
3857 goto err_free_wq;

关键函数是alloc_and_link_pwqs(wq). 函数alloc_and_link_pwqs中对于WQ_UNBOUND类型的工作队列有特别的处理方法, bdi_wq队列就是申请这种UBOUND类型的队列!

对于这种ubound类型的队列,你会发现, 整个NUMA系统中一个节点就只有一个队列!

FUCK! 这样的话也就是说这种队列也对应这一个工作者线程啊,我们抓到的线程都是叫这个名字的:

#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
kworker/u2:1-14 [000] ...1 25750.841022: writeback_pages_written: 0
kworker/u2:1-14 [000] ...1 25755.851673: writeback_pages_written: 0
kworker/u2:1-14 [000] ...1 25760.864366: writeback_pages_written: 0
kworker/u2:1-14 [000] ...1 25765.867211: writeback_pages_written: 0
kworker/u2:1-14 [000] ...1 25770.876580: writeback_pages_written: 0

这些进程的名字叫kworker/u2:1,可以看下这些东西这样的,课件对于这样的一个pool,肯定也是有一个kwoker和他对应的,怎么对应的,我们现在来看下:

alloc_and_link_pwqs

--->apply_wqattrs_prepare

---> alloc_unbound_pwq

  ---->get_unbound_pool

    ---->create_worker

终于create_worker就创建了这样的一些诸如这样的线程呢!

6 root [kworker/u2:0]
14 root [kworker/u2:1]

其中,kworker/u2这个ubound类型的等待队列就是我们的bdi_writeback类型的等待队列!

至此, kworker, workqueue_struct,work_struct之间的关系我觉得自己已经清楚了:

1)声明一个等待队列, 如果这个队列没有声明是WQ_UNBOUND,那么直接将它的pool关联到系统的pool上就可以了;

2)如果这个等待队列被声明是WQ_UNBOUND, 那么要干的事情就多了: 需要给它申请一个专门的pool,并且!还要为它专门申请一个叫kworker/u***的线程,守护着它;

基础设施都有了,第一种情况是使用系统自带的(线程+pool)处理; 第二种情况是使用格外申请的(线程+pool)处理;

那么我们只需要把一个work挂到pool上的work_list即可,就可以顺序执行!

以workback的workqueue为例:

当定时器上注册的钩子函数是:delayed_work_timer_fn

delayed_work_timer_fn

--> __queue_work (int cpu, struct workqueue_struct *wq, struct work_struct *work)

--> insert_work(pwq, work, worklist, work_flags);

insert_work的工作是把work结构体链入pool对应的worklist链表中去, 将来工作者线程就是一个死循环,不断地去读取worklist中的work然后一遍遍执行喽,Done!

#0 wb_workfn (work=0xffffffc0b6330638) at fs/fs-writeback.c:1834
#1 0xffffffc0000b5060 in process_one_work (worker=0xffffffc0b708ec00,
work=0xffffffc0b6330638) at kernel/workqueue.c:2032
#2 0xffffffc0000b5768 in worker_thread (__worker=0xffffffc0b708ec00)
at kernel/workqueue.c:2164
#3 0xffffffc0000bc014 in kthread (_create=0xffffffc0b709e700)
at kernel/kthread.c:207
#4 0xffffffc000085980 in ret_from_fork ()
at arch/arm64/kernel/entry.S:664

============

好了,折磨两个周的writeback机制搞清楚了. 时间就像乳沟一样, 挤挤总会有的, 现在算是从混沌中杀出一条路来了.

工作者队列原理解析(后台writeback)的更多相关文章

  1. kafka原理解析

    两张图读懂kafka应用: Kafka 中的术语 broker:中间的kafka cluster,存储消息,是由多个server组成的集群. topic:kafka给消息提供的分类方式.broker用 ...

  2. java线程池原理解析

    五一假期大雄看了一本<java并发编程艺术>,了解了线程池的基本工作流程,竟然发现线程池工作原理和互联网公司运作模式十分相似. 线程池处理流程 原理解析 互联网公司与线程池的关系 这里用一 ...

  3. &lbrack;原&rsqb;&lbrack;Docker&rsqb;特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

  4. 【算法】&lpar;查找你附近的人&rpar; GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  5. Volley 实现原理解析&lpar;转&rpar;

    Volley 实现原理解析 转自:http://blog.csdn.net/fengqiaoyebo2008/article/details/42963915 1. 功能介绍 1.1. Volley ...

  6. 超详细的Guava RateLimiter限流原理解析

    超详细的Guava RateLimiter限流原理解析  mp.weixin.qq.com 点击上方“方志朋”,选择“置顶或者星标” 你的关注意义重大! 限流是保护高并发系统的三把利器之一,另外两个是 ...

  7. vue&period;js响应式原理解析与实现

    vue.js响应式原理解析与实现 从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很 ...

  8. Tengine HTTPS原理解析、实践与调试【转】

    本文邀请阿里云CDN HTTPS技术专家金九,分享Tengine的一些HTTPS实践经验.内容主要有四个方面:HTTPS趋势.HTTPS基础.HTTPS实践.HTTPS调试. 一.HTTPS趋势 这一 ...

  9. tomcat原理解析&lpar;一&rpar;:一个简单的实现

    tomcat原理解析(一):一个简单的实现 https://blog.csdn.net/qiangcai/article/details/60583330 2017年03月07日 09:54:27 逆 ...

随机推荐

  1. arm嵌入式交叉编译工具链

    1.arm-linux-gcc 常用的参数:-o[制定输出文件名] -c[只到编译停止,不连接] -g[键入调试信息] -xO[优化级别] -w/W(警告等级) arm-linux-gcc -o de ...

  2. DelphiXE2 DataSnap开发技巧收集

    DelphiXE2 DataSnap开发技巧收集 作者:  2012-08-07 09:12:52     分类:Delphi     标签: 作为DelphiXE2 DataSnap开发的私家锦囊, ...

  3. mybatis动态SQL中的sql片段

    在mybatis中通过使用SQL片段可以提高代码的重用性,如下情景: 1.创建动态SQL <sql id="sql_count">select count(*)< ...

  4. unity3d与eclipse集成开发android应用

    原地址:http://blog.csdn.net/armoonwei/article/details/7032537 Unity as a Library Once you have eclipse ...

  5. Download SymmetricDS Data Sync Software for Free

    Download SymmetricDS Data Sync Software for Free Download SymmetricDS

  6. ListView的简单使用和性能优化

    起源:ListView是Android开发中使用最广泛的一种控件,它以垂直列表的形式显示所有列表项. 创建ListView有两种方式: ☆ 直接使用ListView进行创建. ☆让Activity继承 ...

  7. VC和gcc在保证功能static对线程安全的差异变量

    VC和gcc不同,不能保证静态变量的线程安全性.这就给我们的程序带来了非常大的安全隐患和诸多不便.这一点应该引起我们的重视!尤其是在构造函数耗时比較长的时候.非常可能给程序带来意想不到的结果.本文从測 ...

  8. process&period;argv

    返回进程启动时的命令行参数. 第一个元素是 process.execPath. 使用 process.argv0 可以获取 argv[0] 原始的值. 第二个元素是当前执行的 JavaScript 文 ...

  9. R语言输出高质量图片

    Rstudio画图之后保存的 图片格式如下 上面的几种格式可以直接插入word文档中,但是图片质量很低,锯齿感很明显.若生成PDF,为矢量图(不懂),但是不可以插入word文档中. 最简便的方法就是对 ...

  10. 二十六、Linux 进程与信号---system 函数 和进程状态切换

    26.1 system 函数 26.1.1 函数说明 system(执行shell 命令)相关函数 fork,execve,waitpid,popen #include <stdlib.h&gt ...