内核如何将线程与进程分开?

4
假设我有一个浏览器进程,比如 Firefox,其 pid = 123。Firefox 有 5 个打开的标签页,每个标签页在单独的线程中运行,因此总共有 5 个线程。
  1. 那么我想深入了解内核是如何将进程分离成线程以在 struct task_structthread_info 中执行。

  2. struct task_struct 是任务列表中的任务描述符。 struct task_struct 在哪里包含对这五个线程的引用或链接。

  3. 像 Firefox 这样的进程的 struct thread_struct 是否包含对所有 5 个线程的引用

    还是

    每个线程都像 Linux 内核中的一个进程一样处理。


task_struct 包含关于进程的与体系结构无关的信息。thread_info 包含与体系结构相关的信息和内核堆栈。task_struct 拥有一个指向 thread_info 的指针,同时从 thread_info 也可以访问到 task_struct。在 Linux 中,进程和线程都基本上是 task_structs 的变种。 - bolov
1个回答

20

与Windows不同,Linux内核中没有实现"线程"。内核提供了一种被称为"轻量级进程"的通用概念,它是"进程"和"线程"概念的推广,可以用来实现两者之一。

当你阅读内核代码并看到thread_structpid(进程ID)时,可能会感到困惑。但实际上,它们是一样的。不要被术语所迷惑。

每个轻量级进程都有完全不同的thread_infotask_struct (嵌套thread_struct)。你可能认为一个轻量级进程的task_struct应该有指向同一(用户空间) "进程"中其他 "线程" 的 task_struct 的指针。然而这并不是这样的。在内核内部,每个“线程”都是一个单独的进程,并且调度程序分别处理每个进程。

Linux 有一个名为clone的系统调用,用于创建新的轻量级进程。当调用clone时,必须提供各种标志,以指示新进程和现有进程之间将共享什么。它们可以共享地址空间,也可以有不同的地址空间。它们可以共享打开的文件,也可以拥有各自的打开文件列表。它们可以共享信号处理程序,也可以拥有各自的信号处理程序。它们可以在同一个“线程组”中,也可以在不同的线程组中等等......

尽管在Linux中"线程"和"进程"是相同的,但你可以使用clone创建不共享地址空间、打开文件、信号处理程序等的进程来实现我们通常认为的"进程"。

你也可以使用clone创建共享地址空间、打开文件、信号处理程序等的进程,以实现我们通常所说的"线程"。

如果你看一下task_struct的定义,你会发现它有指向其他结构体的指针,比如mm_struct(地址空间)、files_struct(打开的文件)、sighand_struct(信号处理程序),等等。当你clone一个新的“进程”时,所有这些结构体都会被复制。当你clone一个新的“线程”时,这些结构体将在新旧task_struct之间共享——它们都指向同一mm_struct、同一files_struct等等。无论哪种方式,你只需向clone提供不同的标志,告诉它该复制什么,该共享什么。
我刚才提到了“线程组”,所以你可能会想知道这个。简而言之,一个“进程”中的每个“线程”都有自己的PID,但它们共享相同的TGID (线程组ID)。TGIDs都等于第一个程序线程的PID。用户空间的“PIDs”,如ps/proc中显示的那些,实际上是内核中的“TGIDs”。自然地,clone有一个标志来确定一个新的轻量级进程是否有一个新的TGID(从而将其放入一个新的“线程组”)。
UNIX进程也有“父进程”和“子进程”。在Linux task_struct中实现了父子关系的指针。正如你可能已经猜到的,clone有一个标志来确定一个新的轻量级进程的父进程是什么。它可以是调用clone的进程,也可以是调用clone的进程的父进程。你能想出在创建“进程”时使用哪个,以及在创建“线程”的时候使用哪个吗?

查看clone的手册,这会非常有益。还可以尝试对使用pthread的程序运行strace,以便查看clone的使用情况。

(很多内容都是凭记忆写的;其他人可以随意进行编辑以进行必要的更正)


太棒了,Alex!你的回答一次性解决了我几个问题 :) - Arun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接