【Linux】进程地址空间(虚拟地址vs物理地址vs页表)
Linux 进程概念补充【Linux】 进程是什么(不熟悉的兄弟可以看看)。
1. C/C++内存分布图
对于有c/c++基础的同学相信对上面的图片并不陌生,实际上其描述的并不是正真的物理内存,而是虚拟内存,我们把它叫做进程地址空间 。
2. 虚拟内存与物理内存的转换
1.在task_struct(PCB)有mm_struct字段描述了虚拟内存。(所以一个进程管理一个mm_struct保证了进程间的数据独立)
2.简单来说在虚拟内存与物理内存之中我们是用页表来建立映射关系的。
3. 虚拟内存的优点
1.将物理内存从无序变为有序,使进程以统一的视角来看待内存。
2.将进程管理与内存管理进行解耦。
3.虚拟内存+页表保证了安全性。例如程序访问野指针程序崩溃而操作系统没有崩溃。
4. 父子进程相关问题
我们知道父子进程代码共享。其中数据各自开辟空间,私有一份(采用写时拷贝)
代码实验:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int g_val = 100;
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 0;
//子进程
while(1)
{
printf("child, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
cnt++;
if(cnt == 5)
{
g_val = 200;
printf("child change g_val: 100->200\n");
}
}
}
else
{
while(1)
{
printf("father, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
sleep(1);
}
}
}
有了上面的理论基础我们不难理解,虽然父子进程代码共享但是每个进程都有自己独立的进程地址空间,所以虽然父子进程的g_val地址相同,但是这只是虚拟地址所以其真正的物理地址并不相同故打印出来的值不同。
下面再来解释一下写时拷贝:
我们把这种操作叫做写时拷贝。
写时拷贝的目的
节省内存:当一个进程创建子进程时,如果不使用写时拷贝,那么操作系统会为子进程复制父进程的所有内存页。这不仅增加了内存占用,还可能导致不必要的数据复制,特别是在子进程只是短暂存在或不会修改大部分数据的情况下。写时拷贝允许父子进程共享相同的物理内存页,直到任何一个进程尝试修改这些页。
快速创建进程:由于不需要立即复制所有的内存页,进程创建速度更快,尤其是在父进程有大量内存占用的情况下。
按需分配资源:只有当实际需要对某一页进行写入操作时,才会触发该页的复制,从而实现更有效的资源管理。
写时拷贝的优点
高效利用资源:通过避免不必要的内存页复制,操作系统可以更有效地利用可用的物理内存,这对于多任务处理环境尤为重要。
提升性能:对于那些只读取数据而不做修改的子进程,性能提升尤为明显,因为它们不需要等待内存页的复制完成就可以开始执行。
简化进程间通信:如果父子进程之间需要共享某些只读数据,写时拷贝机制允许它们直接共享这些数据而无需额外的通信机制。
降低上下文切换开销:由于减少了内存页的复制,也相应地减少了与内存管理相关的上下文切换次数,这有助于提高系统的整体性能。
支持非阻塞操作:子进程可以在不阻塞父进程的情况下启动,这对并行计算和并发编程非常重要