“ 进程、线程有什么区别?虚拟地址和物理地址有什么区别?让我们用一只青蛙的视角,来解读它们背后的秘密”
进程、线程、虚拟地址、物理地址,这些名词既熟悉也陌生!似乎无论看多少资料,都很难准确地弄清楚它们之间的差异和存在的意义。今天我们用CPU的视角,再次会会这个老朋友,看看你是否有新的启发?
先写一个最简单的程序。这个程序只做两件事情:定义一个全局变量 a,并赋值为:1;然后打印出 a 的地址和数值
#include <stdio.h>
#include <unistd.h>
int a = 1;
int main()
{
printf("address: %p, value: %dn", &a, a);
sleep(10000000);
}
将上面的代码,编辑成可执行程序:p1;接着,修改一下代码:给全局变量 a 赋值为:2,再次编译出可执行程序:p2。
好了,两个程序:p1,p2 都准备好了,让我们再以两个进程的形式,运行它们:左边的窗口进程,运行p1;右边的窗口进程,运行p2
发现问题了吗?无论是 p1 还是 p2,它们输出的 a 的地址都是一样的,但 a 的值却不一样!难道同一个内存地址里面,既能存放 1,也能存放 2?
当然不是,原来0x555555558010是虚拟地址,并不是真实的物理内存地址,进程p1的0x555555558010,跟进程p2的0x555555558010,没有任何关系!
虚拟地址,只在进程内,是有意义的;可以用来指示不同变量,所对应的不同内存地址;但一旦跳出单一进程,在进程之间比较虚拟地址,就没有任何意义了!
是什么软件,拥有如此的魔力?能让进程p1和进程p2的内存空间完全隔离?答案不是软件,而是硬件 — 现代CPU的协处理器:MMU
MMU的工作原理,未来我们还会详细阐述,这里只说结论:无论是进程p1,还是进程p2的,它们的变量 a 的地址都是虚拟地址,看上去是同一个地址,但实际上,已经被 MMU 映射到了不同的物理地址上去了。这就是“进程”最显著的特点:空间独立性。
举个例子,进程就像一只井底之蛙,它固执地认为:自己已经拥有整个天空;但它永远不知道:天空到底有多大。更不知道:周围还有很多跟它有一样想法的井底之蛙,而 MMU 就是束缚这些青蛙视野的井,每一口井,就是一个:进程空间。
那看来全是 MMU 惹的祸,不要 MMU 行吗?当然可以,但一旦没有井的束缚,所有的青蛙,都跳到地面上,它们都可以看到一个完整的天空,所以,没有 MMU,进程也就不存在了,进程被降级成了:线程。
这样的例子很多,例如:在没有 MMU 的单片机。你就只会遇到线程或者叫:task,根本没有“进程”的概念。
在 MMU 出现之前,计算机的世界里面,只有“线程”,在 MMU 出现之后,“进程”才真正落地,因为没有 MMU,就没有办法实现:内存空间的隔离,也就根本无法实现“进程”要求的:空间独立性。
至于“进程”中的多“线程”就很容易理解了。就是:一堆青蛙,都放在一个井里。而且,它们都认为自己拥有整个天空。
因为,这些“线程”都处于同一个“进程”空间中,大家可以相互访问,完全没有任何限制。这使得用“线程”实现多任务编程,会非常便利。
但也因为这种对安全的忽视,一旦任何一个“线程”崩溃
所有的“线程”都不能幸免,大家一荣俱荣,一损俱损!
所以,网络服务器一般都会使用“多进程”,而很少使用“多线程”。这样即使某一个用户的服务进程崩溃了,其他“进程”还能继续正常工作。这样,就不会因为某个用户的访问失败,而导致其他用户也无法访问服务器。
好了,这可能就是你永远弄不清楚:“线程”和“进程”的原因,因为,这不仅仅是:一个软件问题,更是一个 MMU 的问题。以后你再跟人讨论“线程”、“进程”的时候,一定要先问一下:有 MMU 吗?
任何一个游戏程序的崩溃,都需要通过reset来重启/恢复系统。
3. 进程间的空间隔离,让恶意程序无法再扫描整其他程序的内存或整个物理内存,任何程序只能在自己的一亩三分地里面干活。通过游戏修改器,玩《仙剑奇侠传》的日子,不会再有了。
这样看来:生活在“井”里,也没有什么不好。
Q1:谁在分配:虚拟地址?
A1:程序员在代码中编写的:任何变量、函数、数据结构,在编译过程中,都会被编译器安排了一个内存地址,这个地址就是虚拟地址。
Q2:虚拟内存的好处?
A2:好处是:编译器在生成可执行程序时,可以更加自由的分配:代码中变量的内存地址,不用关心它在实际运行环境中,应该是多少?也不用关心运行环境的内存是否够用。
相同的 exe文件,同时运行2次,也不用担心它们同名的变量或函数,会在内存中重叠在一起。因为它们都用的是虚拟地址,无论外表多么相似,都可能被分配在不同的“物理地址”上。
此外,当程序所需的内存大于计算机的实际内存时,虚拟内存机制可以用硬盘来给内存“扩容”。
Q3:我电脑的内存有8GB,那跑在该电脑上的程序,其虚拟地址空间,也只能有8GB吗?
A3:虚拟地址的空间大小,取决于CPU的位数,32位CPU对应的虚拟地址空间为:0~0xFFFFFFFF(4GB),64位CPU对应的虚拟地址空间为:0~0xFFFFFFFFFFFFFFFF(16384PB),它们跟真实的物理内存大小,没有关系。
Q4:为什么我执行实例代码的时候,输出的 a 的内存地址总是在变化?
A4:新版本的linux对虚拟内存进行了保护,作了随机化处理,为了达到文章所述的效果,需要取消这种随机化处理。请在运行程序之前,在命令行中输入如下命令:
echo 0 > /proc/sys/kernel/randomize_va_space