kthread_worker 和 kthread_work

本文深入探讨了Linux内核中kthread_worker和kthread_work的工作原理及其相关函数的使用方法,包括它们的数据结构、初始化过程、队列化操作及如何确保所有任务被执行。

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

kthread_worker 和 kthread_work

作者: 李云鹏(qqliyunpeng@sina.cn)
版本号: 20170107
更新时间: <2017-01-07>
原创时间: <2017-01-06>
版权: 本文采用以下协议进行授权,自由转载 - 非商用 - 非衍生 - 保持署名 | Creative Commons BY-NC-ND 3.0,转载请注明作者及出处.


1. 简介:

        在spi驱动中用到了内核的线程,用的函数就是跟 kthread_worker 和 kthread_work 相关的函数,对于这两个名词的翻译,在网上暂时没有找到合适的,先翻译成线程内核线程相关的:工人和工作,这么直白的翻译是根据其工作原理相关的,本来想翻译成别的,一想到他的实现方式,直白的翻译,更能让人理解。

        此部分介绍的函数主要在 include/linux/kthread.h 文件,这里可以推测,也许是内核为了方便我们使用内核的线程,而设计的kthread_work和kthread_worker。


2. 函数:


2.1 先来看这两个结构体:


kthread_worke

kthread_worker

struct kthread_worker {
	spinlock_t		lock;
	struct list_head	work_list;
	struct task_struct	*task;
	struct kthread_work	*current_work;
};

struct kthread_work {
	struct list_head	node;
	kthread_work_func_t	func;
	wait_queue_head_t	done; //  等待队列,内部成员是一个锁和一个链表节点
	struct kthread_worker	*worker;
};

【1】其中的 wait_queue_head_t 结构体需要解析一下:

struct __wait_queue_head { // 是一个带锁的链表节点
	spinlock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

2.2 声明:


DEFINE_KTHREAD_WORK宏

DEFINE_KTHREAD_WORKER宏

#define KTHREAD_WORKER_INIT(worker)	{				\
	.lock = __SPIN_LOCK_UNLOCKED((worker).lock),			\ // 初始化worker中lock
	.work_list = LIST_HEAD_INIT((worker).work_list),		\ // 初始化worker中的链表节点 work_list
	}

#define KTHREAD_WORK_INIT(work, fn)	{				\
	.node = LIST_HEAD_INIT((work).node),				\ // 初始化work中的链表节点 node (next和pre指针指向自己的首地址)
	.func = (fn),							\ // func成员赋值
	.done = __WAIT_QUEUE_HEAD_INITIALIZER((work).done),		\ // 初始化 done 成员 (初始化等待队列中的锁和链表节点,
	}                                                                 // 链表节点的初始化就是next和pre指针指向节点的首地址)

#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {				\
	.lock		= __SPIN_LOCK_UNLOCKED(name.lock),		\
	.task_list	= { &(name).task_list, &(name).task_list } }

#define DEFINE_KTHREAD_WORKER(worker)					\
	struct kthread_worker worker = KTHREAD_WORKER_INIT(worker)

#define DEFINE_KTHREAD_WORK(work, fn)					\
	struct kthread_work work = KTHREAD_WORK_INIT(work, fn)

2.3 初始化


init_kthread_work宏

init_kthread_worker宏

