Linux 进程(二) 进程地址空间

上一节我们提到过父子进程的一个概念:父子进程代码共享,数据各自开辟空间。

因为子进程从父进程的PCB中拷贝了数据,所以它的代码、数据以及运行的位置,都与父进程一模一样。但是为什么这个代码是无法修改的?为什么又需要再各自开辟空间呢?Linux是如何实现权限控制以及空间映射的呢?

我们用这段代码进行试验

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int val = 0;

int main()
{
        pid_t id = fork();
        if(id == 0)
        {
                val = 100;
                //子进程
                printf("子进程pid:[%d]  val:[%d] val地址:[%p]\n", getpid(), val, &val);
        }
        else if(id > 0)
        {
                //父进程
                sleep(3);
                printf("父进程pid:[%d]  val:[%d] val地址:[%p]\n", getpid(), val, &val);

        }
        return 0;

}

我们利用一个全局变量val,看看修改子进程中的变量val,父进程会不会发生变化,他们的地址又是否相同。

因为子进程运行的位置和父进程一样,所以先让父进程睡眠一会,让子进程先修改
在这里插入图片描述
奇怪的事情发生了,明明子进程已经修改了val,但是父进程的却没变,同时明明父子进程中全局变量val的大小都不一样,发生了变化,但是他们的地址确还是一样的,这就有些不符合逻辑了,因为一个地址中不可能有两个同名的变量。

这里就让我们确定了一件事情,我们在代码中所看到的地址,并不是真正的地址。

这就引入了程序地址空间的概念。


进程地址空间

程序是不占用内存的,它只是一个没有生命的实体, 只有运行起来的程序(进程)才会被加载到内存中,这才会占用内存。

地址:地址就是对内存单元的编号,通过这个编号来访问数据。

从上面的例子我们发现,代码中看到地址并不是真正的内存地址,而是虚拟内存地址

为什么要创建这样一个虚拟的内存地址呢?

操作系统为了不让进程直接访问物理内存,通过mm_struct结构体来为进程描述了一个虚拟的,连续的,完整的地址空间(只有编号,无法存储),也就是我们所说的虚拟地址空间

为什么不让进程直接访问物理内存呢?

在这里插入图片描述
假设我们内存中有6m的空间,其中已经存入了3m,这时我们想再存入一个3m,但是问题来了,因为物理空间还剩下的3m是不连续,所以这时会再找一个连续的空间来存储这个3m。这样就造成了内存的大量浪费

还有这样一种情况。
当几个进程同时访问物理内存时,各进程的操作可能会产生冲突,可能会产生无法预料的后果。缺乏访问控制的内存是非常不安全的。


页表

操作系统再引入虚拟地址空间的时候还引入了一种东西,叫做页表

通过页表来映射虚拟地址和物理地址的关系。
在这里插入图片描述

  • 通过在虚拟地址来使数据进行连续的存储,然后再通过页表映射到物理内存上,来实现离散式的存储,提高了内存的利用率。

  • 同时页表可以针对某个地址设置访问权限,让某个地址设置为只读,通过这种方法来实现内存的访问控制。

  • 为了能够使进程具有独立性,彼此之间不会相互干预,每一个进程都会有它自己的页表和虚拟地址空间。


回到最开始的问题。

为什么父子进程的代码相同,且无法修改?
:因为通过页表将代码段的权限设置为只读,所以无法修改。

为什么父子进程数据各自开辟空间?
:其实父子进程一开始物理地址和虚拟地址都是相同的,但是当任意一个进程中数据发生变化的时候,这个时候操作系统会找到另外一块物理空间,将数据全部拷贝过去给发生修改的进程使用,并且修改原来的物理空间的权限,使原来的物理空间给另一个进程使用。

发布了60 篇原创文章 · 获赞 78 · 访问量 6322

猜你喜欢

转载自blog.csdn.net/qq_35423154/article/details/105294963