是的,我已经阅读了许多与操作系统相关的资料,并且还在继续阅读。但似乎所有的材料都是以“抽象”的方式描述进程和线程,这使得它们对行为和逻辑组织进行了许多高层次的阐述。我想知道它们在“物理上”是什么? 我认为,它们只是由内核代码维护和使用的一些“内存数据结构”,以促进程序的执行。例如,操作系统使用一些进程数据结构(PCB)来描述分配给某个程序的进程的各个方面,例如其优先级、地址空间等。这样可以吗?
是的,我已经阅读了许多与操作系统相关的资料,并且还在继续阅读。但似乎所有的材料都是以“抽象”的方式描述进程和线程,这使得它们对行为和逻辑组织进行了许多高层次的阐述。我想知道它们在“物理上”是什么? 我认为,它们只是由内核代码维护和使用的一些“内存数据结构”,以促进程序的执行。例如,操作系统使用一些进程数据结构(PCB)来描述分配给某个程序的进程的各个方面,例如其优先级、地址空间等。这样可以吗?
了解进程和线程之间的区别的首要是事实是,进程不运行,而线程运行。
那么,什么是线程?最接近的解释是一个执行状态,也就是:CPU寄存器、栈等的组合。您可以在任何给定时刻中断调试器来证明这一点。您看到了什么?调用堆栈、一组寄存器。这就是线程。
现在,什么是进程。嗯,它就像是运行线程的抽象“容器”实体。就操作系统而言,在第一次近似中,它是一个OS为其分配了一些VM,并分配了一些系统资源(如文件句柄、网络套接字)的实体。
它们如何协同工作呢?操作系统通过为其保留一些资源并启动一个“主”线程来创建“进程”。然后该线程可以生成更多的线程。这些是一个进程中的线程。它们或多或少可以以某种方式共享这些资源(例如,锁定可能需要避免它们破坏其他人的乐趣等)。从那里开始,操作系统通常负责维护那些线程在那个VM“内部”(检测和防止尝试访问不属于该进程的内存),提供某种类型的调度这些线程,以便它们可以“一个接一个地运行而不仅仅是一直运行一个”。
main
函数开始。指令按顺序一次执行一个,就像一个只有一只手的人,线程在进行下一个操作之前只能一次执行一个操作。线程和进程难以进行非抽象化的描述的原因之一是它们本身就是抽象的。
它们具体的实现方式差异极大。
例如,比较 Erlang 进程和 Windows 进程:Erlang 进程非常轻量级,通常不到 400 字节。在不太新的笔记本上,您可以启动 1000 万个进程而没有任何问题。它们启动非常快,死亡也非常快,并且您应该能够将它们用于非常短的任务。每个 Erlang 进程都有其自己的垃圾回收器。Erlang 进程永远无法共享内存。
Windows 进程非常重,有时高达数百兆字节。如果您很幸运,可以在强大的服务器上启动几千个进程。它们的启动和死亡速度相对较慢。Windows 进程是 IDE、文本编辑器或文字处理器等应用程序的单元,因此它们通常需要存在相当长的时间(至少几分钟)。它们有自己的地址空间,但没有垃圾回收器。Windows 进程可以共享内存,尽管默认情况下它们不会。
线程也是类似的问题:在 x86 上的 NPTL Linux 线程最小可以只有 4 KiB,通过一些技巧,您可以在 32 位 x86 计算机上启动超过 800000 个线程。使用数千个甚至数万个线程后,计算机肯定是可用的。.NET CLR 线程的最小大小约为 1 MiB,这意味着只要启动 4000 个就会耗尽您在 32 位计算机上的整个地址空间。因此,虽然通常情况下 4000 个 NPTL Linux 线程不是问题,但在 .NET CLR 中,您甚至无法启动 4000 个线程,因为在那之前你的内存就已经耗尽了。
操作系统进程和操作系统线程的实现在不同的操作系统之间也非常不同。主要有两种方法:内核仅知道进程,线程由用户空间库实现,完全不涉及内核。在这种情况下,又有两种方法:1:1(每个线程映射到一个内核进程)或m:n(m个线程映射到n个进程,其中通常m>n,并且经常n == #CPU)。这是许多操作系统在线程被发明后采取的早期方法。然而,它通常被认为效率低下,并且几乎所有系统都已将其替换为第二种方法:线程在内核中实现(至少部分实现),因此内核现在了解两个不同的实体,即线程和进程。
一种采用第三种方法的操作系统是Linux。在Linux中,线程既不在用户空间中实现,也不在内核中实现。相反,内核提供了一个抽象,既可以表示线程也可以表示进程(以及更多其他内容),称为任务(Task)。任务是内核调度实体,它携带一组标志,这些标志确定它与其兄弟共享哪些资源,哪些是私有的。
根据你设置的标志,你可以得到一个线程(共享几乎所有内容)或一个进程(共享系统资源,如系统时钟、文件系统命名空间、网络命名空间、用户ID命名空间、进程ID命名空间,但是不共享地址空间)。但是你还可以得到其他非常有趣的东西。你可以轻松地获得类似于BSD样式的jails(基本上与进程相同的标志,但是不共享文件系统或网络命名空间)。或者你可以得到其他操作系统称为虚拟化容器或区域的东西(像一个jail,但不共享UID和PID命名空间和系统时钟)。几年前通过一种叫做KVM(内核虚拟机)的技术,你甚至可以获得一个完整的虚拟机(什么都不共享,甚至不共享处理器的页表)。[这个很酷的事情是,你可以重用内核中高度调优成熟的任务调度程序来处理所有这些东西。Xen虚拟机经常被批评其调度程序性能较差。KVM开发人员有比Xen更优秀的调度程序,最好的事情是他们甚至不需要编写任何代码!]
所以,在Linux上,线程和进程的性能比在Windows和许多其他系统上更接近,因为在Linux上,它们实际上是相同的东西。这意味着使用模式非常不同:在Windows上,你通常基于它们的权重来决定使用线程还是进程:我能负担得起一个进程吗?或者我应该使用线程,即使我实际上不想共享状态?在Linux(通常也适用于Unix),你根据它们的语义来决定:我是否真的想共享状态?
进程在Unix上比Windows上轻量级的原因之一是不同的使用方式:在Unix上,进程既是并发性的基本单元,也是功能的基本单元。如果你想使用并发性,你使用多个进程。如果你的应用程序可以分解成多个独立的部分,你使用多个进程。每个进程只做一件事情,而且仅限于那一件事情。即使是简单的一行Shell脚本通常也涉及数十个或数百个进程。应用程序通常由许多经常是短暂存在的进程组成。
在Windows操作系统中,线程是并发的基本单位,而COM组件或.NET对象是功能的基本单位。应用程序通常由一个长时间运行的进程组成。
需要注意的是,它们用于非常不同的目的,并具有非常不同的设计目标。并不是说其中一个比另一个更好或更差,只是它们非常不同,因此只能以非常抽象的方式描述共同特征。
关于线程和进程,几乎唯一可以说的几件事情就是:
我认为:
一个进程具有内存空间、打开的文件等,并且可能有一个或多个线程。
线程是一个指令流,可以由系统在处理器上进行调度。
线程是操作系统调度程序中的内存结构,正如您所说。线程指向内存中某些指令的起始位置,并在调度程序决定它们应该被处理时进行处理。当线程正在执行时,硬件计时器将运行。一旦达到所需时间,将调用中断。在此之后,硬件将停止当前程序的执行,并调用已注册的中断处理程序函数(该函数将是调度程序的一部分),以通知当前线程已完成执行。
物理上:
进程是一个结构体,维护了拥有的凭证、线程列表和打开的句柄列表。
线程是一个包含上下文(即保存的寄存器集合和要执行的位置)、描述哪些页面映射到进程虚拟地址空间的一组PTEs以及所有者的结构体。
当然,这只是一个极其简化的解释,但它涵盖了重要的部分。Linux和Windows上的基本执行单元都是线程 - 内核调度程序并不关心进程(非常少关注)。这就是为什么在Linux上,一个线程只是一个与另一个进程共享PTEs的进程。
进程是由操作系统管理的在内存中运行应用程序的区域。线程是进程内的一个小区域,用于运行专门的任务。