用户态/内核态和线程模型

2019-01-15 23:46:29 +08:00
 thomaswang
如果线程调用系统资源就会切换到内核态, 这个内核态和线程模型(n:1 1:1 n:m)中的内核线程都是啥关系
3790 次点击
所在节点    程序员
22 条回复
besto
2019-01-15 23:48:48 +08:00
我猜 lz 正在看程序员的自我修养
halo117
2019-01-16 00:14:20 +08:00
https://www.cnblogs.com/varcp/p/5434716.html
线程的实现可以分为两类:用户级线程(User-LevelThread, ULT)和内核级线程(Kemel-LevelThread,  KLT)。内核级线程又称为内核支持的线程。

在用户级线程中,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。应用程序可以通过使用线程库设计成多线程程序。通常,应用程序从单线程起始,在该线程中开始运行,在其运行的任何时刻,可以通过调用线程库中的派生例程创建一个在相同进程中运行的新线程。图 2-2(a)说明了用户级线程的实现方式。

在内核级线程中,线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只有一个到内核级线程的编程接口。内核为进程及其内部的每个线程维护上下文信息,调度也是在内核基于线程架构的基础上完成。图 2-2(b)说明了内核级线程的实现方式。

在一些系统中,使用组合方式的多线程实现。线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行。一个应用程序中的多个用户级线程被映射到一些(小于或等于用户级线程的数目)内核级线程上。图 2-2(c)说明了用户级与内核级的组合实现方式。
w4ngzhen
2019-01-16 07:35:26 +08:00
@halo117 额,内核级别 kernel 吧,kemel 怕不是你手写导致 rn 识别成了 m😂
thomaswang
2019-01-16 09:32:34 +08:00
@besto 那倒没有
402124773
2019-01-16 14:08:18 +08:00
好问题,我也没法完全回答。等我问到答案后回答你
GeruzoniAnsasu
2019-01-16 14:44:13 +08:00
系统调用切换到内核态只是会运行内核中的代码而已,线程还是线程,可以说是两个方面的东西,没什么联系可言

我猜 lz 可能是对内核到底是怎么工作的怎么与用户态协同有疑问?
iwtbauh
2019-01-16 14:44:48 +08:00
有的线程库中的线程是在用户空间模拟的,对内核而言,这些“线程”是透明的而且是一个内核线程。

或者你可以理解为用户空间又重新实现了一遍调度。而内核线程则使用内核的调度程序。

所以,自然用户空间线程库可以把单一用户线程与单一内核线程绑定。多个用户线程与单一内核线程绑定。
hx1997
2019-01-16 15:26:03 +08:00
不太清楚 LZ 问什么… 线程使用系统调用时陷入内核,CPU 切换到内核态,但陷入内核后跑的就是 OS 内核的代码,而非线程本身的代码了。

用户态 /内核态是 CPU 的状态,用户级线程 /内核级线程是线程的实现方式,是两个维度的东西。
thomaswang
2019-01-17 15:02:57 +08:00
@GeruzoniAnsasu 线程模型(n:1 1:1 n:m), 那这个是谁比谁啊
thomaswang
2019-01-17 15:05:09 +08:00
@hx1997 “陷入内核后跑的就是 OS 内核的代码", 这个和内核线程有关系吗,是不是内核线程来处理, 我说的这个内核线程是 N:M 中的 M
GeruzoniAnsasu
2019-01-17 19:48:41 +08:00
@thomaswang 操作系统提供的原生线程,是由内核实现的,当发生时钟中断,内核的终端处理流程会切换线程上下文并调度到另一个线程继续执行;原生线程是必须依赖硬件和内核共同工作的,也就是你说的“内核线程”

一般来说一个原生线程只干一件事,但有时候不满足需要

现在不考虑原生线程,假设你要在一个单一线程里模拟同时执行多个任务能实现吗
—— 是可以的,比如实现一个虚拟机:

def main_loop():
..for t in tasks:
....c = next(task.codes)
....ctx = task.context
....exec_opcode(c,ctx)

取得每个 task 的下一条指令,执行一遍然后马上返回主循环,每个 task 中执行流都会往下走,但这都完全发生在同一个线程里

当然拆到这么细粒度需要的代价非常大(实现虚拟机过于复杂而且慢得很)刚好很多语言提供了协程或者撸协程用的 feature,比如 yield:

def fake_sleep():
..yield

def thread1():
..while True:
....print('thread1')
....yield from fake_sleep()

