int main()
{
pid_t ret = fork();
if(ret == 0)
{
int cnt = 0;
while(1)
{
printf("I am child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",\
getpid(), getppid(), g_val, &g_val);
sleep(1);
++cnt;
if(cnt == 5)
{
g_val = 200;
printf("child change g_val 100 -> 200 success\n");
}
}
}
else if(ret > 0)
{
while(1)
{
printf("I am father, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",\
getpid(), getppid(), g_val, &g_val);
sleep(1);
}
}
else
{
perror("fork");
return 1;
}
return 0;
}
上面代码运行之后,竟然出现了相同的地址所打印的值并不相同的结果,这到底是为什么呢?
几乎所有的语言,如果有“地址”的概念,那么这个地址一定不是物理意义上的地址,而是虚拟地址!
int un_g_val;
int g_val = 100;
int main(int argc, char* argv[], char* env[])
{
char* p = "helloworld";
printf("read only addr: %p\n", p);
printf("code addr: %p\n", main);
static int un_val;
static int val = 1;
printf("init static addr: %p\n", &val);
printf("uninit static addr: %p\n", &un_val);
printf("init gloval addr: %p\n", &g_val);
printf("uninit gloval addr: %p\n", &un_g_val);
char* p1 = (char*)malloc(16);
char* p2 = (char*)malloc(16);
char* p3 = (char*)malloc(16);
char* p4 = (char*)malloc(16);
printf("heap addr: %p\n", p1);
printf("heap addr: %p\n", p2);
printf("heap addr: %p\n", p3);
printf("heap addr: %p\n", p4);
printf("stack addr: %p\n", &p1);
printf("stack addr: %p\n", &p2);
printf("stack addr: %p\n", &p3);
printf("stack addr: %p\n", &p4);
int i = 0;
for(i = 0; i < argc; ++i)
{
printf("argv[%d]: %p\n", i, argv[i]);
}
for(i = 0; env[i]; ++i)
{
printf("env[%d]: %p\n", i, env[i]);
}
return 0;
}
进程地址空间是一种内核数据结构,他至少要有对各个区域的划分。
对于虚拟地址到物理地址的映射是通过页表来转换的。
地址空间和页表(用户级)是每一个进程都私有一份的。
只要保证,每一个进程的页表,映射的是物理内存的不同区域,就能保证进程之间的独立性!
这里可以回答开始提出的问题,为什么相同的地址所打印的值却不相同。
子进程相当于是对父进程各项数据拷贝得到的,所以会很相似。当父进程和子进程需要对同一块内存空间进行写入操作时,为了保证进程的独立性,会在物理内存上给子进程开辟一块空间进行写入。所谓地址相同只是虚拟地址的相同,两个进程地址通过页表的转换到物理内存中却是不同的空间。
页表的对应关系能够被填充,是因为不仅操作系统内部会遵守进程地址空间,编译器同样要遵守。
编译器在编译代码的时候,会按照进程地址空间的样子使程序中每一个字段都具有一个虚拟地址。
当程序被加载进内存后,就成为了进程,进程既然是在内存中的,即物理空间中,就会有物理地址。
而程序在为变成进程前,内部就已经存在虚拟内存地址,这些虚拟内存地址会和物理地址进行映射,这样页表的映射关系就能够实现。
主要有三点原因: