当前位置: 首页 > news >正文

内核线程创建-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, &param);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详解 - 知乎 大概意思就是早期创建内核线程&#xff0c;是交由内核处理&#xff0c;由内核自己完成&#xff08;感觉好像也不太对呢&#xff09;&#xff0c;创建一个内核线程比较麻烦&#xff0c;会导致内核阻塞。因此就诞生了工作队列以及…...

uniappVue3版本中组件生命周期和页面生命周期的详细介绍

一、什么是生命周期&#xff1f; 生命周期有多重叫法&#xff0c;有叫生命周期函数的&#xff0c;也有叫生命周期钩子的&#xff0c;还有钩子函数的&#xff0c;其实都是代表&#xff0c;在 Vue 实例创建、更新和销毁的不同阶段触发的一组钩子函数&#xff0c;这些生命周期函数…...

任务驱动式编程

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基础知识点&#xff0c;终于来到了新的模块——数据可视化。 我理解的数据可视化是对大量的数据进行分析以更直观的形式展现出来。 今天我们用python数据可视化来实现一个2023年三大购物平台销售额比重的折线图。 准备工作&#xff1a;我们需要下载用于生成图表的第…...

QT工具栏开始,退出

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

@Async正确使用姿势

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

试除法判定质数算法总结

知识概览 质数的定义 在大于1的整数中&#xff0c;如果只包含1和本身这两个约数&#xff0c;就被称为质数&#xff0c;或者叫素数。 质数的判定——试除法 暴力算法 时间复杂度 改进算法 时间复杂度 暴力算法&#xff1a;时间复杂度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已经非常熟悉了&#xff0c;学习过计算机网络的同学对于它的连接和断开流程应该已经烂熟于心了吧。 那么Socket是什么&#xff1f; Socket是应用层与TCP/IP协议簇通信的中间软件抽象层&#xff0c;它是一组接口。在设计模式中&#xff0c;Socket其实就是…...

RKE安装k8s及部署高可用rancher之证书私有证书但是内置的ssl不放到外置的LB中 4层负载均衡

先决条件# Kubernetes 集群 参考RKE安装k8s及部署高可用rancher之证书在外面的LB&#xff08;nginx中&#xff09;-CSDN博客CLI 工具Ingress Controller&#xff08;仅适用于托管 Kubernetes&#xff09; 创建集群k8s [rootnginx locale]# cat rancher-cluster.yml nodes:- …...

使用爬虫爬取热门电影

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

【unity小技巧】实现没有动画的FPS武器摇摆和摆动效果

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

C语言基础知识(6):UDP网络编程

UDP 是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在 UDP 的情况下&#xff0c;虽然可以确保发送消息的大小&#xff0c;却不能保证消息一定会到达。因此&#xff0c;应用有时会根据自己的需要进行重发处理。 1.UDP协议的主要特点&#xff1a; &#xf…...

12月笔记

#pragma once 防止多次引用头文件&#xff0c;保证同一个&#xff08;物理意义上&#xff09;文件被多次包含&#xff0c;内容相同的两个文件同样会被包含。 头文件.h与无.h的文件&#xff1a; iostream是C的头文件&#xff0c;iostream.h是C的头文件&#xff0c;即标准的C头文…...

三、C语言中的分支与循环—for循环 (6)

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

tolist()读取Excel列数据,(Excel列数据去重后,重新保存到新的Excel里)

从Excel列数据去重后&#xff0c;重新保存到新的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 日第十九届中国图象图形学学会青年科学家会议在广州召开&#xff0c;该会…...

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&#xff0c;用得比较浅&#xff0c;不知道可以用来解析arxml。最近有个需求…...

探索Comsol中的散射体手性:从基板上的周期性金属阵列到散射光谱多极分解

Comsol散射体手性 基板上的周期性金属阵列 散射光谱多极分解在光学与电磁学领域&#xff0c;理解散射体的手性特性以及相关的光谱分析至关重要。今天咱们就通过Comsol这个强大工具&#xff0c;来深入研究基板上周期性金属阵列的散射体手性以及散射光谱多极分解。 基板上的周期性…...

SeqGPT-560M企业部署Checklist:GPU驱动版本、CUDA兼容性、磁盘空间预留

SeqGPT-560M企业部署Checklist&#xff1a;GPU驱动版本、CUDA兼容性、磁盘空间预留 重要提示&#xff1a;本文提供的是企业级部署的实用检查清单&#xff0c;帮助您快速完成SeqGPT-560M模型的部署和验证。 1. 部署前环境检查 1.1 GPU驱动版本要求 SeqGPT-560M支持CUDA加速推理…...

SenseVoice Small会议纪要生成:多人对话分离+发言人标注实战教程

SenseVoice Small会议纪要生成&#xff1a;多人对话分离发言人标注实战教程 1. 项目概述与核心价值 在日常工作中&#xff0c;会议记录是个让人头疼的问题。特别是多人讨论的场景&#xff0c;既要听清楚每个人说了什么&#xff0c;又要准确记录发言内容&#xff0c;往往需要专…...

立创开源无线开关功率计Pro:ESP32-C3+INA228打造50V/320A机器人安全监控方案

立创开源无线开关功率计Pro&#xff1a;ESP32-C3INA228打造50V/320A机器人安全监控方案 大家好&#xff0c;我是老张&#xff0c;一个在机器人圈子里摸爬滚打了十来年的嵌入式工程师。今天想跟大家聊聊一个在调试机器人时特别头疼&#xff0c;但又特别重要的事儿——安全。 不…...

Ubuntu20.04下PL2303驱动安装避坑指南:从虚拟机映射到CuteCom调试全流程

Ubuntu 20.04下PL2303驱动安装与CuteCom调试实战手册 1. 环境准备与核心问题定位 在Ubuntu 20.04虚拟机环境中使用PL2303芯片的USB转串口设备时&#xff0c;开发者常会遇到三大典型问题&#xff1a;驱动缺失导致的设备识别失败、虚拟机USB设备映射异常以及权限配置不当引发的调…...

Whisper-large-v3语音识别效果展示:99种语言的精准转录案例

Whisper-large-v3语音识别效果展示&#xff1a;99种语言的精准转录案例 1. 引言 语音识别技术正在以前所未有的速度改变我们与设备交互的方式。想象一下&#xff0c;一个模型能够准确识别99种不同语言的语音&#xff0c;从常见的英语、中文到小众的方言&#xff0c;这种能力简…...

语音剪辑神器:Qwen3-ForcedAligner精准定位音频中的每个词语

语音剪辑神器&#xff1a;Qwen3-ForcedAligner精准定位音频中的每个词语 1. 什么是音文强制对齐技术&#xff1f; 在音频剪辑和字幕制作过程中&#xff0c;最耗时耗力的环节之一就是为语音内容打上精确的时间戳。传统方法需要人工反复听录音、标记每个词语的开始和结束时间&a…...

市面上的可视挖耳勺怎么样?掏耳神器哪种最好用?耳勺品牌排行榜

​一、引言可视挖耳勺如今热度持续攀升&#xff0c;消费者的购买需求也在稳步增长&#xff0c;但市场上不少产品都存在明显短板 —— 要么图传模糊卡顿&#xff0c;要么操作不稳易划伤耳道&#xff0c;要么续航太短无法满足全家使用。这些问题不仅让掏耳过程变得小心翼翼&#…...

fast-DTW算法优化指南:如何提升时间序列匹配效率与准确性

Fast-DTW算法实战优化&#xff1a;从原理到参数调优的全方位指南 时间序列分析在金融预测、工业设备监控、医疗诊断等领域扮演着关键角色。当我们面对两条长度不同的时间序列数据时&#xff0c;如何准确衡量它们的相似度&#xff1f;传统DTW&#xff08;动态时间规整&#xff0…...

丹青识画系统应对“403 Forbidden”等API调用错误的实战处理指南

丹青识画系统应对“403 Forbidden”等API调用错误的实战处理指南 当你兴致勃勃地准备将丹青识画的强大能力集成到自己的应用里&#xff0c;满心期待地发送第一个API请求时&#xff0c;屏幕上却弹出一个冷冰冰的“403 Forbidden”错误&#xff0c;这种感觉就像兴冲冲去开门&…...