def thread2():
..while True:
....print('thread2')
....yield from fake_sleep()

while True:
..for t in [thread1(),thread2()]:
....t.send(None)

每一个 fake_sleep 都是调度点,此时我们就可以把 thread1 和 thread2 看做是“线程”(协程),在一段时间内这两个函数会“并行”执行然后交替输出 thread1/thread2

于是我们就可以用协程来模拟多任务并行了,只不过需要手动实现切换 task 的代码

以上你已经了解了 用户线程内核线程 1:1 以及 N:1 的模型
那么任意比例的模型也就是把若干个 N:1 模型放进几个真正的原生线程里而已,当然其中复杂度和涉及的细节都会多很多,此处不展开



可以看到这几种模型其实跟内核代码怎么切换根本就没什么关联,是两种意义层次
hx1997
2019-01-18 03:33:25 +08:00
@thomaswang #10 https://en.wikipedia.org/wiki/System_call#Processor_mode_and_context_switching
根据维基的说法,似乎可以这么说:用户级线程的系统调用是由其对应的内核级线程来处理的。(原文:All system calls from a user thread pool are handled by the threads in their corresponding kernel thread pool )

不过在现代操作系统里情况可能有些不同,比如 Windows NT 上是同一个线程有时跑在用户态,有时跑在内核态,在用户态使用系统调用时,切换到内核态,只是把这个线程的上下文切换到内核上下文,线程还是同一个线程,只是跑内核代码了。
hx1997
2019-01-18 03:45:25 +08:00
@thomaswang 注意“跑在用户态的线程”≠“用户级线程”,用户级线程大概可以说是“库提供和调度的线程”。我上面第二段话的线程都是内核级线程(或者说,操作系统提供和调度的线程)。
thomaswang
2019-01-21 16:42:21 +08:00
@iwtbauh 大神,你前面部分我理解了 , ”自然用户空间线程库可以把单一用户线程与单一内核线程绑定。多个用户线程与单一内核线程绑定“,这段我还有疑惑, 用户态进程(p)中线程库创建了很多线程(Ts),p 竞争到 CPU 资源了,分配给 Ts 用, 大家都说 Ts 会调内核线程(就是你说的这个内核线程绑定), 它为什么要去找内核线程呢, 找内核线程干嘛呢,p 竞争到 CPU,Ts 不就可以用了吗, 需要特殊权限的话,中断,然后陷入内核态不就可以了
thomaswang
2019-01-21 17:04:16 +08:00
@hx1997 #12, 我反复的看你的解答,你的意思有这两种情况,我看了很多博客, 感觉有的人说的是第一种情况, 有的很说的是第二种情况, 所以我很懵
thomaswang
2019-01-21 17:21:23 +08:00
@GeruzoniAnsasu 你说的我明白, 你的意思很多个应用的线程,对应 M 个内核线程, 这样每个内核线程对应很多个用户线程, 用户线程 yield 来切换, 我不明白的是, 每个用户进程有很多线程(Ts),进程分配到 CPU 之后, 不就可以执行了吗, 为什么要去调内核线程, 如果需要系统调用,那么把 CPU 状态切换到高权限级别,不就可以执行了吗
iwtbauh
2019-01-21 20:11:06 +08:00
@thomaswang #14

这时候你要把内核线程看是内核调度的单位(实际上很多操作系统也是这么实现的)。即进程是一个或多个内核线程的集合。

实际上现上,在 Linux 内核里面,getpid 返回的是 tgid (即线程组领头线程 id )
thomaswang
2019-01-22 09:55:03 +08:00
@iwtbauh #17
执行这样的代码:
int i = 0;
i++;
用户线程需要绑定内核线程吗?
还是用户线程要得到 CPU 就必须绑定内核线程,CPU 只分配给内核线程
iwtbauh
2019-01-22 13:33:41 +08:00
@thomaswang

用户线程要得到 CPU 就必须绑定内核线程,CPU 只分配给内核线程
GeruzoniAnsasu
2019-01-22 15:42:59 +08:00
@thomaswang 前面不是说了,只要是原生的线程必定是需要内核来实现切换的,而进程只不过是一组线程的集合,硬件时间中断->进入内核->调度线程->切换回用户空间->执行用户空间代码->时间中断,这是一个循环。你可能以为写的所有代码都是 ring3 的用户代码,不需要进入内核空间,可现在的情况是,线程本身就是内核中的一个结构,并且用户代码也是需要内核来帮助切换执行的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/527409

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX