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

🎁个人主页:我们的五年****

🔍系列专栏:Linux课程学习****

🌷追光的人,终会万丈光芒

🎉欢迎大家点赞👍评论📝收藏⭐文章

Linux学习笔记:

https://blog.csdn.net/djdjiejsn/category_12669243.html

前言:

之前听过一遍课,但是如今还对它很模糊,不知道它到底是如何进行的。所以今天重新听课,重新学习。进程地址空间话题很大,这次也只能讲很少一部分,能对进程地址空间的宏观了解就OK。

目录

一.小实验(不是物理地址,而是虚拟地址/线性地址)

1.1实现目的和预想:

1.2修改gval的值继续进行实验:

二.虚拟地址是什么?

2.1小故事(画饼):

2.2理解虚拟地址空间:

2.3mm_struct结构体

2.4内存编址

三.页表

3.1代码共享:

3.2页表和物理地址的映射

3.3页表中的标志位

3.3.1rwx权限:

3.3.2isexit标记位:

四.为什么要有虚拟地址+页表

4.1安全和隐私保护:

4.2进程管理和文件管理解耦合:

4.3有效利用内存资源:

4.4让进程以统一的视角看待物理内存:


一.小实验(不是物理地址,而是虚拟地址/线性地址)

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一样还是相同的数据放在一起。

无序变有序。

相关推荐
悄悄敲敲敲1 分钟前
Linux:基础开发工具
linux·运维·服务器
YRr YRr4 分钟前
深入理解ROS中的参数服务器及其应用
运维·服务器·ros
龙的爹23335 分钟前
2024论文翻译 | Multi-Review Fusion-in-Context
人工智能·深度学习·自然语言处理·prompt
红茶要加冰9 分钟前
一、docker简介
运维·docker·容器
Industio_触觉智能11 分钟前
瑞芯微方案主板Linux修改系统串口波特率教程,触觉智能RK3562开发板演示
linux·开发板·串口调试·rk3562·波特率
万能菜道人17 分钟前
window下docker使用一些多媒体应用
运维·docker·容器
梁小憨憨19 分钟前
变分推断(Variational Inference)
人工智能·算法·机器学习
wxgnolux36 分钟前
ssh 登入报错问题解决过程记录
运维·ssh
橘子真甜~40 分钟前
Linux操作系统3-文件与IO操作1(从C语言IO操作到系统调用)
linux·运维·服务器·c语言·io·文件操作·文件fd
资讯分享周43 分钟前
思特奇亮相2024数字科技生态大会,以“智”谋新共赢AI新时代
人工智能·科技