工作者队列原理解析(后台writeback)
每一个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)的更多相关文章
- 从零开始实现lmax-Disruptor队列(二)多消费者、消费者组间消费依赖原理解析
		MyDisruptor V2版本介绍 在v1版本的MyDisruptor实现单生产者.单消费者功能后.按照计划,v2版本的MyDisruptor需要支持多消费者和允许设置消费者组间的依赖关系. 由于该 ... 
- 从零开始实现lmax-Disruptor队列(三)多线程消费者WorkerPool原理解析
		MyDisruptor V3版本介绍 在v2版本的MyDisruptor实现多消费者.消费者组间依赖功能后.按照计划,v3版本的MyDisruptor需要支持多线程消费者的功能. 由于该文属于系列博客 ... 
- 从零开始实现lmax-Disruptor队列(四)多线程生产者MultiProducerSequencer原理解析
		MyDisruptor V4版本介绍 在v3版本的MyDisruptor实现多线程消费者后.按照计划,v4版本的MyDisruptor需要支持线程安全的多线程生产者功能. 由于该文属于系列博客的一部分 ... 
- 从零开始实现lmax-Disruptor队列(五)Disruptor DSL风格API原理解析
		MyDisruptor V5版本介绍 在v4版本的MyDisruptor实现多线程生产者后.按照计划,v5版本的MyDisruptor需要支持更便于用户使用的DSL风格的API. 由于该文属于系列博客 ... 
- 从零开始实现lmax-Disruptor队列(六)Disruptor 解决伪共享、消费者优雅停止实现原理解析
		MyDisruptor V6版本介绍 在v5版本的MyDisruptor实现DSL风格的API后.按照计划,v6版本的MyDisruptor作为最后一个版本,需要对MyDisruptor进行最终的一些 ... 
- kafka原理解析
		两张图读懂kafka应用: Kafka 中的术语 broker:中间的kafka cluster,存储消息,是由多个server组成的集群. topic:kafka给消息提供的分类方式.broker用 ... 
- java线程池原理解析
		五一假期大雄看了一本<java并发编程艺术>,了解了线程池的基本工作流程,竟然发现线程池工作原理和互联网公司运作模式十分相似. 线程池处理流程 原理解析 互联网公司与线程池的关系 这里用一 ... 
- RocketMQ架构原理解析(四):消息生产端(Producer)
		RocketMQ架构原理解析(一):整体架构 RocketMQ架构原理解析(二):消息存储(CommitLog) RocketMQ架构原理解析(三):消息索引(ConsumeQueue & I ... 
- RocketMQ架构原理解析(一):整体架构
		RocketMQ架构原理解析(一):整体架构 RocketMQ架构原理解析(二):消息存储(CommitLog) RocketMQ架构原理解析(三):消息索引(ConsumeQueue & I ... 
随机推荐
- SharePoint 禁用本地回环的两个方法
			有两种方法中,若要变通解决此问题,请根据您的具体情况使用下列方法之一. 方法 1: 指定主机名 (如果需要 NTLM 身份验证,请首选方法) 指定的主机名的映射到环回地址,并可以连接到 Web 站点在 ... 
- 【转】R语言笔记--颜色的使用
			转自该网站:http://research.stowers-institute.org/efg/R/Color/Chart/ 科学可视化中常用的一些颜色表:http://geog.uoregon.ed ... 
- GitHub 基本常用知识解答2
			1.如何拥有一个Git仓库的两种途径 (1)在已有的目录中,初始化一个新的. (2) 比如一个新的项目,或者一个已存在的项目,但该项目尚未有版本控制.如果你想要复制一份别人的项目, 或者与别人合作某个 ... 
- Android底部TabHost API
			今天在项目中遇到了底部TabHost,顺便就写了一个底部TabHost的api继承即可使用非常简单,以下为源代码: 首先是自定义的TabHostActivity,如果要使用该TabHost继承该类即可 ... 
- android 显示意图
			//显示意图 public void enter(View view) { Intent intent = new Intent();//创建一个空的意图 intent.setClassName(ge ... 
- XAlign:用于代码对齐的Xcode插件
			除下面的插件对齐,xcode自带有cmd+v,在没有复制或者剪切的情况下,直接按cmd+v会有着对齐参数标签冒号的作用. XAlign 是一个 Xcode 的实用插件,用于对齐规范代码.除了插件作者 ... 
- js检测浏览器型号
			公司要求做内部统计,要求监控客服玩游戏使用的浏览器的型号,是火狐的.谷歌的.还是IE的等等. [code lang="javascript"] /**** * 目前识别范围 * M ... 
- JavaScript Patterns 3.8 Error Objects
			The error objects created by constructors(Error(), SyntaxError(), TypeError(), and others) have the ... 
- 长文件名导致的0x80070057
			今天遇到件怪事. 把一个视频集(86G)从电脑硬盘转移动硬盘的时候里面时报里面的两个文件夹里的视频和字幕不能复制 错误代码0x80070057 这个视频集是从校内PT是下下来的,电脑是联想Y560-w ... 
- jacob 实现Office Word文件格式转换
			关于jacob用法,百度一下就会发现几乎都是复制2004年一个代码,那段代码实现的是从一个目录读取所有doc文件,然后把它转html格式. 为了便习学习和使用,我把代码看懂后精简了一下,得出不少新结论 ... 
