用一个工作队列的实例来讲解其使用

本文详细探讨了Linux工作队列机制的工作原理,通过四种不同场景的实验,对比分析了mdelay与msleep在work_func函数中的使用差异,以及自定义PerCpu工作队列在多线程调度中的表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值