Linux内核工作队列workqueue分析(七)

1.概述

工作队列(workqueue)是除了软中断softirq和小任务tasklet以外最常用的一种中断下半部分机制,由内核统一管理。工作队列把推迟执行的任务交给内核线程来执行,其运行在进程上下文,允许重新调度、睡眠。工作队列解决了软中断和tasklet执行时间过长导致系统实时性下降的问题,同时避免了驱动模块自身创建线程导致内核线程过多的问题。
早期的工作队列设计的比较简单,由多线程(Multi threaded,每个CPU默认一个工作线程)和单线程(Single threaded,用户自行创建的工作线程)组成,在使用中,出现了如下问题:
(1)内核线程数量太多。虽然系统中默认有一套工作线程,但有很多工程师系统喜欢自行创建工作线程,对于CPU数量比较多的机器,系统启动完可能就耗尽了PID资源。
(2)并发性比较差。Multi threaded的工作线程和CPU是一一绑定的,某个本地CPU上的工作任务是串行执行的。如某个线程上的工作任务发生了睡眠,之后的工作任务只能等待,前一个任务没有执行完成,则后一个任务永远无法得到执行,也不能迁移到其他空闲CPU上执行。
(3)死锁问题。如果有很多的工作任务运行在系统默认的工作队列上,并且他们有一些数据依赖关系,那么很有可能产生死锁问题。
为了解决上述问题,Linux内核引入了concurrency-managed workqueues(CWMQ),和旧的workqueue接口兼容,明确划分了workqueue的前端接口和后端实现机制。CWMQ提出了工作线程池(worker_pool)概念,不和特定的工作队列关联。工作线程池有两种。一种和具体CPU绑定,为Per-CPU类型,有两个线程池,一个给高优先级的work使用,另一个给低优先级的work使用。另外一种不和具体CPU绑定,可以运行在任意CPU上,工作线程池中的线程是动态分配和管理,线程数量不固定,缺省情况下会创建一个线程来处理工作任务。用户在使用workqueue时,无需关心放在哪个CPU上执行,也无需再创建额外的线程,只需要设置相关flag和优先级,内核会将工作任务放在合适的线程池中执行。当某个工作任务阻塞时,CWMQ会唤醒或创建新的线程执行后续的工作任务,以提高并发效率。

2.工作队列数据结构

工作队列的工作任务用work_struct描述,是工作队列处理工作任务的最小单位。data的存放内容由WORK_STRUCT_PWQ标志位决定。entry是串联工作任务的链表指针。func是处理工作任务的回调函数。
关于工作任务work的data成员设置场景(32位CPU):当调用schedule_work函数将work插入到工作队列中时,将WORK_STRUCT_PENDINGWORK_STRUCT_PWQ标记设置到data的低8位,同时将pool_workqueue的指针设置到data的高24位,pool_workqueue结构体按256字节对其,低8位地址可以忽略;当调用process_one_work函数执行work的回调函数之前,将低8位的标记清除,即清除WORK_STRUCT_PENDINGWORK_STRUCT_PWQ标记,将worker_pool的id设置到data的高27位,低5位用来存放标记。当调用cancel_work_sync删除work时,清除WORK_STRUCT_PENDINGWORK_STRUCT_PWQ标记,将WORK_STRUCT_NO_POOL标记设置到data中。WORK_STRUCT_PENDING标记同步work的插入和删除。

    [include/linux/workqueue.h]
    struct work_struct {
        // 低比特部分存放work的标志位,剩余的存放上一次运行worker_pool的ID号或pool_workqueue的指针
        atomic_long_t data; 
        struct list_head entry;  // 串联工作任务的指针
        work_func_t func;  // 工作任务的回调函数
        ......
    };
    // 工作任务的回调函数类型
    typedef void (*work_func_t)(struct work_struct *work);  

处理工作任务的工作线程用worker描述,每个worker都对应一个内核线程。worker根据工作状态,可以添加到worker_pool的空闲链表和忙碌链表中。处于空闲状态的workr收到工作处理请求后,将唤醒workr描述的内核线程。

    [kernel/workqueue_internal.h]
    struct worker {
        union {
            /* 如果worker处于idle状态,则将entry挂到worker_pool的idle_list链表中 */
            struct list_head	entry;
            /* 如果worker处于busy状态,则将hentry添加到worker_pool的busy_hash哈希表中 */
            struct hlist_node	hentry;
        };
        struct work_struct	*current_work;	  /* 当前正在处理的工作 */
        work_func_t		current_func;	      /* 当前正在执行的work回调函数 */
        struct pool_workqueue	*current_pwq; /* 当前work所属的pool_workqueue */
        bool			desc_valid;	          /* 字符数组desc是否有效 */
        /* 所有被调度并正准备执行的work都挂入该链表中,只要挂入此链表中的工作任务会被worker处理 */
        struct list_head	scheduled;
        struct task_struct	*task;		      /* 该工作线程的task_struct结构体,调度的实体 */
        struct worker_pool	*pool;		      /* 该工作线程所属的worker_pool */
        struct list_head	node;		      /* 挂到worker_pool->workers链表中 */
        /* 最近一次运行的时间戳,用于判定该工作者线程是否可以被destory时使用 */
        unsigned long		last_active;	  
        unsigned int		flags;		      /* 标志位 */
        int			id;		                  /* 工作线程的ID号,用ps命令在用户空间可以看到具体的值 */
        char			desc[WORKER_DESC_LEN];/* 工作线程的描述说明*/  
        struct workqueue_struct	*rescue_wq;	  /* I: the workqueue to rescue */
    };

工作线程池使用worker_pool描述,管理多个worker描述的内核线程。针对和CPU绑定的工作队列,worker_pool是Per-CPU类型,每个CPU都有两个worker_pool,对应不同的优先级,nice值分别为0和-20。针对不和CPU绑定的工作队列,worker_pool创建后会添加到unbound_pool_hash哈希表中。worker_pool管理一个空闲链表和忙碌链表,其中忙碌链表由哈希表管理。DEFINE_PER_CPU_SHARED_ALIGNED定义了2个Per-CPU类型的worker_poolNR_STD_WORKER_POOLS为2。

    [kernel/workqueue.c]
    struct worker_pool {
        spinlock_t		lock;		        /* 保护worker_pool的自旋锁 */
        int			cpu;		            /* 绑定类型-绑定的CPU ID,非绑定类型为-1 */
        int			node;		            /* 非绑定类型-表示该worker_pool所属内存节点的ID编号 */
        int			id;		                /* I: worker_pool的ID号 */
        unsigned int		flags;		    /* X: flags */
        unsigned long		watchdog_ts;	/* L: watchdog timestamp */
        struct list_head	worklist;	    /* pending状态的工作任务挂到此链表中 */
        int			nr_workers;	            /* 线程(worker)数量 */
        int			nr_idle;	            /* 空闲线程数量 */
        struct list_head	idle_list;	    /* idle状态的线程挂到此链表中 */
        struct timer_list	idle_timer;	    /* L: 任务的空闲超时时间 */
        struct timer_list	mayday_timer;	/* L: SOS timer for workers */
        DECLARE_HASHTABLE(busy_hash, BUSY_WORKER_HASH_ORDER);  /* busy状态的线程添加到本哈希表中 */
        struct mutex		manager_arb;	/* manager arbitration */
        struct worker		*manager;	    /* L: purely informational */
        struct mutex		attach_mutex;	/* worker绑定和解绑的互斥锁 */
        struct list_head	workers;	    /* worker_pool管理的所有worker添加到本链表中 */
        struct completion	*detach_completion; /* all workers detached */
        struct workqueue_attrs	*attrs;		/* 工作线程的属性 */
        struct hlist_node	hash_node;	    /* 用于添加到unbound_pool_hash中 */
        // 用于管理worker线程的创建和销毁,表示正在运行中的worker数量
        atomic_t		nr_running ____cacheline_aligned_in_smp;
    } ____cacheline_aligned_in_smp  // SMP系统中高速缓存对齐
     // 静态定义和CPU绑定的worker_pool,每个CPU有两个,名称为cpu_worker_pools
    static DEFINE_PER_CPU_SHARED_ALIGNED(struct worker_pool [NR_STD_WORKER_POOLS], cpu_worker_pools);  

pool_workqueue数据结构充当纽带作用,用于将workqueue_structworker_pool连接起来。pool_workqueue结构体分配内存时按256字节对齐,内存地址的低8位可以存放其他内容。

    [kernel/workqueue.c]
    struct pool_workqueue {
        struct worker_pool	*pool;		/* 指向worker_pool的指针 */
        struct workqueue_struct *wq;    /* 指向所属的工作队列 */
        int			work_color;	/* L: current color */
        int			flush_color;	/* L: flushing color */
        int			refcnt;		/* 引用计数,若为0,则说明此pool_workqueue将被释放 */
        int			nr_in_flight[WORK_NR_COLORS]; /* L: nr of in_flight works */
        int			nr_active;	/* 活跃的works数量 */
        int			max_active;	/* 活跃的works最大数量 */
        struct list_head	delayed_works;	/* 延迟的works可以挂入该链表 */
        struct list_head	pwqs_node;	/* WR: node on wq->pwqs */
        struct list_head	mayday_node;	/* MD: node on wq->maydays */
        struct work_struct	unbound_release_work;
        struct rcu_head		rcu;
    } __aligned(1 << WORK_STRUCT_FLAG_BITS)  // 256字节对齐

