当谈到 Linux 内核时,我是一个新手,但我正在阅读这篇关于原纤维的文章其中提到它们是(或曾经?不确定它们是否最终保留在内核中)内核空间线程。
文章继续说道:
fibril 是仅在内核空间中运行的执行线程。 ...原纤维有自己的堆栈,但除此之外它们共享其父进程的所有资源。它们保存在附加到任务结构的链接列表中。
拥有自己的堆栈但与父进程共享其余内存看起来就像线程的定义一样。
但我想知道的是:内核中的进程或线程可以是用户空间进程的子进程吗?或者我不明白这篇文章的意思,所有原纤维都有一个大的父进程?我不这么认为,因为:
一个进程可以有任意数量的活动原纤维,但在任何给定时间只有其中一个可以在处理器中实际执行
一般来说,原纤维应该从调用用户空间进程执行任务。
答案1
内核线程是正在运行的线程永久在内核模式下。
发出ps -efl
命令。您将认出这些内核线程,因为它们的名称位于方括号之间。并且,注意父进程 ID,您会发现其中大多数是 PID 2 ([kthreadd]) 的子进程,其他进程是 PID 1 (init[3]) 的子进程
所以,基本上,你的问题的答案是:不!
然而,这并不能阻止任何线程发出系统调用,这将使它们在执行调用时暂时切换到内核模式。但这并不能让他们内核线程它只是在内核模式下运行一段时间的线程。
因此,虽然您找不到内核线程的用户进程父级,但您可以让用户进程父级线程(有时在内核模式下执行)。
编辑:以下来自@Stephen Kitt的有趣评论:
虽然 init 确实如此将要通勤到用户空间,本质上,从一开始(内核初始化)就只是一个真正的内核线程。 (VG 不是“常规流程”)必须首先创建的内核线程(为了获得 PID=1),但如果碰巧在 kthreadd 之前调度,则会创建 kthread。
我们只需要看一下代码(init/main.c)
static noinline void __ref rest_init(void){
...
rcu_scheduler_starting();
...
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
...
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
答案2
内核线程只能由其他内核线程(或内核本身,在任何内核线程之前)启动。引用Al Viro 描述设计的电子邮件:
除了初始进程(
init_task
启动 CPU 上的最终空闲线程)之外,所有进程均由do_fork()
.它们分为三类:内核线程、用户态进程和空闲线程。涉及的低级操作很少:
- 一个内核线程可以产生一个新的内核线程;最原始的做法是
kernel_thread()
。- 用户态进程可以生成新的用户态进程;这是由
sys_fork()
//sys_vfork()
/完成的sys_clone()
。sys_clone2()
- 内核线程可以成为用户态进程。原语是
kernel_execve()
.- 内核线程可以生成未来的空闲线程;这是由 完成的
fork_idle()
。结果是不是调度直到辅助 CPU 初始化并且其状态在进程中被大量覆盖。在任何情况下,用户态进程都不能成为内核线程或派生线程。 内核线程永远不会这样做
fork(2)
等。
(强调我的。)这是旧的,但规则仍然适用。
内核线程始终是 的子线程kthreadd
,其 pid 2(pid 1 被保留给init
)。这样做的目的是为了让内核线程有一个干净的环境,无论它们是如何创建的。这也有助于cgroup初始化。
用户空间组件发起的操作可以导致内核线程启动;例如,加载内核模块,或挂载文件系统。但内核线程是由内核启动的,为内核执行工作,不与任何单独的用户空间进程关联;它们不得与除kthreadd
例如用于信号处理目的。
方括号之间显示的进程不一定是内核线程,它们是没有命令行的进程。