进程地址空间详解

进程地址空间

文章目录

  • 进程地址空间
        • 验证地址空间的基本排布
        • 进程地址空间究竟是什么?
      • 地址空间和物理内存之间的关系
      • 为什么要存在地址空间?

image-20211128195336713

我们在学习C语言期间,经常可以提及到这些区域,有一个问题:这里的地址空间是内存吗?答案是这里的地址空间并不是内存。这里的地址空间是进程地址空间,下面我们就讲解进程地址空间。

这段空间中自下而上,地址是增长的,栈是向地址减小方向增长(栈是先使用高地址),而堆是向地址增长方向增长(堆是先使用低地址),堆栈之间的共享区,主要用来加载动态库。

接下来我们来说明四个问题:

  1. 验证地址空间的基本排布
  2. 进程地址空间究竟是什么?
  3. 为什么要存在地址空间?
  4. 地址空间和物理内存之间的关系

验证地址空间的基本排布

#include<stdio.h>
#include<stdlib.h>
int g_unval;//未初始化
int g_val = 100;//初始化
int main(int argc,char *argv[],char *env[])
{printf("code addr:           %p\n",main);//代码区起始地址const char* p = "hello bit";//p是指针变量(栈区),p指向字符常量h(字符常量区)printf("read only :          %p\n",p);printf("global val:          %p\n",&g_val);printf("global uninit val:   %p\n",&g_unval);char *q = (char *)malloc(10);printf("heap addr:           %p\n",q);printf("stack addr:          %p\n",&p);//p先定义,先入栈printf("stack addr:          %p\n",&q);printf("args addr            %p\n",argv[0]);//命令行参数printf("args addr            %p\n",argv[argc-1]);printf("env addr:            %p\n",env[0]);//环境变量return 0;
}

image-20211128202507130

我们可以看到代码区的地址是最小的,这里就验证了地址空间的基本排布:p和q都是定义在栈区的,p先定义,先入栈,可以看到p的地址大于q,说明了栈是先使用高地址再使用低地址。

这里我们首先得出两点结论:

1、进程地址空间不是内存

2、进程地址空间,会在进程的整个生命周期内一直存在,直到进程退出

这也就解释了全局变量为什么会一直存在,原因是未初始化数据,初始化数据,这些区域是一直存在的

进程地址空间究竟是什么?

下面我们通过一个代码来看一个现象,我们定义了一个全局变量,fork创建一个子进程,让父进程和子进程完成自己的任务,在子进程中定义count来计数,当子进程的打印任务进行到第五次时,让子进程将这个全局变量改成100:

