走进Linux的世界:虚拟内存空间

​

嘿,各位技术潮人!好久不见甚是想念。生活就像一场奇妙冒险,而编程就是那把超酷的万能钥匙。此刻,阳光洒在键盘上,灵感在指尖跳跃,让我们抛开一切束缚,给平淡日子加点料,注入满满的

passion。准备好和我一起冲进代码的奇幻宇宙了吗?Let's go!

我的博客:yuanManGan

我的专栏:C++入门小馆 C言雅韵集 数据结构漫游记 闲言碎语小记坊 进阶数据结构 走进Linux的世界 题山采玉 领略算法真谛


程序地址空间回顾


我相信大家对这张图不陌生了。

写个代码测试一下

cpp 复制代码
  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("init global addr: %p\n", &g_val);
 11     printf("uninit global addr: %p\n", &g_unval);
 12     static int test = 10;
 13     char *heap_mem = (char*)malloc(10);
 14     char *heap_mem1 = (char*)malloc(10);
 15     char *heap_mem2 = (char*)malloc(10);
 16     char *heap_mem3 = (char*)malloc(10);
 17     printf("heap addr: %p\n", heap_mem); //heap_mem(0), &heap_mem(1)
 18     printf("heap addr: %p\n", heap_mem1); //heap_mem(0), &heap_mem(1)
 19     printf("heap addr: %p\n", heap_mem2); //heap_mem(0), &heap_mem(1)
 20     printf("heap addr: %p\n", heap_mem3); //heap_mem(0), &heap_mem(1)
 21 
 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 
 28     printf("read only string addr: %p\n", str);
 29     for(int i = 0 ;i < argc; i++)
 30     {
 31        printf("argv[%d]: %p\n", i, argv[i]);
 32     }
 33     for(int i = 0; env[i]; i++)
 34     {
 35         printf("env[%d]: %p\n", i, env[i]);
 36     }
 37 
 38     return 0;
 39 }

我们的栈是向下生长,而堆却是向上生长 ,但我们上一篇博客讲到,我们的

一个变量的地址是它的众多地址中最小的那个即:


虚拟地址


来段代码感受⼀下

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 int g_val = 0;
  5 int main()
  6 {
  7     pid_t id = fork();
  8     if(id < 0)
  9     {
 10         perror("fork");
 11         return 0;
 12     }
 13     else if(id == 0)
 14     {
 15         //child
 16         printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
 17     }
 18     else
 19     {
 20         //parent
 21         printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
 22     }
 23     sleep(1);
 24     return 0;
 25 }        

我们发现父子进程对应的地址是一样的,这就说明父子进程共用一份代码和数据。

这就好比我们之前遇到过的浅拷贝一样的概念

我们改一下代码:

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 int g_val = 0;
  5 int main()
  6 {
  7     pid_t id = fork();
  8     if(id < 0)
  9     {
 10         perror("fork");
 11         return 0;
 12     }
 13     else if(id == 0)
 14     {
 15         //child                                                                                                                                                                                                                                   
 16         int cnt = 5;
 17         while(cnt--)
 18         {
 19             printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
 20             g_val++;
 21             sleep(1);
 22         }
 23     }
 24     else
 25     {
 26         //parent
 27         int cnt = 5;
 28         while(cnt--)
 29         {
 30             printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
 31             sleep(1);
 32         }
 33     }
 34     return 0;
 35 }

咦为什么,我们的地址没有改变,子进程改了g_val,但父进程的g_val没有改呢?

这就说明这个地址不是物理地址,而是虚拟地址,真正的物理地址我们看不到,被OS隐藏起来了,目的是保护内存。

要回答上面的问题,就要真正的了解虚拟地址了

这个是虚拟内存真正的样子,存在一个页表将虚拟内存映射到物理内存。

子进程不仅回继承父进程的代码和数据,还会继承这个页表。

当我们要修改g_val的值的时候,我们就会发生写时拷贝,将g_val在物理内存中拷贝一份,再让你修改,这样就使得我们在看到g_val的地址是一样的,但更改g_val的值互相不会有影响。

那Linux是怎样管理整个虚拟内存的呢?

在每个进程里面都有一个描述地址空间的结构体mm_struct

cpp 复制代码
1 struct task_struct
2 {
3 		/*...*/
4		 struct mm_struct  *mm; //对于普通的⽤⼾进程来说该字段指向他的
		虚拟地址空间的⽤⼾空间部分,对于内核线程来说这部分为NULL。
5 		struct mm_struct  *active_mm; // 该字段是内核线程使⽤的。当该
		进程是内核线程时,它的mm字段为NULL,表⽰没有内存地址空间,可也并不是真正的没有,这是因为所
		有进程关于内核的映射都是⼀样的,内核线程可以使⽤任意进程的地址空间。
6
7 /*...*/
8 }```


```cpp
1 struct mm_struct
2 {
3 	/*...*/
4 	struct vm_area_struct *mmap; /* 指向虚拟区间(VMA)链表 */
5 	struct rb_root mm_rb; /* red_black树 */
6 	unsigned long task_size; /*具有该结构体的进程的虚拟地址空间的⼤⼩*/
7 	/*...*/
8 	// 代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。
9 	 unsigned long start_code, end_code, start_data, end_data;
10	 unsigned long start_brk, brk, start_stack;
11	 unsigned long arg_start, arg_end, env_start, env_end;
12 	/*...*/
13}

这里面就要描述一个区域开始的结束的成员变量。

那我们的常量数据段是怎么做到不让你修改这个数据的呢?

在页表的它不止单单有一个映射关系,它还有权限位。

​

相关推荐
Julien200416 分钟前
管理机密(一)
运维·服务器·开发语言·学习方法
十碗饭吃不饱1 小时前
后端运维:网络联通、进程查询、Java服务启动脚本
java·运维·网络
好用斋1 小时前
拾光坞安装网盘聚合LitePan:把Docker命令翻译成图形界面
运维·docker·容器
贝锐2 小时前
金仓、达梦、TDSQL全适配:向日葵补齐信创数据库层远程运维能力
运维·数据库·信创
CodeYp20194 小时前
第14讲:mmap 实现文件 LRU 缓存
linux·lru·mmap
程序开发阿元4 小时前
一台 20G 的 VPS 被日志吃掉一半:我按这个顺序查了二十分钟
linux·ssh
AOI小白新手上路5 小时前
深度学习算力购置方案分析:本地 RTX 4090 服务器 vs 云端租赁
服务器·人工智能·深度学习
.冰块.5 小时前
流量大了自动加 Pod!K8s HPA 扩缩容与 ResourceQuota/LimitRange 资源管控实战
linux·云原生·容器·kubernetes·hpa·quota·limitrange
千桐科技5 小时前
qKnow智能体构建平台专业版 v3.1.5 发布:扩容工作流编排调用能力,上线Text2SQL插件,拓宽数据类智能应用构建边界
服务器·数据库·oracle·qknow
承渊政道5 小时前
Linux网络学习【深入理解多路转接select、poll的代码改写】
linux·ubuntu·select·多路转接·poll