• 工作者队列原理解析(后台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机制搞清楚了. 时间就像乳沟一样, 挤挤总会有的, 现在算是从混沌中杀出一条路来了.

  • 相关阅读:
    MySQL数据库小结
    使用Python操作MySQL数据库
    MySQL索引原理
    MySQL性能分析之Explain
    201907 TIOBE 编程语言排行榜-Python坐稳第三
    MySQL索引的数据结构-B+树介绍
    MySQL多表查询综合练习答案
    MySQL记录操作
    MySQL多表查询
    javascript实现无缝上下滚动(转)
  • 原文地址:https://www.cnblogs.com/honpey/p/5061524.html
Copyright © 2020-2023  润新知