用户级线程是如何调度/创建的,内核级线程是如何创建的?

2024-10-28 08:37:00
admin
原创
70
摘要:问题描述:如果这个问题很愚蠢,请原谅。我尝试在网上寻找答案很长时间,但找不到,所以我在这里提问。我正在学习线程,我一直在浏览这个链接和这个关于内核级和用户级线程的 2013 年 Linux Plumbers Conference 视频,据我所知,使用 pthreads 在用户空间中创建线程,而内核对此并不了解...

问题描述:

如果这个问题很愚蠢,请原谅。我尝试在网上寻找答案很长时间,但找不到,所以我在这里提问。我正在学习线程,我一直在浏览这个链接和这个关于内核级和用户级线程的 2013 年 Linux Plumbers Conference 视频,据我所知,使用 pthreads 在用户空间中创建线程,而内核对此并不了解,只将其视为单个进程,不知道里面有多少个线程。在这种情况下,

  • 谁来决定在进程获得的时间片内这些用户线程的调度,因为内核将其视为单个进程并且不知道线程,以及如何进行调度?

  • 如果 pthreads 创建用户级线程,那么如果需要,如何从用户空间程序创建内核级或 OS 线程?

  • 根据上面的链接,操作系统内核提供系统调用来创建和管理线程。那么clone()系统调用是创建内核级线程还是用户级线程?

+ 如果它创建了一个内核级线程,那么`strace`简单的pthreads 程序在执行时也会显示使用 clone(),但为什么它会被视为用户级线程?
+ 如果它不创建内核级线程,那么如何从用户空间程序创建内核线程?
  • 根据链接,它说“它需要每个线程都有一个完整的线程控制块 (TCB) 来维护有关线程的信息。 因此,开销很大,并且内核复杂性增加。”,那么在内核级线程中,只有堆是共享的,其余的都是线程独有的?

编辑:

我问的是用户级线程创建及其调度,因为 这里引用了多对一模型,其中许多用户级线程映射到一个内核级线程,线程管理由线程库在用户空间中完成。我只看到使用 pthreads 的引用,但不确定它是创建用户级线程还是内核级线程。


解决方案 1:

这是以热门评论作为前提的。

您正在阅读的文档是通用的 [不是 Linux 特定的] 并且有点过时。更重要的是,它使用了不同的术语。我相信这就是造成混乱的根源。所以,请继续阅读...

它所谓的“用户级”线程就是我所说的 [过时的] LWP 线程。它所谓的“内核级”线程就是 Linux 中所谓的本机线程。在 Linux 下,所谓的“内核”线程是完全不同的东西 [见下文]。

使用 pthreads 在用户空间中创建线程,而内核对此并不了解,仅将其视为单个进程,不知道里面有多少个线程。

这就是在 (原生 posix 线程库) 出现之前用户空间线程的实现方式NPTL。这也是 SunOS/Solaris 所称的LWP轻量级进程。

有一个进程可以多路复用自身并创建线程。如果我没记错的话,它被称为线程主进程 [或类似名称]。内核不知道这一点。内核还不理解或不支持线程。

但是,因为这些“轻量级”线程是由基于用户空间的线程主机(又名“轻量级进程调度程序”)[只是一个特殊的用户程序/进程]中的代码切换的,所以它们切换上下文的速度非常慢。

此外,在“本机”线程出现之前,您可能有 10 个进程。每个进程获得 10% 的 CPU。如果其中一个进程是具有 10 个线程的 LWP,则这些线程必须共享这 10%,因此每个线程只能获得 1% 的 CPU。

所有这些都被内核调度程序知道的“本机”线程所取代。这一转变是在 10-15 年前完成的。

现在,在上面的例子中,我们有 20 个线程/进程,每个线程/进程获得 5% 的 CPU。而且,上下文切换要快得多。

在本机线程下仍然可以拥有 LWP 系统,但是,现在,这是一种设计选择,而不是必需品。

此外,如果每个线程都“合作”,LWP 就能很好地工作。也就是说,每个线程循环都会定期显式调用“上下文切换”函数。它会主动放弃进程槽,以便另一个 LWP 可以运行。

但是,NPTL 之前的实现glibc还必须 [强制] 抢占 LWP 线程(即实现时间分片)。我不记得使用了哪种机制,但这里有一个例子。线程主控必须设置一个闹钟,进入睡眠状态,然后唤醒,然后向活动线程发送信号。信号处理程序将影响上下文切换。这很混乱、丑陋,而且有点不可靠。

Joachim 提到的pthread_create函数创建了内核线程

