Context Of Excution
前言
最近读了挺多的洋文,发现自己的阅读能力或许还算不错,但是缺点在于一篇文章总是读完就忘了。
所以我打算新开一个坑,自己写一些翻译,一方面是提升洋文水平,一方面也可以深入理解一些讨论的东西
这篇文章要翻译的是linus在1996年也就是28年前有关进程和线程的讨论,原文存档在这个网址,虽然年代久远,但内容却完全没有过时,可以说是讨论进程和线程的最佳回答之一了,可以用来回答各种有关”进程和线程的区别”的问题
在多了解了linus的一些事迹后不得不承认他的成功绝非偶然,他就是一个真正的天才
逐段翻译+译注
Re: proc fs and shared pids
Linus Torvalds (torvalds@cs.helsinki.fi)
Tue, 6 Aug 1996 12:47:31 +0300 (EET DST)
On Mon, 5 Aug 1996, Peter P. Eiserloh wrote:
We need to keep a clear the concept of threads. Too many people seem to confuse a thread with a process. The following discussion does not reflect the current state of linux, but rather is an attempt to stay at a high level discussion.
在1996年的八月6号Peter P. Eiserloh写到
我们需要保持一个清晰的线程概念,很多人似乎把进程和线程搞混了。接下来的讨论并不反映当前linux的状态(实现),而是试图保持在一个较高层次的讨论中。
(译注:linus这里引用的是这封邮件,这封邮件中Eiserloh根据经典的进程线程模型向一位不太理解线程和进程的人做了介绍,linus接下来批评的主要是第一句话)
NO!
There is NO reason to think that “threads” and “processes” are separate entities. That’s how it’s traditionally done, but I personally think it’s a major mistake to think that way. The only reason to think that way is historical baggage.
不!
将“线程”和“进程”认为是独立的实体是没有理由的。这是一个传统的观点,但是我个人认为这样的思维方式是个重大错误。之所以会去这样思考只不过是因为历史遗留因素罢了。
Both threads and processes are really just one thing: a “context of execution”. Trying to artificially distinguish different cases is just self-limiting.
线程和进程实际上是同一个东西:“执行上下文”。试图人为地去区分两者只会限制自身(的思维)。
A “context of execution”, hereby called COE, is just the conglomerate of all the state of that COE. That state includes things like CPU state (registers etc), MMU state (page mappings), permission state (uid, gid) and various “communication states” (open files, signal handlers etc).
“执行上下文”(以下将其简称为COE),就是所有该COE的状态的总和。这些状态包括CPU状态(寄存器状态等),MMU状态(页表映射),权限状态(uid,gid)和各种“通信状态”(打开的文件,signal处理程序等)。
Traditionally, the difference between a “thread” and a “process” has been mainly that a threads has CPU state (+ possibly some other minimal state), while all the other context comes from the process. However, that’s just one way of dividing up the total state of the COE, and there is nothing that says that it’s the right way to do it. Limiting yourself to that kind of image is just plain stupid.
传统上,“线程”和“进程”的主要不同在于线程只有CPU状态(也许还有些其他最小状态),而其他全部的上下文都属于进程。然而,这仅仅是划分COE总状态的一种方式,而且也没有理由说这种方式一定是正确的。将你自己局限在此是非常愚蠢的。
The way Linux thinks about this (and the way I want things to work) is that there is no such thing as a “process” or a “thread”. There is only the totality of the COE (called “task” by Linux). Different COE’s can share parts of their context with each other, and one subset of that sharing is the traditional “thread”/”process” setup, but that should really be seen as ONLY a subset (it’s an important subset, but that importance comes not from design, but from standards: we obviusly want to run standards-conforming threads programs on top of Linux too).
Linux的设计思路(以及我个人希望这么做)是:系统中没有所谓的“进程”或者“线程”,所有的东西都是COE(在Linux中称为“task”)。不同的COE之间可以互相共享它们的部分上下文,这些共享情况中的一个子集就是传统的“线程”/“进程”组织方式,但是这种组织方式真的应该被仅仅视为一个子集。(这是一个很重要的子集,但是它的重要性不是来自于设计,而是因为标准:我们显然也希望符合标准线程模型的程序能够在Linux上运行)
In short: do NOT design around the thread/process way of thinking. The kernel should be designed around the COE way of thinking, and then the pthreads library can export the limited pthreads interface to users who want to use that way of looking at COE’s.
简而言之:不要围绕线程/进程的思维方式进行设计,内核应该围绕COE的思维方式进行设计,然后pthreads库能够导出受限的(译注:相比于COE能做的到功能来说)pthreads接口给那些希望以这种方式看待COE的用户。
Just as an example of what becomes possible when you think COE as opposed to thread/process:
这有些例子展示了如果你从COE思维方式而不是线程/进程的思维方式思考时,能够实现些什么
-
You can do a external “cd” program, something that is traditionally impossible in UNIX and/or process/thread (silly example, but the idea is that you can have these kinds of “modules” that aren’t limited to the traditional UNIX/threads setup). Do a:
你可以实现一个外置的“cd”程序,这在传统的UNIX或者进程/线程模型下是不可能实现的。(很蠢的示例,但是主要的思想是你可以实现类似的不受限于传统UNIX/线程模型的“模组”)
clone(CLONE_VM|CLONE_FS); child: execve("external-cd"); /* the "execve()" will disassociate the VM, so the only reason we used CLONE_VM was to make the act of cloning faster */ /* 因为 “execve()”会取消关联所有的虚拟内存, 所以这里使用CLONE_VM的唯一理由是为了加快克隆速度 */
(译注:cd通常是shell的内建命令,因为它要修改shell的工作目录。在传统的UNIX/线程模型中,一个子进程创建后和原来的进程是完全分离的,所以正确情况下无法通过子进程修改shell的工作目录,这里的正常指的是在不使用进程间通信的情况下。)
(译注:linus这里的意思是让shell在检测到用户输入cd后进行这样的操作,这样shell就能实现cd命令的“模组化“,用户可以自行实现他们希望的cd行为。比如我希望”cd favo”跳转到我的一个常用目录,我就可以编写一个external-cd程序,在这个程序中处理cd的参数时对”favo”进行特殊处理,最后修改工作目录时使用我的常用目录来替换,因为external-cd的工作目录是和shell绑定的,所以在external-cd中对工作目录的修改也会反映到shell上。)
-
You can do “vfork()” naturally (it meeds minimal kernel support, but that support fits the CUA way of thinking perfectly):
你可以自然地实现“vfork()”(这需要最小的内核支持,但是这种支持完全符合CUA的思维方式)
clone(CLONE_VM); child: continue to run, eventually execve() mother: wait for execve
(译注:“vfork()”是上古时期(Unix时期)用于优化fork速度的做法,在copy-on-write(COW)的做法还没有被提出之前,传统的fork复制会复制整个地址空间,而vfork的优化是让子进程继续使用父进程的地址空间,父进程暂停执行以此避免对地址空间的修改影响子进程的执行,直到子进程执行execve之后父进程才能继续执行。很显然这种支持在以COE为基础的Linux是很自然的,虽然需要内核提供一个方法检查子进程是否执行了execve,这就是所谓的最小内核支持。至于CUA,上下文没有提及到这个缩写,网络上指向的资料也不像是适合这里的讨论语境的。我猜测应该是linus个人的提法或者是28年前流行的一种说法。)
-
you can do external “IO deamons”:
你可以实现一个外部的“IO守护程序”
clone(CLONE_FILES); child: open file descriptors etc mother: use the fd's the child opened and vv.
All of the above work because you aren’t tied to the thread/process way of thinking. Think of a web server for example, where the CGI scripts are done as “threads of execution”. You can’t do that with traditional threads, because traditional threads always have to share the whole address space, so you’d have to link in everything you ever wanted to do in the web server itself (a “thread” can’t run another executable).
上述的这些之所以能实现,是因为你不再受限于线程/进程的思维方式。例如,想象一个web服务器,将其中的CGI脚本作为“执行的线程”。在传统的线程中你无法这么做,因为传统线程必须要共享所有的地址空间,因此你必须要将所有需要的东西都链接到web服务器本身(一个“线程”不能执行另一个可执行文件)
Thinking of this as a “context of execution” problem instead, your tasks can now chose to execute external programs (= separate the address space from the parent) etc if they want to, or they can for example share everything with the parent except for the file descriptors (so that the sub-“threads” can open lots of files without the parent needing to worry about them: they close automatically when the sub-“thread” exits, and it doesn’t use up fd’s in the parent).
当你从“执行上下文”的角度来考虑问题,现在你的任务可以选择执行一个外部的程序(即将地址空间和父进程分离),如果需要的话,或许它们也能与父进程共享除了文件描述符以外的所有内容(这样子”线程“就可以打开大量的文件而父进程无需担心它们:文件描述符会在自“线程”退出时自动关闭,而且它们也不会导致父进程的文件描述符被用完)
(译注:CGI,指通用网关接口(Common Gateway Interface)。一种早期web服务器实现的方式,一个CGI脚本作为单独的进程执行,由web服务器进程作为父进程为其传递参数。这种方式具有极强的扩展性,能够分离web服务器和具体的功能实现。其被淘汰的主要原因是性能问题,创建一个进程的成本相比于线程而言还是太高了。但现在仍有一些网站在使用CGI接口,例如qq邮箱。)
Think of a threaded “inetd”, for example. You want low overhead fork+exec, so with the Linux way you can instead of using a “fork()” you write a multi-threaded inetd where each thread is created with just CLONE_VM (share address space, but don’t share file descriptors etc). Then the child can execve if it was a external service (rlogind, for example), or maybe it was one of the internal inetd services (echo, timeofday) in which case it just does it’s thing and exits.
例如,再考虑一个线程化的“inetd”。你希望获得一个低成本的fork+exec,因此在Linux的方式中,你可以不使用“fork()”,而是编写一个多线程的“inetd“,其中每个线程创建时只使用CLONE_VM
(共享地址空间,但是不共享文件描述符等)。然后如果是一个外部服务(例如rlogind),子“线程”可以使用execve;如果是一个内部服务(例如echo,timeofday),子“线程”可以直接执行并退出。
(译注:相当于基于CGI和基于线程的服务器的混合体,子“线程”可以根据服务的类型来决定具体要怎么做。如果是一个扩展的外部服务,就通过CGI的方式,如果是一个简单的内部服务,就用传统线程的方式。)
You can’t do that with “thread”/”process”.
你无法用“线程”/“进程”的方式做到这一点。
Linus
单纯翻译
在1996年的八月6号Peter P. Eiserloh写到
我们需要保持一个清晰的线程概念,很多人似乎把进程和线程搞混了。接下来的讨论并不反映当前linux的状态(实现),而是试图保持在一个较高层次的讨论中。
不!
将“线程”和“进程”认为是独立的实体是没有理由的。这是一个传统的观点,但是我个人认为这样的思维方式是个重大错误。之所以会去这样思考只不过是因为历史遗留因素罢了。
线程和进程实际上是同一个东西:“执行上下文”。试图人为地去区分两者只会限制自身(的思维)。
“执行上下文”(以下将其简称为COE),就是所有该COE的状态的总和。这些状态包括CPU状态(寄存器状态等),MMU状态(页表映射),权限状态(uid,gid)和各种“通信状态”(打开的文件,signal处理程序等)。
传统上,“线程”和“进程”的主要不同在于线程只有CPU状态(也许还有些其他最小状态),而其他全部的上下文都属于进程。然而,这仅仅是划分COE总状态的一种方式,而且也没有理由说这种方式一定是正确的。将你自己局限在此是非常愚蠢的。
Linux的设计思路(以及我个人希望这么做)是:系统中没有所谓的“进程”或者“线程”,所有的东西都是COE(在Linux中称为“task”)。不同的COE之间可以互相共享它们的部分上下文,这些共享情况中的一个子集就是传统的“线程”/“进程”组织方式,但是这种组织方式真的应该被仅仅视为一个子集。(这是一个很重要的子集,但是它的重要性不是来自于设计,而是因为标准:我们显然也希望符合标准线程模型的程序能够在Linux上运行)
简而言之:不要围绕线程/进程的思维方式进行设计,内核应该围绕COE的思维方式进行设计,然后pthreads库能够导出受限的pthreads接口给那些希望以这种方式看待COE的用户。
这有些例子展示了如果你从COE思维方式而不是线程/进程的思维方式思考时,能够实现些什么
-
你可以实现一个外置的“cd”程序,这在传统的UNIX或者进程/线程模型下是不可能实现的。(很蠢的示例,但是主要的思想是你可以实现类似的不受限于传统UNIX/线程模型的“模组”)
clone(CLONE_VM|CLONE_FS); child: execve("external-cd"); /* 因为 “execve()”会取消关联所有的虚拟内存, 所以这里使用CLONE_VM的唯一理由是为了加快克隆速度 */
-
你可以自然地实现“vfork()”(这需要最小的内核支持,但是这种支持完全符合CUA的思维方式)
clone(CLONE_VM); child: continue to run, eventually execve() mother: wait for execve
-
你可以实现一个外部的“IO守护程序”
clone(CLONE_FILES); child: open file descriptors etc mother: use the fd's the child opened and vv.
上述的这些之所以能实现,是因为你不再受限于线程/进程的思维方式。例如,想象一个web服务器,将其中的CGI脚本作为“执行的线程”。在传统的线程中你无法这么做,因为传统线程必须要共享所有的地址空间,因此你必须要将所有需要的东西都链接到web服务器本身(一个“线程”不能执行另一个可执行文件)。
当你从“执行上下文”的角度来考虑问题,现在你的任务可以选择执行一个外部的程序(即将地址空间和父进程分离),如果需要的话,或许它们也能与父进程共享除了文件描述符以外的所有内容(这样子”线程“就可以打开大量的文件而父进程无需担心它们:文件描述符会在自“线程”退出时自动关闭,而且它们也不会导致父进程的文件描述符被用完)
例如,再考虑一个线程化的“inetd”。你希望获得一个低成本的fork+exec,因此在Linux的方式中,你可以不使用“fork()”,而是编写一个多线程的“inetd“,其中每个线程创建时只使用CLONE_VM
(共享地址空间,但是不共享文件描述符等)。然后如果是一个外部服务(例如rlogind),子“线程”可以使用execve;如果是一个内部服务(例如echo,timeofday),子“线程”可以直接执行并退出。
你无法用“线程”/“进程”的方式做到这一点。