#include<stdio.h>
#include<unistd.h>
int g_val = 0;
int main()
{printf("begin.....%d\n",g_val);pid_t id = fork();if(id==0){//childint count = 0;while(1){printf("child: pid: %d,ppid: %d, g_val:%d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);sleep(1);count++;if(count == 5){g_val = 100;}}}else if(id>0){//fatherwhile(1){printf("father: pod: %d,ppid: %d, g_val:%d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);sleep(1);}}else{//todo}return 0;
}

image-20211128204600905

代码共享,所以看到前五次打印的g_val的地址都是一样的,这我们不意外,等到了第六次时,我们发现父进程g_val依然是0,子进程的g_val变成了100,因为我们将它改了,这也不意外,因为前面说了,父子进程之间代码共享,而数据是各自私有一份的(写时拷贝),但是令人奇怪的是地址竟然是一样的!

如果我们看到的地址,是物理地址,这种情况可不可能呢?

这是绝对不可能的,如果可能,那么父子进程在同一个地址处读取数据怎么会是不同的值呢?所以,我们曾经所学到的所有的地址,绝对不是物理地址

其实这种地址本质就是一种虚拟地址!

任何我们学过的语言,里面的地址都绝对不会是物理地址,虚拟地址这种地址是由操作系统给我们提供的,操作系统如何给我们提供呢?既然这种地址是虚拟地址,那么一定有某种途径将虚拟地址转化为物理地址,因为数据和代码一定在物理内存上,因为冯诺依曼规定任何数据在启动时必须加载到物理内存,所以肯定需要将虚拟地址转化成物理地址,这里的转化工作由操作系统完成,所有的程序都必须运行起来,运行起来之后,该程序立即变成了进程,那么刚刚打印的虚拟地址大概率和进程有某种关系

我们在上学期间,经常可能会和同桌画三八线,比如一张课桌是100cm,我们用一把尺子来划分区域,女孩的区域是0,50,男孩的区域是50,100,那么我们再计算机当中怎么描述这个事情呢?我们可以这样定义:

struct area
{unsigned long start;unsigned long end;
};
struct area girl = {0,50};
struct area boy = {50,100};

此时我们就划分好了区域,这时,不管是男孩还是女孩,大脑里都有了这样的一个区域:

image-20211128210824535

当女孩觉得自己活动范围不够,想扩大自己的区域时,就可以调整自己认为的[start,end],划分三八线的过程,就是划分区域的过程,调整区域的过程,本质就是调整自己认为的[start,end]

其中我们将桌子认为是物理内存,男孩和女孩认为是每一个进程,而男孩和女孩本质上都认为自己有一把尺子(脑海里的尺子),这把尺子就是进程地址空间,男孩想放自己的书包、铅笔等物品时,男孩就在自己的进程地址空间再划分区域放自己的物品。

那么如何划分进程地址空间的区域呢?在Linux当中,进程地址空间本质上是一种数据结构,是多个区域的集合。

在Linux内核中,有这样一个结构体:struct mm_struct,在这个结构体去表示我们开始说的一个一个的区域呢?这样去表示:

struct mm_struct
{unsigned long code_start;//代码区unsigned long code_end;unsigned long init_start;//初始化区unsigned long init_end;unsigned long uninit_start;//未初始化区unsigned long uninit_end;unsigned long heap_start;//堆区unsigned long heap_end;unsigned long stack_start;//栈区unsigned long stack_end;//...等等
}

在上面的例子中,男孩脑海里有一把尺子,想着自己拥有桌子的一半,女孩脑海里也有一把尺子,想着自己也拥有桌子的一半,而此时我们改变了:男孩和女孩关系比较好,不进行什么划分三八线,男孩脑海里有一把尺子,想着自己拥有0-100cm的桌子,女孩脑海里有一把尺子,想着自己也拥有0-100cm的桌子,他们在放东西时,只要记住了尺子的刻度就可以了。

为了更深一步的理解进程地址空间,我们再来举一个例子:

比如有一个富豪,他拥有10个亿的身家,这个富豪有10个私生子,这10个私生子互相并不知道自己的存在,富豪对自己的每一个私生子都说孩子你好好学习,老爸现在有10个亿的家产,以后就全是你的,请问在这十个私生子的视觉来看,他们认为他们有多少的家产?当然是10亿,当每个私生子向这个富豪要钱时,只要能接受,富豪肯定都会给,不能接受,富豪可以直接拒绝,在这个例子中富豪给每个私生子脑海里建立了虚拟的10个亿,此时每个私生子都认为自己有10个亿,每个人要的钱都是不一样的。

在这个例子中:富豪称之为操作系统,私生子称之为进程,富豪给私生子画的10亿家产,当前私生子的地址空间,对比言之:操作系统默认会给每个进程构成一个地址空间的概念(32位下,地址空间是从000000…0000到FFFFFF…FFF)4GB的空间,每个进程都认为自己有4GB的空间,每个进程都可以向内存申请空间,只要能接受都会给你,不能接受操作系统会直接拒绝,但是并没有什么影响,进程依旧认为自己有4GB的空间。

再回到男孩和女孩的例子,我们的进程地址空间就相当于是那把尺子,而尺子是有刻度的,进程地址空间也是从全000的地址到全FFF的地址,可以在这上面进行区域划分:比如代码区:[code_start,code_end],比如代码区的地址区间是这个:[0x10000,0x20000],那么区间的每一个地址单位就称为虚拟地址

总结

进程地址空间本质是进程看待内存的方式,抽象出来的一个概念,内核:struct mm_struct,这样的每个进程,都认为自己独占系统内存资源(每个私生子都认为自己独占10亿家产),地址空间区域划分本质:将线性地址空间划分成为一个一个的area,[start,end]。虚拟地址本质,在[start,end]之间的各个地址叫做虚拟地址

地址空间和物理内存之间的关系

我们写了三个程序,将这三个程序运行起来,生成了可执行程序,此时系统存在三个进程,我们有三个task_struct结构体,那么对应的三个进程都有各自的进程地址空间mm_struct,这三个task_struct里面各自会有一个指针指向对应的进程地址空间,我们知道可执行程序运行起来需要将代码和数据加载到内存当中,那么是怎么加载到内存当中的呢?进程将自己的代码和数据首先放在虚拟地址空间的对应的区域,在这其中会有一种表结构,叫做页表,页表的核心工作就是完成虚拟地址到物理地址之间的映射,最终我们的可执行程序的代码和数据可以加载到物理内存的任意位置,因为最终只需要建立代码和数据与物理内存之间的映射关系,就可以通过虚拟地址找到物理内存的对应地址

不同进程的虚拟地址可以完全一样吗?答案是可以完全一样,因为每个进程都有各自的页表,每个进程都是独立的进行通过各自页表中虚拟地址和物理内存的映射关系去找代码和数据

image-20211130175546310

那么不同进程的虚拟地址在页表中映射的物理地址可能会重吗?答案是不会的,如果会重操作系统就挂掉了,有一种可能性会重,但这是我们可以刻意为之,比如创建子进程,让父子进程代码共享:

image-20211130190604080

总结

虚拟地址和物理空间之间是通过页表完成的映射关系

为什么要存在地址空间?

为什么进程不直接访问物理内存呢?这样不行吗?为什么要存在地址空间呢?

  • 保护物理内存不受到任何进程内地址的直接访问,在虚拟地址到物理地址的转化过程中方便进行合法性校验

在早些时候是没有地址空间的:

image-20211130191609309

如果进程直接访问物理内存,那么看到的地址就是物理地址,而语言中有指针,如果指针越界了,一个进程的指针指向了另一个进程的代码和数据,那么进程的独立性,便无法保证,因为物理内存暴露,其中就有可能有恶意程序直接通过物理地址,进行内存数据的篡改,如果里面的数据有账号密码就可以改密码,即使操作系统不让改,也可以读取。

后来就发展出来了虚拟地址空间,那么虚拟地址空间如何避免这样的问题呢?

由上面我们所了解的知识,一个进程有它的task_struct,有地址空间,有页表,页表当中有虚拟地址和物理内存的映射关系,有了页表的存在,虚拟地址到物理地址的一个转化,由操作系统来完成的,同时也可以帮系统进行合法性检测

我们在写代码的时候肯定了解过指针越界,我们知道地址空间有各个区域,那么指针越界一定会出现错误吗?

不一定,越界可能他还是在自己的合法区域。比如他本来指向的是栈区,越界后它依然指向栈区,编译器的检查机制认为这是合法的,当你指针本来指向数据区,结果指针后来指向了字符常量区,编译器就会根据mm_struct里面的start,end区间来判断你有没有越界,此时发现你越界了就会报错了,这是其中的一种检查,第二种检查为:页表因为将每个虚拟地址的区域映射到了物理内存,其实页表也有一种权限管理,当你对数据区进行映射时,数据区是可以读写的,相应的在页表中的映射关系中的权限就是可读可写,但是当你对代码区和字符常量区进行映射时,因为这两个区域是只读的,相应的在页表中的映射关系中的权限就是只读,如果你对这段区域进行了写,通过页表当中的权限管理,操作系统就直接就将这个进程干掉。

所以进程地址空间的存在也使得可以通过start和end以及页表的权限管理来判断指针是否合法访问

  • 将内存管理和进程管理进行解耦

操作系统有四种核心管理:

1、进程管理

2、内存管理

3、驱动管理

4、文件管理

这里我们主要讲的是进程管理和内存管理:

如果没有进程地址空间,进程直接访问物理内存,当进程退出时,内存管理需要尽快将该进程回收,在这个过程当中必须得保证内存管理得知道某个进程退出了,并且内存管理也得知道某个进程开始了,这样才能给他们及时的分配资源和回收资源,这就意味着内存管理和进程管理模块是强耦合的,也就是说内存管理和进程管理关系比较大,通过我们上面的理解,如果有了进程地址空间,当一个进程需要资源的时候,通过页表映射去要就可以了,内存管理就只需要知道哪些内存区域(配置)是无效的,哪些是有效的(被页表映射的就是有效的,没有被页表映射的就是无效的),当一个进程退出时,它的映射关系也就没了,此时没有了映射关系,物理内存这里就将该进程的数据设置为无效,所以第二个好处就是将内存管理和进程管理进行解耦,内存管理是怎么知道有效还是无效的呢?比如说在一块物理内存区域设置一个计数器count,当页表中有映射到这块区域时,count就++,当一个映射去掉时,就将count–,内存管理只需要检测这个count是不是0,如果为0,说明它是没人用的。

没有进程地址空间时,内存也可以和进程进行解耦,但是代码会设计的特别复杂,所以最终会有进程地址空间

内存管理是怎么将一些大型数据加载到物理内存的?

内存管理是通过延迟加载的方式加载到物理内存的,什么意思呢?比如说你有一个16GB的大型进程,内存管理首先会给你加载小一部分先供你使用,当你使用完时,会先将进程置为睡眠状态,然后再加载一部分,然后将进程再唤醒,进程再继续使用就可以了。对于用户来说,唯一感觉到的是我的游戏运行的慢了。

  • 让每个进程,以同样的方式(虚拟地址),看待代码和数据,明确程序运行的地址

    目标文件和可执行程序,本身就已经被划分成为了一个个的区域:

image-20211130202009002

磁盘上的可执行程序分区域的每个大小单位为4KB,每个这个大小的数据称为页帧,在物理内存中的每个大小单位也为4KB,每个这个大小的数据称为页框,那么为什么要分区域呢?因为方便生成可执行程序,在这之后其中有一个链接库的过程,如果可执行程序是乱的,那么这个链接过程非常困难,所以需要分好区域,由此进程地址空间才有了区域划分这样的概念,进程的地址空间连续化,也让顺序语句的执行成为了可能:当前语句的起始地址+当前代码的长度就等于下一条语句的地址。如果没有进程地址空间,因为物理内存空余的地方不一定是连续的空间,可能是零散的,那么将可执行程序的数据加载到内存当中时,那么这些数据就是零散的放在各个位置,而这些位置我们又是不确定的,此时很难找到代码和数据的位置了,进程地址空间的存在,进程地址空间又是进行区域划分的,通过页表的映射关系可以很好的找到物理内存,所以这也是存在地址空间的一个理由:让每个进程,以同样的方式(虚拟地址),明确程序运行的地址

最开始我们的四个问题已经全部讲解完,回到最初的那个问题:为什么父进程和子进程的数据不一样,这个我们不意外,因为数据是私有的,但是地址却也是相同的,这是什么原因呢?到达这里我想这个问题已经显而易见了:

image-20211130222152087

image-20211130222120405

此时g_val的虚拟地址没有变化,而子进程的g_val的虚拟地址对物理内存地址的映射已经发生了变化,指向的数据区的g_val已经变为了100。

进程和程序有什么区别??

提到进程需要知道这三个东西:task_struct,mm_struct,页表。进程是加载进内存的程序,由进程常见的数据结构(struct task_struct(控制块) && struct mm_struct(地址空间))和代码数据组成

运行队列、等待队列

task_struct中是包含了很多的进程链接信息的,本质是把进程PCB进行排队的过程

若干PCB在一个队列中进行等待使用CPU的资源,该对称为运行队列

若干PCB在一个队列中进行等待访问磁盘的资源,该对称为等待队列

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/73185.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【GlobalMapper精品教程】044:空间操作(1)——交集(Intersection)

GlobalMapper提供的空间分析(操作)的方法有:交集、并集、单并集、差异、对称差集、相交、重叠、接触、包含、等于、内部、分离等,本文主要讲述交集工具的使用。 文章目录 一、实验数据二、符号化设置三、交集运算四、结果展示1. 运行提示2. 空间查看3. 属性表查看五、心灵感…

PostgreSQL的表空间

PostgreSQL之表空间 1、什么是PG中的表空间&#xff08;tablespace&#xff09;&#xff1f; pg中的表空间实际上就是文件系统中的一个目录, 是pg中数据库对象&#xff08;包括表、索引等&#xff09;的容器。 Pg使用操作系统的文件系统进行对象存储&#xff0c;每个数据库对…

Oracle表空间、用户详解

目录 新建连接三者关系表空间创建表空间修改表空间和数据文件修改数据文件容量新增表空间的数据文件重命名数据文件修改表空间状态修改数据文件状态 删除表空间查询 用户创建删除查询修改 新建连接 工具选择&#xff1a; 我们一般会选择一个工具来连接本地的Oracle&#xff0c…

49-Linux_malloc及4G虚拟空间分布

文章目录 malloc及4G虚拟空间分布1.malloc2.32位操作系统进程的4G虚拟空间分布1) 代码区(Text egment)&#xff1a;2) 全局初始化数据区/静态数据区(Data Segment)3) 未初始化数据区(BSS)&#xff1a;4) 堆区(heap)&#xff1a;5) 栈区(stack)&#xff1a; malloc及4G虚拟空间分…

