1、概述

在阅读内核源码时,可以看到kthread_worker、kthread_work两个数据结构配合内核线程创建函数一起使用的场景。刚开始看到这块时,比较困惑,紧接着仔细分析源码后,终于弄清楚了其中的机制,也不由的感叹内核的设计者内功之深厚以及生活处处皆学问。其实,这块使用机制就是抽象了现实生活中的经常看到的现象——工人(worker)和工作(work)间的关系。我们知道,在上班期间每个工人会被不定时的分配到一些不同的工作,当有工作来了,工人会对自己接到的工作进行处理,当手头上的工作做完后可以进行稍微的休息,等待后面任务的到来时再继续的处理接到的工作。

内核中要实现这种机制其实也不难,内核线程创建函数创建一个内核线程,该线程模仿工人的这个特性,它去判断属于这个线程的kthread_worker中是否有要处理的kthread_work,如果有,就取出这个kthread_work,然后调用kthread_work上面指定的处理函数,如果没有这个线程就进行休眠,当有新的kthread_work添加到kthread_worker上时,会再次唤醒kthread_worker的处理线程重复上述工作。

2、内核使用场景

前面总的概况了kthread_worker和kthread_work这一机制,让大家有了个大致的了解,下面结合内核中对这一机制的实际使用场景的源码再进行详细的分析。

内核在SPI驱动的SPI主机控制器这块使用了这一机制,先来看下源码中对kthread_worker、kthread_work这一机制进行初始化的函数

static int spi_init_queue(struct spi_controller *ctlr)
{
... kthread_init_worker(&ctlr->kworker);
ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker,
"%s", dev_name(&ctlr->dev));
...
kthread_init_work(&ctlr->pump_messages, spi_pump_messages); ...
}

spi_init_queue函数中调用了kthread_init_worker、kthread_run和kthread_init_work函数,下面按照顺序依次对这三个函数进行分析

2.1 kthread_init_worker函数

#define kthread_init_worker(worker)                    \
do { \
static struct lock_class_key __key; \
__kthread_init_worker((worker), "("#worker")->lock", &__key); \
} while (0)
void __kthread_init_worker(struct kthread_worker *worker,
const char *name,
struct lock_class_key *key)
{
memset(worker, 0, sizeof(struct kthread_worker));
raw_spin_lock_init(&worker->lock);
lockdep_set_class_and_name(&worker->lock, key, name);
INIT_LIST_HEAD(&worker->work_list);
INIT_LIST_HEAD(&worker->delayed_work_list);
}

kthread_init_worker是一个宏,它内部接着调用了__kthread_init_worker,主要的工作是在__kthread_init_worker函数中完成的

__kthread_init_worker函数初始化传入的kthread_worker结构体的成员变量,如初始化了该结构体内部的链表和自旋锁

2.2 kthread_run函数

#define kthread_run(threadfn, data, namefmt, ...)               \
({ \
struct task_struct *__k \
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k)) \
wake_up_process(__k); \
__k; \
})

kthread_run也是一个宏,它内部调用kthread_create创建一个内核线程。当内核线程创建成功后,立刻调用wake_up_process去唤醒创建的这个线程,让创建的线程进入就绪态等待内核调度

thread_fn这个参数传入的是这个内核线程要执行的函数,data是传给内核线程函数的参数。在上面spi_init_queue函数中,thread_fn参数对应的是kthread_worker_fn,data对应的是spi_controller结构中的kthread_worker类型的对象

这里的传入的kthread_worker_fn是由内核实现的函数,我们来看下它的作用是否和我们前面想象的一样,取出kthread_worker中挂接的kthread_work,并调用每个kthread_work中指定的处理函数

int kthread_worker_fn(void *worker_ptr)
{
struct kthread_worker *worker = worker_ptr;
struct kthread_work *work;
... repeat:
...
work = NULL;
raw_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;
raw_spin_unlock_irq(&worker->lock); if (work) {---------------------------------------->③
__set_current_state(TASK_RUNNING);
work->func(work);
} else if (!freezing(current))
schedule();
...
goto repeat; ------------------------------------>④
}

① 判断kthread_worker上是否有要处理的kthread_work,kthread_work挂接kthread_worker的work_list链表上

② 从kthread_worker的work_list链表中删除①中取出kthread_work节点

③ 如果在kthread_worker的work_list链表中找到了kthread_work,就执行kthread_work上的处理函数,如果没有,就让本线程进入休眠状态