从技术上来说,将其称为内核线程是错误的。pthread_create创建一个本机线程。它在用户空间中运行,并与进程平等地争夺时间片。一旦创建,线程和进程之间就没有什么区别了。

主要区别在于进程有自己独特的地址空间。而线程是与同一线程组中的其他进程/线程共享地址空间的进程。

如果它不创建内核级线程,那么如何从用户空间程序创建内核线程?

内核线程不是用户空间线程、NPTL、本机线程或其他线程。它们由内核通过函数创建kernel_thread。它们作为内核的一部分运行,与任何用户空间程序/进程/线程无关。它们对机器具有完全访问权限。设备、M​​MU 等。内核线程在最高特权级别运行:ring 0。它们还在内核的地址空间中运行,而不是在任何用户进程/线程的地址空间中运行。

用户空间程序/进程不得创建内核线程。请记住,它使用创建本机pthread_create线程,并调用clone系统调用来执行此操作。

线程对于执行操作很有用,甚至对于内核也是如此。因此,它会在各种线程中运行其部分代码。您可以通过执行来查看这些线程ps ax。查看后您就会看到kthreadd, ksoftirqd, kworker, rcu_sched, rcu_bh, watchdog, migration,等等。这些是内核线程,而不是程序/进程。


更新:

您提到内核不知道用户线程。

请记住,如上所述,有两个“时代”。

(1) 在内核获得线程支持之前(大约在 2004 年?)。这使用了线程主控(在这里,我将其称为 LWP 调度程序)。内核只有系统fork调用。

(2) 此后的所有内核都理解线程。没有线程主控,但是,我们有pthreadsclone系统调用。现在,fork实现为cloneclone类似于fork,但需要一些参数。值得注意的是,一个flags参数和一个child_stack参数。

更多信息如下...

那么,用户级线程如何可能拥有单独的堆栈?

处理器堆栈没有什么“魔力”。我将主要讨论 x86,但这适用于任何架构,甚至那些没有堆栈寄存器的架构(例如 1970 年代的 IBM 大型机,如 IBM System 370)

在 x86 下,堆栈指针是%rsp。x86 有pushpop指令。我们用它们来保存和恢复事物:push %rcx和 [稍后] pop %rcx

但是,假设 x86没有%rsp或指令?我们还能有push/pop堆栈吗?当然,按照惯例。我们(作为程序员)同意(例如)%rbx是堆栈指针。

在这种情况下,“推送”%rcx将是[使用 AT&T 汇编程序]:

subq    $8,%rbx
movq    %rcx,0(%rbx)

并且,“流行”的%rcx将是:

movq    0(%rbx),%rcx
addq    $8,%rbx

为了方便理解,我将使用 C“伪代码”。以下是上述 push/pop 的伪代码:

// push %ecx
    %rbx -= 8;
    0(%rbx) = %ecx;

// pop %ecx
    %ecx = 0(%rbx);
    %rbx += 8;

要创建线程,LWP 调度程序必须使用 创建一个堆栈区域malloc。然后,它必须将此指针保存在每个线程结构中,然后启动子 LWP。实际代码有点棘手,假设我们有一个LWP_create类似于 的(例如)函数pthread_create

typedef void * (*LWP_func)(void *);

// per-thread control
typedef struct tsk tsk_t;
struct tsk {
    tsk_t *tsk_next;                    //
    tsk_t *tsk_prev;                    //
    void *tsk_stack;                    // stack base
    u64 tsk_regsave[16];
};

// list of tasks
typedef struct tsklist tsklist_t;
struct tsklist {
    tsk_t *tsk_next;                    //
    tsk_t *tsk_prev;                    //
};

tsklist_t tsklist;                      // list of tasks

tsk_t *tskcur;                          // current thread

// LWP_switch -- switch from one task to another
void
LWP_switch(tsk_t *to)
{

    // NOTE: we use (i.e.) burn register values as we do our work. in a real
    // implementation, we'd have to push/pop these in a special way. so, just
    // pretend that we do that ...

    // save all registers into tskcur->tsk_regsave
    tskcur->tsk_regsave[RAX] = %rax;
    // ...

    tskcur = to;

    // restore most registers from tskcur->tsk_regsave
    %rax = tskcur->tsk_regsave[RAX];
    // ...

    // set stack pointer to new task's stack
    %rsp = tskcur->tsk_regsave[RSP];

    // set resume address for task
    push(%rsp,tskcur->tsk_regsave[RIP]);

    // issue "ret" instruction
    ret();
}

