首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Coding
V2EX  ›  程序员

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

  •  1
     
  •   thomaswang · 333 天前 · 2042 次点击
    这是一个创建于 333 天前的主题,其中的信息可能已经有所发展或是发生改变。
    如果线程调用系统资源就会切换到内核态, 这个内核态和线程模型(n:1 1:1 n:m)中的内核线程都是啥关系
    22 回复  |  直到 2019-11-11 17:15:01 +08:00
        1
    besto   333 天前
    我猜 lz 正在看程序员的自我修养
        2
    halo117   333 天前 via iPhone
    https://www.cnblogs.com/varcp/p/5434716.html
    线程的实现可以分为两类:用户级线程(User-LevelThread, ULT)和内核级线程(Kemel-LevelThread,  KLT)。内核级线程又称为内核支持的线程。

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

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

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

    我猜 lz 可能是对内核到底是怎么工作的怎么与用户态协同有疑问?
        7
    iwtbauh   333 天前 via Android
    有的线程库中的线程是在用户空间模拟的,对内核而言,这些“线程”是透明的而且是一个内核线程。

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

    所以,自然用户空间线程库可以把单一用户线程与单一内核线程绑定。多个用户线程与单一内核线程绑定。
        8
    hx1997   333 天前 via Android
    不太清楚 LZ 问什么… 线程使用系统调用时陷入内核,CPU 切换到内核态,但陷入内核后跑的就是 OS 内核的代码,而非线程本身的代码了。

    用户态 /内核态是 CPU 的状态,用户级线程 /内核级线程是线程的实现方式,是两个维度的东西。
        9
    thomaswang   332 天前
    @GeruzoniAnsasu 线程模型(n:1 1:1 n:m), 那这个是谁比谁啊
        10
    thomaswang   332 天前
    @hx1997 “陷入内核后跑的就是 OS 内核的代码", 这个和内核线程有关系吗,是不是内核线程来处理, 我说的这个内核线程是 N:M 中的 M
        11
    GeruzoniAnsasu   332 天前
    @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 模型放进几个真正的原生线程里而已,当然其中复杂度和涉及的细节都会多很多,此处不展开



    可以看到这几种模型其实跟内核代码怎么切换根本就没什么关联,是两种意义层次
        12
    hx1997   331 天前
    @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 上是同一个线程有时跑在用户态,有时跑在内核态,在用户态使用系统调用时,切换到内核态,只是把这个线程的上下文切换到内核上下文,线程还是同一个线程,只是跑内核代码了。
        13
    hx1997   331 天前 via Android
    @thomaswang 注意“跑在用户态的线程”≠“用户级线程”,用户级线程大概可以说是“库提供和调度的线程”。我上面第二段话的线程都是内核级线程(或者说,操作系统提供和调度的线程)。
        14
    thomaswang   328 天前
    @iwtbauh 大神,你前面部分我理解了 , ”自然用户空间线程库可以把单一用户线程与单一内核线程绑定。多个用户线程与单一内核线程绑定“,这段我还有疑惑, 用户态进程(p)中线程库创建了很多线程(Ts),p 竞争到 CPU 资源了,分配给 Ts 用, 大家都说 Ts 会调内核线程(就是你说的这个内核线程绑定), 它为什么要去找内核线程呢, 找内核线程干嘛呢,p 竞争到 CPU,Ts 不就可以用了吗, 需要特殊权限的话,中断,然后陷入内核态不就可以了
        15
    thomaswang   328 天前
    @hx1997 #12, 我反复的看你的解答,你的意思有这两种情况,我看了很多博客, 感觉有的人说的是第一种情况, 有的很说的是第二种情况, 所以我很懵
        16
    thomaswang   328 天前
    @GeruzoniAnsasu 你说的我明白, 你的意思很多个应用的线程,对应 M 个内核线程, 这样每个内核线程对应很多个用户线程, 用户线程 yield 来切换, 我不明白的是, 每个用户进程有很多线程(Ts),进程分配到 CPU 之后, 不就可以执行了吗, 为什么要去调内核线程, 如果需要系统调用,那么把 CPU 状态切换到高权限级别,不就可以执行了吗
        17
    iwtbauh   328 天前 via Android
    @thomaswang #14

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

    实际上现上,在 Linux 内核里面,getpid 返回的是 tgid (即线程组领头线程 id )
        18
    thomaswang   327 天前
    @iwtbauh #17
    执行这样的代码:
    int i = 0;
    i++;
    用户线程需要绑定内核线程吗?
    还是用户线程要得到 CPU 就必须绑定内核线程,CPU 只分配给内核线程
        19
    iwtbauh   327 天前 via Android
    @thomaswang

    用户线程要得到 CPU 就必须绑定内核线程,CPU 只分配给内核线程
        20
    GeruzoniAnsasu   327 天前
    @thomaswang 前面不是说了,只要是原生的线程必定是需要内核来实现切换的,而进程只不过是一组线程的集合,硬件时间中断->进入内核->调度线程->切换回用户空间->执行用户空间代码->时间中断,这是一个循环。你可能以为写的所有代码都是 ring3 的用户代码,不需要进入内核空间,可现在的情况是,线程本身就是内核中的一个结构,并且用户代码也是需要内核来帮助切换执行的
        21
    GeruzoniAnsasu   327 天前
    然后,线程的确还分为只在内核中执行的线程以及包含了用户空间代码的线程,你可能是混淆了这两种内核线程 /用户线程?

    就 linux 来说,它的内核可以看做是单线程的,所以所谓的“内核线程”更像是内核中的协程,毕竟没有更上层的结构去切换上下文,必须自行实现多路复用。内核线程是无法中断的,一旦某个内核线程中有死循环,那么整个内核+用户空间的所有线程都会挂掉。这与多对多的线程模型没有关系,是另一个层面的东西
        22
    ruandao   34 天前
    多核 cpu 中,一个进程陷入 内核态的时候, 这个进程的所有线程都被切换掉了吗? 还是只是 进行系统调用的那个线程进入内核态
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2104 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 29ms · UTC 15:40 · PVG 23:40 · LAX 07:40 · JFK 10:40
    ♥ Do have faith in what you're doing.