科叼 发表于 2024-12-5 17:48:52

谁看谁服! Linux 创始人对于进程和线程的理解是…


作者:五阳

1996 年,linux系统的创始人 linus(林纳斯)在邮件中表达了自己对于进程和线程的深刻理解!以下是翻译的邮件内容。

翻译内容
传统认为“线程”和“进程”是独立的实体,但我个人认为这样想是一个重大错误,唯一这样想的理由是历史包袱。

线程和进程实际上都是一回事:即“执行上下文”,试图人为地区分两者的不同只是自我设限。

所谓“执行上下文”(以下简称COE),只不过是该COE的所有状态的集合。这些状态包括诸如CPU状态(寄存器等)、MMU状态(页映射)、权限状态(uid、gid)和各种“通信状态”(打开的文件、信号处理程序等)。

传统认为,“线程”和“进程”之间的区别主要在于线程具有CPU状态(加上可能的一些其他最小状态),而所有其他上下文来自进程。然而,这仅仅是一种划分计算环境(COE)整体状态的方法,并没有什么规定这种方法是正确的。局限于这种思维方式简直是愚蠢的。

Linux对此的理解(以及我希望事情运作的方式)是,没有所谓的“进程”或“线程”。只有整个计算环境的总和(在Linux中称为“任务”)。不同的计算环境可以共享它们上下文的部分内容,而这种共享能力所能实现的能力之一是传统的“线程/进程” 能力,但是这应该被看作一个 “子集“(这是一个重要的子集,这不是来自于设计,而是来自标准:显然,我们也希望在Linux上运行符合标准的线程程序)。

简而言之:不要围绕线程/进程的思维方式来设计。内核应该围绕COE的思维方式进行设计,然后pthreads库可以向希望以这种方式看待COE的用户导出有限的pthreads接口。

举个例子,当你用COE的思维方式而不是线程/进程的思维方式进行思考时,会变得可能的事情:

你可以做一个外部的 "cd" 程序,这是在UNIX、其他传统的进程线程操作系统上不可能做到的事(虽然例子很简单,但意思是你可以拥有这些不受传统UNIX/线程设置限制的“模块”)。执行:

父进程:clone(CLONE_VM|CLONE_FS);
子进程:execve("external-cd");
/* 由于 "execve()" 会解除 VM 关联,所以我们使用 CLONE_VM 的唯一理由是使克隆操作更快捷 */

你可以自然地使用 "vfork()"(它需要最少的内核支持,这种支持非常符合 CUA 的思路):
父进程:clone(CLONE_VM);
子进程:继续运行,最后调用 execve()
父进程:等待 execve

你可以创建外部 "IO 守护进程":
父进程:clone(CLONE_FILES);
子进程:打开文件描述符等
父进程:使用子进程打开的文件描述符,反之亦然。

插播解释 clone 和 execve
clone

execve


继续翻译原文~~~

上述所有工作之所以可行,是因为你没有被线程/进程的思维方式束缚住。举个例子,想象一个Web服务器,其中CGI脚本作为“执行线程”。如果使用传统线程的话,你无法做到这一点,因为传统线程总是需要共享整个地址空间,所以你必须将所有你想在Web服务器中进行的操作(即脚本)全部链接到服务器本身(传统做法,一个“线程”不能运行另一个可执行文件)。

如果将其视为一个“执行上下文”的问题,那么你的任务现在可以选择执行外部程序(即从父进程分离地址空间)等,或者例如可以与父进程共享除了文件描述符之外的一切(这样子线程可以打开很多文件,而父进程无需担心这些文件:当子线程退出时,这些文件描述符会自动关闭,而且不会占用父进程的文件描述符)。


想象一下一个线程化的 "inetd",比如说。你希望有低开销的 fork+exec,所以按照 Linux 的方式,你可以不用 "fork()",而是编写一个多线程的 inetd,每个线程只使用 CLONE_VM 创建(共享地址空间,但不共享文件描述符等)。然后子线程可以执行 execve,如果它是一个外部服务(例如 rlogind),或者它可能是 inetd 的内部服务之一(比如 echo、timeofday),在这种情况下,它只执行它的功能然后退出。

你无法使用 "线程"/"进程" 来做到这一点。

插播解释 inetd
在嵌入式场景受限于硬件资源,使用inetd较多。

<顺便插播一句,如果你在看新的机会→前、后端 or 测试 ,技术大厂核心团队,薪酬一线标准,base 武汉、深圳、苏州等多地;语言:Java、Js、测试、python、ios、安卓、C++ 等 >

我的思考
林纳斯想要表达 什么内容呢?

在应用开发中存在进程和线程的概念,比如单进程多线程应用程序、多进程应用程序等模式。此外,还有诸如 pthreads 等线程标准库,它们在 Linux 系统中依靠系统调用 API 接口来实现相关功能。

然而,林纳斯认为系统调用API层 区分进程和线程的需要,不应该干扰 linux内核的设计。他坚持认为应该保持内核的抽象性和简洁性。这是因为操作系统的核心任务是管理软硬件资源,例如进程管理、内存管理等,如果额外增加另一个模型,会不可避免地增加系统设计的复杂性,并可能降低未来的扩展性。

因此综合考虑下,纳斯决定在 Linux 内核中,不对进程和线程进行严格区分,而是将其统一抽象为 "执行上下文"(Context of Execution,COE)。这种设计确保了内核结构的简洁性、可维护性、可扩展性。

即便是 30 年前的想法,依然不过时,依然值得我们所有人借鉴!

页: [1]
查看完整版本: 谁看谁服! Linux 创始人对于进程和线程的理解是…