系统中所有不和CPU绑定的工作队列,包括系统默认的工作队列,例如system_wqsystem_highpri_wq等,以及驱动开发者新创建的工作队列,共享一组worker_pool。对于绑定CPU的工作队列,每个CPU拥有两个线程池,每个线程池可以对应多个工作队列。工作队列由workqueue_struct描述。

    [kernel/workqueue.c]
    struct workqueue_struct {
        struct list_head	pwqs;		/* 所有的pool-workqueue数据结构都挂入该链表 */
        struct list_head	list;		/* 系统定义了全局的链表workqueue,所有workqueue挂入该链表 */
        struct list_head	maydays;	/* 所有rescue状态下的pool-workqueue挂入该链表 */
        /* rescue内核线程。内存紧张时创建新的工作线程可能会失败,如果创建workqueue时设置了WQ_MEM_RECLAIM,
           那么rescuer线程会接管这种情况 */
        struct worker		*rescuer;
        struct workqueue_attrs	*unbound_attrs;	/* 未绑定CPU工作队列属性 */
        struct pool_workqueue	*dfl_pwq;	/* 指向未绑定CPU工作队列的pool_workqueue */
        char			name[WQ_NAME_LEN]; /* 工作队列的名字 */
        /* 标志位经常被不同的CPU访问,因此要和cache line对其。标志位包括WQ_UNBOUND、
           WQ_HIGHPRI、WQ_FREEZABLE等 */
        unsigned int		flags ____cacheline_aligned; /* WQ: WQ_* flags */
        struct pool_workqueue __percpu *cpu_pwqs; /* 指向Per-CPU类型的pool_workqueue */
    };

BOUND类型的工作队列数据结构关系如下图所示。
BOUND类型的工作队列数据结构关系
UNBOUND类型的工作队列数据结构关系如下图所示,工作线程池、工作线程、工作任务之间关系和上图类似。
UNBOUND类型的工作队列数据结构关系

3.初始化工作队列

Linux内核使用init_workqueues初始化工作队列。首先遍历系统中可用的CPU,设置CPU的两个线程池worker_pool,一个为普通优先级,线程nice值为0,另一个为高优先级,线程nice值为-20,同时为每个线程池worker_pool分配ID。接着针对每个online CPU(可用的CPU)的线程池创建内核线程并设置线程池标志为~POOL_DISASSOCIATED,即此线程池内的线程是和CPU绑定的。然后创建UNBOUND工作队列的属性,工作队列属性workqueue_attrs主要描述内核线程的nice值、cpumask值(允许在哪些CPU上执行)及NUMA系统的亲和性,Per-CPU类型的工作队列只能在一个CPU上执行,nice值采用WQ_HIGHPRI或0来设置,因此Per-CPU类型的工作队列不需要设置workqueue_attrs的属性。最后使用alloc_workqueue宏创建了系统默认的工作队列。

    [include/linux/cpumask.h]
    // 遍历系统中可能(cpu_possible_mask)的CPU(最多可以有多少个CPU,包含可热插拔的CPU)
    #define for_each_possible_cpu(cpu) for_each_cpu((cpu), cpu_possible_mask)
    #define for_each_cpu(cpu, mask)				\
        for ((cpu) = -1;				\
            (cpu) = cpumask_next((cpu), (mask)),	\
            (cpu) < nr_cpu_ids;)

    [kernel/workqueue.c]
    NR_STD_WORKER_POOLS	= 2,		/* # standard pools per cpu */
    HIGHPRI_NICE_LEVEL	= MIN_NICE,
    #define MIN_NICE	-20
    // 遍历和CPU绑定的Per-CPU类型的worker_pool,每个CPU对用两个worker_pool,
    // cpu_worker_pools由宏DEFINE_PER_CPU_SHARED_ALIGNED静态定义
    #define for_each_cpu_worker_pool(pool, cpu)				\
        for ((pool) = &per_cpu(cpu_worker_pools, cpu)[0];		\
            (pool) < &per_cpu(cpu_worker_pools, cpu)[NR_STD_WORKER_POOLS]; \
            (pool)++)

    init_workqueues  // 系统的workqueues的初始化函数
      std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL }  // 设置优先级
      ->alloc_cpumask_var(&wq_unbound_cpumask, GFP_KERNEL)  // 分配CPU掩码位图内存
      // 将cpu_possible_mask拷贝到wq_unbound_cpumask指向的内存中
      ->cpumask_copy(wq_unbound_cpumask, cpu_possible_mask)
      ->KMEM_CACHE    // 创建一个pool_workqueue数据结构的slab缓存对象
      // 注册CPU事件的通知链,主要用于处理CPU热插拔时候,将该CPU上的工作队列迁移到online的CPU上 
      // 在CMWQ中,将这种机制叫做trustee
      ->cpu_notifier(workqueue_cpu_up_callback, CPU_PRI_WORKQUEUE_UP)
      ->wq_numa_init  // 处理NUMA系统的情况
      for_each_possible_cpu(cpu) { // 遍历系统中可用(cpu_possible_mask)的CPU
          struct worker_pool *pool;
          i = 0;
          // 遍历并设置Per-CPU类型的worker_pool,一个CPU对应两个worker-pool
          for_each_cpu_worker_pool(pool, cpu) {
              // 初始化worker-pool,设置工作线程空闲超时时执行的函数为idle_worker_timeout
              // 线程空闲的超时时间为300秒
              BUG_ON(init_worker_pool(pool));  
              pool->cpu = cpu;  // 设置关联的CPU
              cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu)); // 拷贝CPU掩码
              // 设置worker-pool线程的nice属性,第一个为0(低优先级),第二个为-20(高优先级)
              pool->attrs->nice = std_nice[i++];
              pool->node = cpu_to_node(cpu);  // 将CPU编号转换为节点
              /* 分配worker-pool的ID */
              mutex_lock(&wq_pool_mutex);
              BUG_ON(worker_pool_assign_id(pool));
              mutex_unlock(&wq_pool_mutex);
          }
      }
      // 对每个online CPU的两个线程池分别创建一个线程worker,创建线程的函数为create_worker
      for_each_online_cpu(cpu) {
          struct worker_pool *pool;
          for_each_cpu_worker_pool(pool, cpu) {
              // 设置worker_pool的标志,表示此worker_pool和CPU绑定
              pool->flags &= ~POOL_DISASSOCIATED;
              // 给Per-CPU类型的worker_pool创建内核线程
              create_worker(pool)
          }
      }
      // 创建UNBOUND工作队列workqueue_attrs的属性,主要描述内核线程的nice值、cpumask值
      // (允许在哪些CPU上执行)及NUMA系统的亲和性。Per-CPU类型的工作队列只能在一个CPU上执行,
      // nice值采用WQ_HIGHPRI来设置,因此Per-CPU类型的工作队列不需要设置workqueue_attrs的属性。
      // UNBOUND类型工作队列属性包含两种,分别为默认属性和ordered属性,ordered属性的
      // workqueue严格按照顺序执行,不存在并发问题。
      for (i = 0; i < NR_STD_WORKER_POOLS; i++) {
          struct workqueue_attrs *attrs;
          BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
          attrs->nice = std_nice[i];
          unbound_std_wq_attrs[i] = attrs;  // unbound工作队列默认属性
          BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
          attrs->nice = std_nice[i];
          attrs->no_numa = true;
          ordered_wq_attrs[i] = attrs;  // unbound工作队列ordered属性
      }      
      // 下面开始分配系统默认的工作队列workqueue,创建workqueue的函数为alloc_workqueue
      // 普通优先级类型的工作队列system_wq,名称为events,schedule[_delayed]_work[_on]
      // 函数添加的work就是填到此工作队列中,system_wq工作队列要求添加的work运行时间不宜过长
      system_wq = alloc_workqueue("events", 0, 0);
      // system_highpri_wq和system_wq类似,只是添加的work优先级较高
      system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
      // system_long_wq和system_wq类似,只是添加的work运行时间较长
      system_long_wq = alloc_workqueue("events_long", 0, 0);
      // system_unbound_wq是UNBOUND类型的工作队列
      system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, 
                          WQ_UNBOUND_MAX_ACTIVE);
      //system_freezable_wq和system_wq类似,可用于工作队列suspend时可冻结的work
      system_freezable_wq = alloc_workqueue("events_freezable", WQ_FREEZABLE, 0);
      // system_power_efficient_wq可用于节能而牺牲性能的work,当wq_power_efficient使能后,
      // system_power_efficient_wq可转为WQ_UNBOUND类型以节省电力消耗
      // WQ_POWER_EFFICIENT标记的工作队列默认是Per-CPU类型的
      system_power_efficient_wq = alloc_workqueue("events_power_efficient", 
                    WQ_POWER_EFFICIENT, 0);
      // 可用于节能和工作队列Suspend时可冻结的work
      // 当WQ_POWER_EFFICIENT禁止后,system_freezable_power_efficient_wq和system_wq相同
      system_freezable_power_efficient_wq = alloc_workqueue(
          "events_freezable_power_efficient", WQ_FREEZABLE | WQ_POWER_EFFICIENT, 0);
      ->wq_watchdog_init  // 初始化工作队列的看门狗

