10 Workqueue
A small recap about interrupt IRQ, when handling an interrupt, if the job that need to be handled seems big, then Linux kernel divides into 2 part to handle that job, called Top halves and Bottom halves. And we have walked through how to handle top halves with gpio driver using input interrupt. Now we will talk about how to handle bottom halves with workqueue.
Workqueue is one of mechanisms of bottom halves.
We may heard about tasklet, but it is marked as deprecation. Because, they have some limitations, such as not being able to sleep and being less flexible compared to other mechanisms. That covers some points about workqueue:
Process Context: Workqueues run in the context of a kernel thread, which means they can sleep and perform operations that require blocking, such as memory allocation or I/O operations.Schedulable: You can schedule work to be done later, allowing interrupt handlers to return quickly and improving system responsiveness.Concurrency: Workqueues can be configured to run on multiple threads, allowing for parallel execution of tasks. This can improve performance, especially on multi-core systems.
There are two ways to implement Workqueue in the Linux kernel.
- Using global workqueue
- Creating Own workqueue
Dive into workqueue mechanism
https://www.cnblogs.com/LoyenWang/p/13185451.html
https://habr.com/en/companies/embox/articles/244155/
https://litux.nl/mirror/kerneldevelopment/0672327201/ch07lev1sec4.html
Workqueue key components
Work (or work item)is the struct that holds the pointer to the function to be executed asynchronously. Defined asstruct work_struct.Work queue: a queue of work items. Defined asstruct workqueue_struct.Worker: is the struct that holds a special-purpose thread which execute the functions from the work queue, one after the other. Or we can sayWorkerprocessesWorks(or work items). But in general there is no strict, sequential implementation: after all, here there is suppression, sleep, waiting, etc. Defined asstruct worker

Worker pool: is a group ofWorker, it also holds shared resources, providesWorkersabilities to process differentWork item. Defined asstruct worker_pool.There are 2 types ofworker pool:- Normal worker pool, used for general works.
- Unbound worker pool, used for works type WQ_UNBOUND.
Pool workqueue: is a “bridge” connectsworkqueueandworker_pool. Defined asstruct pool_workqueue.
Here is a pic about workqueue subsystem:

There are 2 types of workqueue:
Bind: That workqueue created will be bound to specific CPU core(processor) to run.Unbound: The work queue is not bound to a processor. The flag must be specified when creating the WQ_UNBOUND work queue. Core threads(jobs) can move between CPU cores(processors).
By default, the kernel creates several work queues (users can also create them):
system_wq: Ifwork itemexecution time is short, use this queue. This is also known asglobal_workqueue.system_highpri_mq: High priority job queue.system_long_wq: Ifwork itemexecution time is long, use this queue.system_unbound_wq: The core threads of this work queue are not tied to a specific processor(CPU).system_freezable_wq: This work queue is used to freeze when pausingwork item.system_power_efficient_wq: This work queue is chosen to sacrifice performance for saving energy.system_freezable_power_efficient_wq: Combination ofsystem_freezable_wqandsystem_power_efficient_wq.
Workqueue creation
DECLARE(_DELAYED)_WORK(name, void (*function)(struct work_struct *work)); /* Statically at linking time*/
INIT(_DELAYED)_WORK(_work, _func); /* Dynamically at running time*/
PREPARE(_DELAYED)_WORK(_work, _func); /* Change the function being executed*/
name: The name of the “work_struct” structure that has to be created.function: The function to be scheduled in this workqueue.
And then you can add work item to specific workqueue(system_wq, system_highpri_mq, …).
bool queue_work(struct workqueue_struct *wq, struct work_struct *work);
bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay); /* Add workitem to queue after delay time */
schedule_work
Put work item to global workqueue(system_wq)
bool schedule_work( struct work_struct *work );
work:work item(job) to be doneReturn: zero if work was already on the kernel-global workqueue and non-zero otherwise.
schedule_delayed_work
Put work item to global workqueue(system_wq) after delay time.
bool schedule_delayed_work( struct delayed_work *dwork, unsigned long delay );
dwork: delay-work item(delay job) to be donedelay: number of jiffies to wait or 0 for immediate executionReturn: zero if work was already on the kernel-global workqueue and non-zero otherwise.
schedule_work_on
Put work item to global workqueue(system_wq) on a specific CPU.
bool schedule_work_on( int cpu, struct work_struct *work );
cpu: CPU to put the work task onwork:work item(job) to be done
schedule_delayed_work_on
Put work item to global workqueue(system_wq) on a specific CPU after delay time.
bool schedule_delayed_work_on(int cpu, struct delayed_work *dwork, unsigned long delay);
cpu: CPU to put the work task ondwork: delay-work item(delay job) to be donedelay: number of jiffies to wait or 0 for immediate execution
Delete work from workqueue
Wait for a work to finish executing the last queueing instance.
bool flush_work( struct work_struct *work );
work: the work to flushReturn:trueifflush_work()waited for the work to finish execution,falseif it was already idle.
And for a delayed work
Wait for a dwork to finish executing the last queueing.
bool flush_delayed_work(struct delayed_work * dwork);
dwork: the delayed work to flushReturn:trueifflush_delayed_work()waited for the dwork to finish execution,falseif it was already idle.
Cancel work from workqueue
Cancel a work and wait for it to finish.
bool cancel_work_sync(struct work_struct * work);
work: the work to cancelReturn:trueif work was pending,falseotherwise.
And for delayed work
Cancel a delayed work and wait for it to finish.
bool cancel_delayed_work_sync(struct delayed_work * dwork);
dwork: the delayed work to cancel.Return:trueif dwork was pending,falseotherwise.
Check the workqueue
Find out whether a work item is currently pending.
work_pending(work)
work: The work item in question
Find out whether a delayable work item is currently pending
delayed_work_pending(work)
work: The work item in question
Our code
The code located in folder 10_workqueue
# To build ko driver
make build-10
# To clean ko driver
make clean-10
# To upload ko driver to our board
make upload-10
[root@luckfox root]# insmod ki_wq_drv.ko
[root@luckfox root]# dmesg | tail
[ 8.744105] rwnx_send_sm_connect_req drv_vif_index:0 connect to Ae809810(8) channel:2412 auth_type:0
[ 9.627080] of_dma_request_slave_channel: dma-names property of node '/serial@ff4b0000' missing or empty
[ 9.627116] dw-apb-uart ff4b0000.serial: failed to request DMA, use interrupt mode
[ 141.689537] 8
[ 142.342961] 8
[ 151.469222] 8
[ 159.327201] ki_drv driver initializing...
[ 159.328421] ki_drv module inserted successfully.
[ 159.330231] Executing dynamic workqueue function
[ 159.330254] Executing static workqueue function
[root@luckfox root]#