目录
前言:之前我们了解到进程有自己的内核数据结构task_struct和自己的代码和数据。我们在gcc我们的.c文件时过程中,会生成.s汇编文件,而汇编文件里是不是会有call、mov+地址,这些地址是怎么来的呢?
一、程序地址空间
首先每个程序都有自己的程序地址空间struct mm_struct* mm,被存储在task_struct里,这是OS给每个进程独立的进程地址空间,也叫虚拟地址空间。

为什么叫虚拟地址空间呢,我们来看个例子。
cpp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int global_val = 100; // 定义一个全局变量
int main()
{
int ret = fork();
if (ret == 0)
{
while (1)
{
printf("I am a child, pid: %d, ppid: %d, global: %d, gb_addr: %p\n",/
getpid(), getppid(), global_val, &global_val);
sleep(1);
}
}
else if (ret > 0)
{
int cnt = 0;
while (1)
{
if(cnt == 5)
global_val = 200;
printf("I am a father, pid: %d, ppid: %d, global: %d, gb_addr: %p\n",/
getpid(), getppid(), global_val, &global_val);
sleep(1);
cnt++;
}
}
return 0;
}

上面定义一个全局变量,再创建父子进程,可以发现在两个进程中全局变量的地址是一样的,为什么父进程修改该变量,而子进程的没有改变呢?
因为父子进程是两个独立的进程,在创建子进程时,子进程的虚拟地址空间内容是拷贝父进程的 ,所以他们看到的全局变量地址是相同的,但虚拟地址虚拟地址,型如其名,是虚拟呢,不是真正的地址,在真正的物理内存的上父子进程的地址是不同的,那他们是怎么找到真正的地址的?
二、进程地址空间
OS不仅会给程序分配程序地址空间,当程序跑起来 时,还会给程序分配页表来进行访问物理地址。

页表的作用是:就是建立虚拟地址到物理地址的映射关系 ,让CPU 执行指令 时,能通过虚拟地址找到实际对应的物理内存单元,从而完成数据读写。
在这个过程中CPU是看不到真正的物理内存的,CPU在执行程序时,通过mm_struct中的代码段,得到虚拟地址,通过页表来访问物理内存的。CPU看到的全是逻辑地址(编译器/汇编器生成的)。
**(写时拷贝)**如果父进程创建子进程后,大家都没发生任何改写操作,那他们页表映射时指向的是物理内存中的同一内容。如果发生改写了,就会给子进程重新开辟新的空间。
三、为什么需要进程地址空间
那这时候就会有好奇宝宝了,为什么不把程序直接加载到物理内存中。
在早期,我们的电子设备内存小,多个程序同时运行时,如果直接超载了呢?还有我们的程序能直接加载到物理内存里,也就意味着,一个进程可以修改任意的进程,如果一个病毒加载进来呢?
所以我们在程序运行前,提前分配虚拟进程地址,规划好内存大小,而且通过虚拟地址进行访问,(1)可以大大增强进程间的独立性
(2)保障了内存数据的安全性
还有就是,虚拟地址相当于物理内存的白纸状态,一切都是空的,让进程产生本身代码和数据可以随意存放物理内存任何位置的幻觉:
进程不需要管物理内存环境怎么样,栈永远从上往下长,堆永远从下往上长,只需按照统一规则运行就行。
这使得编译器可以按照统一、固定的布局来进行编译,如该地址为0x0001,也不需要管物理内存环境怎么样,哪里被占用。
(3)进程地址空间的存在让所有进程以统一视角来看待进程代码和数据的各个区域。
(4)进程地址空间的存在让编译器以统一视角来进行编译代码。
以上就是本次内容,所以我们的.c文件在编译时,就已经有虚拟地址了。
如有错误,请斧正。