create_worker用来创建内核线程worker。首先根据线程池类型,设置线程池内线程的名称;接着在本地内存节点分配一个task_struct结构体,并将线程的入口函数设置为worker_thread;接着将创建的内核线程worker和线程池worker_pool绑定;最后设置线程数量信息。

    [kernel/workqueue.c]
    create_worker(pool)
      ->ida_simple_get  // 通过IDA子系统获取ID号并设置到worker_ida中,可用于内核线程名称
      ->alloc_worker  // 在worker_pool的node节点中分配一个worker结构体
      worker->pool = pool  // 使worker的pool指向worker属于的worker_pool
      // pool->cpu >= 0表示BOUND类型的工作线程。worker的名字一般是"kworker/+CPU_ID
      // +:+worker_id",如果属于高优先级类型的wirkqueue(nice值小于0),末尾还要加上H。
      // pool->cpu < 0表示UNBOUND类型的工作线程(UNBOUND类型的工作线程的pool->cpu设置为-1),
      // worker的名字一般是"kworker/u+CPU_ID+:+worker_id"
      if (pool->cpu >= 0)  // 给和CPU绑定的worker_pool的worker设置线程名称
          snprintf(id_buf, sizeof(id_buf), "%d:%d%s", pool->cpu, id,
            pool->attrs->nice < 0  ? "H" : "");
      else  // 给不和CPU绑定的worker_pool的worker设置线程名称
          snprintf(id_buf, sizeof(id_buf), "u%d:%d", pool->id, id); 
      // 在本地内存节点分配一个task_struct结构体并创建一个内核线程,线程的入口函数为worker_thread
      ->kthread_create_on_node              
      ->set_user_nice  // 根据worker_pool的attrs属性设置创建线程的nice值
      ->kthread_bind_mask  // 根据worker_pool的attrs属性设置创建线程的cpumask
      ->worker_attach_to_pool  // 将内核线程worker绑定到worker_pool
        ->mutex_lock  // 互斥锁attach_mutex加锁
        // 设置此线程和CPU的亲和性,若cpumask中没有任何online CPU,则设置会失败
        ->set_cpus_allowed_ptr(worker->task, pool->attrs->cpumask)
        // POOL_DISASSOCIATED是worker_pool内部使用的标志,一个worker_pool可以是associated状态
        // 或者disassociated状态。associated状态表示worker_pool绑定了某个CPU,disassociated状态
        // 表示没有绑定某个CPU,也有可能是绑定的CPU被offline了,因此可以在任意CPU上运行,此处应是
        // associated状态
        if (pool->flags & POOL_DISASSOCIATED)
            worker->flags |= WORKER_UNBOUND;   
        // 将worker的node挂到worker_pool的workers链表中,实现了worker绑定到worker_pool上
        ->list_add_tail(&worker->node, &pool->workers)       
        ->mutex_unlock  // 互斥锁attach_mutex解锁
      ->spin_lock_irq(&pool->lock)  // 自旋锁lock加锁                                 
      worker->pool->nr_workers++    // worker的数量加1
      ->worker_enter_idle  // 将worker设置为idle状态
        worker->flags |= WORKER_IDLE  // 设置WORKER_IDLE标志
        pool->nr_idle++  // worker_pool空闲线程数量加1
        worker->last_active = jiffies  // 设置最近一次运行的时间戳
        // 将空闲的worker挂到worker_pool的idle_list链表中
        ->list_add(&worker->entry, &pool->idle_list)
        // too_many_workers返回true的条件是:空闲线程数量大于2且空闲线程数量减2乘以4大于等于
        // 忙碌线程数量。timer_pending返回true的条件是:有定时处于pending状态
        if (too_many_workers(pool) && !timer_pending(&pool->idle_timer))
            // 修正idle_timer定时的时间,IDLE_WORKER_TIMEOUT为300*HZ,如HZ为100,则时间为5分钟
            mod_timer(&pool->idle_timer, jiffies + IDLE_WORKER_TIMEOUT);
      ->wake_up_process  // 唤醒创建的内线线程worker
      ->spin_unlock_irq(&pool->lock)  // 自旋锁lock解锁

创建工作队列workqueue的函数很多,并且基本上和旧版本的workqueue兼容。最通用的一个函数是alloc_workqueue,分别有3个参数,分别是nameflagsmax_active,其他函数都是对workqueue的封装,主要区别是flags不同。主要的flags如下:
(1)WQ_UNBOUND:工作任务work会加入UNBOUND线程池中,UNBOUND工作队列线程没有和具体的CPU绑定,不需要额外的同步措施,但会损失因局部性原理带来的性能提升。比较适合以下场景:
(a)某些内核组件会在不同的CPU上运行,如果创建BOUND类型工作队列,会创建很多线程。
(b)长时间运行的内核组件(标记WQ_CPU_INTENSIVE标志位)通常会创建UNBOUND类型的工作队列,内核调度器会自动选择此工作队列内核线程运行的CPU。
(2)WQ_FREEZABLE:电源管理相关的标志,此类工作队列会参与到系统的suspend过程,系统suspend后,工作线程处理完当前所有的work后冻结。一旦冻结完成,不会再执行新的work,直到解冻。
(3)WQ_MEM_RECLAIM:当内存紧张时,在此类工作队列内创建新的工作线程可能会失败,此时系统的rescuer内核线程会接管这种情况。
(4)WQ_HIGHPRI:此工作队列内的线程优先级较高。
(5)WQ_CPU_INTENSIVE:此类工作队列中的内核线程执行特别消耗CPU资源的work,这类work的执行线程由调度器进行调度。排在这类work后面non-CPU-intensive类型的work会推迟执行。
(6)__WQ_ORDERED:此类工作队列中的线程同一时间只能执行一个work。
参数max_active决定了每个CPU最多可以有多少个work挂入一个工作队列中。例如max_active=16,则说明每个CPU最多可以有16个work挂入到工作队列中执行。对于BOUND类型的工作队列,max_active最大可以是512,如果max_active被设置为0,则表示指定max_active为256.对于UNBOUND类型工作队列,max_active最大值为512和4*num_possible_cpus之间的最大值。通常建议将max_active设为0,如希望work被串行执行,则考虑使用max_active=1和WQ_UNBOUND的组合。

    [include/linux/workqueue.h]
    #define alloc_workqueue(fmt, flags, max_active, args...)		\
        __alloc_workqueue_key((fmt), (flags), (max_active),		\
                    NULL, NULL, ##args)
    #define alloc_ordered_workqueue(fmt, flags, args...)			\
        // `max_active`为1
        alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED | (flags), 1, ##args)
    #define create_workqueue(name)						\
        alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))
    #define create_freezable_workqueue(name)				\
        alloc_workqueue("%s", __WQ_LEGACY | WQ_FREEZABLE | WQ_UNBOUND |	\
                WQ_MEM_RECLAIM, 1, (name))
    #define create_singlethread_workqueue(name)				\
        alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)

