走进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}

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

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

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


相关推荐
落羽的落羽12 分钟前
【Linux系统】解明进程优先级与切换调度O(1)算法
linux·服务器·c++·人工智能·学习·算法·机器学习
宇钶宇夕20 分钟前
自动化工程师入门:TIA Portal V19 从下载到运行完整攻略(避坑指南 + 功能详解)
运维·自动化
代码游侠32 分钟前
复习笔记——C语言指针
linux·c语言·开发语言·笔记·学习
百***69441 小时前
Linux下MySQL的简单使用
linux·mysql·adb
飞凌嵌入式2 小时前
飞凌嵌入式RK3568开发板的TFTP烧写文件系统指南
linux·嵌入式硬件·嵌入式
q***55582 小时前
使用 Nginx 搭建代理服务器(正向代理 HTTPS 网站)指南
运维·nginx·https
❀͜͡傀儡师2 小时前
修改centos服务器启动画面
linux·服务器·centos
倔强的石头1065 小时前
openGauss数据库:从CentOS 7.9部署到实战验证
linux·数据库·centos
梁正雄6 小时前
linux服务-Nginx+Tomcat+Redis之Session 共享 - 容器单机版
linux·nginx·tomcat