每一个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. 从零开始实现lmax-Disruptor队列(二)多消费者、消费者组间消费依赖原理解析

    MyDisruptor V2版本介绍 在v1版本的MyDisruptor实现单生产者.单消费者功能后.按照计划,v2版本的MyDisruptor需要支持多消费者和允许设置消费者组间的依赖关系. 由于该 ...

  2. 从零开始实现lmax-Disruptor队列(三)多线程消费者WorkerPool原理解析

    MyDisruptor V3版本介绍 在v2版本的MyDisruptor实现多消费者.消费者组间依赖功能后.按照计划,v3版本的MyDisruptor需要支持多线程消费者的功能. 由于该文属于系列博客 ...

  3. 从零开始实现lmax-Disruptor队列(四)多线程生产者MultiProducerSequencer原理解析

    MyDisruptor V4版本介绍 在v3版本的MyDisruptor实现多线程消费者后.按照计划,v4版本的MyDisruptor需要支持线程安全的多线程生产者功能. 由于该文属于系列博客的一部分 ...

  4. 从零开始实现lmax-Disruptor队列(五)Disruptor DSL风格API原理解析

    MyDisruptor V5版本介绍 在v4版本的MyDisruptor实现多线程生产者后.按照计划,v5版本的MyDisruptor需要支持更便于用户使用的DSL风格的API. 由于该文属于系列博客 ...

  5. 从零开始实现lmax-Disruptor队列(六)Disruptor 解决伪共享、消费者优雅停止实现原理解析

    MyDisruptor V6版本介绍 在v5版本的MyDisruptor实现DSL风格的API后.按照计划,v6版本的MyDisruptor作为最后一个版本,需要对MyDisruptor进行最终的一些 ...

  6. kafka原理解析

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

  7. java线程池原理解析

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

  8. RocketMQ架构原理解析(四):消息生产端(Producer)

    RocketMQ架构原理解析(一):整体架构 RocketMQ架构原理解析(二):消息存储(CommitLog) RocketMQ架构原理解析(三):消息索引(ConsumeQueue & I ...

  9. RocketMQ架构原理解析(一):整体架构

    RocketMQ架构原理解析(一):整体架构 RocketMQ架构原理解析(二):消息存储(CommitLog) RocketMQ架构原理解析(三):消息索引(ConsumeQueue & I ...

随机推荐

  1. ASP.NET MVC another entity of the same type already has the same primary key value

    ASP.NET MVC项目 Repository层中,Update.Delete总是失败 another entity of the same type already has the same pr ...

  2. ABAP程序系统字段中英文详解

    SY-SUBRC: 系统执行某指令后,表示执行成功与否的变量,’0’ 表示成功SY-DBLNT: 被处理过的记录的笔数 SY-UNAME: 当前使用者登入SAP的USERNAME;SY-DATUM: ...

  3. IOS 杂笔-19(属性与变量的优缺点)

    IOS 杂笔-19(属性与变量的优缺点) 在前面的文章中我介绍了属性与变量的区别.这篇博客我将会简单介绍一下属性与变量的优缺点. 变量 优点: 访问速度快 缺点: 使用不灵活 属性 缺点: 耗时 优点 ...

  4. Silverlight项目笔记8:层次布局、客户端读取shp、ExecuteCountAsync、柱状图、url传参

    1.层次布局 由于地图窗口和菜单栏都在一个父容器内,在浏览器缩小到一定程度点击地图弹出infoWindow时,会出现菜单栏遮挡infoWindow中间部分的现象,于是通过设置Canvas.ZIndex ...

  5. IOS之未解问题--给UITableView提取UITableViewDataSource并封装瘦身失败

    前言:阅读了<更轻量的 View Controllers>,发现笔者这个优化重构代码的想法真的很不错,可以使得抽取的UITableViewDataSource独立写在一个类文件里,并且也写 ...

  6. C++ 构造函数中调用虚函数

    我们知道:C++中的多态使得可以根据对象的真实类型(动态类型)调用不同的虚函数.这种调用都是对象已经构建完成的情况.那如果在构造函数中调用虚函数,会怎么样呢? 有这么一段代码: class A { p ...

  7. 团队管理_效率开会[持续更新ing]

    1.明确开会目的,这个会议是用来解决什么问题,得出什么结果. 2.明确会议内容与流程,简要说明会议分几个部分,一步一步推进会议的进行. 3.保证参会人员守时参加,会议准时开始. 4.保证会议时间尽量为 ...

  8. virtualbox 安装 虚拟机的时候报错不能创建新任务

    找到原因是因为自己的windows是破解的, 找到C:\Windows\system32\uxtheme.dll这个文件,我的破解的windows在这里自带了一个uxtheme.dll.backup的 ...

  9. MySQL 如何修改字符集 utf8 改为 utf8mb4

    在实行sql server 向 mysql 迁移数据时,报错: Incorrect string value: '\xF0\x9F\x98\x8A' 原因是mysql 采用的是 utf8 的字符集,而 ...

  10. web前端开发教程系列-2 - 前端开发书籍分享(转)

    目录: 前言 一. CSS 二. JavaScript 三. jQuery 四. 后记   前言 前端书籍在每个商城或书架上面都是琳琅满目,很多初学者又不能很好的判断书的质量或层次.因为今天给同学们分 ...