程序:只是一段代码,保存在文件中。
编译器在编译程序生成可执行文件时,会对每一条指令和数据,进行地址排号。
程序运行时,就会将指令和数据放到指定的内存当中去。而程序只有在运行的时候才会占据内存,因此程序地址空间又被叫做进程地址空间。
内存空间是这样的。
若运行中的程序直接访问物理地址,会怎么样呢?
所以OS中设置了虚拟内存,通过虚拟地址空间映射到物理内存上。而使用C语言/C++时,变量或函数的地址,都是虚拟空间地址,物理内存地址用户一概看不到,由OS统一管理。而OS负责将虚拟地址映射到对应的物理地址。
每运行一段程序,就会开辟连续的地址空间,若是每个程序占据的空间比较大,很多程序共同运行,就会导致有的程序在内存中无法运行。而连续开辟的内存地址空间的空间使用率是很低的。
而进程使用了虚拟内存之后,每个进程都拥有自己的虚拟地址空间,都会有一块连续的空间使用。
看一下这段代码:
#include
#include
#include
int global_val = 200;
int main()
{
pid_t pid = fork();//创建子进程
if(pid < 0)
{
printf("fork error\n");
return 0;
}
else if(pid == 0)
{
printf("child:%d %p\n",global_val,&global_val);
}
else
{
printf("parent:%d %p\n",global_val,&global_val);
}
return 0;
}
对代码进行一点小更改:
#include
#include
#include
int global_val = 200;
int main()
{
pid_t pid = fork();//创建子进程
if(pid < 0)
{
printf("fork error\n");
return 0;
}
else if(pid == 0)
{
global_val = 100;
printf("child:%d %p\n",global_val,&global_val);
}
else
{
sleep(3);
printf("parent:%d %p\n",global_val,&global_val);
}
return 0;
}
其输出为:
可以看到子进程的变量改变了,而父进程的变量是没有改变的。
为什么子进程变量改变了,而父进程的变量没有改变?
子进程是父进程的一份拷贝,子进程拷贝了父进程所有的信息。在子进程中数据未发生改变的时候,子进程使用父进程的所有信息。
在第一份代码中,子进程中变量没有改变,父进程中变量没有改变。所以第一份代码中,地址相等,变量也相等。
第二份代码中,子进程中变量发生了改变,父进程中变量没有发生改变。相同的虚拟地址映射到了不同的物理地址。所以第二份代码,地址相同,变量不同。
这里的相同是指:子进程拷贝了父进程所有的信息,进程地址空间、PCB…
第二份代码中,这里涉及到了写时拷贝技术:Linux中fork()使用写时拷贝实现。写时拷贝是一种推迟或者免除拷贝的技术。OS并不复制整个进程地址空间,而是子进程父进程共享一个地址,当有数据写入,发生改变时,数据才会被复制,使每个进程都有了自己的拷贝。资源的复制只有在写入的时候才进行。 而在此之前,子进程只是可读共享的,这样就保证了父子进程的代码共享,数据独立。
写时拷贝技术带来的好处:
那么为什么OS要使用虚拟地址空间?或者说虚拟地址空间带了什么好处?
虚拟地址是如何映射到物理地址的?
操作系统中内存管理方式:
段表:操作系统记录内存分了多少块。
通过段号寻找对应的物理内存起始地址,再加上段内偏移量,就找到了物理地址。
4* 1024* 1024* 1024/4* 1024
个页号,即页表项。2^20个
页表项/页号。将内存分为很多个细小的块。通过找到对应的页号,其物理地址和页内偏移就可找到变量的物理地址。
当前计算机使用的段页式管理。
虚拟页会缓存在物理内存中。如图:
虚拟内存可缓存到页表中:页命中,VP2就会缓存在内存中。
缓存不命中:缺页 ,VP3不会命中,发生缺页中断。那么OS就会从磁盘复制VP3到内存中PP3,再更新PTE3,随后返回。
VP3:虚拟内存3.
PP3:物理内存3
PTE3:页表条目3。0:发生中断,1:可以缓存。
经过缺页中断之后:缺页处理程序会选择一个作为牺牲页,并从磁盘上VP3的副本取代它,
那么该选择牺牲页呢?
采用内存置换算法: