工作队列代码分析

1.   首先应该了解一下表示工作队列的结构体workqueue_struct,
     该结构体定义在文件kernel/workqueue.c中。定义如下:
     
     struct workqueue_struct {
         ...
         /* hot fields used during command issue, aligned to cacheline */
         unsigned int        flags ____cacheline_aligned; /* WQ: WQ_* flags */
         struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
         struct pool_workqueue __rcu *numa_pwq_tbl[]; /* FR: unbound pwqs indexed by node */
     };

     在该结构体中需要关注的最后两个成员,cpu_pwqs是一个每CPU类型
     的变量,它指向pool_workqueue类型的结构体,该结构体我们将在后
     面进行介绍。
     
     最后一个成员numa_pwq_tbl是对于位绑定到具体CPU的工作队列,给
     每一个节点(node)分配一个pool_workqueue结构体,这些结构体

     最终都指向了同一个worker_pool结构体。


2.   下面对pool_workqueue结构体进行介绍,pool_workqueue结构体定
     义如下:

     /*
      * The per-pool workqueue.  While queued, the lower WORK_STRUCT_FLAG_BITS
      * of work_struct->data are used for flags and the remaining high bits
      * point to the pwq; thus, pwqs need to be aligned at two's power of the
      * number of flag bits.
      */
     struct pool_workqueue {
         struct worker_pool  *pool;      /* I: the associated pool */
         struct workqueue_struct *wq;        /* I: the owning workqueue */
         ...
     } __aligned(1 << WORK_STRUCT_FLAG_BITS);

     为什么是1 << WORK_STRUCT_FLAG_BITS对齐的,在注释中已经说的
     非常清楚了。该结构体第一个成员pool指向与值相关联的worker_pool,
     第二个成员指向其所属的workqueue_struct,因此可以说pool_workqueue
     是workqueue_struct和worker_pool之间的桥梁。


3.   worker_pool用来表示工作者线程池,我们在内核文档中已经可以
     看到每个CPU包含两个线程池,因此可以找到下面的代码:

     /* the per-cpu worker pools */
     static DEFINE_PER_CPU_SHARED_ALIGNED(struct worker_pool [NR_STD_WORKER_POOLS],
                     cpu_worker_pools);

     其中NR_STD_WORKER_POOLS=2。

     未绑定的线程池都是动态分配的,对于未绑定的线程池,有一个哈
     希表与之对应:

     /* PL: hash of all unbound pools keyed by pool->attrs */
     static DEFINE_HASHTABLE(unbound_pool_hash, UNBOUND_POOL_HASH_ORDER); 

     另外还有一些未绑定线程池会使用的属性:

     /* I: attributes used when instantiating standard unbound pools on demand */
     static struct workqueue_attrs *unbound_std_wq_attrs[NR_STD_WORKER_POOLS]; 



4.   worker表示工作者线程,由worker_pool进行管理。所有的工作者线
     程都是基于普通的内核线程实现的,在工作队列中包含两种类型的
     线程:普通的工作者线程和rescuer工作者线程(用来服务特定的工
     作队列,例如设置了WQ_MEM_RECLAIM的工作队列就会有rescuer线程)。
     针对这两种工作者线程,内核分别提供了worker_thread函数和
     rescuer_thread函数。


     在workqueue.c的create_worker函数中可以发现下面的代码:

     worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
                                           "kworker/%s", id_buf);