// LWP_create -- start a new LWP
tsk_t *
LWP_create(LWP_func start_routine,void *arg)
{
    tsk_t *tsknew;

    // get per-thread struct for new task
    tsknew = calloc(1,sizeof(tsk_t));
    append_to_tsklist(tsknew);

    // get new task's stack
    tsknew->tsk_stack = malloc(0x100000)
    tsknew->tsk_regsave[RSP] = tsknew->tsk_stack;

    // give task its argument
    tsknew->tsk_regsave[RDI] = arg;

    // switch to new task
    LWP_switch(tsknew);

    return tsknew;
}

// LWP_destroy -- destroy an LWP
void
LWP_destroy(tsk_t *tsk)
{

    // free the task's stack
    free(tsk->tsk_stack);

    remove_from_tsklist(tsk);

    // free per-thread struct for dead task
    free(tsk);
}

对于理解线程的内核,我们使用pthread_createclone,但我们仍然必须创建新线程的堆栈。内核不会新线程创建/分配堆栈。clone系统调用接受一个child_stack参数。因此,pthread_create必须为新线程分配一个堆栈并将其传递给clone

// pthread_create -- start a new native thread
tsk_t *
pthread_create(LWP_func start_routine,void *arg)
{
    tsk_t *tsknew;

    // get per-thread struct for new task
    tsknew = calloc(1,sizeof(tsk_t));
    append_to_tsklist(tsknew);

    // get new task's stack
    tsknew->tsk_stack = malloc(0x100000)

    // start up thread
    clone(start_routine,tsknew->tsk_stack,CLONE_THREAD,arg);

    return tsknew;
}

// pthread_join -- destroy an LWP
void
pthread_join(tsk_t *tsk)
{

    // wait for thread to die ...

    // free the task's stack
    free(tsk->tsk_stack);

    remove_from_tsklist(tsk);

    // free per-thread struct for dead task
    free(tsk);
}

内核只会为进程或主线程分配其初始堆栈,通常位于高内存地址。因此,如果进程不使用线程,通常它只会使用预先分配的堆栈。

但是,如果创建了线程(无论是LWP 还是本机线程),则启动进程/线程必须使用 为拟议的线程预分配区域malloc旁注:使用malloc是正常方式,但线程创建者可以拥有一个大型全局内存池:char stack_area[MAXTASK][0x100000];如果它希望这样做的话。

如果我们有一个不使用[任何类型]线程的普通程序,它可能希望“覆盖”给定的默认堆栈。

如果该进程正在执行大型递归函数,则它可能决定使用malloc上述汇编程序技巧来创建更大的堆栈。

在这里查看我的答案:用户定义堆栈和内置堆栈在内存使用方面有什么区别?

解决方案 2:

用户级线程通常是协程,形式多种多样。在用户模式下,在执行流之间切换上下文,无需内核参与。从内核的角度来看,所有线程都是一个线程。线程实际执行的操作由用户模式控制,并且用户模式可以暂停、切换、恢复逻辑执行流(即协程)。这一切都发生在为实际线程安排的量子期间。内核可以并且会毫不留情地中断实际线程(内核线程),并将处理器的控制权交给另一个线程。

用户模式协程需要协作式多任务处理。用户模式线程必须定期将控制权移交给其他用户模式线程(基本上,执行将上下文更改为新的用户模式线程,而内核线程却不会注意到任何事情)。通常情况下,代码比内核更清楚何时需要释放控制权。编码不当的协程可能会窃取控制权,并使所有其他协程陷入困境。

使用过的历史实现setcontext,但现在已弃用。Boost.context提供了它的替代品,但不是完全可移植的:

Boost.Context 是一个基础库,可在单个线程上提供一种协作式多任务处理。通过提供当前线程中当前执行状态的抽象,包括堆栈(带有局部变量)和堆栈指针、所有寄存器和 CPU 标志以及指令指针,execution_context 表示应用程序执行路径中的特定点。

毫不奇怪,Boost.coroutine是基于 Boost.context 的。

Windows 提供的Fibers。.Net运行时有 Tasks 和 async/await。

解决方案 3:

LinuxThreads 遵循所谓的“一对一”模型:每个线程实际上是内核中的一个独立进程。内核调度程序负责调度线程,就像调度常规进程一样。线程是通过 Linux clone() 系统调用创建的,它是 fork() 的泛化,允许新进程共享父进程的内存空间、文件描述符和信号处理程序。

来源 - Xavier Leroy(LinuxThreads 的创建者)的采访
http://pauillac.inria.fr/~xleroy/linuxthreads/faq.html#K

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用