Linux---进程地址空间
2026/9/7 3:06:39 网站建设 项目流程

程序地址空间

在之前的学习中,我们初步了解过内存分区相关知识:如当前函数内定义的局部变量存储于栈区,而通过malloc new动态申请的内存空间则分配在堆区。

接下来通过下面代码打印各个区域的地址结合程序地址空间再来理解一下。

现象及结论

1.栈区 堆区

堆区是向上增长的 ,即先定义的变量地址低,后定义的越来越大。

栈区是向下增长的 ,即先定义的变量地址高,后定义的越来越小。

不过图中显示的现象栈区也在越来越大,是因为变量的地址分配顺序取决于编译器的实现

而且可以发现栈区堆区地址相差很大,他们之间有一大段空的区域,这在之后其他学习在了解。

2.字符串是不能被修改的,因为其被放在字符常量区,而可以发现其地址和代码区地址非常相似,其实字符常量区就是在代码区,因为字符串被编译器编码的时候硬编码在代码区域,而代码区域是只读的,所以所以字符串是只读的

初始化的全局变量地址比未初始化的全局变量的地址低,在代码中是先定义未初始化的全局变量g_unval,后定义已初始化的全局变量g_val,但是g_val的地址却比g_unval低可以证明。

3.函数内的局部变量test被static修饰而变成了生命周期全局的,而其实static修饰的变量test就是被编到已初始化全局区的,只不过只能被具体函数访问而已。

那么程序地址空间是内存吗?

先说结论:程序地址空间不是内存,它也叫虚拟地址空间。

接下来可以证明一下该结论

如上的程序,父子进程都会有一个全局变量的gval=10子进程每一次只是打印一下gval的值,而父进程每一次在打印了gval后会把gval+=1

现象

但是程序运行之后发现:子进程gval一直是10没有改变,父进程的gval每次加一,根据之前的学习这一点是我们所理解的,因为子进程会继承父进程的数据而他们其中一方在对变量进行修改的时候会发生写时拷贝,但是他们两个的gval地址始终是一样的!

那么假设该地址是在内存上的,不是说明一个地址所指向的变量会有两个不同的值? 这也是在进程基础那里遗留的问题"一个地址为什么会有两个不同的值?"

而这是不可能的,所以这里的地址一定不是内存中真的物理地址

结论

该地址是虚拟地址,程序地址空间其实就是一张虚表,它里面的地址不是真实内存当中的物理地址!并且之前我们在c、c++的学习中所遇到的指针全部都是这样的虚拟地址。

所以之前说程序的地址空间是不准确的,准确上是进程地址空间,接下来具体了解。

进程地址空间

先说结论

一个进程会有一个虚拟地址空间,也就是一个task_struct会对应着一个虚拟地址空间。

所有的数据包括代码本身都有地址,而我们所接触看到的地址都是虚拟地址,在内存中有对应真实的物理地址,在内存当中还存在页表,它由操作系统管理,里面存着对应虚拟地址和物理地址映射的关系(并不是真的这样直接一一映射的,暂时这样理解),通过虚拟地址结合页表就能访问到真实的物理地址进而访问对应的数据。

例子

有一个数据val,它一定是需要被加载到内存当中的,那么它就会有一个真实的物理地址,而在程序地址空间中它还会存在一个虚拟地址,在页表中有着虚拟地址和物理地址的映射关系(哈希或者map来理解),这样虽然我们只有虚拟地址却可以通过页表找到真实地址进而访问数据,而里面的东西上层用户使用的时候不需要关心。

在上图程序地址空间的宽度为一个字节,在32位机器下有2^32个地址,那么空间大小就是4GB,下面3GB是用户空间的,而上面1GB是内核空间(这里不关心这一点)。

那么程序地址空间的宽度是一个字节,那么像val这样是一个int类型,它有四个字节,那么对应的地址需要有四个字节,但是在里面却只能存一个字节,怎么解决的?

里面存地址是所有地址最小的,然后根据类型知道偏移量就可以找到完整的数据了。

在通过上图理解了上面的结论之后我们就可以来理解为什么父子进程的gval值相同地址却不同的问题了。

父进程通过fork创建了一个子进程后,根据上面的结论,我们知道子进程也需要有一个虚拟地址空间并且有一张页表的,并且其继承自父进程会和父进程完全一模一样,此时父子进程中变量的虚拟地址和真实地址是完全一样的。如下图

此时子进程对val发生了修改,就会发生写时拷贝的现象内存中为子进程重新开一块空间来存修改后的val,然后这个修改后val的真实物理地址,替换子进程页表中原有的物理地址,

此时父子进程对于val这个变量的的虚拟地址是相同的,而物理地址是不同的。

实际上父进程通过这个虚拟地址映射的是父进程val的真实地址 ,子进程通过这个虚拟地址映射的是子进程val的真实地址,只不过他们的虚拟地址相同并且我们所看到的地址其实就是虚拟地址,所以父子进程的val地址打印的结果是完全相同的。 这就解释了代码的现象及在进程基础最后遗留的问题。

