【Linux】进程(6)虚拟地址空间
·
一、之前所学的程序地址空间
1. 1 空间分布图

在这里插入图片描述
- 经过c语言的学习,相信大家对这张图肯定并不陌生。自下而上,地址递增,分别是正文代码区、字符串常量区、初始化数据区、未初始化数据区、堆区、共享区和栈区。但其实这个图也并不完整,只有在接触了系统之后才能明白完整的图。
- 栈区:是用来存储局部变量和函数调用上下文信息的,无论是整型变量、浮点型变量又或是指针变量,都是存储在栈里面的。地址从高向低增长。
- 堆区:用来存储动态申请的内存的。地址从低向高增长。
- 未初始化数据区即BBS段:是用来存储没有初始化的全局/静态变量,程序启动时会被系统自动初始化为0。
- 初始化数据区:用来存储已经初始化的全局/静态变量。
- 字符串常量区:用于存储字符串常量。
- 正文代码区:存放程序的机器指令(即编译后的二进制可执行代码),是 CPU 执行的操作序列。它包含函数体、控制流(分支、循环)对应的指令等。
- 未初始化数据区/初始化数据区、字符串常量区和正文代码区的大小的固定的。
- 堆、栈相对而生。
1.2 验证
- 接下来用下面的一段代码来进行验证上面的空间分布图:
代码语言:javascript
AI代码解释
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 int g_unval;
5 int g_val = 100;
6 int main(int argc, char *argv[], char *env[])
7 {
8 const char *str = "helloworld";
9 printf("code addr: %p\n", main);
10 printf("read only string addr: %p\n", str);
11 printf("init global addr: %p\n", &g_val);
12 printf("uninit global addr: %p\n", &g_unval);
13 static int test = 10;
14 char *heap_mem = (char*)malloc(10);
15 char *heap_mem1 = (char*)malloc(10);
16 char *heap_mem2 = (char*)malloc(10);
17 char *heap_mem3 = (char*)malloc(10);
18 printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)
19 printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)
20 printf("heap addr: %p\n", heap_mem2); //heap_mem(0), &heap_mem(1)
21 printf("heap addr: %p\n", heap_mem3); //heap_mem(0), &heap_mem(1)
22 printf("test static addr: %p\n", &test); //heap_mem(0), &heap_mem(1)
23 printf("stack addr: %p\n", &heap_mem); //heap_mem(0), &heap_mem(1)
24 printf("stack addr: %p\n", &heap_mem1); //heap_mem(0), &heap_mem(1)
25 printf("stack addr: %p\n", &heap_mem2); //heap_mem(0), &heap_mem(1)
26 printf("stack addr: %p\n", &heap_mem3); //heap_mem(0), &heap_mem(1)
27 return 0;
28 }
- main是函数名,存放在正文代码区。g_unval 是未初始化的全局变量,存放在未初始化数据区,g_val是已经初始化的全局变量,存放在初始化数据区。str是指针变量,str就是字符串"helloworld"的地址,而&str是指针变量的地址,前者存储在字符串常量区,后者在栈区。test是已经初始化的静态变量,存储在初始化数据区。heap_mem,heap_men1,heap_men2和heap_mem3是指针变量,本身也是地址,它指向堆申请的空间,&heap_mem在栈区,heap_mem在堆区。

在这里插入图片描述
二、进程/虚拟地址空间
在我们对进程和内存有了一定了解之后,那么这个程序地址空间是指内存吗?若说它是内存,它把空间划分的这样绝对,PCB应该存储在哪里?若说它不是内存,那它究竟是什么东西?根据冯-诺依曼体系,代码和数据就是要放在内存里面的。之所以会有这样的矛盾,这是因为之前学习的东西并不完整,接下来我们来了解进程/虚拟地址空间。
1.1虚拟地址
- 进程/虚拟地址空间一定离不开虚拟地址,那进程/虚拟地址是什么呢?我们通过实验认识来认识它:
代码语言:javascript
AI代码解释
//实验代码
int num = 100;
6 int main()
7 {
8 pid_t id = fork();
9 if (id == 0)
10 {
11 printf("我是一个子进程,pid:%d, ppid:%d, num = %d, &num = %p\n", getpid(), getppid(), num, &num);
12 num = 200;
13 printf("我是一个子进程,我修改了num的值, num = %d, &num = %p\n", num, &num);
14 }
15 else
16 {
17 while(1)
18 {
19 printf("我是一个父进程,pid:%d, ppid:%d, num = %d, &num = %p\n", getpid(), getppid(), num, &num);
20 sleep(1);
21 }
22 }
23 return 0;
24 }

在这里插入图片描述
- 我设置了一个全局变量num,这个变量子进程和父进程都能够获取到(父子进程代码共享),在子进程修改num这个值之前,父子进程对应num的值都是100,对应num地址都是0x601054。但是在子进程修改了num这个值之后,子进程里显示num值为200,父进程里显示num值为100,子进程没能影响父进程num的值,但它们的地址还是一样的。
- 但一个变量能够存储两个不同的值?这显然是不可能的,这说明父子进程输出的并不是同一个变量。也说明这个地址并不是真正的物理地址,而是系统给我们看的一个虚拟地址。
- 我们所能看到的一切地址都是经过系统处理之后得到的虚拟地址,真正的物理地址我们是看不到的,它被系统给隐藏起来了。
2.1进程/虚拟地址空间
进程/虚拟地址空间不是真正意义上的物理内存,也不是程序地址空间,但对于进程来说它就是内存,完整图如下,相对于程序地址空间它多出来命令行参数环境变量区以及内核空间。
更多推荐




所有评论(0)