(微信开发)Laya转发H5网页到微信,带图片

网页转发到微信时&#xff0c;带图片和自定义标题。2022年11月22号 关键解说 _wx.config({ debug: _wx_configdebug, appId: e.appId, timestamp: e.timestamp, nonceStr: e.nonceStr, signature: e.signature, jsApiList: [ // 所有要调用的 API 都要加到这个列表中 ‘onMen…

移动端H5页面开发的几种方法

单位&#xff1a; px 宽度百分比&#xff0c;高度px 在不同屏幕下的效果是不一样的&#xff0c;可用媒体查询&#xff08;media&#xff09;对不同宽度的手机进行适配&#xff0c;麻烦&#xff0c;不推荐使用 单位&#xff1a;rem 1、flexible 用法&#xff1a;https://blog.c…

H5页面调用微信支付

1、H5页面使用微信支付&#xff0c;首先需要注册微信公众号&#xff0c;在设置与开发>公众号设置>功能设置中配置业务域名、JS接口安全域名、网页授权域名。支付功能页面需在此域名链接下的页面。 2、加入域名后&#xff0c;就可以在H5页面调用微信支付功能。首先请求后台…

H5实现移动端,PC端页面分享

想做个页面分享功能&#xff0c;既要兼容移动端&#xff0c;也要兼容PC端。 找了一轮&#xff0c;决定PC端用百度分享的api&#xff1a; http://share.baidu.com/code/advance 移动端一开始想用soshm.js&#xff0c;发现ios没问题&#xff0c;但安卓手机在微信上点微博完全没…

