在讲述本篇文章之前,我们先来看一段代码。
从上图输出可以看到,我们的子进程继承了父进程的全局变量val,当子进程中的val产生了修改时,父进程的val值并没有变化,但父子进程在打印val的地址时,会发现val的地址是一样的。但这不就冲突了吗,我们知道地址是唯一的,一段相同的地址可以同时打印不同的值,这并不符合我们之前理解的概念。
所有为了解释这个事情,小编将要在接下来通过进程地址空间概念来解释这件事情
进程地址空间概念:
进程地址空间(Process Address Space)是指操作系统为每个进程分配的虚拟内存区域,它定义了进程可以访问的内存范围。每个进程都认为自己独占整个内存空间,但实际上,操作系统通过虚拟内存管理将物理内存和磁盘空间结合起来,为每个进程提供独立的、受保护的地址空间。
让我们用图来认识一下进程地址空间
从上图可以看到,进程地址空间里包含着好几个区,地址的增长由正文代码区的低地址向上增长。其中栈区是从高地址向低地址增长,堆区正好相反。
页表:
一个程序想要运行首先需要载入到内存,而程序载入的内存我们称之为物理内存(物理地址)。
进程地址空间其实也称为虚拟地址。所以我们看到的进程地址空间不是物理内存。
所以,在操作系统中,我们有两种地址,一种为物理地址,一种为虚拟地址。而页表是什么呢?
从上图观察不难得出结论,页表其实是一种映射关系。用于物理地址与虚拟地址的相互映射。
现在我们重新回顾遗留的问题:一段相同的地址可以同时打印不同的值。
这里小编需要先阐述两个结论
- 一个进程,一个虚拟地址空间
- 一个进程,一套页表
当我们创建一个子进程时,子进程会从父进程那里“继承”代码和数据。这就好比父进程是一张原始的文档,子进程是这张文档的复印件。虽然看起来内容一样,但它们是独立的,子进程可以随意修改自己的“复印件”,而不会影响父进程的“原始文档”。
不过,这里有个关键点:子进程并不是直接复制父进程的所有数据,而是共享了父进程的代码和数据。这就像两个人住在同一栋楼里,虽然他们的房间看起来一样,但实际上每个人都有自己独立的门禁卡。这里的“门禁卡”就是“页表”,它决定了每个进程能看到哪些数据。
当子进程试图修改数据时,操作系统会触发一个机制,叫“写时拷贝”这个机制的工作原理是:如果子进程想修改某段数据,操作系统会先在内存中找一块新的空地,把需要修改的数据复制到这块新地方,然后更新子进程的“门禁卡”(页表),让它指向这个新的地址。这样,子进程的修改就不会影响到父进程的原始数据。
小结:
通过上述的例子论证,我们可以得出进程具有独立性的结论,首先是PCB独立,再其次是加载进入内存的代码和数据独立,不受其他进程影响。
虚拟地址与进程地址空间:
为了更好理解虚拟地址与进程地址空间,将举一个例子。
有个大富翁,大富翁有三个私生子,三个私生子互相不知道彼此。此时大富翁对各自的私生子分别画大饼说,你们现在就好好干各自的事情,等我驾鹤西去的时候,我的财产全部给你。那私生子们一听,就更加有干劲,更加努力干自己的事情。
其中大富翁=操作系统,私生子=task_struct,大饼=虚拟内存。这个大饼就会让task_struct认为自己是独占物理内存。但是对于大饼,大饼同样也需要管理。怎么管理呢?答案是先描述再组织。
相信在以前学校上学的时候,如果小伙伴是跟异性同桌的话可能都有经历过,异性在桌子上画一条三八线,标识区域划分,你不能超,我也不能超。那么桌子就相当与地址空间,而桌子上的刻度就相当于地址,对桌子的划分就转化为对地址的划分。所以区域划分就只需要确认区域的开始和结束。
在内核中见见
上图是内核中虚拟地址空间的结构体,在Linux中被明明为mm_struct。其中可以看到mm_struct中记录着代码段,数据段,堆栈区的起始地址和结束地址。所以当某个区间的内存不够的时候,只需要让end++,我们将这种行为称之为调整区域
再探虚拟内存:
在操作系统的内存管理中,task_struct 是一个核心数据结构,用于描述一个进程的运行状态。task_struct 中包含一个指向 mm_struct 的指针 mm,mm_struct 是虚拟内存管理的核心结构,用于记录进程虚拟地址空间中各个区域的起始和结束位置。
当程序从磁盘加载到内存时,首先初始化的是 task_struct,随后为 mm_struct 分配空间并完成初始化。此时,磁盘上的代码和数据会被加载到物理内存中,并通过页表建立虚拟地址与物理地址的映射关系。
值得注意的是,程序的代码段和数据段通常是只读的,这意味着它们的内容在运行时不可修改。为了确保这一点,操作系统在内存映射时会通过页表记录每个内存区域的访问权限。页表中会存储每个虚拟地址对应的物理地址以及访问权限(如读、写、执行)。当进程访问内存时,操作系统会检查当前地址所在的区域,并根据页表中的权限记录来决定是否允许访问。如果访问权限不匹配,操作系统会触发异常,防止非法操作。
通过这种方式,操作系统不仅完成了虚拟地址到物理地址的映射,还确保了内存访问的安全性和正确性。这种机制是现代操作系统内存管理的重要组成部分,为程序的稳定运行提供了保障。
所以通过上述结论也能解释了野指针以及在字符常量区写入时,程序发生崩溃的问题。原因就是当在进行页表查询时,发现所需修改的地址权限不匹配,操作系统进行了拦截甚至杀死当前进程导致程序崩溃。
-----------------本片文章就到这里,感谢各位观看