下面重点分析一下__alloc_workqueue_keyWQ_POWER_EFFICIENT标志位和wq_power_efficient共同影响系统的功耗。当系统定义CONFIG_WQ_POWER_EFFICIENT_DEFAULT选项时,wq_power_efficient为1。BOUND类型的线程池worker_pool内的线程只会在一个CPU上执行,由于局部性原理,性能会得到提高,但会增加能量消耗。UNBOUND类型线程池中的work由那个CPU执行,由调度器决定,调度器调度的时候会考虑CPU的状态,尽量使空闲的CPU(idle)保持空闲状态,以降低功耗。对于工作队列workqueue,如果工作队列中有WQ_UNBOUND标记,则说明此workqueue是Per-CPU类型的,运行在那个CPU上初始化时就指定了,不由调度器控制。工作队列workqueue要在性能和能耗之间平衡,既要获得更好的性能(尽量让Per-CPU的线程池处理worker),也要降低能耗(让空闲的CPU继续保持空闲,而不是反复在空闲、忙碌状态中切换)。

    [include/linux/workqueue.h]
    WQ_MAX_ACTIVE		= 512,	  /* I like 512, better ideas? */
    WQ_DFL_ACTIVE		= WQ_MAX_ACTIVE / 2,
    [kernel/workqueue.c]
    __alloc_workqueue_key
      // 如果flags中包含了WQ_POWER_EFFICIENT,且系统配置了CONFIG_WQ_POWER_EFFICIENT_DEFAULT,
      // 则向workqueue中的flags设置WQ_UNBOUND标记
      if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
          flags |= WQ_UNBOUND;
      // 如果是UNBOUND类型,则还需要分配额外的空间用来保存NUMA节点信息,SMP系统中nr_node_ids为1
      if (flags & WQ_UNBOUND)
          tbl_size = nr_node_ids * sizeof(wq->numa_pwq_tbl[0]);
      ->kzalloc  // 分配workqueue_struct结构体占用的内存
      // 如果有WQ_UNBOUND标志,则说明是UNBOUND类型的workqueue,则需要分配属性内存空间
      if (flags & WQ_UNBOUND) {  
          wq->unbound_attrs = alloc_workqueue_attrs(GFP_KERNEL);
          if (!wq->unbound_attrs)
              goto err_free_wq;
      }
      // 如果max_active为0,则设置max_active为256
      max_active = max_active ?: WQ_DFL_ACTIVE;
      // 如果是UNBOUND类型,max_active最大值为512和4*num_possible_cpus之间的最大值
      max_active = wq_clamp_max_active(max_active, flags, wq->name);
      wq->flags = flags  // 设置workqueue的标志
      wq->saved_max_active = max_active  // 设置workqueue的最大in-flight work数量
      ->alloc_and_link_pwqs
        bool highpri = wq->flags & WQ_HIGHPRI  // workqueue是否为高优先级
        // 如果不含有WQ_UNBOUND标志,则说明此workqueue为BOUND类型
        if (!(wq->flags & WQ_UNBOUND)) {  
            // 对于BOUND类型的workqueue,则对每一个CPU分配一个pool_workqueue结构体
            // cpu_pwqs是一个Per-CPU类型的指针
            wq->cpu_pwqs = alloc_percpu(struct pool_workqueue);
            if (!wq->cpu_pwqs)
                return -ENOMEM;
            // 对于每个CPU,
            for_each_possible_cpu(cpu) {
                // 获取CPU对应的pool_workqueue指针
                struct pool_workqueue *pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
                // 获取CPU对应的worker_pool指针,cpu_worker_pools是静态定义Per-CPU
                // 类型的worker_pool
                struct worker_pool *cpu_pools = per_cpu(cpu_worker_pools, cpu);
                init_pwq(pwq, wq, &cpu_pools[highpri]);
                  ->memset(pwq, 0, sizeof(*pwq))  // 将pool_workqueue清零
                  pwq->pool = pool  // 使pool_workqueue中的pool指针指向worker_pool
                  pwq->wq = wq  // 使pool_workqueue中的wq指针指向workqueue_struct
                  // 初始化pool_workqueue中的unbound_release_work成员,回调函数指向
                  // pwq_unbound_release_workfn
                  INIT_WORK(&pwq->unbound_release_work, pwq_unbound_release_workfn)
                mutex_lock(&wq->mutex);
                link_pwq(pwq);  // 将pool_workqueue添加到workqueue_struct的pwqs链表中
                mutex_unlock(&wq->mutex);
            }
            return 0;
        } else if (wq->flags & __WQ_ORDERED) {
            ret = apply_workqueue_attrs(wq, ordered_wq_attrs[highpri]);
            return ret;
        } else {
            return apply_workqueue_attrs(wq, unbound_std_wq_attrs[highpri]);
        }
      ->alloc_worker  // 如果定义WQ_MEM_RECLAIM标志,则分配一个worker
      ->kthread_create  // 创建一个线程,线程执行函数为rescuer_thread
      ->wake_up_process  // 唤醒创建的线程

对于ORDERED和UNBOUND类型的workqueue,都通过调用apply_workqueue_attrs来分配workqueue,只是第二个参数不同,ORDERED传入的是ordered_wq_attrs,UNBOUND传入的是unbound_std_wq_attrs。ORDERED和UNBOUND类型的workqueue关联的worker_pool是从系统定义的哈希表unbound_pool_hash根据属性匹配的,如匹配不到,则重新分配和初始化一个,并添加到unbound_pool_hash中.在init_pwq函数中,使pool_workqueue中的pool指针指向worker_pool,使pool_workqueue中的wq指针指向workqueue_struct,使pool_workqueue中的unbound_release_work回调函数指向pwq_unbound_release_workfn。最后在link_pwq,将pool_workqueue添加到workqueue_struct的pwqs链表中。

    [kernel/workqueue.c]
    // 静态定义的unbound_pool_hash哈希表
    static DEFINE_HASHTABLE(unbound_pool_hash, UNBOUND_POOL_HASH_ORDER);
    apply_workqueue_attrs
      ->apply_wqattrs_lock  // 互斥锁wq_pool_mutex加锁
      ->apply_workqueue_attrs_locked
        ->apply_wqattrs_prepare
          ->kzalloc  // 分配一个struct apply_wqattrs_ctx临时对象
          ->alloc_workqueue_attrs  // 分配workqueue属性空间
          ->alloc_workqueue_attrs  // 分配workqueue临时属性空间
          ->copy_workqueue_attrs   // 将传入的属性参数拷贝到分配的属性空间中
          ->copy_workqueue_attrs   // 将属性拷贝到临时属性空间中
          ->alloc_unbound_pwq
            // 根据属性在unbound_pool_hash中查找worker_pool,如果没有,那就重新分配和初始化一个,并添加到
            // unbound_pool_hash中
            // 系统定义了一个哈希表unbound_pool_hash,用于管理系统中所有的UNBOUND类型的worker_pool,通过
            // wqattrs_equal判断系统中是否已经有了类型相关的worker_pool。
            ->get_unbound_pool 
            ->kmem_cache_alloc_node  // 从pwq_cache中分配一个struct pool_workqueue结构体
            // 使pool_workqueue中的pool指针指向worker_pool
            // 使pool_workqueue中的wq指针指向workqueue_struct
            // 使struct pool_workqueue中的unbound_release_work回调函数指向pwq_unbound_release_workfn
            ->init_pwq
        ->apply_wqattrs_commit
          ->mutex_lock  // 互斥锁mutex加锁
          ->copy_workqueue_attrs  // 将属性拷贝到unbound_attrs
          ->link_pwq  // 将pool_workqueue添加到workqueue_struct的pwqs链表中
          ->mutex_unlock  // 互斥锁mutex解锁
        ->apply_wqattrs_cleanup
      ->apply_wqattrs_unlock  // 互斥锁wq_pool_mutex解锁

4.添加工作任务

内核默认定义了7个工作队列workqueue,一般情况下足够使用,不需要额外创建新的工作队列。要使用工作队列,需要添加工作任务work。在添加工作队任务之前,需要先初始化工作任务work,内核提供了INIT_WORK宏来初始化工作任务work。这里重点关注data成员的初始化。data成员被划分为两个位域,低比特位域存放work相关的flags,高比特位域用于存放上次执行该work的worker_pool的worker_pool的ID号或保存上一次pool_workqueue数据结构指针。可通过get_work_pool函数获取执行该work的线程池worker_pool的指针。

    [include/linux/workqueue.h]
    #define INIT_WORK(_work, _func) __INIT_WORK((_work), (_func), 0)
    #define __INIT_WORK(_work, _func, _onstack)				\
        do {								\
            __init_work((_work), _onstack);				\
            (_work)->data = (atomic_long_t) WORK_DATA_INIT();  \  // 初始化data成员
            INIT_LIST_HEAD(&(_work)->entry);  \  // 初始化链表节点
            (_work)->func = (_func);  \  // 设置回调函数
        } while (0)
    #define WORK_DATA_INIT()	ATOMIC_LONG_INIT(WORK_STRUCT_NO_POOL)

以32bit的CPU来说,当data字段包含WORK_STRUCT_PWQ标志,表示高比特位域保存着上一次pool_workqueue数据结构的指针,这时低8位存放标志。当data字段没有包含WORK_STRUCT_PWQ标志,表示高比特位域存放上次执行该work的worker_pool的ID号,低5位用于存放标志。

   enum {
       // 带_BIT的表示是第几位
       WORK_STRUCT_PENDING_BIT	= 0,
       WORK_STRUCT_DELAYED_BIT	= 1,
       WORK_STRUCT_PWQ_BIT	= 2,
       WORK_STRUCT_LINKED_BIT	= 3,
       WORK_STRUCT_COLOR_SHIFT	= 4,
       WORK_STRUCT_COLOR_BITS	= 4,
       /* 该work正在等待执行 */
       WORK_STRUCT_PENDING	= 1 << WORK_STRUCT_PENDING_BIT,
       /* 该work被延迟执行了 */
       WORK_STRUCT_DELAYED	= 1 << WORK_STRUCT_DELAYED_BIT,
       /* data的高比特位域保存着指向pool_workqueue结构的指针,
          pool_workqueue需要按照256字节对其,低8位可以忽略 */
       WORK_STRUCT_PWQ		= 1 << WORK_STRUCT_PWQ_BIT,
       /* 下一个work连接到该work上 */
       WORK_STRUCT_LINKED	= 1 << WORK_STRUCT_LINKED_BIT,
       ......
   }

初始化完工作任务work后,可以调用schedule_workschedule_work_on函数将工作任务work挂入系统默认的workqueue中。schedule_workschedule_work_on函数将work挂入系统默认BOUND类型的工作队列system_wq中,不同的是schedule_work_on可以指定挂入工作队列所在的CPU。cancel_work_sync取消一个工作任务。flush_scheduled_work函数将system_wq工作队列中的所有工作任务销毁。如果创建了新的工作队列,则可使用queue_workqueue_work_on向创建的工作队列中加入work。

    [include/linux/workqueue.h]
    static inline bool schedule_work(struct work_struct *work)
    {
        return queue_work(system_wq, work);
    }
    static inline bool schedule_work_on(int cpu, struct work_struct *work)
    {
        return queue_work_on(cpu, system_wq, work);
    }
    bool cancel_work_sync(struct work_struct *work)
    {
        return __cancel_work_timer(work, false);
    }
    static inline void flush_scheduled_work(void)
    {
        flush_workqueue(system_wq);
    }