app端内h5页面使用微信h5支付

app端内h5页面使用微信h5支付 app端内h5页面使用微信h5支付一、起初使用的方法二、解决方法三、最后 app端内h5页面使用微信h5支付 一、起初使用的方法 在对接支付宝支付的时候&#xff0c;我是使用window.open()直接打开后端返回的链接&#xff0c;完全没有任何问题&#xf…

H5页面内嵌到微信小程序和APP,做分享操作

前言 最近接到项目新需求&#xff0c;H5项目需要内嵌到微信小程序和APP里&#xff0c;然后将H5页面分享出去&#xff0c;被分享的人可以点击消息跳转到H5页面。H5页面不难&#xff0c;难的是要与微信小程序和APP进行交互&#xff0c;因为以前也没有接触过&#xff0c;所以这里…

H5及H5页面是什么意思?如何制作H5页面?

H5是HTML5的简称。HTML5是HTML最新的修订版本&#xff0c;是一种超文本标记语言。H5有两大特点&#xff1a;首先&#xff0c;强化了 Web 网页的表现性能。其次&#xff0c;追加了本地数据库等 Web 应用的功能。 H5页面就是利用html5制作出来的页面&#xff0c;尤其在微信中发展…

不懂技术,如何轻松制作微信H5页面?

H5这个由HTML5简化而来的词汇&#xff0c;正通过微信广泛传播。H5是集文字、图片、音乐、视频、链接等多种形式的展示页面&#xff0c;丰富的控件、灵活的动画特效、强大的交互、实现信息传播&#xff0c;非常适合通过手机的展示、分享。也因其灵活性高、开发成本低、制作周期短…

