你看到的未必是真实的
在C/C++中,我们常常对一个变量取地址,我们称取出的地址为物理地址,那这真的是内存的物理地址吗?通过一个实验我们来证明。
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
pid_t pid = fork();
int num = 100;
if(pid == 0){
int count = 0;
while(true){
if(count == 3)
num = 200;
sleep(1);
count++;
cout<<"child proccess:"<<"num:"<<num<<" num address"<< &num<<endl;
}
}
else{
while(1){
cout<<"Parent proccess:"<<"num:"<<num<<" num address"<< &num<<endl;
sleep(1);
}
}
return 0;
}
在上面这段程序中,我们通过创建一个子进程,在程序运行3秒后在子进程中对num值进行了修改,修改后我们可以看到父进程和子进程打印出来的是同一个地址,然而值确实不相同的。这段代码说明了,我们平时取出来的地址并不是物理地址。
虚拟地址空间
在一个进程被创建时,操作系统会为其创建一批数据结构,包括PCB,虚拟地址空间、页表等。
每个进程都会有自己的虚拟地址空间,在32位平台下,虚拟地址空间会映射4G。
为什么要使用虚拟地址空间
如果操作系统直接把物理地址暴露给进程,那么恶意程序就会很轻松的破坏操作系统和其他的进程。
使用虚拟地址空间方便了对内存的管理。
在linux内核中用mm_struct进行描述虚拟地址空间
struct mm_struct {
....
unsigned long total_vm, locked_vm, shared_vm, exec_vm;
unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
...
}
通过虚拟地址空间是如何找到物理内存的?
页表
在页表中记录了虚拟地址对应的物理地址。
虚拟地址空间按照固定大小划分成被称为页面的若干单元。在物理内存中对应的单元称之为页框。
页表项中记录了以下信息
- 保护位指出了一个页允许什么类型的访问,比如0表示读和写,1表示只读;
- 为了记录页面的使用状况,写入了修改位和访问位,在写入一页时由硬件设置修改位,该位在操作系统重新分配页框时非常有用,如果一个页面已经被修改过,则必须将它写回硬盘,如果没有被写改过,则可以直接将他丢弃,因为他在磁盘上的副本仍然有效。
- “在不在位”:这一位是1时表示该表项是有效的,可以使用,为0时表示该表项对应的虚拟页面不在内存中,访问该页面会引起缺页中断。
最后一由硬件完成虚拟地址到物理地址的转换,这个硬件就是MMU;
当你向操作系统动态申请了一块空间,那么申请后这个空间一定存在吗?
答案是不一定,当你向操作系统申请空间时,只是调整了mm_struct中的堆的大小,并不一定立即就会在内存中为你开辟空间。当你要操作申请的空间时,MMU查询页表发现没有被映射,于是CPU陷入到操作系统,这个陷阱称为缺页中断,此时为你开辟好空间后,修改页表,然后重新执行引起陷阱的指令。
到此我们验证了上面的那个代码,fork后,会以父进程为模板创建子进程,此时子进程也会有PCB,虚拟地址空间和页表,父子进程共享代码和数据,当子进程对数据进行修改时,触发写时拷贝,此时会修改页表中变量的虚拟地址和物理地址之间的映射关系。所以我们就看到了,同一个地址打印出了两种结果。
址空间和页表,父子进程共享代码和数据,当子进程对数据进行修改时,触发写时拷贝,此时会修改页表中变量的虚拟地址和物理地址之间的映射关系。所以我们就看到了,同一个地址打印出了两种结果。
|