内核线程创建-kthread_create
文章参考Linux内核线程kernel thread详解 - 知乎
大概意思就是早期创建内核线程,是交由内核处理,由内核自己完成(感觉好像也不太对呢),创建一个内核线程比较麻烦,会导致内核阻塞。因此就诞生了工作队列以及现在的kthreadd 2号进程。这样我们在创建内核线程时,只需要将消息告诉它们,实际进行内核线程创建的任务有kthreadd完成,感觉类似一个下半部。
我环境使用的是kthreadd进行内核线程的创建
内核线程创建kthread_create
kthread_create-->kthread_create_on_node-->__kthread_create_on_node
#define kthread_create(threadfn, data, namefmt, arg...) \kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)
可以看到这里只是将创建内核线程的任务加入了链表里面,然后唤醒kthreadd进行内核线程的创建
struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),void *data, int node,const char namefmt[],va_list args)
{DECLARE_COMPLETION_ONSTACK(done);struct task_struct *task;struct kthread_create_info *create = kmalloc(sizeof(*create),GFP_KERNEL);if (!create)return ERR_PTR(-ENOMEM);/* 被创建的内核线程的信息被存放到了create_info里面 */create->threadfn = threadfn;create->data = data;create->node = node;create->done = &done;spin_lock(&kthread_create_lock);/* 将create_info加入到链表中,然后唤醒kthreadd_task(2号进程)进行后续的内核线程创建 */list_add_tail(&create->list, &kthread_create_list);spin_unlock(&kthread_create_lock);wake_up_process(kthreadd_task);/** Wait for completion in killable state, for I might be chosen by* the OOM killer while kthreadd is trying to allocate memory for* new kernel thread.*//* 这里是等待内核线程创建完成,内核线程创建完成后会释放这样完成量函数kthread里面会释放这个completion*/if (unlikely(wait_for_completion_killable(&done))) {/** If I was SIGKILLed before kthreadd (or new kernel thread)* calls complete(), leave the cleanup of this structure to* that thread.*/if (xchg(&create->done, NULL))return ERR_PTR(-EINTR);/** kthreadd (or new kernel thread) will call complete()* shortly.*/wait_for_completion(&done);}/* 函数kthread里面会将result赋值为创建好的内核线程的task_struct */task = create->result;if (!IS_ERR(task)) {static const struct sched_param param = { .sched_priority = 0 };char name[TASK_COMM_LEN];/** task is already visible to other tasks, so updating* COMM must be protected.*/vsnprintf(name, sizeof(name), namefmt, args);set_task_comm(task, name);//这里设置内核线程的名字/** root may have changed our (kthreadd's) priority or CPU mask.* The kernel thread should not inherit these properties.*/sched_setscheduler_nocheck(task, SCHED_NORMAL, ¶m);set_cpus_allowed_ptr(task, cpu_all_mask);}kfree(create);return task;
}
那2号进程kthreadd干了什么事情呢?
2号进程在rest_init里面创建,其处理函数为kthreadd
noinline void __ref rest_init(void)
{...............................pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);rcu_read_lock();kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);rcu_read_unlock();
............................
}
kthreadd-->create_kthread-->kernel_thread
int kthreadd(void *unused)
{struct task_struct *tsk = current;/* Setup a clean context for our children to inherit. */set_task_comm(tsk, "kthreadd");ignore_signals(tsk);set_cpus_allowed_ptr(tsk, cpu_all_mask);set_mems_allowed(node_states[N_MEMORY]);current->flags |= PF_NOFREEZE;cgroup_init_kthreadd();/*其实就是一直检查kthread_create_list是否为空如果不为空,将不断的处理链表里面的任务处理,创建内核线程*/for (;;) {set_current_state(TASK_INTERRUPTIBLE);if (list_empty(&kthread_create_list))schedule();__set_current_state(TASK_RUNNING);spin_lock(&kthread_create_lock);while (!list_empty(&kthread_create_list)) {struct kthread_create_info *create;create = list_entry(kthread_create_list.next,struct kthread_create_info, list);list_del_init(&create->list);spin_unlock(&kthread_create_lock);create_kthread(create);spin_lock(&kthread_create_lock);}spin_unlock(&kthread_create_lock);}return 0;
}
可以看到 内核线程的创建最终还是调用的kernel_thread。创建的内核线程会执行kthread,在函数kthread里面执行了我们设置的内核线程处理函数threadfun
static void create_kthread(struct kthread_create_info *create)
{int pid;#ifdef CONFIG_NUMAcurrent->pref_node_fork = create->node;
#endif/* We want our own signal handler (we take no signals by default). *//* 最终在kthread里面调用到我们设置的回调函数 */pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);if (pid < 0) {/* If user was SIGKILLed, I release the structure. */struct completion *done = xchg(&create->done, NULL);if (!done) {kfree(create);return;}create->result = ERR_PTR(pid);complete(done);}
}
kthread运行线程处理函数
执行到这里,就算内核线程创建成功了.只不过它不会立即执行我们的threadfn(即创建内核线程时指定的函数),它会先释放completion,并让出cpu。这就是kthread_create后还需要wake_up_process的原因。
static int kthread(void *_create)
{/* Copy data: it's on kthread's stack */struct kthread_create_info *create = _create;int (*threadfn)(void *data) = create->threadfn;void *data = create->data;struct completion *done;struct kthread *self;int ret;self = kzalloc(sizeof(*self), GFP_KERNEL);set_kthread_struct(self);/* If user was SIGKILLed, I release the structure. *//* 将create->done赋值为NULL,并返回create->done原来的值 */done = xchg(&create->done, NULL);if (!done) {kfree(create);do_exit(-EINTR);}if (!self) {create->result = ERR_PTR(-ENOMEM);complete(done);do_exit(-ENOMEM);}self->data = data;init_completion(&self->exited);init_completion(&self->parked);/* 此时的current就已经是我们创建好的内核线程了 */current->vfork_done = &self->exited;/* OK, tell user we're spawned, wait for stop or wakeup */__set_current_state(TASK_UNINTERRUPTIBLE);//__kthread_create_on_node里面将result当做返回值的原因在这里体现create->result = current;/* 在这里释放的completion,__kthread_create_on_node才会继续往下走 */complete(done);/*可以看到内核线程创建完了会先让出cpu,并不会立即执行我们的线程处理函数这就是我们为什么需要wake_up_process的原因,需要wake之后,才会继续从这里执行然后走到我们的threadfn*/schedule();ret = -EINTR;/*这个检查,我怀疑就是导致kthread_stop表现出不同行为的原因*/if (!test_bit(KTHREAD_SHOULD_STOP, &self->flags)) {cgroup_kthread_ready();__kthread_parkme(self);/* 执行内核线程设置的处理函数 */ret = threadfn(data);}/* 可以看到如果threadfn执行完了,内核线程退出是do_exit */do_exit(ret);
}
经过实际验证确实是kthread调用了complete(done);,kthread_create才能返回,否则__kthread_create_on_node会一直等待completion
测试代码如下
起了个定时器,定时器里面唤醒了一个内核线程.内核线程里面做了两个事情,一个是将comp_block设置为true,即跳过complete(done),另外一个是创建一个内核线程,看看是否会阻塞
struct task_struct *task;
struct timer_list timer;
/* 通过该变量控制是否是否completion */
extern bool comp_block;int kill_thread(void* a)
{/* 不释放completion,然后再看看kthread_create是否会阻塞 */comp_block = true;printk(KERN_EMERG "\r\n before create thread\n");kthread_create(test_thread, NULL, "test_task");printk(KERN_EMERG "\r\n after create thread\n");return;
}
void timer_work(unsigned long data)
{wake_up_process(task);return;
}static int smsc911x_init(struct net_device *dev)
{
...............................printk(KERN_EMERG "\r\n softlockup simulate, in_interrupt %u in_softirq %u, NR_CPUS %d\n", in_interrupt(), in_softirq(), NR_CPUS);timer.expires=jiffies+msecs_to_jiffies(20000);timer.function=timer_work;init_timer(&timer);add_timer(&timer);printk(KERN_EMERG "\r\n create thread\n"); task = kthread_create(kill_thread, NULL, "kill_task");printk(KERN_EMERG "\r\n create thread end\n");
....................................
}
bool comp_block = false;
static int kthread(void *_create)
{
.............................../* OK, tell user we're spawned, wait for stop or wakeup */__set_current_state(TASK_UNINTERRUPTIBLE);create->result = current;if (false == comp_block){complete(done);}schedule();
..........................................
}
效果展示 :可以看到并未打印kthread_create后面的log,并且内核线程kill_task也是一直无法退出
如果定时器里面不设置comp_block的值,即正常释放completion,log如下
内核线程退出kthread_stop
kthread_stop:只是告诉内核线程应该退出了,但是要不要退出,还需要看内核线程处理函数是否检查该消息,并且检查到以后还必须主动退出。
1、设置内核线程为KTHREAD_SHOULD_STOP,当内核线程的处理函数用kthread_should_stop检查标记时,能感知到该事件(如果内核线程一直不检查,那么即使调用了kthread_stop也是没有用的)
2、重新唤醒内核线程,如何内核线程没有运行,那么也是无法感知到这个事件的
3、等待completion释放
int kthread_stop(struct task_struct *k)
{struct kthread *kthread;int ret;trace_sched_kthread_stop(k);get_task_struct(k);kthread = to_kthread(k);set_bit(KTHREAD_SHOULD_STOP, &kthread->flags);kthread_unpark(k);wake_up_process(k);wait_for_completion(&kthread->exited);ret = k->exit_code;put_task_struct(k);trace_sched_kthread_stop_ret(ret);return ret;
}
wait_for_completion(&kthread->exited);
这个是在哪里释放的呢?
exited其实就是vfork_done,
static int kthread(void *_create)
{
........................................self->data = data;init_completion(&self->exited);init_completion(&self->parked);/* 此时的current就已经是我们创建好的内核线程了 */current->vfork_done = &self->exited;..............................do_exit(ret);
}
那么vfork_done是在哪里释放的呢?
do_exit-->exit_mm-->exit_mm_release-->mm_release
static void mm_release(struct task_struct *tsk, struct mm_struct *mm)
{
.................................../** All done, finally we can wake up parent and return this mm to him.* Also kthread_stop() uses this completion for synchronization.*/if (tsk->vfork_done)complete_vfork_done(tsk);
}
相关文章:

内核线程创建-kthread_create
文章参考Linux内核线程kernel thread详解 - 知乎 大概意思就是早期创建内核线程,是交由内核处理,由内核自己完成(感觉好像也不太对呢),创建一个内核线程比较麻烦,会导致内核阻塞。因此就诞生了工作队列以及…...

uniappVue3版本中组件生命周期和页面生命周期的详细介绍
一、什么是生命周期? 生命周期有多重叫法,有叫生命周期函数的,也有叫生命周期钩子的,还有钩子函数的,其实都是代表,在 Vue 实例创建、更新和销毁的不同阶段触发的一组钩子函数,这些生命周期函数…...

任务驱动式编程
main /** 模板代码*/#include "gd32f4xx.h" #include "systick.h" #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include "main.h" #include "USART0.h" #include &quo…...

python数据可视化之折线图案例讲解
学习完python基础知识点,终于来到了新的模块——数据可视化。 我理解的数据可视化是对大量的数据进行分析以更直观的形式展现出来。 今天我们用python数据可视化来实现一个2023年三大购物平台销售额比重的折线图。 准备工作:我们需要下载用于生成图表的第…...

QT工具栏开始,退出
QT工具栏开始,退出 //初始化场景QMenuBar *bar menuBar();setMenuBar(bar);QMenu *startbar bar->addMenu("开始");QAction * quitAction startbar->addAction("退出");connect(quitAction , &QAction::triggered,[](){this->c…...

@Async正确使用姿势
Async注解可以使被修饰的方法成为异步方法,简单且方便,这篇文章将教你如何正确的使用它 先谈谈大多数人对Aysnc的认识: 如果直接使用Async,未指定线程池 并且 容器内也没有beanName为taskExecutor的bean,则会使…...

试除法判定质数算法总结
知识概览 质数的定义 在大于1的整数中,如果只包含1和本身这两个约数,就被称为质数,或者叫素数。 质数的判定——试除法 暴力算法 时间复杂度 改进算法 时间复杂度 暴力算法:时间复杂度O(n) 算法模版 bool is_pr…...
vuetify 回到顶部
参考链接 // 回到id#app-content-container 的dom节点顶部 onScroll() {const ele document.querySelector(#app-content-container)// this.$vuetify.goTo(0, duration)this.$vuetify.goTo(#app-content-container, { container: ele })},...

Socket与TCP的关系
前言 相信大家对于TCP已经非常熟悉了,学习过计算机网络的同学对于它的连接和断开流程应该已经烂熟于心了吧。 那么Socket是什么? Socket是应用层与TCP/IP协议簇通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是…...

RKE安装k8s及部署高可用rancher之证书私有证书但是内置的ssl不放到外置的LB中 4层负载均衡
先决条件# Kubernetes 集群 参考RKE安装k8s及部署高可用rancher之证书在外面的LB(nginx中)-CSDN博客CLI 工具Ingress Controller(仅适用于托管 Kubernetes) 创建集群k8s [rootnginx locale]# cat rancher-cluster.yml nodes:- …...

