Linux内核实现透视---kthread_work

内核线程工作队列

内核线程工作队列和普通工作队列看着十分相似,很多抽象概念如work和worker等都很相同并且执行对象也都是内核想成,但是内核线程工作队列没有普通工作队列的线程池的概念一个worker(工作组)对应到一个实际的内核线程而内核线程则按顺序依次执行worker上的每一个work从数据结构上也能看出来如下:

工作组

struct kthread_worker {
	unsigned int		flags;
	spinlock_t		lock;
	struct list_head	work_list;
	struct list_head	delayed_work_list;
	struct task_struct	*task;
	struct kthread_work	*current_work;
};

一个工作

struct kthread_work {
	struct list_head	node;
	kthread_work_func_t	func;
	struct kthread_worker	*worker;
	/* Number of canceling calls that are running at the moment. */
	int			canceling;
};

一个工作组中包括两个工作list,一个是普通的工作list另一个是延时执行的工作队列。内核工作队列处理程序由内核在kernel\kthread.c 文中实现在kthread_worker_fn中如下,实际上这个就是这个内核线程的执行体。

内核线程工作队列处理

int kthread_worker_fn(void *worker_ptr)
{
	struct kthread_worker *worker = worker_ptr;
	struct kthread_work *work;

	/*
	 * FIXME: Update the check and remove the assignment when all kthread
	 * worker users are created using kthread_create_worker*() functions.
	 */
	WARN_ON(worker->task && worker->task != current);
	worker->task = current;
    //休眠相关的
	if (worker->flags & KTW_FREEZABLE)
		set_freezable();
    //未持有锁时内核线程可以被中断
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
	work = NULL;
	spin_lock_irq(&worker->lock);
	if (!list_empty(&worker->work_list)) {
		work = list_first_entry(&worker->work_list,
					struct kthread_work, node);
		list_del_init(&work->node);
	}
	worker->current_work = work;
	spin_unlock_irq(&worker->lock);
    //找到了第一个work开始处理
	if (work) {
		__set_current_state(TASK_RUNNING);
		work->func(work);
    //没有work需要处理则放弃时间片
	} else if (!freezing(current))
		schedule();
    //未使能KTW_FREEZABLE时可能调用这里
	try_to_freeze();
	cond_resched();
	goto repeat;
}

这是内核工作线程的普通工作的处理过程,而延时工作不需要内核线程运行便利调用,延时工作的调用依赖内核的定时器timer由定时器到期时回调从而完成处理。

使用

内核线程的工作队列使用时刻意直接仅使用内核线程,也可以给内核线程传递上面的函数作为线程函数则此时就能将这个内核线程作为内核线程工作队列使用了。示例:

struct kthread_worker	kworker;
struct task_struct		*kworker_task;
struct kthread_work		work;
//初始化工作组
kthread_init_worker(&kworker);
//创建一个内核线程并运行kthread_worker_fn 参数是kworker 后面的参数是线程名称
kworker_task = kthread_run(kthread_worker_fn, &kworker,"%s", "xxxx");
//初始化线程
kthread_init_work(&work, kthread_work_func_t fun);
//向工作组添加一个工作,这个接口可能唤醒内核线程,执行完成后这个工作就被删除了
kthread_queue_work(kworker, &work);
//添加一个延时内核工作
kthread_queue_delayed_work(kworker, &work,delay)
//停止内核线程,依赖内核线程中调用(kthread_should_stop)检查,否则这个接口会阻塞直到内核线程退出
kthread_stop(kworker_task)

API

除了上面的经常使用API接口外还有一些不常用的API 接口这里简单记录下

//释放进程控制块内存
void free_kthread_struct(struct task_struct *k);
//将线程绑定到指定cpu上
void kthread_bind(struct task_struct *k, unsigned int cpu);
//将线程绑定到几个cpu上用掩码
void kthread_bind_mask(struct task_struct *k, const struct cpumask *mask);
//标记内核线程停止并等待线程停止
int kthread_stop(struct task_struct *k);
//检查线程是否要停止
bool kthread_should_stop(void);
//检查线程是否要park
bool kthread_should_park(void);
//睡眠使用停止模式
bool kthread_freezable_should_stop(bool *was_frozen);
//获取线程data
void *kthread_data(struct task_struct *k);
//
void *kthread_probe_data(struct task_struct *k);
//标记线程park
int kthread_park(struct task_struct *k);
//取消标记park
void kthread_unpark(struct task_struct *k);
// 等待park标记是否清除
void kthread_parkme(void);
//内核线程的管理接口
int kthreadd(void *unused);

总结

总的来说内核线程队列的实现和普通工作队列[https://www.cnblogs.com/w-smile/p/13499279.html]的相比实现上简单的一些,但是使用上内核线程的更加清晰明了,内核在kthreadd维护了内核线程队列,而驱动开发通过kthread_worker_fn 维护了内核线程工作队列而普通工作队列的的实现上增加了工作组的线程池的概念从而支持并发而内核线程工作队列是不支持并发工作的。二者的区别和共同点都很明显。
共同点:本质上都是内核线程来处理的。实现和使用上内核线程工作队列更加简单明了。同一个内核工作队列不会被并发里的work是顺序执行的不会出现并发。也都支持延迟工作。
不同点:增加了线程池的概念,所以一个工作队列同时刻意被多个内核线程处理也就会出现work并发,同时消耗的系统资源也更加多。

Linux内核实现透视---kthread_work

上一篇:Mac SSH免密码登录 Linux服务器


下一篇:随机生成mac地址