2.2 内存虚拟化
现在让我们思考内存。现代机器所呈现的物理存储模型非常简单。内存只是一个字节数组;要读取内存,必须指定一个地址才能访问存储在那里的数据;要写入(或更新)内存,还必须指定要写入到给定地址的数据。
/**********************************************************************************> File Name: mem.c> Author: wq> Mail: 1509802731@qq.com > Created Time: 2018年12月26日 星期三 15时18分06秒>Function:间隔一秒打印一次P的值,尝试同时运行多个此程序,体会共享内存(对内存的虚拟化)**********************************************************************************/#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
int main(int argc,char *argv[])
{int *p = malloc(sizeof(int));assert(p != NULL);/*%08X表示八位对齐的十六进制格式,如0X00000001*/printf("(%d) memory address of p: %08X\n",getpid(), (unsigned) p);*p = 0;while (1){sleep(1);*p = *p + 1;printf("(%d) p: %d\n",getpid(), *p);}return 0;
}
当程序运行时,内存一直在被访问。程序将所有数据结构保存在内存中,并在执行工作的时候,通过各种指令访问它们,例如加载和存储或其他访问内存的显式指令。不要忘记每条程序指令也在内存中;在每次取指令时都要访问内存。让我们看看上面那个程序mem.c,通过调用malloc()函数申请内存。程序输出如下:
图2.6
这个程序做了这样几件事情。首先,通过malloc()函数申请了部分内存。然后打印出了内存地址,再将数字0赋值给最新申请的内存地址中。最后,每延迟一秒,循环递增存储在该地址上的值并打印出来,随之打印的还有该程序的进程号,该进程号是唯一的。
同样,前面的结果不足为奇。新申请的内存地址是00200000(这是原文的地址,我此处分配的地址是09327008,这是随机分配的).随着程序的运行,它缓慢的更新值并输出结果。
图2.7同时运行多个程序
现在,我们再次同时运行多个mem程序来看看会发生什么(图2.7)。翻译这段我发现和作者所说及运行结果不一致,我的运行结果如上图2.7所示,作者原文如下:
想知道为什么吗?请见文末!
现在,我们再次同时运行多个mem程序来看看会发生什么(译者实际运行情况见图2.7,原文见图2.8)。我们从图2.8中看到,每个运行的程序都在同一个地址(00200000)分配内存,而每个程序似乎都在独立地更新00200000的值!就好像每个运行中的程序都有自己的私有内存,而不是与其他运行中的程序共享相同的物理内存。
实际上,这里所发生的事情,是因为操作系统正在虚拟化内存。 每个进程都访问自己的专用虚拟地址空间(有时只是称为地址空间),操作系统以某种方式映射到机器的物理内存。 内存中的内存引用一个正在运行的程序不会影响其他进程的地址空间(或操作系统本身); 就运行程序而言,它认为享有的是他自己的物理内存。 然而,真相是物理存储是由操作系统管理的共享资源。 这些究竟是如何实现的也在本书第一部分的主题:虚拟化讨论范围之内。
答案揭晓:要成为作者那样子,需要让系统不要随机分配地址。实际上,随机化,可以很好的防御某些安全缺陷。作者在GitHub给出解决方案如下:
Details
One issue with mem.c is that address space randomization is usually on by default. To turn it off:
macOS
From stackoverflow
Just compile/link as follows: gcc -o mem mem.c -Wall -Wl,-no_pie
Linux
From Giovanni Lagorio:
Under Linux you can disable ASLR, without using a debugger, in (at least) two ways:
- Use the command setarch to run a process with ASLR disabled; I typically run bash, with which I can execute examples, like this:
setarch $(uname --machine) --addr-no-randomize /bin/bash
- Writing 0 into
/proc/sys/kernel/randomize_va_space
; you need to be root to do this and this change has (a non-permament) effect on the whole system, which is something you probably don't want. I use this one only inside VMs.
还有一位同学将代码修改后如下:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
int main(int argc,char *argv[])
{int *p = malloc(sizeof(int));pid_t fpid;assert(p != NULL);/*%08X表示八位对齐的十六进制格式,如0X00000001*/printf("(%d) memory address of p: %08X\n",getpid(), (unsigned) p);*p = 0;fpid=fork();if (fpid < 0) printf("error in fork!"); else if (fpid == 0) { printf("child (%d) memory address of p: %08X\n",getpid(), (unsigned) p);while (1){sleep(1);*p = *p + 1;printf("(%d) p: %d\n",getpid(), *p);}} else { printf("parent (%d) memory address of p: %08X\n",getpid(), (unsigned) p);while (1){sleep(1);*p = *p + 1;printf("(%d) p: %d\n",getpid(), *p);}} return 0;
}
运行结果如下:
子进程与父进程共享内存
本节完!