④ 跳转到repeat处,重复执行①~④之间操作

果然kthread_worker_fn函数和我们之前预想的一样,从kthread_worker中取出挂接的kthread_work,并调用每个kthread_work中指定的处理函数

2.3 kthread_init_work函数

#define kthread_init_work(work, fn)                    \
do { \
memset((work), 0, sizeof(struct kthread_work)); \
INIT_LIST_HEAD(&(work)->node); \
(work)->func = (fn); \
} while (0)

kthread_init_work和kthread_init_worker一样,也是一个宏,它有两个参数,第一个参数work传入的是要初始化的kthread_work结构,第二个参数fn传入的是给kthread_work指定的处理函数。kthread_init_work初始化tkhread_work,设置了tkread_work的处理函数。

前面虽然初始化了kthread_worker、kthread_work结构,创建了处理kthread_worker的内核线程,但似乎还不够完整,还没有向kthread_worker中添加kthread_work呢?

创建的内核线程只有kthread_worker中挂接有kthread_work后,内核线程被唤醒工作,那么我们如何将kthread_work挂接到kthread_worker中并且唤醒休眠的内核线程呢?同样,内核中也提供了相应的函数,我们来看SPI的驱动中是怎么用的。

__spi_queued_transfer:

static int __spi_queued_transfer(struct spi_device *spi,
struct spi_message *msg,
bool need_pump)
{
struct spi_controller *ctlr = spi->controller;
...
list_add_tail(&msg->queue, &ctlr->queue);
if (!ctlr->busy && need_pump)
kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);----------->①
...
}

当SPI设备驱动程序访问SPI设备时最终会调用到__spi_queued_transfer函数,该函数将构造的消息挂入SPI主机控制器的链表上,然后调用kthread_queue_work将kthread_work挂入到kthread_woke链表中。

①中的ctlr->kworker和ctlr->pump_messages是描述SPI主机控制器的结构体里的成员,其实对应的就是kthread_worker和kthread_work类型数据结构。

可以看出kthread_queue_work就是我们想要找将wok(工作)交给worker(工人)的函数,下面我们来进行详细分析

2.2 kthread_queue_work函数

bool kthread_queue_work(struct kthread_worker *worker,
struct kthread_work *work)
{
...
if (!queuing_blocked(worker, work)) {
kthread_insert_work(worker, work, &worker->work_list);
ret = true;
}
...
}

kthread_queue_work函数中继续调用了kthread_insert_work函数,具体的工作是在这个函数中完成了,我们直接来看kthread_insert_work这个函数

static void kthread_insert_work(struct kthread_worker *worker,
struct kthread_work *work,
struct list_head *pos)
{
kthread_insert_work_sanity_check(worker, work); list_add_tail(&work->node, pos);-------------------->①
work->worker = worker;
if (!worker->current_work && likely(worker->task))
wake_up_process(worker->task);---------------->②
}

① 将传入的kthread_work挂接到对应的kthread_worker链表中

② 调用wake_up_process函数,唤醒休眠的处理kthread_worker的内核线程

可以看出,当有work要处理时,直接调用kthread_queue_work将work交给所属的worker,worker的处理线程会被唤醒去处理它里面的work

3、小结

好了,到此我们也将内核中kthread_worker和kthread_work机制讲解清楚了。这里引用了内核中SPI驱动对该机制的使用,如果想更深入的了解可以去阅读driver/spi目录下的内容。SPI驱动中巧妙的运用了这一机制,在后面未讲解的kthread_work的处理函数spi_pump_messages中,它会不断的去取出SPI设备驱动程序挂接到SPI主机控制器上的消息,将这些消息内容的通过硬件一个个的发送出去。

我们在内核编程中也可以通过内核提供的接口函数去使用这种机制,步骤如下:

  1. 使用kthread_init_worker初始化一个kthread_worker结构
  2. 调用kthread_run创建一个处理kthread_worker的内核线程,线程的执行函数一定是kthread_worker_fn
  3. 使用kthread_work_init初始化一个kthread_work,并指定它的处理函数
  4. 当有kthread_work要处理时,调用kthread_queue_work将kthread_work挂接到kthread_worker上

