<返回更多

坐井观天的:进程 | 虚拟内存 | 虚拟地址

2022-10-08  今日头条  阿布编程
加入收藏

 进程、线程有什么区别?虚拟地址和物理地址有什么区别?让我们用一只青蛙的视角,来解读它们背后的秘密

进程、线程、虚拟地址、物理地址,这些名词既熟悉也陌生!似乎无论看多少资料,都很难准确地弄清楚它们之间的差异和存在的意义。今天我们用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,没有任何关系!

虚拟地址,只在进程内,是有意义的;可以用来指示不同变量,所对应的不同内存地址;但一旦跳出单一进程,在进程之间比较虚拟地址,就没有任何意义了!

 

MMU

是什么软件,拥有如此的魔力?能让进程p1和进程p2的内存空间完全隔离?答案不是软件,而是硬件 — 现代CPU的协处理器:MMU

MMU的工作原理,未来我们还会详细阐述,这里只说结论:无论是进程p1,还是进程p2的,它们的变量 a 的地址都是虚拟地址,看上去是同一个地址,但实际上,已经被 MMU 映射到了不同的物理地址上去了。这就是“进程”最显著的特点:空间独立性。

 

举个例子,进程就像一只井底之蛙,它固执地认为:自己已经拥有整个天空;但它永远不知道:天空到底有多大。更不知道:周围还有很多跟它有一样想法的井底之蛙,而 MMU 就是束缚这些青蛙视野的井,每一口井,就是一个:进程空间。

 


 

进程 vs 线程

那看来全是 MMU 惹的祸,不要 MMU 行吗?当然可以,但一旦没有井的束缚,所有的青蛙,都跳到地面上,它们都可以看到一个完整的天空,所以,没有 MMU,进程也就不存在了,进程被降级成了:线程。

这样的例子很多,例如:在没有 MMU 的单片机。你就只会遇到线程或者叫:task,根本没有“进程”的概念。

在 MMU 出现之前,计算机的世界里面,只有“线程”,在 MMU 出现之后,“进程”才真正落地,因为没有 MMU,就没有办法实现:内存空间的隔离,也就根本无法实现“进程”要求的:空间独立性。

至于“进程”中的多“线程”就很容易理解了。就是:一堆青蛙,都放在一个井里。而且,它们都认为自己拥有整个天空。

 

因为,这些“线程”都处于同一个“进程”空间中,大家可以相互访问,完全没有任何限制。这使得用“线程”实现多任务编程,会非常便利。

 

但也因为这种对安全的忽视,一旦任何一个“线程”崩溃

 

所有的“线程”都不能幸免,大家一荣俱荣,一损俱损!

 

所以,网络服务器一般都会使用“多进程”,而很少使用“多线程”。这样即使某一个用户的服务进程崩溃了,其他“进程”还能继续正常工作。这样,就不会因为某个用户的访问失败,而导致其他用户也无法访问服务器。

 

好了,这可能就是你永远弄不清楚:“线程”和“进程”的原因,因为,这不仅仅是:一个软件问题,更是一个 MMU 的问题。以后你再跟人讨论“线程”、“进程”的时候,一定要先问一下:有 MMU 吗?

 

总结

  1. 进程就像一只井底之蛙,虽然看上去,它可以读/写整个64位(假设CPU是64位的)的虚拟内存空间:void write_memory()
    {char*p = 0;
    for(unsigned long offset = 0; offset<= 0xFFFFFFFFFFFFFFFF; offset++)
    {*(p + offset) = 0x55;
    }} 但天到底有多大,真实的物理内存空间到底有多少?除了操作系统和MMU,没有人能知道。
  2. 进程间的空间隔离,让进程之间的信息共享没有线程那么方便,但也大大提高了整个系统的安全性;再也不会因为某一个应用程序的崩溃,导致计算机重启。还记得红白机上的reset按键吗?

 

任何一个游戏程序的崩溃,都需要通过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
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>