schedule_work函数最后调用了queue_work_on函数,queue_work_on函数有三个参数,第一个参数cpu为绑定处理器的编号,如不绑定特定的处理器,则传入WORK_CPU_UNBOUND,使用本地CPU。第二个参数wq为要挂入的工作队列的指针,第三个参数work为工作任务结构体指针。若work中设置了WORK_STRUCT_PENDING标记,说明work已经在工作队列中,还未得到处理,不能再次添加。首先根据工作队列的类型,查找对应的pool_workqueue,接着判断该work上一次运行的worker_pool和本次要运行的worker_pool是否一致,若不一致则选择上一次运行的worker_pool,同时更新对应的pool_workqueue。若pool_workqueue活跃的work数量,如果少于最高限制,就加入pending链表worklist,否则加入链表delayed_works。最后调用insert_work执行插入操作,插入时向work的data成员设置了pool_workqueue指针、WORK_STRUCT_PENDING标记和WORK_STRUCT_PWQ标记,WORK_STRUCT_PENDING表示work已加入工作队列但处于PENDING状态,等待被处理,WORK_STRUCT_PWQ表示work的data成员是pool_workqueue的指针。若没有工作线程运行,则唤醒第一个空闲的线程。schedule_work只是将work加入到工作队列workqueue对应的worker_pool链表中,并没有得到执行。schedule_work函数的关键是找到合适的pool_workqueue。

    [kernel/workqueue.c]
    schedule_work
      ->queue_work
        ->queue_work_on
          ->local_irq_save  // 禁止本地中断
          // 如果work设置了WORK_STRUCT_PENDING,则说明work已经在工作队列中了,
          // 不需要额外添加,直接返回false,反之则需要添加,添加成功返回true
          if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)))
              ->__queue_work(cpu, wq, work)
              // 如果设置__WQ_DRAINING标记,说明工作队列要销毁,则不再添加新的工作任务,直接返回
              if (unlikely(wq->flags & __WQ_DRAINING) && WARN_ON_ONCE(!is_chained_work(wq)))
                  return;   
              // 如果cpu的参数为WORK_CPU_UNBOUND,则直接选择本地CPU
              if (req_cpu == WORK_CPU_UNBOUND)
                  cpu = wq_select_unbound_cpu(raw_smp_processor_id());
              // 根据工作队列的标记是否含有WQ_UNBOUND选择pool_workqueue
              // 含有WQ_UNBOUND,说明是BOUND类型的工作队列,则从Per-CPU变量cpu_pwqs中获取
              // 不含有WQ_UNBOUND,说明是UNBOUND类型的工作队列,则从numa_pwq_tbl变量中获取,
              // numa_pwq_tbl变量是一个指针数组,索引为node节点,元素为此node节点的pool_workqueue指针
              if (!(wq->flags & WQ_UNBOUND))
                  pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);
              else
                  pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu));
              // 获取上一次运行的worker_pool
              ->get_work_pool
              // last_pool为上次一运行work的work_pool,pwq->pool为本次运行该work的work_pool,
              // 若两者不一致,则应添加到上一次运行work的work_pool,这样可以利用缓存热度
              if (last_pool && last_pool != pwq->pool) {
                  struct worker *worker;
                  spin_lock(&last_pool->lock);
                  // 判断work_pool是否正在运行此work并返回执行此work的线程worker
                  worker = find_worker_executing_work(last_pool, work);
                  // 获取运行此worker对应的pool_workqueue
                  if (worker && worker->current_pwq->wq == wq) {
                      pwq = worker->current_pwq;
                  } else {
                      spin_unlock(&last_pool->lock);
                      spin_lock(&pwq->pool->lock);  // 1
              } else {  // 如果work是第一次加入,则直接加入本地CPU或者本地node节点的工作队列,
                        // 无需考虑上一次在哪里执行
                  spin_lock(&pwq->pool->lock);  // 2
              }
              // 判断当前的pool_workqueue活跃的work数量,如果少于最高限制,就加入pending链表worklist,
              // 否则加入delayed_works
              if (likely(pwq->nr_active < pwq->max_active)) {
                  pwq->nr_active++;
                  worklist = &pwq->pool->worklist;  // 获取worklist
                  if (list_empty(worklist))
                      pwq->pool->watchdog_ts = jiffies;
              } else {
                  work_flags |= WORK_STRUCT_DELAYED;  // 设置延迟执行的标志
                  worklist = &pwq->delayed_works;  // 获取delayed_works
              }
              ->insert_work(pwq, work, worklist, work_flags)
                ->set_work_pwq
                  // 向work中的data成员设置pool_workqueue指针、WORK_STRUCT_PENDING、
                  // WORK_STRUCT_PWQ标记, WORK_STRUCT_PENDING表示work处于PENDING状态,等待被处理,
                  // WORK_STRUCT_PWQ表示work的data成员是pool_workqueue的指针
                  ->set_work_data
                  ->list_add_tail  // 将work加入worklist链表
                  ->get_pwq  // 增加pool_workqueue的引用计数,和put_pwq成对使用
                  ->smp_mb  // SMP系统内存屏障,确保前面的代码都执行完毕
                  // 如果worker_pool中没有运行的线程,则唤醒第一个空闲的线程
                  if (__need_more_worker(pool))
                      wake_up_worker(pool);                 
              ->spin_unlock(&pwq->pool->lock)  // 和1或2处的spin_lock配对
          ->local_irq_restore  // 开启本地中断

5.执行工作任务

在初始化工作队列的函数init_workqueues中,使用create_worker函数创建了工作线程,执行了工作线程的入口函数为worker_thread,下面分析一下worker_thread的执行流程。首先判断此工作线程是否要退出,即是否设置了WORKER_DIE标志,若设置了,则线程退出;接着退出空闲状态;然后判断是否需要创建额外的线程,需要的话调用manage_workers创建线程,这里会循环创建,直到不需要创建线程或者有空闲的线程才停止创建线程;最后在开始处理工作任务,如果work后面没有再连接work,则调用process_one_work处理此work,反之则调用move_linked_works将所有待执行的work迁移到工作线程的scheduled链表中,然后调用process_scheduled_works一并处理,线程保持运行(keep_working)的条件是线程池中的worker_pool的worklist非空(即还有work要处理)和线程池中正在运行的线程数量小于等于1,当不满足keep_working的条件,工作线程会睡眠。keep_working可以动态管理活跃的线程数量。

    [kernel/workqueue.c]    
    worker_thread(void *__worker)
      // 设置PF_WQ_WORKER标志,告诉调度器这是一个工作队列的工作线程
      ->worker->task->flags |= PF_WQ_WORKER
    woke_up:
      ->spin_lock_irq(&pool->lock)  // 加锁
      // 如果设置了WORKER_DIE标志,说明此工作线程要退出了
      if (unlikely(worker->flags & WORKER_DIE)) {
          spin_unlock_irq(&pool->lock);
          worker->task->flags &= ~PF_WQ_WORKER;  // 清除PF_WQ_WORKER标记
          set_task_comm(worker->task, "kworker/dying");
          ida_simple_remove(&pool->worker_ida, worker->id);
          worker_detach_from_pool(worker, pool);  // 将worker和worker_pool分离
          kfree(worker);  // 释放wirker的内存
          return 0;  // 返回后此线程退出
      }
      // 创建此线程时被设置成空闲状态,现在退出空闲状态
      ->worker_leave_idle
        ->worker_clr_flags(worker, WORKER_IDLE)  // 清除WORKER_IDLE标志
        pool->nr_idle--  // 空闲线程数量减1
        ->list_del_init  // 从空闲链表entry中删除
    recheck:
      // 如果worker_pool的worklist链表为空且有线程在运行,则跳转到sleep中睡眠
      if (!need_more_worker(pool))
          goto sleep;
      // 如果没有空闲的线程,则调用manage_workers函数创建工作线程,创建成功后跳转到recheck处
      // 这里是循环创建线程,直到不需要创建线程或者有空闲的线程才停止创建线程
      // 也就是循环创建会额外创建一个空闲的线程
      if (unlikely(!may_start_working(pool)) && manage_workers(worker))
          goto recheck;
      // 清除WORKER_PREP和WORKER_REBOUND标志,表示马上要执行work的回调函数了
      // WORKER_PREP:工作线程worker准备运行,WORKER_REBOUND:工作线程worker重新绑定
      // 对于UNBOUND类型的workqueue还会增加work_pool->nr_running的引用计数
      ->worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND)
      do {
          // 遍历 worker_pool的worklist链表,拿出工作任务work
          struct work_struct *work = list_first_entry(&pool->worklist, struct work_struct, entry);
          pool->watchdog_ts = jiffies;
          // 如果work后面没有连接work时,调用process_one_work处理work
          // WORK_STRUCT_LINKED:表示work后面还连接着work
          if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) {
              // 如果该work正在本地CPU的其他线程执行,则将此work加入到执行的线程的scheduled链表后返回,
              // 反之则执行该work(只执行一个work)
              process_one_work(worker, work);
              // 如果该线程的scheduled链表中不为空,即还有需要执行的work,
              // 则调用process_scheduled_works处理所有work
              if (unlikely(!list_empty(&worker->scheduled)))
                  ->process_scheduled_works(worker);
                      // 遍历scheduled链表,调用process_one_work处理所有work
                      while (!list_empty(&worker->scheduled)) {
                        struct work_struct *work = list_first_entry(&worker->scheduled, 
                                                  struct work_struct, entry);
                        ->process_one_work(worker, work);
                     }
          } else {
              // 如果work后面还连接着work,则使用move_linked_works将work迁移到scheduled链表中,
              // 然后调用process_scheduled_works一并处理所有work
              move_linked_works(work, &worker->scheduled, NULL);
              process_scheduled_works(worker);
          }
      // 工作线程worker继续运行的条件有两个(keep_working返回true的条件):
      // 1: 线程池中的worker_pool的worklist非空,即还有work要处理
      // 2: 线程池中正在运行的线程数量小于等于1
      } while (keep_working(pool));
      sleep:
      ->worker_enter_idle  // 工作线程worker进入idle状态
      ->__set_current_state(TASK_INTERRUPTIBLE)  // 设置线程的状态,可以被中断、信号唤醒
      ->spin_unlock_irq(&pool->lock)  // pool->lock解锁
      ->schedule  // 将此线程调度出CPU
      goto woke_up  // 线程被唤醒后跳转到woke_up标号处执行

