【Linux课程学习】: 进程地址空间,小故事理解虚拟地址,野指针

举报
我们的五年 发表于 2024/12/06 16:07:08 2024/12/06
【摘要】 一.小实验(不是物理地址,而是虚拟地址/线性地址)Linux大哥,你别骗我,我之前一直给我的时物理地址,没想到你给我一个虚拟的地址,我真的看透你了。线性路上的一个下BUG刚刚在进行运行代码的时候,运行结果没有输出,一直卡在那,我以为是出现什么错误了。结果是我没有换行,因为我创建了子进程,这时候的显示器文件缓冲区采取的刷新模式可能是满刷新,当缓冲区满的时候,才会进行刷新。所以一开始在显示器上没...

一.小实验(不是物理地址,而是虚拟地址/线性地址)
Linux大哥,你别骗我,我之前一直给我的时物理地址,没想到你给我一个虚拟的地址,我真的看透你了。线性

路上的一个下BUG

刚刚在进行运行代码的时候,运行结果没有输出,一直卡在那,我以为是出现什么错误了。结果是我没有换行,因为我创建了子进程,这时候的显示器文件缓冲区采取的刷新模式可能是满刷新,当缓冲区满的时候,才会进行刷新。所以一开始在显示器上没有看见东西。后面啪的一下出现很多内容。大概就是这个原因。啊哈哈哈哈哈。下面我又无知了,请原谅我。

1.1实现目的和预想:
因为之前不是说父进程和子进程的代码共享,数据会独立一份吗?现在我们创建一个子进程,打印同一个值的地址是不是一样的,按理来说,他们地址是不一样的,因为数据相互独立。但是真实的结果是如何呢?


看上面的图,我们居然发现他们的地址是一样,啊?这好像不对啊,但好像又可以理解。我们之前学C++的时候,比如字符串string的拷贝的时候,不会立马进行拷贝,不会立即复制字符串的内容,而是增加一个引用计数。这里好像也可以这样理解,如果你不对数据进行修改,那么我还是用原来的呗,你要修改的时候,我再给你申请空间,进行赋值,再让你修改呗!那么我们下面就把gval的值修改一下,看是不是地址会不一样。

好像结果不是这样的,不是要写时拷贝,是因为(Linux大哥)C语言给了我们一个虚拟的地址。

1.2修改gval的值继续进行实验:
我们每次循环让子进程的gval发生变化,他们的地址还是一样的。为什么会这样啊,这就真的不能理解的啊?上面我还能去想想可能是写时拷贝的原因,在这里写时拷贝也关不上啊!

#include<stdio.h>    
#include<sys/types.h>    
#include<unistd.h>    
    
int gval=100;    
    