为什么要这样设计---画饼

本质上是画饼! 让每一个进程误认为自己占着所有的内存空间!

1.将物理地址的无序变为虚拟地址的有序

我们知道虚拟地址空间中空间的分配是有序的,而物理内存的频繁分配/释放会形成了大量的不连续的碎片化的空间,并且不同进程的一些资源的分配具有随机性,而采用这种方式就使得无序性的问题得到解决,我们上层用户只需要对当前进程有序的空间进行处理,操作系统会帮我们完成通过页表找到对应物理内存中数据的任务。

2.地址转换过程会进行进程合法性判断,保护物理内容。

在页表中除了虚拟地址和物理地址的映射之外还存在着检查权限等功能,例如当对代码进行修改的时候在页表中发现只有r权限没有w权限,就不能对内容进行修改 (权限拦截)或者当前虚拟地址并没有在页表中存在映射关系,其实这就是野指针的问题。

3.让进程管理和内存管理,进行一定程度的解耦合。

即关联程度降低了,例如在内存中数据移动了,此时物理地址发生了变化,那此时只需要把页表中对应的物理地址修改一下就好,相应的虚拟地址不需要改变,task_struct中的这样的数据就不需要改动。

饼画多了怎么办?

操作系统要给每一个进程都画饼,但是如果此时进程非常多,那此时操作系统需要记住给每一个进程所画的饼,要不然可能让进程发现只是再给自己画饼 ,也就是要把这些饼给管理起来。使用的方式同样是先描述,后组织。用一个结构体记录下所有画饼的内容,然后把所有的结构体对象用链表连接起来,此时操作系统对这些饼的管理转换为了对这个链表的增删查改。

所以虚拟地址空间本质上是一个数据结构!里面会记录着各种区域的起始和终止地址, 而地址实际上就是数字,所以实际上虚拟地址空间是mm_struct的对象,以mm_struct*类型存在于task_struct中,该结构体中存在各个数据段的起始和终止地址的unsigned long类型

所以当一个程序代码和数据加载到内存中之前,会先在虚拟地址空间中开需要的空间,然后加载程序申请物理空间,然后把虚拟地址和真实物理地址用页表连接起来。

通过这样的操作就将物理地址转换为虚拟地址,为了提供给上层的用户去使用

而mm_struct的初始化实际上就是在加载的时候进程初始化的。

缺页中断及概念澄清

缺页中断

例如此时有1GB的数据需要加载到内存当中,但因为数据太多了,不会一下把所有的内容都放到内存当中,只会放一部分,例如放了四分之一的内容到了内存当中,但是在虚拟地址空间当中却是真实的开辟了完整的1GB的数据,此时在页表当中有四分之一的内容会有对应的物理地址,而剩下的四分之三没有对应的物理地址,只有在访问这四分之三内容的时候,操作系统会首先知道,然后把需要的部分继续加载到内存当中,这样就节省了内存空间

概念澄清

1.可以不加载任何数据和代码到内存当中,到时候需要的时候缺页中断来解决。

2.创建进程,先有task_struct、mm_struct,后加载代码和数据。

3.重新理解挂起!

进程的挂起是在内存空间不足的时候操作系统所采取的一种措施,把该进程的代码和数据放到磁盘中的swap分区,而PCB内容保留,把内存省出来了使用的时候再重新加载。

把代码和数据放到swap分区后,页表当中相应的物理内存就为空了,而PCB内容保留也就是虚拟地址保证进程正常存在,这也就是画饼起到的减耦合起到的作用。

再理解mm_struct

上面的初步认识是下图这样,里面记录中各个区域的起始和终止地址,但是如果此时一个区域有多块的内容呢?例如多次的malloc或者new后,此时在堆区对每一块空间都应该记录下他们的起始和终止地址来分别对这多个堆区内容来做管理。

所以在mm_struct中除了有着各个区域的起始及终止地址外,对于每一个区域还存在着各自的vm_area_struct的指针,而在该结构体中用链表的方式存着第一个堆区的起始及终止地址,里面还存储着下一个堆区空间的指针。

总结

1.我们之前所见到的地址(指针)其实都是虚拟地址,其通过页表可以映射物理地址进而找到数据。

2.每一行代码每一个变量都要有自己的地址,在加载的时候会先在虚拟地址空间中开好空间,并不一定在内存当中直接全部开好,在使用时候通过缺页中断解决。

3.虚拟地址空间和物理地址存储之前耦合性很低,每一个进程都有自己的PCB、虚拟地址空间及页表,虚拟和物理之前通过页表连接。

进程=自己的内核数据结构+代码和数据,内核数据结构是独立的,代码和数据也是独立的,所以进程之间具有独立性 。

进程地址空间的第一阶段就学习完毕了,现有的知识只能了解这么多,在之后学习了更多的知识之后,我们还会多次对进程地址空间有重新的认识。并且进程的概念也结束了,接下来进入进程控制的学习。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询