Java基础语法练习题

2023.2.18刷题 1、java的4类流程控制语句 解析&#xff1a; java的4类流程控制语句 循环语句&#xff1a;while&#xff0c;for&#xff0c;do while 选择语句&#xff08;分支语句&#xff09;&#xff1a;if,switch 跳转语句&#xff1a;break,continue,break,label 异常处理…

java里在做TCP通讯的时候,一直报java.net.ConnectException: Connection timed out: connect的错误?

可能的你的ip地址发生了变化&#xff1a; 1.在cmd里面输入&#xff1a; ipconfig 2.找到ipv4地址 3.把客户端的Socket S new Socket("192.168.31.65",10002);的192.168.31.65改成现在的即可。

websock报错:The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for caller

网上看到了一些关于这个错误的产生场景 参考&#xff1a;场景&#xff1a;使用websocket遇到的一个小问题 The remote endpoint was in state [TEXT_PARTIAL_WRITING] which is an invalid stat 我这里产生错误的场景是不同的&#xff0c;记录一下 背景 提供websocket服务的…

神经元模型简单制作方法,神经元的简单模型图解

神经元结构图示 &#xff08;1&#xff09;由图一可知&#xff0c;图一结构中涉及到3个神经元&#xff0c;含有2个突触&#xff0c;其中A是轴突&#xff08;神经纤维&#xff09;&#xff0c;B是树突&#xff0e; &#xff08;2&#xff09;图二中①是感受器、②是传入神经、③…

