IT评测·应用市场-qidao123.com
标题:
[Linux]——进程(4)
[打印本页]
作者:
张国伟
时间:
2025-1-24 11:11
标题:
[Linux]——进程(4)
目录
一、媒介
二、正文
1.地址空间的概念
2.地址空间的意义
3.页表
4.总结和思考
三、结语
一、媒介
本文我们将对进程中的地址空间和页表举行详细的讲解!
二、正文
1.地址空间的概念
在C/C++语言的学习中,我们常常会听到有人谈论起内存中地址的相干概念,其实在Linux中确切的概念叫做进程地址空间,
对于每一个进程而言,都有其对应的进程地址空间
,其内大抵空间分布如下图:
上面的栈区,堆区,代码区,常量区,相信小伙伴们肯定耳熟能详了,对于栈是向下生长,堆是向上生长,而且图中这几个地域的地址分布也是有规律的,从最下面的正文代码,初始化数据,未初始化的数据一直到栈,命令行参数,他们的
地址分布是从低到高
的,但是我们只是听闻是这样,下面让我们来验证一下。
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 #include<unistd.h>
5
6 int g_val1=100;
7 int g_val2;
8 int main()
9 {
10 printf("code addr:%p\n",main);
11 const char *str="hello world";
12 printf("read only string addr:%p\n",str);
13 printf("init global value addr:%p\n",&g_val1);
14 printf("uninit global value addr:%p\n",&g_val2);
15 char *mem1=(char*)malloc(100);
16 char *mem2=(char*)malloc(100);
17 char *mem3=(char*)malloc(100);
18 printf("m1 heap addr:%p\n",mem1);
19 printf("m2 heap addr:%p\n",mem2);
20 printf("m3 heap addr:%p\n",mem3);
21 printf("stack addr:%p\n",&str);
22 int a;
23 int b;
24 int c;
25 printf("a stack addr:%p\n",&a);
26 printf("b stack addr:%p\n",&b);
27 printf("c stack addr:%p\n",&c);
28 return 0;
29 }
30
复制代码
上面只是地址空间的大概划分,那么
什么叫做地址空间
,
所谓的地址空间,本质是一个猫叔进程可视范围的大小,地址空间内肯定要存在各种地域划分,即对线性地址举行start和end
即可
2.地址空间的意义
那么
为什么要有地址空间呢,也就是它存在的意义是什么?
其一是让进程以统一的视角对待内存
,因为有了地址空间的存在,进程无需再管现实分配的物理地址在物理内存是怎样的一个分布,在进程看来都只有一个CPU分配的地址空间,进程直接使用它就可以了。
其二是增加进程虚拟地址空间可以让我们访问内存的时候,增加一个转换的过程
,在这个转换的过程中,可以对我们的寻址请求举行审查,以是一旦非常访问,直接拦截,该请求不会到达物理内存,保护物理内存。就说我们是小孩子的时候,父母总会要求我们将压岁钱交给他们管理,当我们想要买东西的时候找他们拿即可,如果我们买一个10元的文具盒,父母会给我们10元,但是我们想买一个200元的游戏机,父母可能就会以好好学习为由制止我们的请求,与这个道理是类似的。
其三就是有了地址空间和页表的存在,就可以将进程管理模块和内存管理模块举行解耦合
,因为在进程看来它只必要处理虚拟地址即可,无需关注内存是如何存放和处理代码和数据的。
3.页表
在讲解页表之前,我们先来回顾之前创建子进程的一个小尾巴,就是当我们在folk创建一个子进程时,当时我们发现falk函数的返回值在父子进程的值是不一样的,子进程为0,父进程为创建子进程的id,那么这到底是怎么实现的,让我们我们先来看看下面这几段代码的结果
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 #include<unistd.h>
5
6 int g_val=100;
7 int main()
8 {
9 pid_t id=fork();
10 if(id==0)
11 {
12 int cnt=5;
13 //子进程
14 while(1)
15 {
16 printf("I'm a child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
17 sleep(1);
18 }
19 }
20 else
21 {
22 //父进程
23 while(1)
24 {
25 printf("I'm a parent, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
26 sleep(1);
27 }
28 }
29 return 0;
30 }
复制代码
上面这一段代码中,我们在父子进程中分别打印g_val的值和地址,我们发现两者是一模一样的,这也和我们对子进程会对父进程的资源举行继承的认知是相符合的,但是当我们对该值举行修改,结果又会如何呢?
pid_t id=fork();
10 if(id==0)
11 {
12 int cnt=5;
13 //子进程
14 while(1)
15 {
16 printf("I'm a child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
17 sleep(1);
18 if(cnt) cnt--;
19 else
20 {
21 g_val=200;
22 printf("子进程chang:100->200\n");
23 }
24 }
25 }
26 else
27 {
28 //父进程
29 while(1)
30 {
31 printf("I'm a parent, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
32 sleep(1);
33 }
34 }
复制代码
当我们在子进程中修改g_val的值的时候,按照我们的认识,这时候子进程应该对其举行写实拷贝,即深拷贝,那么这时候打印出来g_val的地址在父子进程中应该是不雷同的,但是现实情况我们发现地址却仍旧是雷同的,但是他们的值确实不同的?
这也就阐明白这个
g_val的地址并不是现实的物理地址,而是一种线性地址,或者说常说的虚拟地址
,那么到底是怎么做到虚拟地址雷同的情况下,值确不一样,这就是涉及到页表了。
我们之前在讲解进程的时候,说到进程是由PCB和代码数据组成的,今天我们又可以进一步丰富进程的概念。
进程是由PCB,程序地址空间,页表和存储在物理内存中的代码和数据组成的
。
那么对于页表,首先我们先来了解
什么是页表
?简单来说,
其由三部分组成,第一部分是虚拟地址,第二部分是虚拟地址对应到物理内存中的物理地址以及标志位,标志位即标定物理地址中存储数据是可读的还是可读写的
,这也是
为什么代码区和常量区的代码和数据我们不能修改的缘故起因
。
其次,我们再来了解页表的周边知识。一个是
CPU中的cr3寄存器
,该寄存器中存放的是页表的地址,也属于进程在硬件的上下文,因此在举行进程切换的时候,cr3寄存器中页表的地址也会被所属进程打包带走,方便下次再次执行进程时上下文的恢复。
再而是
惰性加载
,我们都知道对与一个很大的文件,操作体系有时候并不能一下子将这个文件的全部内容加载到磁盘上,往往是采取分批加载的方式,比方对于一个40G的文件,一次加载300M,或者是500M这样子。在了解加载这个过程后,那么什么是惰性加载呢,就是比如说操作体系在加载一个进程的时候,它可以一下子加载说500M的代码和数据,但是它并不会这样做,因为你进程自己可能一下子就跑5MB的代码或者用到的数据没那么多,如果对于每一个进程CPU都这样做的话就会极大的占据CPU的资源,因此它并不会一下子加载那么多,而是按需加载,即在必要时才加载资源或对象,而不是在程序启动时立即加载所有资源。这种技能可以有效减少初始开销,进步应用性能和用户体验。
最后就是
缺页制止
,其定义是是指在执行指令时,所需访问的页面不在内存中,导致制止当前指令的执行,并从外存(如硬盘)中加载所需页面到内存的过程。缺页制止是分页存储中的一种常见现象,重要发生在虚拟内存体系中。那么在进程中,就是说当一个进程在创建的时候,其实是先创建内核的数据结构,即地址空间,页表等,这时候页表中可能会有虚拟地址,但是现实的物理地址还并没有分配,只有当你这个进程开始执行的时候,用到哪些代码和资源,再触发缺页制止,为页表分配物理地址。
4.总结和思考
通过对地址空间和页表的学习,我们更一步的深入的对进程的了解,
进程是由内核数据结构(task_struct && mm_struct && 页表)+程序的代码和数组组成的
。
最后,看完本文,小伙伴们能不能办理一下几个问题呢:
①地址空间的概念
②地址空间的意义
③如何做到让代码和字符常量区是只读的
④惰性加载的概念
⑤什么是缺页制止
三、结语
到此为止,本文有关地址空间和页表的讲解就到此结束了,如有不足之处,欢迎小伙伴们指出呀!
关注我 _麦麦_分享更多干货:_麦麦_-CSDN博客
各人的「关注❤️ + 点赞
欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/)
Powered by Discuz! X3.4