manage_workers函数是动态管理创建工作线程的函数。manager_arb保护创建线程的临界区,确保只有一个线程进入创建线程的临界区。真正创建线程的工作在manager_arb函数中完成,在while循环中调用create_worker创建工作线程,创建成功或者不需要创建工作线程时跳出循环。

    [kernel/workqueue.c]
    // 管理工作线程的函数
    static bool manage_workers(struct worker *worker)
    {
        struct worker_pool *pool = worker->pool;
        // 获取管理工作线程的互斥锁
        if (!mutex_trylock(&pool->manager_arb))
            return false;
        // 获取锁成功,则将此工作线程worker设置到worker_pool中
        pool->manager = worker;
        // 如果需要,则创建新的线程
        maybe_create_worker(pool);
        pool->manager = NULL;  // 处理完管理工作,将manager设置为空
        mutex_unlock(&pool->manager_arb);  // 解锁管理工作线程的互斥锁
        return true;
    }
    MAYDAY_INITIAL_TIMEOUT  = HZ / 100 >= 2 ? HZ / 100 : 2,  // 一般为20毫秒
    static void maybe_create_worker(struct worker_pool *pool)
    {
    restart:
        spin_unlock_irq(&pool->lock);  // 解锁
        /* 修正mayday_timer的时间,超时时间为 */
        mod_timer(&pool->mayday_timer, jiffies + MAYDAY_INITIAL_TIMEOUT);
        while (true) {
            // 调用create_worker创建工作线程,创建成功则退出,need_to_create_worker用来
            // 判断是否还需要创建线程,不需要退出
            if (create_worker(pool) || !need_to_create_worker(pool))
                break;
            // 创建工作线程失败且还需要创建线程,则休眠一段时间
            schedule_timeout_interruptible(CREATE_COOLDOWN);
            if (!need_to_create_worker(pool))
                break;
        }
        // 删除定时器
        del_timer_sync(&pool->mayday_timer);
        spin_lock_irq(&pool->lock);
        // 如果需要创建工作线程,则继续跳转到restart处
        if (need_to_create_worker(pool))
            goto restart;
    }

process_one_work是执行work的核心函数。首先调用find_worker_executing_work判断当前要执行的work是否在本地CPU上其他线程中执行,若是,则将此work加入到执行线程的scheduled链表中,然后返回,并不会执行此work;反之则将此work加入到工作线程的忙碌链表hentry中,将工作线程worker加入到busy_hash哈希表中,准备执行此work;执行之前需要设置当前工作线程执行的工作任务、回调函数、相关标志等,比较重要的是将work从worklist链表中删除,调用set_work_pool_and_clear_pending函数,该函数清除了调用schedule_work时设置的WORK_STRUCT_PENDINGWORK_STRUCT_PWQ标记,同时将执行线程所属线程池worker_pool的id设置到data成员中,此后work处于空闲状态;执行完成之后清除设置的当前工作线程执行的工作任务、回调函数等。
从ARMv7指令集开始,ARM提供3条内存屏障指令:
(1)数据存储屏障( Data Memory Barrier,DMB)
数据存储器隔离。DMB指令保证仅当所有在它前面的存储器访问操作都执行完毕后,才提交在它后面的存取访问操作指令。当位于此指令前的所有内存访问均完成时,DMB指令才会完成。
(2)数据同步屏障( Data synchronization Barrier,DSB)
数据同步隔离。比DMB要严格一些,仅当所有在它前面的存储访问操作指令都执行完毕后,才会执行在它后面的指令。即任何指令都要等待DSB前面的存储访问完成。位于此指令前的所有缓存,如分支预测和TLB维护操作全部完成。
(3)指令同步屏障( Instruction synchronization Barrier,ISB)。
指令同步隔离。它最严格,会冲洗流水线,ISB前面的指令执行完毕后才会从cache或者内存中预取ISB指令之后的指令。ISB通常用来保证上下文切换的效果,例如更改ASID、TLB维护操作和C15寄存器的修改等。

    [kernel/workqueue.c]
    process_one_work
      ->get_work_pwq  // 获取工作线程对应的pool_workqueue
      // 此工作队列是否是CPU敏感类型的
      bool cpu_intensive = pwq->wq->flags & WQ_CPU_INTENSIVE
      // 检查此work是否在同一个CPU上的不同线程中运行,同一个work,不允许在单个CPU上多个线程中运行
      ->collision = find_worker_executing_work
          // 遍历worker_pool的busy_hash表中的忙碌工作线程,忙碌的工作线程都在busy_hash哈希表中
          hash_for_each_possible(pool->busy_hash, worker, hentry,(unsigned long)work)
              // 比较工作任务work的地址和回调函数func的地址是否一致,一致返回工作线程worker的地址
              if (worker->current_work == work && worker->current_func == work->func)
                  return worker;
          return NULL;  // 遍历完没找到,则返回空
      // 如果此work在同一个CPU上的不同线程worker中运行,要将此work迁移到运行的线程worker上
      if (unlikely(collision)) {
          // 迁移work
          move_linked_works(work, &collision->scheduled, NULL);
          return;  // 迁移完work后返回
      }
      // 将此工作线程worker加入到busy_hash哈希表中,表示此工作线程要处理工作任务work了
      ->hash_add(pool->busy_hash, &worker->hentry, (unsigned long)work)
      // 设置current_work和current_func,软件锁存,当清除WORK_STRUCT_PENDING标记后,work就处于idle
      // 状态,可以被取消
      worker->current_work = work;  // 保存现在执行的work
      worker->current_func = work->func;  // 保存现在执行work的回调函数
      worker->current_pwq = pwq;  // 设置此worker对应的pool_workqueue
      list_del_init  // 从entry链表中删除准备执行的work
      // 如果是CPU敏感型的工作,则向worker中设置WORKER_CPU_INTENSIVE标记,WORKER_CPU_INTENSIVE标记的
      // 线程调度由调度器负责,不再由工作队列进行并发管理,
      if (unlikely(cpu_intensive))
          worker_set_flags(worker, WORKER_CPU_INTENSIVE);
      // 是否需要更多的工作线程worker,如果需要,则唤醒线程池work_pool中的线程
      if (need_more_worker(pool))
          wake_up_worker(pool);
      // 清除work中的WORK_STRUCT_PENDING和WORK_STRUCT_PWQ标记并将此次执行线程
      // 所属线程池的ID设置的work的data成员中,清除WORK_STRUCT_PENDING标记后
      // work处于idle状态
      ->set_work_pool_and_clear_pending(work, pool->id)
        ->smp_wmb  // SMP系统内存屏障,通过ARM特有的DMB指令实现,确保前面访问内存的指令执行完毕
        ->set_work_data  
        ->smp_mb  // SMP系统内存屏障
      ->spin_unlock_irq  // 解锁pool->lock,执行工作任务的回调函数时释放自旋锁
      ->worker->current_func(work)  // 执行work的回调函数
      ->spin_lock_irq   // 加锁pool->lock
      // 清除WORKER_CPU_INTENSIVE标记
      if (unlikely(cpu_intensive))
          worker_clr_flags(worker, WORKER_CPU_INTENSIVE);
      hash_del(&worker->hentry)  // 从工作线程的忙碌链表hentry中删除被执行完的work
      worker->current_work = NULL
      worker->current_func = NULL
      worker->current_pwq = NULL

下面的函数是上面用到的一些功能性函数。

    [kernel/workqueue.c]
    // 判断是否需要更多的工作线程worker,need_more_worker返回true需要同时满足两个条件:
    // 1: worklist不为空,即还有work要处理
    // 2: nr_running为0,即没有运行的线程
    // 如不满足上面的两个条件,则不会创建工作线程worker
    static bool need_more_worker(struct worker_pool *pool)
    {
        return !list_empty(&pool->worklist) && __need_more_worker(pool);
    }
    static bool __need_more_worker(struct worker_pool *pool)
    {
        return !atomic_read(&pool->nr_running);  // 读取运行的线程数量
    }
    // 唤醒线程池中第一个空闲的线程
    static void wake_up_worker(struct worker_pool *pool)
    {
        struct worker *worker = first_idle_worker(pool);
        if (likely(worker))
            wake_up_process(worker->task);
    }
    // 工作线程worker继续运行要同时满足如下两个条件:
    // 1: 线程池中的worker_pool的worklist非空,即还有work要处理
    // 2: 线程池中正在运行的线程数量小于等于1
    // 如不满足,则线程将进入休眠状态
    static bool keep_working(struct worker_pool *pool)
    {
        return !list_empty(&pool->worklist) && atomic_read(&pool->nr_running) <= 1;
    }

当worker长时间处于空闲状态会怎么样呢?前面说到过,在工作线程池初始化函数init_worker_pool中,设置了空闲定时器idle_timer的回调函数为idle_worker_timeout, 在创建worker后worker进入空闲状态(create_worker->worker_enter_idle)时设置超时时间为300秒。下面分析一下工作线程空闲超时后会怎么处理。当工作线程空闲的时间超过300秒后,会触发软中断定时器,在定时器的回调函数中设置线程死亡标志WORKER_DIE,然后唤醒线程,线程运行时检测到WORKER_DIE标志,会自动退出。

    [kernel/workqueue.c]
    static void idle_worker_timeout(unsigned long __pool)
    {
        struct worker_pool *pool = (void *)__pool;
        spin_lock_irq(&pool->lock);
        // 判断是否有更多的线程
        while (too_many_workers(pool)) {
            struct worker *worker;
            unsigned long expires;
            // 获取空闲线程链表的第一个元素
            worker = list_entry(pool->idle_list.prev, struct worker, entry);
            // 计算超时的时间戳
            expires = worker->last_active + IDLE_WORKER_TIMEOUT;
            // 使用现在的时间戳jiffies和超时时间戳expires相比,若jiffies < expires
            // 则修正定时器的超时时间为expires
            if (time_before(jiffies, expires)) {
                mod_timer(&pool->idle_timer, expires);
                break;
            }
            // 若jiffies >= expires,则空闲超时时间已到,则销毁工作线程
            destroy_worker(worker);
  	          pool->nr_workers--  // 线程池线程数量减1
	          pool->nr_idle--     // 线程池空闲线程数量减1
              list_del_init       // 从工作线程的空闲链表entry中删除此worker
              worker->flags |= WORKER_DIE  // 设置工作线程死亡的标志
              // 唤醒工作线程,工作线程唤醒后检测到WORKER_DIE标志后会自己退出
              ->wake_up_process
        }
        spin_unlock_irq(&pool->lock);
    }
    MAX_IDLE_WORKERS_RATIO	= 4
    static bool too_many_workers(struct worker_pool *pool)
    {
        // 管理线程的锁是否被锁住
        bool managing = mutex_is_locked(&pool->manager_arb);
        // 如果有线程获取了manager_arb锁,可认为此线程是空闲的,因此要计算到空闲线程的数量中
        int nr_idle = pool->nr_idle + managing; /* manager is considered idle */
        // 计算忙碌线程数量
        int nr_busy = pool->nr_workers - nr_idle;
        // 返回true表示空闲线程太多,返回true的条件如下:
        // 1: 空闲线程数量大于2
        // 2: 空闲线程数量减2乘以4大于等于忙碌的线程数量
        return nr_idle > 2 && (nr_idle - 2) * MAX_IDLE_WORKERS_RATIO >= nr_busy;
    }

工作线城池中工作线程可以动态管理,管理规则如下:
(1)工作线程有3中状态:空闲(idle)、运行(running)和挂起(suspend)。空闲是指没有执行工作,运行是指正在执行工作,挂起是指执行工作的过程中睡眠。
(2)如果工作线程池中有工作任务要处理,则至少要有一个处于运行状态的线程来处理。
(3)如果处于运行状态的工人在执行工作的过程中进入了挂起状态,为了保证其他工作的执行,需要唤醒空闲的线程处理。
(4)如果有工作任务需要执行,并且处在运行状态的工作线程的数量大于1,会让多余的工作线程进入空闲状态(进程进行上下文切换时,在__schedule函数中会判断此进程是否是工作队列的工作线程,如是则会调用wq_worker_sleeping函数判断是否需要唤醒其他工作线程,如需要则调用try_to_wake_up_local尝试唤醒)。
(5)如果没有工作任务需要执行,则让所有的工作线程进入空闲状态。
(6)如果工作线程的空闲时间超过300秒,则自会被销毁。

6.取消工作任务

使用cancel_work_sync删除一个work,cancel_work_sync是一个同步的接口,若要删除的work正在执行,则会等待此work执行完毕,然后删除此work。删除work就是把work从调用schedule_work时插入的链表中删除。cancel_work_sync不能用于delayed_workcancel_delayed_work_sync可用于delayed_work
首先定义了静态类型的等待队列cancel_waitq;接着调用try_to_grab_pending尝试从worklist链表中删除work,若try_to_grab_pending的返回值小于0,说明删除失败,需要再次调用尝试删除,当返回值是-ENOENT,说明其他线程正在删除此work,需要睡眠等待;跳出循环说明删除成功,调用mark_work_canceling向data中设置worker_pool的id、WORK_OFFQ_CANCELINGWORK_STRUCT_PENDING标记,表示work正在被删除;接着调用flush_work等待该work的回调函数被工作线程执行完毕,执行完毕后调用clear_work_data清理work的data变量;最后,如果有线程在cancel_waitq队列中等待,则唤醒其中的一个。

    [kernel/workqueue.c]
    cancel_work_sync
      ->__cancel_work_timer(work, false)  // false表示不是delayed_work
          static DECLARE_WAIT_QUEUE_HEAD  // 申明一个等待队列cancel_waitq
          do {
              // 尝试从工作队列中取出work,若work处于idle状态(WORK_STRUCT_PENDING标记被清除),
              // 则可直接取出,若work处于PENDING(WORK_STRUCT_PWQ没有被清除)状态,
              // 正要被处理(WORK_STRUCT_PWQ标记被清除)或者work被其他线程
              // 正在取出(设置WORK_OFFQ_CANCELING标记),则稍后取出。
              // try_to_grab_pending返回值意义如下:
              // 1: work未被处理,处于PENDING状态,手动将work从worklist链表中删除
              // 0: work已被处理并且从worklist中删除,此时work处于idle状态
              // -ENOENT: work被正在被其他线程删除
              // -EAGAIN: 需要稍后再调用try_to_grab_pending再尝试删除work
              ret = try_to_grab_pending(work, is_dwork, &flags);
              // 如果该work正在被其他线程取出,则会等待其他线程完成
              if (unlikely(ret == -ENOENT)) {
                  struct cwt_wait cwait;
                  init_wait(&cwait.wait);
                  cwait.wait.func = cwt_wakefn;
                  cwait.work = work;
                  prepare_to_wait_exclusive(&cancel_waitq, &cwait.wait, TASK_UNINTERRUPTIBLE);
                  if (work_is_canceling(work))
                      schedule();
                 finish_wait(&cancel_waitq, &cwait.wait);
              }
          // try_to_grab_pending返回值为负数时会一直循环尝试取出work
          } while (unlikely(ret < 0));
          ->mark_work_canceling
            ->get_work_pool_id  // 获取worker_pool的ID
            pool_id <<= WORK_OFFQ_POOL_SHIFT
            // 向data成员设置pool_id和WORK_OFFQ_CANCELING、WORK_STRUCT_PENDING标记
            // WORK_OFFQ_CANCELING表示work正在被删除
            ->set_work_data(work, pool_id | WORK_OFFQ_CANCELING, WORK_STRUCT_PENDING)
          ->local_irq_restore  // 恢复中断
          // 等待work被执行完,前面只是从worklist中删除了work,
          // 并没有等待worker执行完work的回调函数
          ->flush_work  
          ->clear_work_data  // 将data设置为WORK_STRUCT_NO_POOL,清除标记位
          ->smp_mb  // SMP系统内存屏障
          // 如果有线程在队列cancel_waitq上等待,则唤醒其中一个
          if (waitqueue_active(&cancel_waitq))  
              __wake_up(&cancel_waitq, TASK_NORMAL, 1, work);