#define init_kthread_worker(worker)					\ // 初始化 kthread_worker
	do {								\
		static struct lock_class_key __key;			\
		__init_kthread_worker((worker), "("#worker")->lock", &__key); \
	} while (0)

#define init_kthread_work(work, fn)					\ // 初始化 kthread_work
	do {								\
		memset((work), 0, sizeof(struct kthread_work));		\
		INIT_LIST_HEAD(&(work)->node);				\ // 初始化成员 node 链表节点
		(work)->func = (fn);					\ // 将回调函数的指针指向fn函数,内核线程将会一直执行的函数
		init_waitqueue_head(&(work)->done);			\ // 初始化成员 done (等待队列)
	} while (0)

void __init_kthread_worker(struct kthread_worker *worker,
				const char *name,
				struct lock_class_key *key)
{
	spin_lock_init(&worker->lock);
	lockdep_set_class_and_name(&worker->lock, key, name); // 跟防止死锁有关,此处不深究
	INIT_LIST_HEAD(&worker->work_list); // 初始化 work_list 链表节点
	worker->task = NULL;
}

2.4 内核线程一直执行的函数


kthread_worker_fn函数

/**
 * kthread_worker_fn - kthread 函数目的是执行 kthread_worker中work_list下的work,此函数是作为内核线程中一直执行的函数
 * @worker_ptr: 指向初始化了的 kthread_worker
 */
int kthread_worker_fn(void *worker_ptr)
{
	struct kthread_worker *worker = worker_ptr;
	struct kthread_work *work;

	WARN_ON(worker->task);
	worker->task = current;
repeat:
	set_current_state(TASK_INTERRUPTIBLE);	/* mb paired w/ kthread_stop */

	if (kthread_should_stop()) { // 如果接收到线程停止的信号
		__set_current_state(TASK_RUNNING);
		spin_lock_irq(&worker->lock);
		worker->task = NULL;
		spin_unlock_irq(&worker->lock);
		return 0;
	}

	work = NULL;
	spin_lock_irq(&worker->lock);
	if (!list_empty(&worker->work_list)) { // 如果 worker中的work_list链表不是空的
		work = list_first_entry(&worker->work_list, // 取出头结点后边的第一个结构体kthread_work
					struct kthread_work, node);
		list_del_init(&work->node); // 删除链表中的入口
	}
	worker->current_work = work; // 将正在处理的work地址赋给 worker中的current_work成员
	spin_unlock_irq(&worker->lock);

	if (work) { // 如果有 work
		__set_current_state(TASK_RUNNING); // 启动内核线程
		work->func(work); // 运行work中的func函数
	} else if (!freezing(current)) // 如果没有work要做,并且没有freeze,则主动请求调度,主动放弃cpu时间片
		schedule();

	try_to_freeze();
	goto repeat; // 无限循环
}
【1】可以看到,这个函数的关键就是重复的执行kthread_worker结构体中的work_list链表锁挂接的kthread_work中的func函数,直到work_list变为空为止。

【2】要知道的是kthread是内核线程,是一直运行在内核态的线程

【3】这个函数一般是作为回调函数使用,比如spi.c中的如下程序

master->kworker_task = kthread_run(kthread_worker_fn,
					   &master->kworker,
					   dev_name(&master->dev));

/**
 * kthread_run - 创建并唤醒一个内核线程
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @namefmt: printf-style name for the thread.
 *
 * Description: Convenient wrapper for kthread_create() followed by
 * wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM).
 */
#define kthread_run(threadfn, data, namefmt, ...) ...(此处省略)


2.5 队列化kthread_work


queue_kthread_work 函数

/**
 * queue_kthread_work - 队列化一个 kthread_work,实质是将work中的node节点挂接到worker中的work_list后边,并尝试唤醒worker中的任务
 * @worker: 目标 kthread_worker
 * @work: 要队列化的 kthread_work
 *
 * 队列化 @work 目的是为了让任务异步执行.  @task
 * 必须已经被 kthread_worker_create() 创建了.  
 * 队列化成功,返回true,不成功返回false
 */
bool queue_kthread_work(struct kthread_worker *worker,
			struct kthread_work *work)
{
	bool ret = false;
	unsigned long flags;

	spin_lock_irqsave(&worker->lock, flags);
	if (list_empty(&work->node)) {   // 这里保证要插入到worker中链表节点的work的node节点一定要是一个独立的,不能是一串
		insert_kthread_work(worker, work, &worker->work_list);
		ret = true;
	}
	spin_unlock_irqrestore(&worker->lock, flags);
	return ret;
}

/* 在@worker中的work_list链表中的@pos位置的后边插入@work中的链表节点 */
static void insert_kthread_work(struct kthread_worker *worker,
			       struct kthread_work *work,
			       struct list_head *pos)
{
	lockdep_assert_held(&worker->lock);

	list_add_tail(&work->node, pos);
	work->worker = worker; // work中的worker指针指向worker
	if (likely(worker->task))
		wake_up_process(worker->task); // 尝试唤醒一下 worker 中的task指向的线程来处理work
}

2.6 执行完worker中的work


flush_kthread_worker 函数

struct kthread_flush_work {
	struct kthread_work	work;
	struct completion	done;
};

static void kthread_flush_work_fn(struct kthread_work *work)
{
	struct kthread_flush_work *fwork =
		container_of(work, struct kthread_flush_work, work);
	complete(&fwork->done); // 唤醒完成量
}

/**
 * flush_kthread_worker - flush all current works on a kthread_worker
 * @worker: worker to flush
 *
 * Wait until all currently executing or pending works on @worker are
 * finished.
 */
void flush_kthread_worker(struct kthread_worker *worker)
{
	struct kthread_flush_work fwork = {
		KTHREAD_WORK_INIT(fwork.work, kthread_flush_work_fn),
		COMPLETION_INITIALIZER_ONSTACK(fwork.done), // ON_STACK后缀相当于加了static
	};

	queue_kthread_work(worker, &fwork.work); // 将 fwork中的work成员的node节点过接到worker_list下,并尝试唤醒线程进行kthread_flush_work_fn函数的执行
	wait_for_completion(&fwork.done); // 调用这个函数的线程睡眠等待在这里,等待执行worker中work_list下的fulsh_kthread_work完kthread_flush_work_fn函数
}
【1】如何就flush了呢?看2.7的总结


2.7 总结的一张图:


        说了半天,其实woker和work的关系还是很难理解的,当我们经过一段时间,再次看的时候,难免还要花很长时间,因此,我画了一张图:

【1】worker中的task执行的是各自work中的func指定的函数,此规则同样适用于kthread_flush_work

【2】kthread_flush_work中的函数是kthread.c文件指定的函数,而kthread_work中的函数是用户自己定义的函数

【3】每次唤醒线程执行的work都是worker中的work_list下挂载的正常顺序的第一个

【4】如何实现等待全部的work都执行完呢?调用的是flush_kthread_worker函数中的wait_for_completion(&fwork.done);语句,只有当前边的work都执行完,才能轮到kthread_flush_work中的kthread_flush_work_fn的执行,此函数就是唤醒kthread_flush_work中的done。从而确定了前边的work都执行完了




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值