前言
中篇讲了进程为什么要有优先级,以及环境变量和通过代码获得环境变量 本篇主要讲解什么是地址空间 , 地址空间是怎么设计的?为什么要有地址空间?
程序地址空间
先看下图
验证上图的正文代码至堆的地址是不是从低地址向高地址增加栈相反。我们用下面这段代码试试
#include <stdio.h>2 #include <stdlib.h>3 4 int unval;5 int val = 10;6 7 8 9 10 int main()11 {12 printf("code addr: %p\n", main);13 printf("init global addr: %p\n", &val);14 printf("uninit global addr: %p\n", &unval);15 char *heap_mem = (char*)malloc(10);16 printf("heap addr: %p\n", heap_mem);17 int a = 10;18 printf("test stack addr: %p\n", &a); 19 20 return 0; 21 }
从上图看地址确实是如上图的所画那样从低地址向高地址增加。
我们再看一段代码运行结果
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 0;}else if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取 g_val=100;printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{ //parentsleep(3);printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}
请看下面的视频
虚拟地址
从视频得出的结论,怎么和以往的C语言和C++不一样 在语言中同一个变量,值怎么可能会有两个? 下面我就要引出一个概念:虚拟地址
虚拟地址
1.1 什么是虚拟地址?
首先我先讲一个故事
在一家公司里有小明小美和小红三个员工,有一天老板对这个三个员工分别单独在没有其他人情况下许下了承诺(PUA)"你今年努力干工作年底给你调职加薪"
许下承诺后三个员工就继续干工作了,但是他们都以为自己是唯一一个被老板叫去谈话的且认为自己独享这福利待遇。
故事到这里先暂停一哈
我们先引出几个相对应的概念
这里的老板就是操作系统
三个员工就是进程
调职加薪就是虚拟地址 ->(物理内存)
看到这里你就会明白了 所有的进程都会有一份自己的虚拟地址。之前讲的程序地址空间是不准确的,准确来说应该是进程地址空间
总结:在C/C++言语中所看到的地址,全部都是虚拟地址,物理地址用户看不到,OS按照地址空间这种结构设计出虚拟地址,然后将虚拟地址映射到物理内存中。
1.2地址空间又是如何设计的?
Linux源代码 内核数据结构如下图设计地址空间 感兴趣的自己可以去官网下载源代码看看
地址空间用结构体定义出来,再用数据结构进行管理(先描述后组织)然后把它的指针放在task_struct PCB 这个结构体里。
1.3为什么要有地址空间?
如果进程直接访问物理地址空间会怎么样?
上图是以前计算机设计时 磁盘越过OS 直接访问内存,现在计算机在OS中增加虚拟地址 然后通过页表映射到物理内存中。看下图
加入了虚拟地址和页表映射机制后,你的写程序出现野指针问题OS直接就把你的进程给你杀掉了,它其实是运行了得。在运行那一瞬间 OS发现你的虚拟地址通过映射到物理内存中和之前正在进程访问的是同一块物理内存地址 ,这时OS就不会把你的进程虚拟地址映射到物理内存中,直接kill。这就是程序崩溃的原因。
到这里 我们就能回答刚才的问题了 为什么同一个地址会有两个不同的值看下图
父进程先运行虚拟地址通过页表映射到物理内存中,子进程继承父进程的所有代码和数据
和页表(地址空间和页表每个进程都会私有一份)OS做的就是每一个进程页表,映射到物理内存不同区域中,然后写时拷贝 就改变了子进程的val值。
没看懂在看一次
重新理解挂起
加载本质就是创建进程,那是不是非得要把程序所有代码和数据加载到内存中?
答案不是。
一个大型单机游戏 小的几十个G 大一点的200G 内存才多大?内存要满了OS将进程的内核结构创建出来 ,一个游戏有不同模块
OS很聪明将这些模块分批加载 当然也可以分批换出,换出同时就成了挂起状态。