如果这个问题很愚蠢,请道歉 . 我试图在网上找到一个答案很长一段时间,但不能在这里问这个问题 . 我正在学习线程,我一直在经历关于内核级别和用户级线程的this link和this Linux Plumbers Conference 2013 video,据我所知,使用pthreads在用户空间中创建线程,内核不知道这一点并将其视为只有一个进程,不知道里面有多少线程 . 在这种情况下,
-
谁在流程获得的时间片期间决定这些用户线程的调度,因为内核将其视为单个进程并且不知道线程,以及如何完成调度?
-
如果pthreads创建用户级线程,如果需要,如何从用户空间程序创建内核级或OS线程?
-
根据上面的链接,它说操作系统内核提供系统调用来创建和管理线程 . 那么
clone()
系统调用是否会创建内核级线程或用户级线程? -
如果它创建了一个内核级线程,那么
strace
的一个简单的pthreads program也会在执行时显示使用clone(),但是为什么它会被视为用户级线程呢? -
如果它没有创建内核级线程,那么如何从用户空间程序创建内核线程?
-
根据链接,它说"It require a full thread control block (TCB) for each thread to maintain information about threads. As a result there is significant overhead and increased in kernel complexity.",所以在内核级线程中,只有堆是共享的,其余的都是线程的个体?
编辑:
我问的是用户级线程创建,以及它的调度,因为here,存在对多对一模型的引用,其中许多用户级线程被映射到一个内核级线程,并且线程管理由用户空间在线程完成图书馆 . 我一直只看到使用pthreads的引用,但不确定它是否创建了用户级或内核级线程 .
3 回答
这是前面的评论开头 .
您正在阅读的文档是通用的[不是特定于Linux的]并且有点过时 . 而且,更重要的是,它使用了不同的术语 . 也就是说,我相信,混乱的根源 . 所以,请继续阅读......
它所谓的"user-level"线程就是我所谓的[过时的] LWP线程 . 它所谓的"kernel-level"线程就是linux中的本机线程 . 在linux下,所谓的"kernel"线程完全是另一回事[见下文] .
这是用户空间线程在
NPTL
(本机posix线程库)之前完成的方式 . 这也是SunOS / Solaris称为LWP
轻量级进程的原因 .有一个进程多路复用并创建了线程 . IIRC,它被称为线程主进程[或某些此类] . 内核没有意识到这一点 . 内核还没有理解或提供对线程的支持 .
但是,因为这些“轻量级”线程是由基于用户空间的线程主机(也就是“轻量级进程调度程序”)[只是一个特殊的用户程序/进程]中的代码切换的,所以它们切换上下文非常慢 .
此外,在“本机”线程出现之前,您可能有10个进程 . 每个进程获得10%的CPU . 如果其中一个进程是具有10个线程的LWP,则这些线程必须共享该10%,因此每个进程只有1%的CPU .
所有这些都被内核调度程序知道的"native"线程所取代 . 这种转变是在10 - 15年前完成的 .
现在,通过上面的例子,我们有20个线程/进程,每个进程获得5%的CPU . 并且,上下文切换更快 .
仍然可以在本机线程下拥有LWP系统,但是,现在,这是一种设计选择,而不是必需品 .
此外,如果每个线程"cooperates",LWP工作得很好 . 也就是说,每个线程循环周期性地对"context switch"函数进行显式调用 . 它自愿放弃进程槽,以便另一个LWP可以运行 .
但是,
glibc
中的NPTL前实现也必须[强制]抢占LWP线程(即实现时间分割) . 我可以't remember the exact mechanism used, but, here'一个例子 . 线程主机必须设置警报,进入休眠状态,唤醒然后向活动线程发送信号 . 信号处理程序将影响上下文切换 . 这是混乱,丑陋,有点不可靠 .这在技术上是不正确的,称之为内核线程 .
pthread_create
创建本机线程 . 这在用户空间中运行,并在与进程平等的基础上争夺时间片 . 一旦创建,线程和进程之间几乎没有区别 .主要区别在于进程有自己唯一的地址空间 . 但是,线程是与其他进程/线程共享其地址空间的进程,这些进程/线程是同一线程组的一部分 .
内核线程不是用户空间线程,NPTL,本机或其他 . 它们由内核通过
kernel_thread
函数创建 . 它们作为内核的一部分运行,并且不与任何用户空间程序/进程/线程相关联 . 他们可以完全访问机器 . 设备,MMU等 . 内核线程以最高权限级别运行:ring 0.它们也在内核的地址空间中运行,而不是在任何用户进程/线程的地址空间中运行 .用户空间程序/进程可能不会创建内核线程 . 请记住,它使用
pthread_create
创建一个本机线程,它调用clone
系统调用来执行此操作 .即使对于内核,线程也很有用 . 因此,它在各种线程中运行它的一些代码 . 你可以通过
ps ax
看到这些线程 . 看,你会看到kthreadd, ksoftirqd, kworker, rcu_sched, rcu_bh, watchdog, migration
等 . 这些是内核线程,而不是程序/进程 .UPDATE:
请记住,如上所述,有两个“时代” .
(1)在内核获得线程支持之前(大约2004年?) . 这使用了线程主机(在这里,我将称之为LWP调度程序) . 内核只有
fork
系统调用 .(2)之后的所有内核都能理解线程 . 没有线程主,但是,我们有
pthreads
和clone
系统调用 . 现在,fork
实现为clone
.clone
与fork
类似,但需要一些参数 . 值得注意的是,flags
参数和child_stack
参数 .更多关于此...
关于处理器堆栈没有任何“魔力” . 我将讨论[主要]限于x86,但这适用于任何架构,甚至那些甚至没有堆栈寄存器的架构(例如1970年代的IBM大型机,例如IBM System 370)
在x86下,堆栈指针是
%rsp
. x86具有push
和pop
指令 . 我们使用这些来保存和恢复:push %rcx
和[later]pop %rcx
.但是,假设x86没有
%rsp
或push/pop
指令?我们还能有堆叠吗?当然,按照惯例 . 我们[作为程序员]同意(例如)%rbx
是堆栈指针 .在这种情况下,
%rcx
%rcx
将[使用AT&T汇编程序]:并且
%rcx
的%rcx
将是:为了更容易,我将切换到C“伪代码” . 以下是上面的push / pop in伪代码:
要创建线程,LWP调度程序必须使用
malloc
创建堆栈区域 . 然后它必须将此指针保存在每个线程的结构中,然后启动子LWP . 实际代码有点棘手,假设我们有一个(例如)LWP_create
函数类似于pthread_create
:使用了解线程的内核,我们使用
pthread_create
和clone
,但我们仍然需要创建新线程的堆栈 . 内核不会为新线程创建/分配堆栈 .clone
系统调用接受child_stack
参数 . 因此,pthread_create
必须为新线程分配一个堆栈并将其传递给clone
:内核只为进程或主线程分配其初始堆栈,通常是在高内存地址 . 因此,如果进程不使用线程,通常它只使用预先分配的堆栈 .
但是,如果创建了一个线程,无论是LWP还是本机线程,起始进程/线程必须使用
malloc
预先为所建议的线程分配区域 . 旁注:使用malloc
是正常的方法,但是线程创建者可能只有一个大的全局内存池:char stack_area[MAXTASK][0x100000];
如果它希望这样做的话 .如果我们有一个不使用[任何类型]线程的普通程序,它可能希望"override"给出它的默认堆栈 .
该进程可以决定使用
malloc
和上面的汇编程序技巧来创建一个更大的堆栈,如果它做了很大的递归函数 .在这里看到我的答案:What is the difference between user defined stack and built in stack in use of memory?
用户级线程通常是以一种或另一种形式的协同程序 . 在用户模式下切换执行流之间的上下文,没有内核参与 . 从内核POV,是一个线程 . 线程实际做的是在用户模式下控制,用户模式可以暂停,切换,恢复执行的逻辑流程(即协同程序) . 这一切都发生在为实际线程安排的量子期间 . 内核可以并且将毫不客气地中断实际线程(内核线程)并将处理器的控制权交给另一个线程 .
用户模式协同程序需要协作式多任务处理 . 用户模式线程必须定期放弃对其他用户模式线程的控制(基本上执行将上下文更改为新用户模式线程,而内核线程没有注意到任何内容) . 通常情况是,当代码想要释放内核控制权时,代码知道的要好得多 . 编码不佳的协程可以窃取控制权并使所有其他协同程序饿死 .
历史实现使用setcontext但现在已弃用 . Boost.context提供了替代品,但不是完全可移植的:
毫不奇怪,Boost.coroutine基于Boost.context .
Windows提供Fibers . .Net运行时具有Tasks和async / await .
LinuxThreads遵循所谓的“一对一”模型:每个线程实际上是内核中的一个独立进程 . 内核调度程序负责调度线程,就像它调度常规进程一样 . 线程是使用Linux clone()系统调用创建的,这是fork()的一般化,允许新进程共享父进程的内存空间,文件描述符和信号处理程序 .
来源 - 采访Xavier Leroy(创建LinuxThreads的人)http://pauillac.inria.fr/~xleroy/linuxthreads/faq.html#K