Linux内核工作队列

本文最后更新于:2023年1月2日 晚上

快速上手

内核工作队列使用方法:

struct work_struct my_work;
struct delayed_work my_delay_work;

static void my_work_func(struct work_struct *work)
{
  // do something
}

static void my_delay_work_func(struct work_struct *work)
{
  // do something
}

// 绑定工作函数
INIT_WORK(&my_work, my_work_func);
INIT_DELAYED_WORK(&my_delay_work, my_delay_work_func);

schedule_work(&my_work);  // 立即加入到工作队列中
schedule_delayed_work(&my_delay_work, 1000); // 延时工作

以上是使用工作队列最简单的方式,仔细想想其实上述步骤并没有队列的创建过程,因为 schedule_work()schedule_delayed_work() 是封装过的,使用这两个接口会把 work 加入到系统预定义的 system_wq 工作队列中。

这很方便,但难免会有些问题需要衡量,系统中各个模块都有可能将各种 work 加入到 system_wq 工作队列,如果前面的 work 执行时间过长就会影响后面 work 的实时性。因此往往我们的某些工作比较频繁且耗时,就需要创建一个独立的工作队列,避免别的模块影响到我们,同时也避免我们影响到别的模块。

以下代码展示了基本的独立工作队列创建方法:

struct work_struct my_work;
struct delayed_work my_delay_work;

static void my_work_func(struct work_struct *work)
{
  // do something
}

static void my_delay_work_func(struct work_struct *work)
{
  // do something
}

// 创建一个自己的工作队列,独立的内核线程
struct workqueue_struct *my_wq = alloc_ordered_workqueue("my_wq", 0);

// 绑定工作函数
INIT_WORK(&my_work, my_work_func);
INIT_DELAYED_WORK(&my_delay_work, my_delay_work_func);

// 将指定work加入到自定义工作队列my_wq中
queue_work(my_wq, &my_work);
queue_delayed_work(my_wq, &my_delay_work, 1000);

相关源码分析

相关内核源码:https://elixir.bootlin.com/linux/latest/source/include/linux/workqueue.h

创建工作队列/内核线程的接口还有多个:

#define alloc_ordered_workqueue(fmt, flags, args...)      \
  alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED |    \
      __WQ_ORDERED_EXPLICIT | (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)

内核预定义工作队列:

/*
 * System-wide workqueues which are always present.
 *
 * system_wq is the one used by schedule[_delayed]_work[_on]().
 * Multi-CPU multi-threaded.  There are users which expect relatively
 * short queue flush time.  Don't queue works which can run for too
 * long.
 *
 * system_highpri_wq is similar to system_wq but for work items which
 * require WQ_HIGHPRI.
 *
 * system_long_wq is similar to system_wq but may host long running
 * works.  Queue flushing might take relatively long.
 *
 * system_unbound_wq is unbound workqueue.  Workers are not bound to
 * any specific CPU, not concurrency managed, and all queued works are
 * executed immediately as long as max_active limit is not reached and
 * resources are available.
 *
 * system_freezable_wq is equivalent to system_wq except that it's
 * freezable.
 *
 * *_power_efficient_wq are inclined towards saving power and converted
 * into WQ_UNBOUND variants if 'wq_power_efficient' is enabled; otherwise,
 * they are same as their non-power-efficient counterparts - e.g.
 * system_power_efficient_wq is identical to system_wq if
 * 'wq_power_efficient' is disabled.  See WQ_POWER_EFFICIENT for more info.
 */
extern struct workqueue_struct *system_wq;
extern struct workqueue_struct *system_highpri_wq;
extern struct workqueue_struct *system_long_wq;
extern struct workqueue_struct *system_unbound_wq;
extern struct workqueue_struct *system_freezable_wq;
extern struct workqueue_struct *system_power_efficient_wq;
extern struct workqueue_struct *system_freezable_power_efficient_wq;

schedule_workqueue_workschedule_delayed_workqueue_delayed_work

/**
 * queue_work - queue work on a workqueue
 * @wq: workqueue to use
 * @work: work to queue
 *
 * Returns %false if @work was already on a queue, %true otherwise.
 *
 * We queue the work to the CPU on which it was submitted, but if the CPU dies
 * it can be processed by another CPU.
 *
 * Memory-ordering properties:  If it returns %true, guarantees that all stores
 * preceding the call to queue_work() in the program order will be visible from
 * the CPU which will execute @work by the time such work executes, e.g.,
 *
 * { x is initially 0 }
 *
 *   CPU0        CPU1
 *
 *   WRITE_ONCE(x, 1);      [ @work is being executed ]
 *   r0 = queue_work(wq, work);      r1 = READ_ONCE(x);
 *
 * Forbids: r0 == true && r1 == 0
 */
static inline bool queue_work(struct workqueue_struct *wq,
            struct work_struct *work)
{
  return queue_work_on(WORK_CPU_UNBOUND, wq, work);
}

/**
 * queue_delayed_work - queue work on a workqueue after delay
 * @wq: workqueue to use
 * @dwork: delayable work to queue
 * @delay: number of jiffies to wait before queueing
 *
 * Equivalent to queue_delayed_work_on() but tries to use the local CPU.
 */
static inline bool queue_delayed_work(struct workqueue_struct *wq,
              struct delayed_work *dwork,
              unsigned long delay)
{
  return queue_delayed_work_on(WORK_CPU_UNBOUND, wq, dwork, delay);
}

/**
 * schedule_work - put work task in global workqueue
 * @work: job to be done
 *
 * Returns %false if @work was already on the kernel-global workqueue and
 * %true otherwise.
 *
 * This puts a job in the kernel-global workqueue if it was not already
 * queued and leaves it in the same position on the kernel-global
 * workqueue otherwise.
 *
 * Shares the same memory-ordering properties of queue_work(), cf. the
 * DocBook header of queue_work().
 */
static inline bool schedule_work(struct work_struct *work)
{
  return queue_work(system_wq, work);
}

/**
 * schedule_delayed_work - put work task in global workqueue after delay
 * @dwork: job to be done
 * @delay: number of jiffies to wait or 0 for immediate execution
 *
 * After waiting for a given time this puts a job in the kernel-global
 * workqueue.
 */
static inline bool schedule_delayed_work(struct delayed_work *dwork,
           unsigned long delay)
{
  return queue_delayed_work(system_wq, dwork, delay);
}

Linux内核工作队列
https://mxy493.xyz/202301028721/
作者
mxy
发布于
2023年1月2日
许可协议