1. source code
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/kobject.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/time.h>
#include <linux/time64.h>
#include <linux/of.h>
#include <linux/completion.h>
#include <linux/mfd/core.h>
#include <linux/kernel.h>
#include <linux/seq_file.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
struct work_struct work_demo;
struct work_struct work_demo2;
struct workqueue_struct *workqueue_demo;
static void work_demo_func(struct work_struct *work)
{
printk("%s ,cpu id = %d,taskname = %s\n",
__func__,raw_smp_processor_id(),current->comm);
mdelay(1000*10);
}
static int workqueue_proc_show(struct seq_file *m, void *v)
{
printk("%s ,cpu id = %d\n",__func__,raw_smp_processor_id());
//queue_work(workqueue_demo,&work_demo);
schedule_work(&work_demo);
return 0;
}
static int workqueue_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, workqueue_proc_show, NULL);
}
static ssize_t workqueue_proc_store(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
char buf[count];
copy_from_user(buf, buffer, count);
if(buf[0] == '1')
{
printk("%s ,work_demo,cpu id = %d\n",__func__,raw_smp_processor_id());
queue_work(workqueue_demo,&work_demo);
printk("queue work_demo end\n");
}
else if(buf[0] == '2')
{
printk("%s ,work_demo2,cpu id = %d\n",__func__,raw_smp_processor_id());
queue_work(workqueue_demo,&work_demo2);
}
return count;
}
static const struct file_operations workqueue_proc_fops = {
.open = workqueue_proc_open,
.read = seq_read,
.write = workqueue_proc_store,
.llseek = seq_lseek,
.release = single_release,
};
static int __init workqueue_init(void)
{
INIT_WORK(&work_demo, work_demo_func);
INIT_WORK(&work_demo2, work_demo_func);
//workqueue_demo = create_workqueue("workqueue demo");
workqueue_demo = alloc_workqueue("workqueue demo", 0, 2);
proc_create("workqueue", 0, NULL, &workqueue_proc_fops);
return 0;
}
static void __exit workqueue_exit(void)
{
return ;
}
module_init(workqueue_init);
module_exit(workqueue_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Jon");
MODULE_ALIAS("platform");
MODULE_DESCRIPTION("workqueue demo driver");
2.测试过程
首先说明一下,如果调用的是“cat /proc/workqueue ",调用的是schedule_work,也就是使用系统的默认工作队列。
随后我们连续调度2次schedule_work看看会发生什么
场景一:work_func中使用mdelay延迟10s,---- work_demo_func
/ # cat /proc/workqueue
workqueue_proc_show ,cpu id = 2
wake_up_worker
work_demo_func ,cpu id = 2,taskname = kworker/2:1
/ #
/ #
/ # cat /proc/workqueue
workqueue_proc_show ,cpu id = 3
no wake_up_worker
…
work_demo_func ,cpu id = 2,taskname = kworker/2:1
第一次调度后,由于work_func中调度的是mdelay,cpu 2在空转,因此第二次调度工作队列的时候执行的是cpu 3,但是由于同一个work当前在cpu 2的Normal线程池中执行,因此Linux系统将本次调度的work依然分发给了cpu 2的线程池指向的worklist,等待上一次的work执行完成后再接着执行。
场景二:work_func中使用msleep延迟10s, ----work_demo_func
/ # cat /proc/workqueue
workqueue_proc_show ,cpu id = 3
wake_up_worker
work_demo_func ,cpu id = 3,taskname = kworker/3:1
/ #
/ #
/ # cat /proc/workqueue
workqueue_proc_show ,cpu id = 3
wake_up_worker
/ #
…
work_demo_func ,cpu id = 3,taskname = kworker/3:1
第一次调度的时候,由于work_func中执行的是msleep,因此cpu 3的当前进程进入阻塞,进程发生切换。第二次调度的时候由于上一次是msleep,因此本次执行的还是cpu 3只是进程的id不同罢了,由于当前线程池的所有线程全部都阻塞了,系统判定需要唤醒新的idle线程来执行这个work,随后在work_thread的具体处理(process_one_work)中,系统发现本次work的上一次调度在该cpu的线程池中还没有执行完毕,因此将其插入到scheduled的链表后等待下次执行。
场景三:work_func中使用msleep,同时工作队列采用自定义的PerCpu,max_active为2(意味着最多在每个CPU上并发2次)。同时采取2次调度不同的work。
/ # echo 1 > /proc/workqueue
workqueue_proc_store ,work_demo,cpu id =0
wake_up_worker
queue work_demo end
work_demo_func ,cpu id =0,taskname = kworker/0:1
/ #
/ # echo 2 > /proc/workqueue
workqueue_proc_store ,work_demo2,cpu id = 0
wake_up_worker
work_demo_func ,cpu id = 0,taskname = kworker/0:2
第一次调度的时候,内核是在cpu 0上调度的work_demo,由于work_func中执行的msleep,因此cpu 0在当前的进程中进入阻塞了,随后进程发送切换,第二次调度的时候由于上一次是msleep,因此本次执行的还是cpu 0只是进程的id不同罢了,由于当前线程池的所有线程全部阻塞了,系统判定需要唤醒新的idle线程来执行这个work,随后在work_thread的具体处理(process_one_work)中,系统发现前后2次调度的是不同的work,满足开新线程的条件,因此系统在当前CPU的线程池中开启了一个新的线程kworker/0:2来执行这个work。
场景四:work_func中使用mdelay,同时工作队列采用自定义的PerCpu,max_active为2(意味着最多在每个CPU上并发2次)。同时采取2次调度不同的work。
/ # echo 1 > /proc/workqueue
workqueue_proc_store ,work_demo,cpu id = 0
wake_up_worker
queue work_demo end
work_demo_func ,cpu id = 0,taskname = kworker/0:1
/ #
/ # echo 2 > /proc/workqueue
workqueue_proc_store ,work_demo2,cpu id = 2
wake_up_worker
work_demo_func ,cpu id = 2,taskname = kworker/2:1
第一次调度的时候,内核是在cpu 0上调度的work_demo,由于work_func中执行的mdelay,因此cpu 0在当前的进程中空转,第二次调度的时候由于上一次是mdelay,因此本次执行的是cpu 2,由于当前线程池的所有线程全部阻塞了,系统判定需要唤醒新的idle线程来执行这个work,随后在work_thread的具体处理(process_one_work)中,系统发现该work是第一次调度,满足开新线程的条件,因此系统在当前CPU的线程池中开启了一个新的线程kworker/2:1来执行这个work。