HH神经元模型

1、HH神经元的电路图。 电池表示特定离子的平衡电势&#xff0c;电阻器反映通道对特定离子的渗透性。 电容代表的就是细胞膜&#xff0c;存储电荷&#xff0c;起到了电容的作用。 在这个公式中IL代表的是泄露电流&#xff0c;图中它的电路中就是一个电阻R和一个电源的分路&…

神经网络输出层多个神经元

由于matlab升级&#xff0c;已经不能想以前一样直接在newff函数里面规定好隐含层多少个&#xff0c;或者几层&#xff0c;还可以有多个神经元的输出层。改版后需要输出层变成矩阵的形式&#xff0c;神经网络工具箱可以自动识别输出层有几个神经元。详细代码如何下&#xff1a; …

训练神经网络用什么显卡,cpu可以训练神经网络吗

gpu构架为什么更适合发展神经网络 因为神经网络这种大范围多任务的简单运算来说&#xff0c;正好符合GPU这种多核架构&#xff0c;比如你CPU20核心&#xff0c;同时处理20个任务。但是神经网络可能有20000个任务&#xff08;比喻&#xff09;。 但最近比较有代表性的GPU-Nvid…

单个神经元

先来看一下单个神经元网络模型&#xff1a; 其中 xi 表示输入&#xff0c;wi 和 b 表示参数。图中下方的公式是 1 函数的模型&#xff0c;嗯&#xff0c;就是一个线性模型。那么就这么一个简单的线性模型怎么仿真出人类神经元那么复杂的玩意儿呢&#xff1f;一个线性模型当然满…