?注意这里的问题是为什么进程切换比线程慢,而不是为什么进程比线程慢。当然,这里的线程一定是指同一个进程中的线程。简介在进入正题之前,我觉得有必要解释一下虚拟地址(逻辑地址)和物理地址(new操作返回的堆空间分配的值)和一个整数在栈上的地址的区别:getthe以下输出:我们需要知道的是,所有这些打印出来的地址都是虚拟的,这些地址在物理内存中并不是真实存在的,它们最终会被操作系统和CPU硬件翻译成真实的物理地址,然后取值该地址可以从真实的物理位置获得。OK,以上作为介绍,让大家对物理地址和虚拟地址有一个直观的认识,下面正文开始。PhysicaladdressingPhysicalAddressing物理地址的概念很好理解,你可以称它为真实地址。《深入理解计算机系统 - 第 3 版》中给出的物理地址的定义如下:计算机系统的主内存被组织为一个由M个连续的字节大小的单元组成的数组。每个字节都有一个唯一的物理地址。例如,第一个字节的物理地址为0,下一个字节的地址为1,下一个字节的地址为2,依此类推。鉴于这种简单的结构,CPU访问内存最自然的方式就是像这样使用物理地址。我们称这种方法为物理寻址。例如,当程序执行一条加载指令时,该指令的内容是从物理地址4读取一个4字节的字,并传送到一个寄存器中。物理寻址过程如下:当CPU执行这条指令时,会产生一个物理地址4,然后通过内存主线传给内存,内存从物理地址开始取4字节的字地址4返回给CPU,CPU会把它存到指定的寄存器中。看下图:其实不难发现,在这种物理寻址的方式中,每个程序都是直接访问物理内存,但实际上存在重大缺陷:1)首先,用户程序可以寻址内存的任意字节,而且它们很容易损坏操作系统,使系统缓慢停止。2)同样,这种寻址方式使得在操作系统中同时运行两个或多个程序几乎是不可能的。比如我们打开三个相同的程序(计算器),它们都执行到某个步骤。例如,用户在三个计算器程序的界面上分别输入10、100、1000,对应的指令是将用户输入的数字保存到内存中的某个地址。如果这个位置只能放一个数,应该存哪一个?这不是冲突吗?简单的说,第一个计算器程序给物理内存地址赋值10,第二个计算器程序也给这个地址赋值如果赋值是100,那么第二个程序赋值会覆盖第一个程序赋值,这样就会导致两个程序同时崩溃。当然,我们也说过几乎不可能,不是完全不可能,物理寻址的方式实现多个程序并发运行还是有办法的。最简单的方法是:首先,将空闲进程存储在磁盘上,使它们在不运行时不占用内存,然后让单个程序(或进程)运行一小段时间,使用所有内存,以及当发生某些事情的上下文切换时,停止进程并将其所有状态信息保存在磁盘上,然后加载其他进程的状态信息,然后运行一段时间......只要有如果某个时刻内存中只有一个程序,那么就不会出现上面提到的地址冲突。这样就实现了粗略的并发。为什么粗糙,因为这种方法有一个问题:将所有内存信息保存到磁盘太慢了!特别是当内存增长时。因此,我们考虑将进程对应的内存保留在物理内存中,将每个进程划分到自己的区域,发生上下文切换时切换到特定的区域。如下图所示,有3个进程(A、B、C),每个进程都从512KB的物理内存中切出一小部分内存给它们。可以这样理解,这3个进程共享物理内存:显然,这种方式存在一定的安全隐患。毕竟如果各个进程都可以随意读写内容,那就乱七八糟了。那么如何保护每个进程使用的地址呢?继续使用物理内存模型是肯定不行的,所以操作系统创建了新的内存抽象,引入了新的内存模型,这就是虚拟地址空间,很多书籍会直接称其为“地址空间(AddressSpace)””。Virtualaddressing上面说的VirtualAddressing,对于物理内存模型来说,如果各个进程可以随意读写内容,那就乱七八糟了。因此,每个进程的栈、堆、代码段等实际物理内存地址对本进程来说应该是不可见的,这样任何人都不能直接访问到这个物理地址。那么问题来了,物理地址被隐藏了,我们如何访问这个进程呢?操作系统会给每个进程分配一个虚拟地址空间(vituraladdress),每个进程所包含的栈、堆和代码段都会从这个地址空间中分配一个地址,这个地址称为虚拟地址。底层指令写入的地址也是虚拟地址。每个进程都有自己的虚拟地址空间,独立于其他进程的地址空间。(注意这句话很重要!!!兄弟姐妹们记住了)也就是说一个进程中虚拟地址28对应的物理地址和另一个进程中虚拟地址28对应的物理地址是不一样的,所以不会有冲突。可以理解为物理地址是仓库,虚拟地址是门牌号。比如一共有30个门牌号,那么所有进程都可以看到这30个门牌号,但是他们看到的相同门牌号并不指向不同的仓库。有了虚拟地址空间,CPU就可以通过产生一个虚拟地址来访问主存。这个虚拟地址在被发送到内存之前会被转换成合适的物理地址。从虚拟地址到物理地址的转换过程称为地址转换/地址转换。地址转换需要CPU硬件和操作系统的密切配合:CPU上的内存管理单元(MMU)专门用于将虚拟地址转换为物理地址,但是MMU需要依赖内存中存放的页表,以及该表的内容由操作系统管理。页表是一个非常重要的数据结构!操作系统为每个进程创建一个页表。一个进程对应一个页表,进程的每一页对应一个页表项。每个页表项由一个页号和一个块号(页框号)组成,记录了进程页与实际存放的内存块之间的关系。映射关系。从数学上讲,页表是一个函数,其参数是一个虚拟页码,其结果是一个物理页框号。至此,上述这组CPU产生虚拟地址并进行地址转换的过程就是虚拟寻址:为什么进程切换比线程切换慢?呸,说了很多,但是最重要的还是这句话:每个进程都有自己的虚拟地址空间,与其他进程的地址空间无关。那么,告诉我,进程切换会涉及到哪些切换?是的,进程切换会涉及到虚拟地址空间的切换,而这也正是进程切换比线程切换慢的原因!很多朋友可能一头雾水,啊,是这样吗?想一想,上面不是说了把虚拟地址转换成物理地址需要两件事吗?:CPU上的MMU和内存中的页表每次访问内存的时候,都需要把虚拟地址转换成物理地址,对吧,所以每次都需要访问页表一两次或者更多次instruction,页表存在于内存中。显然,过多的访问页表(内存)已经成为操作系统的性能瓶颈,我们必须想办法解决。因此,TranslationLookasideBuffer(TLB)应运而生,又名快表为什么说他快呢?因为TLB通常内置在CPU的MMU中,访问速度和内存不在一个档次。内存中的页表一般称为慢表。事实上,TLB的出现是基于大多数程序总是多次访问少量页面的现象。因此,只有少数页表项被重复读取,而其他页表项很少被访问。TLB中存储的是那些将被重复读取的页表项。换句话说,TLB中存储的是部分页表的副本。如果TLB命中,则不需要访问内存;如果TLB中没有目标页表项,还需要查询内存中的页表(slowtable),从页表中获取物理页框的地址,同时进行页转换table中的条目被添加到TLB中。简单理解,TLB相当于一个缓存现在回到问题,不知道各位有没有什么想法。由于进程切换会涉及到虚拟地址空间的切换,因此内存中的页表也需要进行切换。确实一个进程对应一个页表,但是CPU里面只有一个TLB,就尴尬了。这个TLB在表切换后就失效了。这样一来,TLB肯定有一段时间不能命中,操作系统必须访问内存,所以虚拟地址到物理地址的转换会变慢,表现为程序运行变慢。至于线程切换,由于不涉及虚拟地址空间的切换,所以不存在这个问题。最后放上本题的背诵版:面试官:为什么进程切换比线程切换慢?小牛:嗯,关于这个问题,我们需要从虚拟地址和物理地址说起。物理地址是真实地址。这种寻址方式很容易损坏操作系统,同时在操作系统中运行两个或多个程序几乎是不可能的(这里举例,第一个程序将物理内存地址赋值10,而第二个程序也给这个地址赋值100,那么第二个程序的赋值会覆盖第一个程序赋的值,会导致两个程序同时崩溃)。当然,也不是完全没有可能。有一种方法可以实现比较粗略的并发,就是我们把空闲的进程存放在磁盘上,让它们在不运行的时候不占用内存,等需要运行的时候再从进程中启动。磁盘转移到内存,但显然这种方法是浪费时间。因此,我们考虑,将所有进程对应的内存保留在物理内存中,将每个进程划分到自己的区域,这样在发生上下文切换时切换到特定区域的问题还是很明显的,即仍然无法避免破坏操作系统,因为各个进程可以随意读写内容。因此,我们需要一种机制来保护每个进程使用的地址,因此操作系统创建了一个新的内存模型,即虚拟地址空间,即每个进程都有自己的虚拟地址空间,并独立于地址其他进程的空间,那么每个进程包含的栈、堆、代码段都会从这个地址空间中分配一个地址,这个地址称为虚拟地址。底层指令写入的地址也是虚拟地址。有了虚拟地址空间,CPU就可以通过将虚拟地址转换为物理地址的过程来间接访问物理内存。地址转换需要两个东西,一是CPU上的内存管理单元MMU,二是内存中的页表,它将虚拟地址映射到页表中存储的物理地址。地址到物理地址的转换,对,这种情况下,页表会被频繁访问,页表存在于内存中。因此,过多的访问页表(内存)成为操作系统的性能瓶颈。因此引入翻译检测缓冲区TLB,即快表。其实就是一个缓存,将经常访问的内存地址映射到TLB中。因为TLB在CPU的MMU中,所以访问起来非常快。那么,正是因为有了TLB,进程切换才比线程切换慢。由于进程切换涉及到虚拟地址空间的切换,因此内存中的页表也需要进行切换。确实一个进程对应一个页表,但是CPU中只有一个TLB。页表切换后,TLB会失效。这样一来,TLB肯定有一段时间不能命中,操作系统必须访问内存,所以虚拟地址到物理地址的转换会变慢,表现为程序运行变慢。至于线程切换,由于不涉及虚拟地址空间的切换,所以不存在这个问题。