int main()    
{    
    printf("我是一个进程,pid:%d , ppid:%d\n",getpid(),getppid());    
    pid_t id=fork();    
    if(id==0)    
    {    
        while(1)    
        {    
           printf("子进程,pid:%d,ppid:%d,gval:%d,&gval:%p\n",getpid(),getppid(),gval,&gval);    
            sleep(1);    
        }    
    }    
    else    
    {    
        while(1)    
        {    
           printf("父进程,pid:%d,ppid:%d,gval:%d,&gval:%p\n",getpid(),getppid(),gval,&gval);
            sleep(1);                                                                                                                                                                                              
        }    
    }
    return 0;    
    

 二.虚拟地址是什么?
这不是程序地址空间哈,这是进程地址空间。为了引入虚拟地址空间,必须把老师说的小故事在这里重复一遍。

2.1小故事(画饼):
在美国有一个大富翁,他很有钱,手上有10亿美金,并且他有很多孩子。有一天他的孩子A跑过来对他说:我想要100美元。大富翁听到以后,觉得没啥,我都有10亿美金了,这100美元能算啥,就把这100美元给了A孩子。但是还没道A孩子的手上,B孩子就冲过来把大富翁手上的100美元给抢走了,B孩子说这100美元是我先看到的,应该是我的。B孩子还没说完,C孩子就抢着说:B孩子都拿了100美元,那我要200美元。A孩子听到以后觉得很不公平,为什么最开始是我先找父亲要的钱,你们反倒比我要的多,为此A孩子说,那我最起码也要200美元。大富翁听到以后,非常头疼,不知道该怎么办?


在美国的另一边,也有一个大富翁,他很有钱,也有10亿美元,也有很多的孩子,但是这些孩子都是他的私生子,他们不知道彼此的存在,都以为大富翁只有我这一个孩子。有一天,大富翁跑到私生子A住的地方去看望A孩子,然后发现A孩子在一家上市公司上班,每天工作非常辛苦。大富翁对孩子A说,好样的!好好干,以后我10亿美金全是你的,孩子A听到以后,非常的开心,认为只要好好干,以后父亲的10亿美金全是我的。

后面大富翁又跑到B私生子那里,B孩子还在读大学,他是他们学习篮球队的主力 ,为了以后能打进NBA,每天训练也是非常的辛苦。大富翁看到以后,对B孩子说:好好打球,只要你好好打球,以后我的10亿美金也是你的。孩子B听到以后,也是非常的开心,觉得马上就能继承父亲的10亿美金了。

大富翁看望完B孩子以后,就去看望私生子C了,C孩子是女儿,现在在读高中,但是跳舞很厉害,她的梦想是去当模特。大富翁看到C孩子以后,也对C孩子说,只要你好好跳,以后我的10亿美金全部都是你的,此时C孩子很开心……当然,大富翁还有其他很多的私生子。

大富翁对每个私生子都是这么说的,每个私生子都以为自己可以拿到父亲的10亿美金,却不知道他们根本拿不到,只能拿到一部分。

以后每个孩子在找父亲要钱的时候,都会有一个潜台词,就是父亲有10亿,而且以后都是我的。

饼画多了,也需要管理。先描述,再组织。这个大富翁就是操作系统!这里的大饼就是程序地址空间!

2.2理解虚拟地址空间:
假如内存大小是4GB,一个进程过来申请了500MB,此时这个进程认为还有3.5GB可以用。但是另外一个进程也申请了500MB,这个进程也觉得它还有3.5GB可以申请,其实此时内存的真实大小只有3GB了。

2.3mm_struct结构体
理解区域划分的本质:

交代区域的开始和结束,就能进行区域划分。

mm_struct中就有每个地址区域的地址空间。有起始位置和结束位置。

PCB中,有一个mm_struct结果体指针,指向一个mm_struct结构体。

mm_struct是由谁来初始化的?信息来源是可执行程序,因为可执行程序被编译好,就有需要多大的空间等信息。可以不加载可执行程序的内容,但是这些信息必须被加载,这些属于PCB的范畴。

栈区是运行的时候,操作系统自己创建的,由操作系统决定。

物理内存延迟开辟,需要的时候,先给你虚拟地址,没有对应的物理地址,只有当真正使用的时候,才会填入物理地址形成映射。

2.4内存编址
最小内存寻址是计算机能访问的最小内存单元。内存是按字节来划分的,32位系统中地址总线通常是32了,能最大表示的地址范围是2的32次方的存储单位,2的10次方是1024,2的32次方就表示4GB大小。

用unsigned long就能表示地址的所以范围,这就是内存编码。


 三.页表
3.1代码共享:
当父进程创建子进程的时候,子进程会拿父进程的PCB进行初始化,mm_struct也相同,页表也相同。页表中的代码区指向同样的物理地址,所以形成了代码共享。父子映射道同样的内存代码区域。所以子进程也是看到fork以上的代码的,只是不会执行它。

3.2页表和物理地址的映射
页表也被子进程继承,所以最开始的虚拟地址和物理地址都是和父进程一样的,用的还是父进程的代码和数据。当时当子进程的数据修改的时候,在此之前,操作系统OS会先申请空间给子进程用,然后修改子进程中的页表物理地址,虚拟地址不变。所以父子进程的有不同的页表,向上返回的虚拟地址是一样的,但是通过不同的页表能映射到不同的物理空间上去。所以就能解释上面小实验了。

在C语言中,变量名就相当于地址。


3.3页表中的标志位
页表中会有很多的标记位,下面就来谈谈rwx标记位和isexit标记位。

3.3.1rwx权限:
rwx标记位可以设置权限,比如代码区,只能是只读的,当如果要写,页表不让你从虚拟地址映射到真实的物理地址了。还有可能直接杀死进程。在代码层面,如果去修改一个只读区,编译器是识别不出来的,这是在运行时,才会去发生映射,才会报错。所以为了让编译器能检查,就引入了const。

3.3.2isexit标记位:
1.分批加载。2.挂起等操作。

表示在页表中这个映射关系是否存在,是都能在内存中找到。因为有种种原因,数据不会一直占着内存,会被放回到磁盘中,内存的大小是有限的。所以这个标记就是说是否在内存中存在。当不存在的时候,如果要进行操作,就会重新加载。所以才能跑大型程序。

四.为什么要有虚拟地址+页表
野指针就是有一个虚拟地址,在进行映射的时候,发现找不到真实的物理地址,或者权限不对,就会发生错误。操作系统就可以控制该进程终止。

可执行程序的代码和数据可以加载到物理内存的任何地址处。有页表进行映射。

4.1安全和隐私保护:
虚拟地址的设定,可以避免程序直接访问物理内存。必须通过页表的映射才能知道物理内存。页表是操作系统来管理的。防止错误程序和恶意软件篡改内存数据,增加系统的稳定性。

4.2进程管理和文件管理解耦合:
进程在进行管理的时候,操作的是虚拟地址,不要去物理地址。这样就大大降低了程序开发的复杂度。

4.3有效利用内存资源:
让进程都以为独占内存空间,但是操作系统可以进行自由切换,没用的时候,就能从内存中放到磁盘中。

4.4让进程以统一的视角看待物理内存:
代码和数据可以加载到物理内存的任意位置,但是对于mm_struct一样还是相同的数据放在一起。

无序变有序。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。