内核线程创建-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_NUMA
	current->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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/297834.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

美格智能5G RedCap模组SRM813Q通过广东联通5G创新实验室测试认证

近日&#xff0c;美格智能5G RedCap轻量化模组SRM813Q正式通过广东联通5G创新实验室端到端的测试验收&#xff0c;获颁测评证书。美格智能已连续通过业内两家权威实验室的测试认证&#xff0c;充分验证SRM813Q系列模组已经具备了成熟的商用能力&#xff0c;将为智慧工业、安防监…

docker - 常用容器部署命令大全(MySQL、Redis、RabbitMQ、ES、Kibana、Nacos、Sentinel)

目录 一、常用容器运行指令 MySQL Redis RabbitMQ ElasticSearch & kibana Nacos Sentinel 一、常用容器运行指令 MySQL docker run -d --name mysql -p 3306:3306 -e TZAsia/Shanghai -e MYSQL_ROOT_PASSWORD1111 mysql:5.7 -e TZAsia/Shanghai&#xff1a;指定…

听GPT 讲Rust源代码--compiler(26)

File: rust/compiler/rustc_target/src/abi/call/mips.rs 在Rust源代码中的rust/compiler/rustc_target/src/abi/call/mips.rs文件是关于MIPS架构的函数调用ABI(Aplication Binary Interface)定义。ABI是编程语言与底层平台之间的接口规范&#xff0c;用于定义函数调用、参数传…

centos7部署minio单机版

一、目标 在centos7上部署minio单机版 二、centos7部署minio 1、下载minio mkdir /usr/local/minio cd /usr/local/minio wget https://dl.minio.io/server/minio/release/linux-amd64/minio chmod x minio 2、新建minio存储数据的目录 mkdir -p /data/minio/data3、新建…

首次引入大模型!Bert-vits2-Extra中文特化版40秒素材复刻巫师3叶奈法

Bert-vits2项目又更新了&#xff0c;更新了一个新的分支&#xff1a;中文特化&#xff0c;所谓中文特化&#xff0c;即针对中文音色的特殊优化版本&#xff0c;纯中文底模效果百尺竿头更进一步&#xff0c;同时首次引入了大模型&#xff0c;使用国产IDEA-CCNL/Erlangshen-Megat…

外包干了1个月,技术退步一大半。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

FFmpeg读取Assets资源文件

在Android开发中我们经常把原生资源文件放在assets目录下以供需要时读取&#xff0c;通过API提供的resources.assets.open(filename)/openFd(filenam)方法可以非常方便获得InputStream或FileDescriptor&#xff08;文件标识符&#xff09;&#xff0c;但是在使用FFmpeg读取Asse…

Keil使用手册

文章目录 1 设置1.1 背景1.2 Project窗口显示.h文件1.3 注释1.4 Project窗口消失TAB转空格的设置keilsourceInsight 显示cannot evaluate普通局部变量静态全局变量静态局部变量 2 报错与解决2.1 warning&#xff1a;#1-D last line of file ends without anewline2.2 中文乱码 …

13. 强化学习编程实验1-在格子世界中寻宝(1)

文章目录 1.实验目的2.任务描述3.任务分析3.1 待求问题是多步决策问题否3.2 问题求解过程是一个马尔科夫决策过程3.3 状态空间S的确定3.4 动作空间A的确定3.5 状态转移概率P的确定3.6 立即回报R的确定3.7 折扣 γ \gamma γ的确定 4. 编程架构4.1 程序中有哪些对象和类4.2 环境…

Python中的@abstractmethod

abstractmethod 是 Python 中 abc 模块&#xff08;Abstract Base Classes&#xff09;提供的一个装饰器&#xff0c;用于声明抽象方法。抽象方法是指在抽象类中声明但没有提供具体实现的方法&#xff0c;而是由其子类提供具体实现。 使用 abstractmethod 装饰器可以使得子类在…

ES -极客学习

Elasticsearch 简介及其发展历史 起源 Lucene 于 Java 语言开发的搜索引擎库类创建于 1999 年&#xff0c;2005 年成为 Apache 顶级开源项目Lucene 具有高性能、易扩展的优点Lucene 的局限性 只能基于 Java 语言开发类库的接口学习曲线陡峭原生并不支持水平扩展原生并不支持水…

TypeScript 从入门到进阶之基础篇(三) 元组类型篇

系列文章目录 TypeScript 从入门到进阶系列 TypeScript 从入门到进阶之基础篇(一) ts基础类型篇TypeScript 从入门到进阶之基础篇(二) ts进阶类型篇TypeScript 从入门到进阶之基础篇(三) 元组类型篇TypeScript 从入门到进阶之基础篇(四) symbol类型篇 持续更新中… 文章目录 …

【编译原理】期末预习PPT后三章笔记+LL(1) II

继续预习O.o 从这一章开始看自己班发的 PPT 了 LL(1)的部分因为班里发了所以又看了一遍hhh感觉比之前那个清楚一点 目录 I. 自顶向下 一、概念&#xff08;看一眼&#xff09; 1、语法分析的两大类分析方法 2、算法基本思想 3、自顶向下介绍 1&#xff09;一般过程 2&a…

初识大数据,一文掌握大数据必备知识文集(12)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

求实创新 不断探索 浙江移动基于亚信科技AntDB数据库率先完成CRM系统全域改造

12日20日&#xff0c;中国信息通信研究院&#xff08;简称&#xff1a;信通院&#xff09;和中国通信标准化协会大数据库技术推进委员会&#xff08;CCSA TC601&#xff09;共同组织的2023年大数据库“星河&#xff08;Galaxy&#xff09;”案例评选结果发布。中国移动通信集团…

k8s声明式资源管理

三种常见的项目发布方式 1、蓝绿发布 2、金丝雀发布&#xff08;灰度发布&#xff09; 3、滚动发布 应用程序升级&#xff0c;面临的最大的问题是新旧业务之间的切换&#xff0c;立项-----定稿------需求发布-----开发------测试------发布&#xff0c;测试之后上线&#x…

YOLOv5改进 | Neck篇 | 利用Damo-YOLO的RepGFPN改进特征融合层

一、本文介绍 本文给大家带来的改进机制是Damo-YOLO的RepGFPN(重参数化泛化特征金字塔网络),利用其优化YOLOv5的Neck部分,可以在不影响计算量的同时大幅度涨点(亲测在小目标和大目标检测的数据集上效果均表现良好涨点幅度超级高!)。RepGFPN不同于以往提出的改进模块,其…

2024CISA开门红,凌晨通过

祝各位新年快乐&#xff0c;万事顺遂 听说最近it内审有很多甲方开始裁员&#xff0c;为了防止波及到各位&#xff0c;想必各位也在考虑考取证书提高自己的权重&#xff0c;就算后面波及到了自己&#xff0c;去换工作的时候也会快人一步 但是大家都知道&#xff0c;最近都忙得…

HIL(硬件在环)技术汇总梳理

HIL&#xff08;Hardware-in-the-Loop&#xff09;测试领域的知名公司有dSPACE、NI、Vector和speedgoat等&#xff0c;以下是针对这几家HIL技术的对比分析&#xff1a; 文章目录 dSPACE NI Vector speedgoat 总结 dSPACE dSPACE成立于1988年&#xff0c;起源自德国的帕德…