与 Windows 不同,Linux 在内核中没有“线程”的实现。内核为我们提供了有时被称为“轻量级进程”的东西,它是“进程”和“线程”概念的概括,可用于实现其中任何一个。
当您阅读内核代码并一方面看到thread_struct 和另一方面pid(进程ID)之类的东西时,可能会感到困惑。实际上,两者都是一回事。不要被术语混淆。
每个轻量级进程都有一个完全不同的thread_info 和task_struct(嵌入了thread_struct)。您似乎认为一个轻量级进程的task_struct 应该具有指向同一(用户空间)“进程”中其他(用户空间)“线程”的task_structs 的指针。不是这种情况。在内核内部,每个“线程”都是一个独立的进程,调度程序分别处理每个进程。
Linux 有一个名为clone 的系统调用,用于创建新的轻量级进程。当您调用clone 时,您必须提供各种标志来指示新进程和现有进程之间将共享什么。他们可以共享他们的地址空间,也可以各自拥有不同的地址空间。他们可以共享打开的文件,也可以各自拥有自己的打开文件列表。他们可以共享自己的信号处理程序,也可以各自拥有自己的信号处理程序。它们可以在同一个“线程组”中,也可以在不同的线程组中。等等……
虽然“线程”和“进程”在 Linux 中是相同的东西,但您可以通过使用 clone 创建不共享它们的进程来实现我们通常认为的“进程”地址空间、打开的文件、信号处理程序等。
您还可以通过使用clone 来实现我们通常认为的“线程”来创建共享其地址空间、打开文件、信号处理程序等的进程。
如果你查看task_struct的定义,你会发现它有指向其他结构体的指针,例如mm_struct(地址空间)、files_struct(打开文件)、sighand_struct(信号处理程序)、等等。当您clone 一个新的“进程”时,所有这些结构都将被复制。当您clone 一个新的“线程”时,这些结构将在新旧task_structs 之间共享——它们都将指向同一个mm_struct,同一个files_struct , 等等。无论哪种方式,您都只是向clone 提供不同的标志来告诉它要复制什么以及要分享什么。
我刚刚在上面提到了“线程组”,所以您可能对此感到疑惑。简而言之,“进程”中的每个“线程”都有自己的 PID,但它们都共享相同的 TGID(线程组 ID)。 TGID 都等于第一个程序线程的 PID。用户空间“PID”,如ps 或/proc 中所示,实际上是内核中的“TGID”。当然,clone 有一个标志来确定一个新的轻量级进程是否会有一个新的 TGID(从而将其放入一个新的“线程组”中)。
UNIX 进程也有“父”和“子”。 Linux task_struct 中有实现父子关系的指针。而且,您可能已经猜到了,clone 有一个标志来确定新轻量级进程的父进程将是什么。它可以是调用clone 的进程,也可以是调用clone 的进程的父。你能弄清楚创建“进程”时使用哪个,创建“线程”时使用哪个?
查看clone 的联机帮助页;这将是非常有教育意义的。还可以在使用 pthread 的程序上尝试 strace 以查看 clone 正在使用中。
(很多都是凭记忆写的;其他人可以根据需要随时进行更正)