进程地址空间
Posted 燕麦冲冲冲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程地址空间相关的知识,希望对你有一定的参考价值。
进程地址空间
曾经所学的c/c++内存分布空间并不是真正的内存,而是进程地址空间。
进程地址空间究竟是什么?
1、创建子进程让子进程修改全局数据,观察父子进程中全局数据的差别。
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int g_val = 0;
5
6 int main()
7
8 printf("the g_val is %d\\n", g_val);
9 pid_t id = fork();
10 if(id == 0)
11
12 //child
13 int count = 0;
14 while(1)
15
16 printf("child--pid:%d ppid:%d g_val:%d &g_val:%p\\n", getpid(), getppid(), g_val, &g_val);
17 sleep(1);
18 if(count == 5)
19
20 g_val = 100;
21
22 count++;
23
24
25 else if(id > 0)
26
27 //parent
28 while(1)
29
30 printf("parent--pid:%d ppid:%d g_val:%d &g_val:%p\\n", getpid(), getppid(), g_val, &g_val);
31 sleep(1);
32
33
34 else
35 //TODO
36
37 return 0;
38
可以发现,数据并不相同,这是因为:父子进程之间,代码共享,而数据是各自私有一份的!
但是地址竟然相同的,如果是物理地址那是不可能的。
而这种地址本质是一种虚拟地址,由操作系统为我们提供。
(1)数据和代码一定在物理内存上(冯诺依曼规定),所以os需要将虚拟地址转换为物理地址。
(2)所有的程序,都必须运行起来,运行后程序变为进程,那么虚拟地址与进程存在某种关系。
操作系统通过页表映射发现g_val只有读权限,但是两者有父子关系,就没有kill,而是为子进程新开辟一块空间,在新空间上进行写,而g_val在两者的地址空间上的地址不会变化。这就是写时拷贝。
2、地址空间小故事
男女同桌间的三八线,各自在脑海中为自己在实际的桌面上分配了区域,划分38线的过程就是划分区域的过程,本质就是调整自己认为的[start, end]。
进程(人),地址空间(脑海中的尺子),物理内存(桌子)
进程就是一个个的task_struct,地址空间就是struct mm_struct(是一个结构体,拥有的成员是各个分区的起始终止位置)
富豪拥有十亿美金,拥有十个私生子,且私生子之间彼此不知道对方的存在。富豪对每个私生子都承诺十亿美金遗产都会全部给这位私生子,那么每一个私生子都认为自己拥有10亿美金,每个私生子要钱时也不可能要光10亿。
os(富豪),进程(私生子),地址空间(10亿美金“大饼”)。
操作系统默认会给各个进程构成一个地址空间的概念(32位下为4GB)。
3、回答标题问题
地址空间本质是进程看待内存的方式,抽象出来的一个概念,内核struct mm_struct,这样的每个进程,都认为自己独占系统内存资源。
区域划分的本质将线性地址空间划分成为一个一个的area[start, end]
虚拟地址本质在[start, end]之间的各个地址叫做虚拟地址。
为什么要存在地址空间?
1⃣️如果进程可以直接通过指针访问暴露的物理内存,那么越界会导致其他信息的非法读取甚至篡改,从而使进程的独立性无法保证。
(1)通过页表和地址空间,实现虚拟地址向物理地址转换,操作系统会进行合法性检测。
(2)合法性检验首先根据地址空间各分区的[start, end]进行初步检测是否有越界行为,在通过页表上的读写权限再次判断是否合法。
举个🌰比如将一个常量字符串修改,经过页表映射后,发现这块空间是只读的,不合法,操作系统立即终止此进程。
2⃣️将内存管理和进程管理解耦。内存管理和进程管理强耦合,但有了地址空间和页表,内存管理只需通过页表知道哪些内存无效,哪些有效。进程管理只需看地址空间。
3⃣️让每个进程以同样的方式来看待代码和数据。
磁盘上的可执行程序,本身就已经被划分为各个区域,方便链接,这样存放于内存上时也方便拆分,高效利用空间。而通过地址空间和页表就不至于让进程的代码和数据看起来杂乱无章了。内存的存储单位页框/4kb,程序是分成一个个的页帧 。
验证地址空间的基本排布
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 int g_unval;
5 int g_val = 100;
6
7 int main(int argc, char* argv[], char* env[])
8
9 printf("code addr: %p\\n", main); //代码区
10
11 const char* p = "hello world!"; //p在栈上,指向字符常量区的一个字符串
12 printf("read only: %p\\n", p);
13
14 printf("global val: %p\\n", &g_val); //已初始化全局数据区
15
16 printf("global uninit val: %p\\n", &g_unval); //未初始化全局数据区
17
18 char* q = (char*)malloc(20); //q在栈上,指向堆区上的一块空间
19 printf("heap addr: %p\\n", q); //堆区
20
21
22 printf("stack addr_first: %p\\n", &p);
23 printf("stack addr_second: %p\\n", &q); //栈区
24
25 printf("args addr_first: %p\\n", argv[0]);
26 printf("args addr_last: %p\\n", argv[argc - 1]);
27
28 printf("env addr: %p\\n", env[0]);
29 return 0;
30
为临时变量加上static修饰后,该变量的地址会跑到全局数据区。
地址空间和物理内存之间的关系?
进程struct task_struct通过指针与进程地址空间struct mm_struct关联。
操作系统会为进程创建页表,记录虚拟内存到物理内存的映射关系。
代码和数据可以加载到物理内存的任意位置。
进程和程序有什么区别?
以上是关于进程地址空间的主要内容,如果未能解决你的问题,请参考以下文章
Linux进程概念——下验证进程地址空间的基本排布 | 理解进程地址空间 | 进程地址空间如何映射至物理内存(页表的引出) | 为什么要存在进程地址空间 | Linux2.6内核进程调度队列
Linux进程概念——下验证进程地址空间的基本排布 | 理解进程地址空间 | 进程地址空间如何映射至物理内存(页表的引出) | 为什么要存在进程地址空间 | Linux2.6内核进程调度队列
Linux进程概念——下验证进程地址空间的基本排布 | 理解进程地址空间 | 进程地址空间如何映射至物理内存(页表的引出) | 为什么要存在进程地址空间 | Linux2.6内核进程调度队列