使用爬虫爬取热门电影
文章目录 网站存储视频的原理M3U8文件解读网站分析代码实现 网站存储视频的原理 首先我们来了解一下网站存储视频的原理。 一般情况下,一个网页里想要显示出一个视频资源,必须有一个<video>标签, <video src"xxx.mp4"&…...

【unity小技巧】实现没有动画的FPS武器摇摆和摆动效果
文章目录 前言开始完结 前言 添加程序摇摆和摆动是为任何FPS游戏添加一些细节的非常简单的方法。但是并不是所以的模型动画都会配有武器摆动动画效果,在本文中,将实现如何使用一些简单的代码实现武器摇摆和摆动效果,这比设置动画来尝试实现类…...

C语言基础知识(6):UDP网络编程
UDP 是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在 UDP 的情况下,虽然可以确保发送消息的大小,却不能保证消息一定会到达。因此,应用有时会根据自己的需要进行重发处理。 1.UDP协议的主要特点: …...

12月笔记
#pragma once 防止多次引用头文件,保证同一个(物理意义上)文件被多次包含,内容相同的两个文件同样会被包含。 头文件.h与无.h的文件: iostream是C的头文件,iostream.h是C的头文件,即标准的C头文…...

三、C语言中的分支与循环—for循环 (6)
本章分支结构的学习内容如下: 三、C语言中的分支与循环—if语句 (1) 三、C语言中的分支与循环—关系操作符 (2) 三、C语言中的分支与循环—条件操作符 与逻辑操作符(3) 三、C语言中的分支与循环—switch语句(4)分支结构 完 本章循环结构的…...

tolist()读取Excel列数据,(Excel列数据去重后,重新保存到新的Excel里)
从Excel列数据去重后,重新保存到新的Excel里 import pandas as pd# 读取Excel文件 file r"D:\\pythonXangmu\\quchong\\quchong.xlsx" # 使用原始字符串以避免转义字符 df pd.read_excel(file, sheet_namenameSheet)# 删除重复值 df2 df.drop_duplica…...

ChatGPT大升级,文档图像识别领域迎来技术革新
写在前面ChatGPT迎来重大升级冲击与机遇并存大模型时代的思考与探索■ 像素级OCR统一模型- UPOCR■ OCR大一统模型- SPTS v3■ 文档识别分析LLM应用 写在最后问卷抽奖 写在前面 2023 年 12 月 31 日第十九届中国图象图形学学会青年科学家会议在广州召开,该会…...
2023年全国职业院校技能大赛软件测试—测试报告模板参考文档
ERP(资源协同)管理平台测试报告 目录 ERP(资源协同)管理平台测试报告 1. 概述...

【BCC动态跟踪PostgreSQL】
BPF Compiler Collection (BCC)是基于eBPF的Linux内核分析、跟踪、网络监控工具。其源码存放于GitCode - 开发者的代码家园 想要监控PostgreSQL数据库的相关SQL需要在编译PostgreSQL的时候开启dtrace。下文主要介绍几个和PostgreSQL相关的工具,其他工具可根据需求自行了解。 …...

汽车架构解析:python cantools库快速解析arxml
文章目录 前言一、安装cantools二、官方说明文档三、cantools方法1、解析message的属性2、解析pdu中的signals3、根据message查找signals4、报文组成bytes 四、总结 前言 曾经有拿cantools来解析过dbc,用得比较浅,不知道可以用来解析arxml。最近有个需求…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...