try_to_grab_pending尝试从worklist链表中删除work。首先关闭中断,防止在中断中又调用schedule_work插入work或调用cancel_work_sync删除work;接着设置并测试WORK_STRUCT_PENDING位,若WORK_STRUCT_PENDING位已被清除(见process_one_work函数),说明work已被从worklist链表中删除,处于idle状态,直接返回0,若test_and_set_bit没有被清除,说明work正在等待被处理,处于PENDING状态,需要手动删除,删除后返回1;使用get_work_pwq获取pool_workqueue的指针,若获取成功,则手动删除work,否则判断是否是别的线程正在删除work,若是返回-ENOENT,若不是返回-EAGAIN。try_to_grab_pending的返回值意义如下:
1: work未被处理,处于PENDING状态,手动将work从worklist链表中删除
0: work已被处理并且从worklist中删除,此时work处于idle状态
-ENOENT: work被正在被其他线程取出
-EAGAIN: 需要稍后再调用try_to_grab_pending再尝试删除work

    // is_dwork表示是否是delayed_work,true表示delayed_work,false表示不是
    static int try_to_grab_pending(struct work_struct *work, bool is_dwork,
                    unsigned long *flags)
    {
        struct worker_pool *pool;
        struct pool_workqueue *pwq;
        local_irq_save(*flags);  // 禁止本地中断
        // 如果是delayed_work,则将delayed_work的定时器关闭
        if (is_dwork) {
            struct delayed_work *dwork = to_delayed_work(work);
            // del_timer返回1表示定时器处于active状态,返回0表示定时器处于inactive状态
            if (likely(del_timer(&dwork->timer)))
                return 1;
        }
        // 设置WORK_STRUCT_PENDING标记并返回原来标记的值;若原来WORK_STRUCT_PENDING
        // 标记被清除,说明work处于空闲状态,设置WORK_STRUCT_PENDING标记并返回0;
        // 若没有被清除,说明work正在等待被处理,处于PENDING状态,需要手动删除
        if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)))
            return 0;
        // 通过data成员获取work对应的worker_pool指针,如果获取失败,跳转到fail处
        pool = get_work_pool(work);
        if (!pool)
            goto fail;
        spin_lock(&pool->lock);
        // 获取work对应的pool_workqueue,若获取成功,说明work处于PENDING状态,手动删除work,
        // 否则不能删除work
        pwq = get_work_pwq(work);
        if (pwq && pwq->pool == pool) {  // 进入if分支,说明成功取到pool_workqueue
            // delayed_work不能直接取出,需要将work激活并挂到worklist链表中
            // 并将活跃work计数nr_active加1
            if (*work_data_bits(work) & WORK_STRUCT_DELAYED)
                pwq_activate_delayed_work(work);
            // 将work从worklist链表中删除并重新初始化entry链表
            list_del_init(&work->entry);
            // 向data中设置WORK_STRUCT_PENDING标记和worker_pool的id
            set_work_pool_and_keep_pending(work, pool->id);
            spin_unlock(&pool->lock);  // 解锁
            return 1;  // 返回1,说明成功删除了work
        }
        spin_unlock(&pool->lock);  // 解锁
    fail:
        local_irq_restore(*flags);
        // 判断此work是否被其他线程取出,如果是返回-ENOENT
        if (work_is_canceling(work))
            return -ENOENT;
        cpu_relax();
        // 删除失败,需要等一会再尝试删除
        return -EAGAIN;
    }
    // 从工作任务的data成员中获取工作线程池worker_pool的指针
    static struct worker_pool *get_work_pool(struct work_struct *work)
    {
        // 原子的读取data成员
        unsigned long data = atomic_long_read(&work->data);
        int pool_id;
        assert_rcu_or_pool_mutex();
        // 若data含有WORK_STRUCT_PWQ标记,说明data的高24位保存了pool_workqueue的指针,
        // 则根据pool_workqueue直接返回worker_pool的指针
        if (data & WORK_STRUCT_PWQ)
            return ((struct pool_workqueue *)
                (data & WORK_STRUCT_WQ_DATA_MASK))->pool;
        // 若data不含有WORK_STRUCT_PWQ标记,说明data的高27位保存的是worker_pool的id
        pool_id = data >> WORK_OFFQ_POOL_SHIFT;
        if (pool_id == WORK_OFFQ_POOL_NONE)  // 若id无效,则返回空
            return NULL;
        // 根据有效id,从worker_pool_idr查找此id对应的worker_pool的指针
        return idr_find(&worker_pool_idr, pool_id);
    }
    // worker_pool的所有id静态定义在worker_pool_idr变量中
    static DEFINE_IDR(worker_pool_idr);

    // 获取工作任务work对用的pool_workqueue指针
    static struct pool_workqueue *get_work_pwq(struct work_struct *work)
    {
        // 原子的读取data成员
        unsigned long data = atomic_long_read(&work->data);
        // 若data含有WORK_STRUCT_PWQ标记,说明data的高24位保存了pool_workqueue的指针
        if (data & WORK_STRUCT_PWQ)
            // 屏蔽低8位,返回pool_workqueue的指针
            return (void *)(data & WORK_STRUCT_WQ_DATA_MASK);
        else  // 否则返回空
            return NULL;
    }
    // 成功删除work后设置WORK_OFFQ_CANCELING标记,起到通知其他线程此work已被删除的作用,
    // 设置WORK_STRUCT_PENDING标记的目的是阻止将此work再次添加到工作队列中
    static void mark_work_canceling(struct work_struct *work)
    {
        unsigned long pool_id = get_work_pool_id(work);

        pool_id <<= WORK_OFFQ_POOL_SHIFT;
        set_work_data(work, pool_id | WORK_OFFQ_CANCELING, WORK_STRUCT_PENDING);
    }
    // 判断此work是否正在被其他线程删除,是返回true,不是返回false
    static bool work_is_canceling(struct work_struct *work)
    {
        unsigned long data = atomic_long_read(&work->data);
        return !(data & WORK_STRUCT_PWQ) && (data & WORK_OFFQ_CANCELING);
    }

flush_work等待work的回调函数执行完毕,返回true表示执行完成,返回false表示work的回调函数已经执行完毕,无需等待。start_flush_work是实现等待功能的核心,通过定义一个额外的work,将work插到要删除的work后面,当插入的work被执行时,说明要删除work的回调函数已经执行完毕。wq_barrier_func为插入work的回调函数,当次函数执行时,将会唤醒在完成量done上等待的线程。

    [kernel/workqueue.c]
    struct wq_barrier {
        struct work_struct	work;
        struct completion	done;
        struct task_struct	*task;	/* purely informational */
    };
    bool flush_work(struct work_struct *work)
    {
        struct wq_barrier barr;
        ......
        // start_flush_work返回true说明要等待,则线程在完成量done上等待
        if (start_flush_work(work, &barr)) {
            wait_for_completion(&barr.done);
            destroy_work_on_stack(&barr.work);
            return true;
        } else {
            return false;
        }
    }
    static bool start_flush_work(struct work_struct *work, struct wq_barrier *barr)
    {
        struct worker *worker = NULL;
        struct worker_pool *pool;
        struct pool_workqueue *pwq;
        might_sleep();
        local_irq_disable();
        // 获取worker_pool
        pool = get_work_pool(work);  
        if (!pool) {
            local_irq_enable();
            // work的回调函数已经执行完毕
            return false;
        }
        spin_lock(&pool->lock);
        // 获取pool_workqueue
        pwq = get_work_pwq(work);
        if (pwq) {
            if (unlikely(pwq->pool != pool))
                goto already_gone;
        } else {
            // 说明此work的回调函数正在被工作线程执行,查找执行此work的工作线程
            worker = find_worker_executing_work(pool, work);
            if (!worker)
                goto already_gone;
            pwq = worker->current_pwq;
        }
        check_flush_dependency(pwq->wq, work);
        insert_wq_barrier(pwq, barr, work, worker);
          // 初始化barr的work成员,work的回调函数为wq_barrier_func
          INIT_WORK_ONSTACK(&barr->work, wq_barrier_func)
          // 设置WORK_STRUCT_PENDING_BIT标记
          ->__set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(&barr->work))
          ->init_completion(&barr->done)  // 初始化完成量
          barr->task = current  // task为本线程
          // 获取work的插入节点位置
          if (worker)
              head = worker->scheduled.next;
          else {
              unsigned long *bits = work_data_bits(target);
              head = target->entry.next;
              linked = *bits & WORK_STRUCT_LINKED;
              __set_bit(WORK_STRUCT_LINKED_BIT, bits);
          } 
          // 将barr的work插入到要等待的work后面,当执行到barr的work的回调函数,
          // 说明等待的work已经执行完成
          ->insert_work  
        spin_unlock_irq(&pool->lock);
        ......
        return true;
    already_gone:
        spin_unlock_irq(&pool->lock);
        // work的回调函数已经执行完毕
        return false;
    }
    // barr中work的回调函数,
    static void wq_barrier_func(struct work_struct *work)
    {
        struct wq_barrier *barr = container_of(work, struct wq_barrier, work);
        complete(&barr->done);  // 唤醒等待在完成量done上的线程
    }

参考资料

  1. Linux kernel V4.6版本源码
  2. 《奔跑吧 Linux内核:基于Linux 4.x内核源代码问题分析》
  3. 《Linux内核深度解析》
  4. http://www.wowotech.net/irq_subsystem/cmwq-intro.html
  5. https://www.cnblogs.com/LoyenWang/p/13185451.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值