kthread_worker和kthread_work机制的更多相关文章

  1. 笔记:Binder通信机制

    TODO: 待修正 Binder简介 Binder是android系统中实现的一种高效的IPC机制,平常接触到的各种XxxManager,以及绑定Service时都在使用它进行跨进程操作. 它的实现基 ...

  2. JAVA回调机制(CallBack)详解

    序言 最近学习java,接触到了回调机制(CallBack).初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义.当然了,我在理解了回 ...

  3. 谈谈DOMContentLoaded:Javascript中的domReady引入机制

    一.扯淡部分 回想当年,在摆脱写页面时js全靠从各种DEMO中copy出来然后东拼西凑的幽暗岁月之后,毅然决然地打算放弃这种处处“拿来主义”的不正之风,然后开启通往高大上的“前端攻城狮”的飞升之旅.想 ...

  4. 路由的Resolve机制(需要了解promise)

    angular的resovle机制,实际上是应用了promise,在进入特定的路由之前给我们一个做预处理的机会 1.在进入这个路由之前先懒加载对应的 .js $stateProvider .state ...

  5. Android权限管理之Permission权限机制及使用

    前言: 最近突然喜欢上一句诗:"宠辱不惊,看庭前花开花落:去留无意,望天空云卷云舒." 哈哈~,这个和今天的主题无关,最近只要不学习总觉得生活中少了点什么,所以想着围绕着最近面试过 ...

  6. Java学习之反射机制及应用场景

    前言: 最近公司正在进行业务组件化进程,其中的路由实现用到了Java的反射机制,既然用到了就想着好好学习总结一下,其实无论是之前的EventBus 2.x版本还是Retrofit.早期的View注解框 ...

  7. .NET Core采用的全新配置系统[10]: 配置的同步机制是如何实现的?

    配置的同步涉及到两个方面:第一,对原始的配置文件实施监控并在其发生变化之后从新加载配置:第二,配置重新加载之后及时通知应用程序进而使后者能够使用最新的配置.要了解配置同步机制的实现原理,先得从认识一个 ...

  8. Go结构体实现类似成员函数机制

    Go语言结构体成员能否是函数,从而实现类似类的成员函数的机制呢?答案是肯定的. package main import "fmt" type stru struct { testf ...

  9. 操作系统篇-分段机制与GDT|LDT

    || 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.前言     在<操作系统篇-浅谈实模式与保护模式>中提到了两种模式,我们说在操作系统中,其实大部分时间是待在保护模式中的. ...

随机推荐

  1. Web 前端如何优雅的处理海量数据

    Web 前端如何优雅的处理海量数据 Q: 如何在 Web 页面上处理上亿条后端返回的数据,并且保证 UI 展示的流畅性 A: 思路: 时间分片, 批处理,Buffer 缓存,虚拟滚动,Web Work ...

  2. Swift 5.3

    Swift 5.3 https://swift.org/blog/ refs xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许注册用户才可以访问!

  3. CSS3 Animation & Weather Icons

    CSS3 Animation & Weather Icons google fonts <link href='https://fonts.googleapis.com/css?fami ...

  4. React + GraphQL 2020 速成课程

    React + GraphQL 2020 速成课程 technologies React (to build our user interface) GraphQL (to get and chang ...

  5. js Memory Management

    js Memory Management 垃圾回收是一个术语,在计算机编程中用于描述查找和删除那些不再被其他对象引用的对象的处理过程. 换句话说,垃圾回收是删除任何其他对象未使用的对象的过程. 垃圾收 ...

  6. js & input event & input change event

    js & input event & input change event vue & search & input change <input @click=& ...

  7. ForkJoin、并行流计算、串行流计算对比

    ForkJoin 什么是 ForkJoin ForkJoin 是一个把大任务拆分为多个小任务来分别计算的并行计算框架 ForkJoin 特点:工作窃取 这里面维护的都是双端队列,因此但其中一个线程完成 ...

  8. 【Notes】现代图形学入门_02

    跟着闫令琪老师的课程学习,总结自己学习到的知识点 课程网址GAMES101 B站课程地址GAMES101 课程资料百度网盘[提取码:0000] 光栅化 着色(Shading) 在图形学中,着色的定义可 ...

  9. Java基本概念:接口

    一.简介 描述: 普通类只有具体实现,抽象类具体实现和规范都有,接口只有规范! 接口就是比抽象类还抽象的抽象类,可以更加规范的对子类进行约束,全面专业地实现了规范和具体实现的分离. 抽象类还提供某些具 ...

  10. Virtual DOM 简直就是挥霍

    彻底澄清"Virtual DOM 飞快"的神话. 注意:原文发表于2018-12-27,随着框架不断演进,部分内容可能已不适用. 近年来,如果你有使用过 JavaScript 框架 ...