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. leetcode & vscode

    leetcode & vscode vscode-leetcode https://marketplace.visualstudio.com/items?itemName=LeetCode.v ...

  2. React Native & CodePush & App Center

    React Native & CodePush & App Center https://docs.microsoft.com/en-us/appcenter/distribution ...

  3. uniapp 万年历

    大量代码来至这里 <template> <view class="calendar-main"> <!-- 当前年月 --> <view ...

  4. NLog整合Exceptionless

    前言 在实际的.Net Core相关项目开发中,很多人都会把NLog作为日志框架的首选,主要是源于它的强大和它的扩展性.同时很多时候我们需要集中式的采集日志,这时候仅仅使用NLog是不够的,NLog主 ...

  5. python 第三方库paramiko

    目录 介绍 三种常用方式 使用密码进行登录 使用密钥免密码登录 SFTP 传输文件 terminal demo 介绍 paramiko是什么可以参考其他人的博客或文章,这里不再赘述,直入正题. 本次测 ...

  6. 深入理解 Web 协议 (三):HTTP 2

    本篇将详细介绍 HTTP 2 协议的方方面面,知识点如下: HTTP 2 连接的建立 HTTP 2 中帧和流的关系 HTTP 2 中流量节省的奥秘:HPACK 算法 HTTP 2 协议中 Server ...

  7. HTTP/1.1报文详解

    本文为<三万长文50+趣图带你领悟web编程的内功心法>第三个章节. 3.HTTP/1.1报文详解 在RFC2616中心详细的描述了HTTP/1.1[1]的报文,感兴趣的朋友也可以前往阅读 ...

  8. windows server2012 搭建FTP服务器过程

    搭建过程链接地址:https://blog.csdn.net/smalllu161226/article/details/53887751 1.打开windows server2012R2 服务器管理 ...

  9. 导出----用Excel导出数据库表

    根据条件导出表格: 前端 <el-form-item label=""> <el-button type="warning" icon=&qu ...

  10. 【HTB系列】靶机Vault的渗透测试详解

    出品|MS08067实验室(www.ms08067.com) 本文作者:大方子(Ms08067实验室核心成员) Kali: 10.10.14.213 靶机地址:10.10.10.109 先用nmap探 ...