88建网站,免费咨询的图片,韩国热搜榜,wordpress 图片服务器文章目录 一、历史问题回顾二、语言层面的地址空间2.1 验证 三、虚拟地址的引入3.1 初步解释这种现象——引入地址空间的概念3.2 再来粗粒度理解上面的现象 四、细节解释4.1 地址空间究竟是什么#xff1f;4.2为什么要有地址空间4.3 页表4.3.1 CR3寄存器4.3.2 页表是由页表项组… 文章目录 一、历史问题回顾二、语言层面的地址空间2.1 验证 三、虚拟地址的引入3.1 初步解释这种现象——引入地址空间的概念3.2 再来粗粒度理解上面的现象 四、细节解释4.1 地址空间究竟是什么4.2为什么要有地址空间4.3 页表4.3.1 CR3寄存器4.3.2 页表是由页表项组成的4.3.3 缺页中断 五、结语 一、历史问题回顾
之前在介绍 fork 函数的时候说过该函数返回了两次至于为什么会返回两次以及 fork 函数是如何做到返回两次的在【Linux取经路】揭秘进程的父与子一文中已经做了详细的解释忘了小伙伴可以点回去看看。在解释一个变量怎么会有两个不同值的时候当时的说法是由于进程具有独立性所以子进程把把父进程的数据拷贝了一份写时拷贝本质上是因为有两块空间但是为什么同一个变量名可以让父子进程看到不同的内容当时并没有过多解释因为这涉及到今天要讲解的程序地址空间问题接下来就让我们来一探究竟吧。
二、语言层面的地址空间
相信大家在学习 C/C 语言的时候一定都见过下面这张图
小Tips上图是以32位机为基础的32位地址线最多可以表示 2 32 2^{32} 232个地址因为每个地址线上只有0和1两种可能最终也就是有4G个地址一个地址对应一个字节所以就是 2 32 2^{32} 232个字节所以总的地址空间大小就是4G。内核空间是给操作系统使用的一个进程对应的所有的数据结构对象都存储在内核空间。
2.1 验证
#include stdio.h
#include stdlib.h int g_val_1; // 定义一个未初始化全局变量
int g_val_2 100; // 定义一个已初始化全局变量
int main(int argc, char* argv[], char* env[])
{ printf(code addr%p\n, main);// 函数名代表的就是地址通过打印 main 函数的地址来查看代码区的地址 const char *str Hello word;// 定义一个字符串常量 printf(read only string addr%p\n, str);// 字符常量区的地址 printf(init global value addr%p\n, g_val_2);// 已初始化全局变量的地址 printf(uninit global value addr%p\n, g_val_1); // 未初始化全局变量的地址 char* men1 (char*)malloc(100); printf(heap addr-men1%p\n, men1);// 堆区的地址 printf(stack addr-str%p\n, str); // 栈区的地址 static int a 10;// 定义一个静态局部变量 printf(static a add%p\n, a); // 静态局部变量的地址 int i 0; for(; argv[i]; i) printf(argv[%d],addr%p\n, i, argv[i]);// 打印命令行参数的地址 for(i 0; env[i]; i) printf(env[%d],addr%p\n, i, env[i]);// 打印环境变量的地址 return 0;
}小Tips堆栈空间是相对“生长”的。即堆区是先使用低地址再使用高地址而栈区是先使用高地址再使用低地址。
// 栈区地址由高向低增长
int main()
{ int a; int b; int c; int d; printf(stack addr%p\n, a); printf(stack addr%p\n, b); printf(stack addr%p\n, c); printf(stack addr%p\n, d);
} // 堆区地址由低向高增长
int main()
{ char* mem1 (char*)malloc(100); char* mem2 (char*)malloc(100); char* mem3 (char*)malloc(100); char* mem4 (char*)malloc(100); printf(Heap addr%p\n, mem1); printf(Heap addr%p\n, mem2); printf(Heap addr%p\n, mem3); printf(Heap addr%p\n, mem4); return 0;
}小Tips这里还有一个小细节堆栈地址其实相聚很远原因是堆栈之间还有一块区域这块区域在讲解动静态库的时候为大家讲解。
// 静态变量地址的验证
int g_val_1; // 定义一个未初始化全局变量
int g_val_2 100; // 定义一个已初始化全局变量
int main()
{ printf(code addr%p\n, main);// 函数名代表的就是地址通过打印 main 函数的地址来查看代码区的地址 const char *str Hello word;// 定义一个字符串常量 printf(read only string addr%p\n, str);// 字符常量区的地址 printf(init global value addr%p\n, g_val_2);// 已初始化全局变量的地址 printf(uninit global value addr%p\n, g_val_1); // 未初始化全局变量的地址 char* men1 (char*)malloc(100); printf(heap addr-men1%p\n, men1); printf(stack addr-str%p\n, str); static int a 10;// 定义一个静态局部变量 printf(static a add%p\n, a); return 0;
}小Tips从打印结果中可以看出静态变量的地址和全局变量的地址十分接近。这是因为 static 修饰的局部变量编译的时候已经被编译到全局数据区了。注意静态局部变量被编译到全局数据区仅仅是延长了该变量的生命周期作用域并没有改变还是只能在 main 函数的作用域中使用该变量。
注意本小节的所有代码验证都是基于 Linux 操作系统的相同的代码放在 Windows 操作系统中的 VS 下跑出来的结果可能会有所不同。
三、虚拟地址的引入
下面通过一个例子来引入虚拟地址的概念。
int g_val 100;
int main()
{pid_t pid fork();if(pid 0){int cnt 5;// 子进程while(1){printf(I am child, pid%d, ppid%d, g_val%d, g_val%p\n, getpid(), getppid(), g_val, g_val);sleep(1);if(cnt){cnt--;} else { g_val 200; } } } else { // 父进程 while(1) { printf(I am parent, pid%d, ppid%d, g_val%d, g_val%p\n, getpid(), getppid(), g_val, g_val); sleep(1); } } return 0;
}代码解释上面这段代码中创建了一个子进程并且定义了一个全局的变量 g_val初始化为100。让父子进程同时去访问变量 g_val。在子进程要执行的代码片段中还定义了一个局部变量 cnt初始化为5每执行一次循环就让 cnt--当 cnt 减到0的时候把 g_val 的值修改为200。 结果分析从打印结果中可以看出在子进程对 g_val 进行修改后父子进程获取到的 g_val 的值并不一样这符合我们的预期。因为父子进程相互独立他们拥有各自的代码和数据子进程在对 g_val 进行修改的时候会发生写时拷贝这一点在前面的文章中已经讲过。但奇怪的是为什么同一个地址从该地址获取到的数据却不相同。那么真想就只有一个这个打印出来的地址一定不是真实存在的物理地址因为真实存在的物理地址中只能存放一个数据不可能同时存储两个不同的数据。因此我们可以得出一个结论我们代码中打印出来的地址不是物理地址一般把这个地址叫做线性地址或者虚拟地址。
3.1 初步解释这种现象——引入地址空间的概念
之前在介绍进程的时候说过一个进程就等于 task_struct 代码和数据。但实际上事情并没有这么简单。一个进程一旦被创建出来操作系统为了让该进程能够更好的运行除了会为该进程创建对应的 PCB 对象之外还会为这个进程创建一个地址空间准确的叫法是进程地址空间。我们平时在编码过程中使用的地址就是这个地址空间中的地址。进程地址空间本质上是内核为该进程创建的一个结构体对象。进程的 PCB 中是有对应的指针指向该地址空间。进程地址空间中的虚拟地址和真是的物理地址是通过页表建立联系的因此每个进程也会有一张页表。
小Tips进程 PCB、进程地址空间、页表、物理地址四者之间的关系如上图所示。进程相关的代码和数据一定是存储在物理内存上的。
3.2 再来粗粒度理解上面的现象
根据进程独立性可知每个进程都要有自己独立的 PCB、进程地址空间、页表。子进程的这些东西大部分都是以父进程为模板创建出来的。对于全局变量 g_val在物理内存上它始终只有一份在父进程中 g_val 有自己的虚拟地址0X601054创建子进程的时候子进程根据父进程的地址空间创建出自己的地址空间此时 g_val 对应的虚拟地址仍然是0X601054。子进程的页表最初也是根据父进程的页表去创建的因此子进程中 g_val 变量的虚拟地址和父进程中的一样还是0X601054并且子进程页表中的虚拟地址和物理地址的映射关系还是继承自父进程。因此在子进程和父进程中都能够访问到 g_val 这个变量并且在子进程和父进程中打印出来的 g_val 的地址都是一样的。父子进程共享同一份代码也是根据这个原理来实现的。当子进程要修改 g_val 变量的时候由于父子进程的数据是相互独立的该独立性体现在在子进程去修改 g_val 的值不能影响到父进程即在父进程中 g_val 本身的值是100在子进程将 g_val 的值修改成200的时候父进程中 g_val 的值仍然得保持100。所以子进程在修改的时候会发生写时拷贝其本质就是操作系统发现子进程要去修改父子进程所共享的数据操作系统会说“子进程你等会儿先别改”。然后操作系统会为子进程在物理内存中开辟一块空间来存储 g_val 的值最后修改页表中的虚拟地址0X601054所对应的物理地址。这就是为什么打印出来的是通过一个地址但是却有两个不同的值。 小Tips写时拷贝是操作系统自动完成的子进程并不知情。这就相当于你有一个朋友要到家里来玩但是家里有点乱你让他等会再来期间你收拾房子的这个过程你朋友并不知情。操作系统就相当于是你子进程就相当于是你的朋友。重新开辟空间但是在这个过程中左侧的虚拟地址是0感知的它不关心也不会影响它。
四、细节解释
4.1 地址空间究竟是什么
所谓的进程地址空间本质是一个描述进程可视范围的大小。并且地址空间内一定存在各种区域划分。因为每个进程将来都会有一个地址空间所以操作系统一定会想办法把这些地址空间管理起来。管理的本质就是先描述再组织。在语言层面要描述一个事物只能通过结构体因此地址空间本质是内核的一个数据结构对象类似 PCB 一样。Linux 中描述地址空间的结构体是 struct mm_struct该结构体中通过定义 start 和 end 字段来确定地址空间的范围以及进行区域划分。
struct mm_struct
{unsigned long start_code; // 代码段的开始地址unsigned long end_code; // 代码段的结束地址unsigned long start_data; // 数据的首地址unsigned long end_data; // 数据的尾地址unsigned long start_brk; // 堆的首地址unsigned long brk; // 堆的尾地址unsigned long start_stack; // 进程栈的首地址//...
};小Tips除了这些 start 和 end 对应的地址外我们更应该注意在 start 和 end 范围内连续的空间中每一个最小单位都可以有地址这个地址可以被直接使用。
4.2为什么要有地址空间 原因一其中一个重要的原因是让进程以统一的视角看待内存。假如没有地址空间和页表进程直接访问存储在内存上的数据和代码是非常麻烦的首先进程需要将对应数据和代码的物理地址保存在进程 PCB 中其次当一个进程从挂起状态或者阻塞状态被变成运行状态的时候其数据和代码会被重新加载到内存中此时物理地址大概率是会发生变化的如果没有地址空间就需要去修改进程 PCB 中的内容这个过程是十分复杂的并且我们的可执行程序加载到内存中并不一定是连续的这一点会在后面进行讲解因为可执行程序并不是一次性全部加载到内存中这就导致我们的代码在物理内存中是东一块西一块很乱。而地址空间的存在会帮我们解决这些问题我们可以对可执行程序的虚拟地址进行连续的编址这样就可以可以根据虚拟地址的先后顺序去“从头”去执行可执行程序我们的进程不再需要担心可执行程序实际存储在内存的什么位置。这一点也可以总结成地址空间的存在将物理内存中的代码和数据从无序变成了有序。 原因二地址空间存在的第二个重要原因是进程在访问内存的时候增加一个转换的过程在这个转换的过程中可以对我们的寻址请求进行审查一旦发生异常访问比如越界访问去修改只读数据等会直接拦截该请求不会到达物理内存起到保护物理内存的作用。 原因三因为有地址空间和页表的存在将进程管理模块和内存管理模块进行解耦合。
小Tips地址空间就相当于是操作系统给进程画的一个大饼。操作系统给每个进程划分的地空间范围都是0~4G以32位操作系统为例。每个进程都不知道对方的存在都以为自己会独享这4G的空间。但实际并不是这样的操作系统并不会一次性把这4G的空间给同一个进程而是每个进程需要多少了就来申请多少。
4.3 页表
关于页表我们今天只做简单的介绍真正的页表并不是向上图中画的那样只有两个字段而是有多个字段并且分为多级页表。今天关于页表只介绍一下几点
4.3.1 CR3寄存器
CPU 中有一个叫做 CR3 的寄存器X86架构该寄存器中保存的就是当前正在运行的进程的页表的地址本质上属于当前进程的硬件上下文当前进程被切换走了是会把和该进程有关的所以硬件上下文数据带走的当然就包括 CR3 寄存器中保存的该进程页表的首地址。该进程重新被 CPU 调度的时候会把该这些硬件上下文数据重新加载到 CPU 里对应的寄存器中。CR3 寄存器中存的是物理地址。
4.3.2 页表是由页表项组成的
页表是有多个页表项组成的一般的页表项有如下几个。Present存在位表示该页是否存在于物理内存当中。如果存在数据才可以访问。如果不存在可能会引发缺页异常。Read/Write读/写位表示是否允许读取和写入。如果设置了“只读”则只允许读取写入会引发保护异常。代码段和字符常量区的数据所对应的页就是“只读”。User/Supervisor用户/超级用户位用于指示是否允许用户态程序访问该页。如果设置了“用户态可访问”则用户程序可以访问否则只有内核态可以访问。这些权限位通过硬件如CPU来执行当程序尝试访问内存时硬件会检查相应的页表项权限位如果权限不符合要求会触发相应的异常例如页故障异常。这样可以确保对内存的合法访问提高系统的安全性和稳定性。
小Tips这里需要注意一下单纯的物理内存是没有“只读”、“只写”等这些权限管理概念的对于物理内存上的任何一块空间来说都应该是可读可写的。所以我们在语言层面所说的代码区和字符常量区是只读的本质上是因为存储代码和字符常量的物理内存所对应的页中设置了“只读”权限。
#include stdio.h int main()
{ char* str Hello World; *str B; return 0;
}小Tips上面这段代码一看就是错误的写法。因为正常来说是需要在 char* str 的前面加上 const。但是加上后这段代码会在编译时就报错语法上说不过去被 const 修饰后是不能再进行修改的。而不加则是在运行时报错这就是这段代码在运行时通过页表去 *str 中的内容被硬件检查出不符合要求进而产生了异常。
4.3.3 缺页中断
现代操作系统几乎不做任何浪费空间和时间的事情操作系统对大文件可以实现分批加载。这个其实很好理解我们平时玩的大型电脑游戏动辄就几十个G而我们的内存一般就只有8个G或者16个G所以我们在玩这种大型游戏的时候一定没有把和该游戏有关的所有文件一次性加载到内存中而是采用分批加载的策略。操作系统对可执行程序一般采用的是惰性加载机制即操作系统承诺给进程分配4G的空间虚拟内存的大小但实际在物理内存上是用多少加载多少。通过页表中的Present存在位页表项去判断去标记该页是否存在于物理内存中如果不存在就会发生缺页中断将需要的代码和数据加载到物理内存中。可不要小瞧加载过程这里会涉及到物理内存这么大该申请那块儿内存加载是加载可执行程序的那一部分加载完后物理地址如何填到页表里呢这一系列和缺页中断相关的问题最终都是由操作系统中的内存管理模块来执行的。整个缺页中断的过程对进程是不可见的。正是因为地址空间和页表的存在才实现了进程管理和内存管理在软件层面的解耦。进程要访问对应的代码和数据只需要知道虚拟地址即可内存管理模块根据虚拟地址去判断是否要进行缺页中断。
小Tips进程在被创建的时候一定是先创建内核数据结构进程 PCB、地址空间、页表…然后再加载对应的可执行程序。挂起状态就是将进程的代码和数据从内存中清出去然后再将Present存在位标志位设置成不存在即可。
五、结语
今天的分享到这里就结束啦如果觉得文章还不错的话可以三连支持一下春人的主页还有很多有趣的文章欢迎小伙伴们前去点评您的支持就是春人前进的动力
文章转载自: http://www.morning.ghphp.cn.gov.cn.ghphp.cn http://www.morning.mlyq.cn.gov.cn.mlyq.cn http://www.morning.ppbqz.cn.gov.cn.ppbqz.cn http://www.morning.rdqzl.cn.gov.cn.rdqzl.cn http://www.morning.zfcfx.cn.gov.cn.zfcfx.cn http://www.morning.alive-8.com.gov.cn.alive-8.com http://www.morning.dhnqt.cn.gov.cn.dhnqt.cn http://www.morning.dtlnz.cn.gov.cn.dtlnz.cn http://www.morning.pzjrm.cn.gov.cn.pzjrm.cn http://www.morning.sfwfk.cn.gov.cn.sfwfk.cn http://www.morning.tqklh.cn.gov.cn.tqklh.cn http://www.morning.npmcf.cn.gov.cn.npmcf.cn http://www.morning.shsh1688.com.gov.cn.shsh1688.com http://www.morning.ghwtn.cn.gov.cn.ghwtn.cn http://www.morning.dycbp.cn.gov.cn.dycbp.cn http://www.morning.xqnzn.cn.gov.cn.xqnzn.cn http://www.morning.dshkp.cn.gov.cn.dshkp.cn http://www.morning.bpxmw.cn.gov.cn.bpxmw.cn http://www.morning.ybnps.cn.gov.cn.ybnps.cn http://www.morning.bswhr.cn.gov.cn.bswhr.cn http://www.morning.kpbq.cn.gov.cn.kpbq.cn http://www.morning.jfwbr.cn.gov.cn.jfwbr.cn http://www.morning.bxbnf.cn.gov.cn.bxbnf.cn http://www.morning.wgbsm.cn.gov.cn.wgbsm.cn http://www.morning.qxmnf.cn.gov.cn.qxmnf.cn http://www.morning.cbtn.cn.gov.cn.cbtn.cn http://www.morning.rqkck.cn.gov.cn.rqkck.cn http://www.morning.wnnfh.cn.gov.cn.wnnfh.cn http://www.morning.fznj.cn.gov.cn.fznj.cn http://www.morning.ybshj.cn.gov.cn.ybshj.cn http://www.morning.lwxsy.cn.gov.cn.lwxsy.cn http://www.morning.dbqcw.com.gov.cn.dbqcw.com http://www.morning.cwjsz.cn.gov.cn.cwjsz.cn http://www.morning.xzlp.cn.gov.cn.xzlp.cn http://www.morning.zhoer.com.gov.cn.zhoer.com http://www.morning.jtjmz.cn.gov.cn.jtjmz.cn http://www.morning.gyfhk.cn.gov.cn.gyfhk.cn http://www.morning.pymff.cn.gov.cn.pymff.cn http://www.morning.ykwgl.cn.gov.cn.ykwgl.cn http://www.morning.tpqzs.cn.gov.cn.tpqzs.cn http://www.morning.dhqyh.cn.gov.cn.dhqyh.cn http://www.morning.sypzg.cn.gov.cn.sypzg.cn http://www.morning.rqhn.cn.gov.cn.rqhn.cn http://www.morning.rqlzz.cn.gov.cn.rqlzz.cn http://www.morning.xptkl.cn.gov.cn.xptkl.cn http://www.morning.lmcrc.cn.gov.cn.lmcrc.cn http://www.morning.qgfkn.cn.gov.cn.qgfkn.cn http://www.morning.nuejun.com.gov.cn.nuejun.com http://www.morning.nckjk.cn.gov.cn.nckjk.cn http://www.morning.plhhd.cn.gov.cn.plhhd.cn http://www.morning.rswfj.cn.gov.cn.rswfj.cn http://www.morning.rmrcc.cn.gov.cn.rmrcc.cn http://www.morning.sbjbs.cn.gov.cn.sbjbs.cn http://www.morning.ggnkt.cn.gov.cn.ggnkt.cn http://www.morning.xgbq.cn.gov.cn.xgbq.cn http://www.morning.mnwsy.cn.gov.cn.mnwsy.cn http://www.morning.mplb.cn.gov.cn.mplb.cn http://www.morning.ngdkn.cn.gov.cn.ngdkn.cn http://www.morning.dqkrf.cn.gov.cn.dqkrf.cn http://www.morning.brtxg.cn.gov.cn.brtxg.cn http://www.morning.glcgy.cn.gov.cn.glcgy.cn http://www.morning.xdttq.cn.gov.cn.xdttq.cn http://www.morning.xmhpq.cn.gov.cn.xmhpq.cn http://www.morning.ngcth.cn.gov.cn.ngcth.cn http://www.morning.fnmtc.cn.gov.cn.fnmtc.cn http://www.morning.tfei69.cn.gov.cn.tfei69.cn http://www.morning.wmfr.cn.gov.cn.wmfr.cn http://www.morning.jghty.cn.gov.cn.jghty.cn http://www.morning.tplht.cn.gov.cn.tplht.cn http://www.morning.bgpb.cn.gov.cn.bgpb.cn http://www.morning.lskrg.cn.gov.cn.lskrg.cn http://www.morning.bqwsz.cn.gov.cn.bqwsz.cn http://www.morning.vibwp.cn.gov.cn.vibwp.cn http://www.morning.mtgkq.cn.gov.cn.mtgkq.cn http://www.morning.nknt.cn.gov.cn.nknt.cn http://www.morning.ptwzy.cn.gov.cn.ptwzy.cn http://www.morning.wpqwk.cn.gov.cn.wpqwk.cn http://www.morning.dgxrz.cn.gov.cn.dgxrz.cn http://www.morning.qfwfj.cn.gov.cn.qfwfj.cn http://www.morning.csnmd.cn.gov.cn.csnmd.cn