5.   下面将分析worker_thread函数:

     if (unlikely(worker->flags & WORKER_DIE)) {
        spin_unlock_irq(&pool->lock);
        WARN_ON_ONCE(!list_empty(&worker->entry));
        worker->task->flags &= ~PF_WQ_WORKER;
        return 0;
     }

     检查当前的工作者线程是不是要被销毁了。

     worker_leave_idle(worker);
     recheck:
     /* no more worker necessary? */
     if (!need_more_worker(pool))
        goto sleep;

     首先将当前的工作者线程从worker_pool的idle链表上删除,然后再次
     检查是否真的需要更多的工作者线程。need_more_worker检测当前
     worker_pool中的worklist如果不为空,且worker_pool中运行的进程
     数为0,说明应当唤醒一个新的工作者线程。对于未绑定类型的工作者
     线程池,该函数总是返回true,只要该worker_pool的wroklist不为空。

     /* do we need to manage? */
     if (unlikely(!may_start_working(pool)) && manage_workers(worker))
        goto recheck;

     may_start_working函数在pool->nr_idle不为0时返回真,否则返回假。
     manage_workers返回false,表明当前可能有另外一个内核路径正在执
     行该函数,因此在这里就可以不用“管理”了,此时就可以安心的处理
     线程需要执行的任务。如果返回true,表明该函数已经释放了pool->lock
     并且重新获取,来执行一些管理函数,因此之前在获取pool->lock
     之后而验证的状态现在可能已经发生了变化,因此需要goto recheck。


     5.1 manage_workers
     管理工作者线程需要两个信号量:manager_abr和manager_mutex。前者
     决定谁是管理者,即第一个获取manager_abr的manage_workers函数是
     管理者,其他后来的函数直接退出并返回false,表明可以开始处理工
     作项了。而manager_mutex函数是用来互斥管理函数,因为manager_mutex
     可能在其他地方也会获取(即仅获取该信号,不获取manager_abr)。
     相应的代码如下:

     if (!mutex_trylock(&pool->manager_arb))
        return ret;

     /*
      * With manager arbitration won, manager_mutex would be free in
      * most cases.  trylock first without dropping @pool->lock.
      */
     if (unlikely(!mutex_trylock(&pool->manager_mutex))) {
         spin_unlock_irq(&pool->lock);
         mutex_lock(&pool->manager_mutex);
         spin_lock_irq(&pool->lock);
         ret = true;
     }

     对获取manager_mutex有点不理解,为什么要这样做?

     /*
      * Destroy and then create so that may_start_working() is true
      * on return.
      */
     ret |= maybe_destroy_workers(pool);

     销毁一个pool中空闲的时间超过IDLE_WORKER_TIMEOUT的工作者线程。


     5.1.1 maybe_destroy_workers

           while (too_many_workers(pool)) {

           如果要销毁某个线程池中的线程,必须首先检查该线程池中的空闲
           线程是否过多,too_many_workers中的代码如下:

           /*
            * nr_idle and idle_list may disagree if idle rebinding is in
            * progress.  Never return %true if idle_list is empty.
            */
           if (list_empty(&pool->idle_list))
               return false;
       
           return nr_idle > 2 && (nr_idle - 2) * MAX_IDLE_WORKERS_RATIO >= nr_busy;
      

           nr_idle与idle_list可能存在不一致的问题,因此如果idle_list
           为空一定要返回false,即不能销毁工作者线程。
           
           由后面的return语句可以看出,只有在空闲的线程数大于2,且空
           闲的线程个数大于1/4的正在运行的工作者线程的话,就应该销毁
           一些工作者线程。
      
           worker = list_entry(pool->idle_list.prev, struct worker, entry);
           expires = worker->last_active + IDLE_WORKER_TIMEOUT;
      
           if (time_before(jiffies, expires)) {
               mod_timer(&pool->idle_timer, expires);
               break;
           }
      
           destroy_worker(worker);
           ret = true;

           首先从空闲工作者线程链表的末尾取一个工作者线程,检查该工作
           者线程是否连续休息的时间已经超过了指定的时间。注意:从这里
           就可以看出链表尾端的工作者线程是空闲时间最长的工作者线程,
           如果空闲最长的工作者线程都没有过期的话,那么说明当前线程池
           中没有需要销毁的线程,将当前线程池空闲定时器修改后,直接break。
      
           如果空闲的工作者线程空闲的时间已经过期,调用destroy_worker
           函数来销毁该工作者线程。

     ret |= maybe_create_worker(pool);  

     如果必要的话,该函数在@pool中创建一个工作者线程,当从该
     函数返回时,可以保证@pool中至少有一个空闲的工作者线程。
     如果创建工作者线程的时间超过了MAYDAY_INTERVAL,就会向该
     @pool中worklist上的所有工作项对应的rescuer发送求救信号,
     来解决可能发生的死锁问题。

     if (!need_to_create_worker(pool))
        return false;

     首先检查是否真的需要创建线程,need_to_create_worker函数
     实现如下:

     return need_more_worker(pool) && !may_start_working(pool);

     need_more_worker的实现如下:

     return !list_empty(&pool->worklist) && __need_more_worker(pool);

     __need_more_worker的实现如下:
     return !atomic_read(&pool->nr_running);

     将上面层层的函数调用概括起来,need_to_create_worker所做的
     事情其实非常简单,即:

     * 检查当前@pool的worklist是否为空;(否)

     * 检查当前@pool中运行的工作者线程数是否为0;(是)

     * 检查当前@pool中的空闲的工作者线程数是否为0;(是)

     即当上面的三个条件满足后面括号中的值时,才可以创建新的工作
     者线程。

     /* if we don't make progress in MAYDAY_INITIAL_TIMEOUT, call for help */
     mod_timer(&pool->mayday_timer, jiffies + MAYDAY_INITIAL_TIMEOUT);

     设置定时器,如果在给定的MAYDAY_INITIAL_TIMEOUT时间内没有创
     建线程成功的话,就向rescuer线程发送求救信号。在init_worker_pool
     函数中可以找到注册该定时器的处理函数:
     setup_timer(&pool->mayday_timer, pool_mayday_timeout,
            (unsigned long)pool);

     该定时器处理函数为:pool_mayday_timeout。

     while (true) {
        struct worker *worker;

        worker = create_worker(pool);

     创建一个新的工作者线程,并设置该线程的相关属性,如绑定的CPU。
     在create_worker中调用函数kthread_create_on_node创建新的内
     核线程,并返回该线程的task_struct。

     start_worker(worker);

     运行新创建的工作者线程。

     5.2 

     do {
        struct work_struct *work =
            list_first_entry(&pool->worklist,
                     struct work_struct, entry);

     获取工作队列的第一个工作项。

     if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) {
     /* optimization path, not strictly necessary */
     process_one_work(worker, work);

     从这里开始,真正开始处理工作项,在process_one_work中可以
     发现下面的代码:
     worker->current_func = work->func;
     ...
     worker->current_func(work);

     可以看到直接调用执行工作项中的函数。

     if (unlikely(!list_empty(&worker->scheduled)))
         process_scheduled_works(worker);

     依次处理scheduled列表上的所有工作项。

     } else {
         move_linked_works(work, &worker->scheduled, NULL);
         process_scheduled_works(worker);
     }

     如果@work->data设置了WORK_STRUCT_LINKED标记,说明这是一
     个以@work开头的工作序列,需要一起进行调度,因此move_linked_works
     将以@work开头,都设置了WORK_STRUCT_LINKED连续的work都添
     加到@work->scheduled上,之后再一次进行处理。

     } while (keep_working(pool));

     就这样不断的进行处理,直到pool->worklist为空,或者当前
     工作者线程池中运行的线程数大于1。

6.   工作者线程在什么时候被调度?

     schedule_work
     |
     \--> queue_work
          |
          \--> queue_work_on
               |
               \--> __queue_work
                    |
                    \--> insert_work
                         |
                         \--> wake_up_worker

     在insert_work中,发现下面的代码:

     if (__need_more_worker(pool))
        wake_up_worker(pool);

     wake_up_worker唤醒@pool中的工作者线程开始执行。

你可能感兴趣的:(代码分析,workqueue,工作队列)