网站建设v,北京专业seo,wordpress被百度收录,wordpress 数据库端口目录 1 代码感受 2 进程地址空间
3 扩展 1 代码感受
在正式讲程序地址空间前我们先来看一段简单的代码来分析分析#xff1a; 1 #includeiostream2 #includeunistd.h3 using namespace std;4 5 int g_val100;6 7 int main()8 {9 pid_t idfork();10 if(i…目录 1 代码感受 2 进程地址空间
3 扩展 1 代码感受
在正式讲程序地址空间前我们先来看一段简单的代码来分析分析 1 #includeiostream2 #includeunistd.h3 using namespace std;4 5 int g_val100;6 7 int main()8 {9 pid_t idfork();10 if(id0)11 {12 //child13 while(true)14 {15 cout我是一个子进程我的pid是getpid()我的ppid是getppid()g_val:g_valg_val:g_valendl;16 g_val200;17 sleep(1);18 }19 }20 else21 {22 //parent 23 while(true)24 {25 cout我是一个父进程我的pid是getpid()我的ppid是getppid()g_val:g_valg_val:g_valendl;26 sleep(1);27 }28 }29 return 0;30 }31 大家可以自己先分析一下结果。
我们来运行一下结果 大家看前面几行可能就会立马发现问题我们定义的g_val是全局变量当子进程修改g_val的值时我们发现父进程的g_val是不受影响的那么说明父子进程所用的g_val并不是同一个变量这个很好理解之前的我们说过父子进程是相互独立的互相不受干扰的但是问题出现在最后一列我们惊奇的发现居然父子进程的g_val变量的地址居然是相同的前面不是说父子进程的g_val不是同一个变量吗这里为啥打印出来的地址会是相同的呢
这里就说明我们打印出来的地址并不是真正的物理地址我们语言层面打印出的地址叫做虚拟地址或者线性地址。我们在用C/C语言所看到的地址全部都是虚拟地址而物理地址用户一概看不到由OS统一管理 。OS必须负责将虚拟地址转化成物理地址 。 2 进程地址空间
首先我们来讲一个故事从前有一个企业家很有钱他的家产大概有一亿美金左右的样子。他有4个私生子并且这四个私生子互相并不知道对方的存在。第一个私生子是个学霸在国内顶尖学校上学这个富豪便对他说你要好好读书将来我这一亿美金全部都是你的第二个私生子是一个三线演员富豪便对他说我帮你打开你红的渠道你不要辜负了我对你的期望好好努力将来我这一亿美金都是你的第3个私生子是个女儿当的是小学老师富豪便对他说你也不用太过努力工作我就你这一个女儿等我老了这一亿美金就是你的了第四个私生子是一个初中的小混混富豪对他说你只要好好听我的话这一亿美金就是你的了。
富豪给每个私生子都做出了承诺要将一亿美金给他们但是实际富豪并没有那么多的钱给每个私生子一亿美金而这一亿美金就是富豪给私生子们画的一张大饼但是它的私生子们却信以为真。 那这个故事与我们讲的知识有什么关系呢其实操作系统就是那个富豪私生子们就是一个一个的进程而那一亿美金就是进程地址空间。
PS:我们在生活中要尽量少画饼。 操作系统给进程画了一张大饼操作系统的资源是有限的所以他就得要好好的把这张饼给管理起来不让这些进程乱来而如何管理呢
那就要先描述再组织Linux中用的是一种叫做mm_struct的内核数据结构来管理的。
我们来用一张图带大家来看看程序地址空间 这张图相信大家多多少少也不会陌生在C语言的学习中我们也见到了很多次。
那么程序地址空间如何编码的呢32位的平台下虚拟地址空间大概是4GB)
ps:下面图每个小空格代表着一个字节。 所以从这里我们也不难看出为啥虚拟地址也叫做线性地址。那么我们究竟是如何管理虚拟地址空间的每个区的呢
我们可以用下面这种方式来描述管理
struct mm_struct
{
long code _start;
long code _end;
long init _start;
long init _end;
…………
long brk _start;
long brk _end;
long stack _start;
long stack _end;
} 而_start和_end限定的区域就是叫做虚拟地址线性地址
那么问题来了既然上面我们讲了那么多虚拟地址真正的物理地址又在哪里呢
我们画一个图方便大家理解 通过这张图大家并不难发现我们在语言层面上的地址是地址空间的虚拟地址而虚拟地址要与物理地址建立映射就需要一张页表页表的工作原理我们将放到后面来讲。
我们在学习C语言时大家在书上看到这样的一句代码const char* strhello world;
这时书上会告诉大家这句str指向的内容是只读的不可修改的但是这时为什么呢这时我们就可以自己来分析分析str指向的内容是在常量字符区当常量字符区通过页表与物理地址建立映射时在页表中就将该数据设置为只读当我们后续有修改操作时就会直接报错。
有了上面的基础我们就可以来解释解释为啥开头我们的g_val是同一个地址但是指向的内容却不相同的问题了 当不修改数据时就不会发生写时拷贝父子进程指向的是同一块物理空间(为了节约资源)当要修改数据时就会发生写时拷贝父子进程指向的是不同的物理空间但是虚拟地址空间是相等的。
我们再来回答为啥fork会有两个返回值的问题就很容易了就是因为父子进程的返回值是不同的所以肯定会发生写时拷贝将不同的返回值用相同的虚拟地址来进行返回虽然虚拟地址是相同的但是他们通过页表建立映射的关系却是不一样的。
到目前为止程序地址空间的基本内容已经ok,接下来给出一些扩展。 3 扩展
首先引出一个问题假如没有程序地址空间OS是如何工作的
我们知道如果没有了地址空间那么cpu将直接跟物理地址打交道这样做的后果是什么
我们不难知道假如cpu直接跟物理地址打交道的话那么当我们从cpu中读到非法地址时那就坏了通过非法地址将我们程序中其他变量的值给修改了那不就扯淡了吗。所以我们要通过一层屏障来保护数据而这一层保护就是通过程序地址空间来进行的当我们访问的数据非法时通过页表的映射就会拒绝你的非法操作。
所以我们得出了程序地址空间的第一个好处防止地址随意访问保护物理内存和其他进程。
在向大家提出一个小问题当我们在堆上new空间时OS是立马就把空间给你还是等你需要的时候再给你
这个问题大家应该都能够答对与我们想得一样OS会在我们需要该空间的时候再去在堆上申请。 而页表暂时没有与物理内存建立映射关系称作页表中断当我们需要空间的时候再与·物理内存建立映射。大家从这张图看出来没有当我们通过页表建立映射时将进程管理与内存管理给解耦合了。我进程管理不需要关心你是怎样在内存上申请空间的内存管理也不需要关心进程是如何管理起来的这样下来维护成本就会变得更低维护效率会更加高效一些。
所以我们得出了程序地址空间的第二个好处将进程管理与内存管理进行解耦合。
再提出一个问题程序在被编译的时候没有被加载到内存那么程序内有没有地址呢
答案是有的。源代码再被编译的时候就是按照虚拟地址空间的方式将对应的代码和数据进行编制编译器也会遵守虚拟地址的规则。
当我们把程序加载到内存程序里保存的地址虚拟地址并不是程序本身在内存中的物理地址)就会被cpu读取,cpu通过虚拟地址找到对应的虚拟地址空间然后虚拟地址空间又通过页表映射到物理内存中这样就将程序的整个运转给联系起来了。
所以我们得出了程序地址空间的第三个好处可以让进程以统一的视角看待自己的代码和数据。