【Linux进行时】进程地址空间

简介: 我们在讲C语言的时候,老师给大家画过这样的空间布局图,但是我们对它不了解

进程地址空间

例子引入:

我们在讲C语言的时候,老师给大家画过这样的空间布局图,但是我们对它不了解

#include<stdio.h>
#include<assert.h>
#include<unistd.h>
int g_value=100;
int main()
{
  pid_t id=fork();
  assert(id>=0);
  if(id==0)
  {
   //child
   while(1)
  {
   printf("我是子进程,我的id是:%d,我的父进程是:%d,  g_value:%d,&g_value:%p\n",getpid(),getppid(),g_value,&g_value);
   sleep(1);
  }
  }                                                                                                                             
  else
  {
   //father
   while(1)
   {
   printf("我是父进程,我的id是:%d,我的父进程是:%d,g_value:%d,&g_value:%p\n",getpid(),getppid(),g_value,&g_value);
   sleep(2);
   }          
   }  
  return 0;
}          
• 1
• 2
• 3
• 4
• 5
• 6
• 7
• 8
• 9
• 10
• 11
• 12
• 13
• 14
• 15
• 16
• 17
• 18
• 19
• 20
• 21
• 22
• 23
• 24
• 25
• 26
• 27
• 28
• 29

这里没什么问题,就是他们的g_valule 和其地址都是一样的,

我们将代码调整一下,让子进程的g_value++

#include<stdio.h>
#include<assert.h>
#include<unistd.h>
int g_value=100;
int main()
{
  pid_t id=fork();
  assert(id>=0);
  if(id==0)
  {
   //child
   while(1)
  {
   printf("我是子进程,我的id是:%d,我的父进程是:%d,g_value:%d,&g_value:%p\n",getpid(),getppid(),g_value,&g_value);
   sleep(1);
   g_value++;//只有子进程会进行修改
  }
  }                                                                                                                             
  else
  {
   //father
   while(1)
   {
   printf("我是父进程,我的id是:%d,我的父进程是:%d,g_value:%d,&g_value:%p\n",getpid(),getppid(),g_value,&g_value);
   sleep(2);
   }          
   }  
  return 0;
}                                 
• 1
• 2
• 3
• 4
• 5
• 6
• 7
• 8
• 9
• 10
• 11
• 12
• 13
• 14
• 15
• 16
• 17
• 18
• 19
• 20
• 21
• 22
• 23
• 24
• 25
• 26
• 27
• 28
• 29

我们可以发现子进程的g_value变了,但是父进程没有变,两个的地址还是一样的

❓为什么他们两个地址相同但是读出来的数据不同呢?(下文会解答)

🔥子进程对全局数据修改,并不影响父进程!——进程具有独立性!

❓这个地址会是物理地址?💡不会

显然这个地址绝对不是物理地址!所以我们平常在语言层面用的地址,绝对不是物理地址,所以以前用的指针绝对不是地址,其实这个地址叫做虚拟地址or线性地址

故事引入:

香港某个老板非常滴有钱,有10亿美金,他有 4个私生子,每个私生子都并不知道对方的存在,他们都以为自己是独生子。因为他们彼此不知道对方的存在,所以他们在生活和工作上也没有交集,不会有任何互相的影响(这就是独立性的体现)。财阀老板为了维护自己的独立性:

他就对大儿子说:“儿子,你好好学习,以后老爹钱都是你的。”,大儿子一听卧槽真好,高枕无忧,就好好学习,一想到自己以后有钱,就更想学习了。

然后又对二儿子说:“儿子,好好工作,等以后我就把公司给你。”,二儿子一听热泪盈眶,于是就好好工作,等着将来有一天可以继承公司。

后来又对三儿子说:“儿子,你好好干活,等你长大老爹的家产交给你!”,三儿子知道自己以后会继承老爹的所有财产,开心坏了,就努力的干活。

后来又对四儿子说:“儿子,你好好干活,等你长大老爹的家产交给你!”,四儿子知道自己以后会继承老爹的所有财产,开心坏了,就努力的干活。

只要在财阀爹的可承受范围内,孩子要多少钱他都给多少钱,所以三个儿子自然都认为自己有很多钱。财阀老板给他的三个儿子画了一张虚拟的、不存在的大饼,让他们都能努力学习工作干活(这个步骤就是给他们分别建立了进程地址空间)。

画的饼:进程地址空间,10亿美金:内存,老板:操作系统,四个私生子是进程

❓大富翁,要不要把“饼”管理起来呢?

显然需要的,遵循先描述再组织的原则

所以,进程地址空间,就是就是给进程画的大饼

进程地址空间 → 逻辑上抽象的概念 → 让每个进程都认为自己独占系统的所有资源

**概念:**操作系统通过软件的方式,给进程提供一个软件视角,认为自己是独占系统的所有资源(内存)。

区域和页表:

什么叫做区域?我们来拿一张桌子来理解,初中的时候小花和小胖分过 “38线”

三八线的本质就是区域划分!

🔥地址空间本身就是一个线性区域,地址空间是线性结构的!

struct mm_struct {
    long code_start;
    long code_end;
    long init_start;
    long init_end;
    long uninit_start;
    long uninit_end;
    long heap_start;
    long heap_end;
    long stack_start;
    long stack_end;
    ...
}
• 1
• 2
• 3
• 4
• 5
• 6
• 7
• 8
• 9
• 10
• 11
• 12
• 13
• 14
• 15
• 16
• 17

如果限定了区域,那么区域之间的数据是什么?

是虚拟地址or线性地址

🔥程序加载到内存,由程序变成进程后,由操作系统给每个进程构建的一个页表结构,就是 页表

🔥数据和代码真正只能在内存中!

找到地址不是目的,而是手段

回到之前那个问题:

❓为什么他们两个地址相同但是读出来的数据不同呢?

💡如果子进程对数据进行了修改,因为进程具有独立性,子进程的修改不能影响父进程

子进程这里的 物理地址改了,但是虚拟地址没有改

写时拷贝发生在物理地址,虚拟地址没有变

因为进程具有独立性,比如如果此时子进程把变量改了(写入),就会导致父进程识别的问题就出现了父进程和子进程不一的情况,因为进程是具有独立性的,所以我们就要做到互不影响。我们的子进程要进行修改了,影响到父进程怎么办?没关系!操作系统会出手!当我们识别到子进程要修改时,操作系统会重新给子进程开辟一段空间,并且把 100 拷贝下来,重新给进程建立映射关系,所以子进程的页表就不再指向父进程所对应的 100 了,而直接指向新的 100。你在做修改时又把它的值从 100 改成 200 时,我们就出现了 “改的时候永远改的是页表的右侧,左侧不变” 的情况,所以最后你看到了父子进程的虚拟地址一样,但是经过页表映射到了不同的物理内存,所以了你看到了一个是 100 一个是 200,父子进程的数据不同的结果。

我们的操作系统当我们的父子对数据进行修改时,操作系统会给修改的一方重新开辟一块空间,并且把原始数据拷贝到新空间当中,这种行为就是 写时拷贝!

当父子有任何一个进程尝试修改对应变量时,有一个人想修改,就会触发写时拷贝,让他去拷贝新的物理内存,这只需要重新构建也表的映射关系,虚拟地址是不发生任何变化的,所以最终你看的结果是虚拟地址不变,而内容不同。

这个结构也体现了进程具有独立性

pid_t id=fork()
if(){}
else
{}
• 1
• 2
• 3
• 4

❓fork在返回的时候,父子都有,return两次,id是不是pid_T类型定义的变量呢?

💡是的,返回的本质就是写入!谁先返回,谁就让OS发生写时拷贝

如果是父进程就返回pid,如果是子进程就返回0

为什么进程地址空间要存在?

❓如果没有地址空间,我们OS是如何工作呢?

💡这里就是害怕野指针的情况,要寻找一个地址因为你的代码错误找到了一个越界地址时写入时会使别人的进程错了而且很不安全,因此有了页表和虚拟空间

🔥这两个存在的意义:1.防止地址随意访问,保护物理内存与其他进程

❓常量字符串不能修改,这是为什么呢?💡因为页表访问的时候是有权限的,权限不能修改

char*str=“hello world”;
*str=‘H’;
• 1
• 2

🔥先来将另外一个扩充:malloc的本质——

❓向OS申请内存,操作系统立马给你,还是说在你需要的时候给你?

💡1.在你需要的时候给你,OS一般不允许任何的浪费或者不高效

2.申请内存==立马使用呢?不一定等于立马使用

3.在你申请成功之后,和你使用之前就有一段小小的时间窗口,这个空间没有被正常使用,但是别人用不了—-闲置状态

🔥如果有500进程这样的话,这样操作系统就有大块的空间处于这种状态,这种情况叫做缺页中断

❓因为有页表,你关心不关心你申请的空间是在物理空间的哪一块呢?💡不关心,一样的


相关文章
|
17天前
|
安全 Linux 虚拟化
网络名称空间在Linux虚拟化技术中的位置
网络名称空间(Network Namespaces)是Linux内核特性之一,提供了隔离网络环境的能力,使得每个网络名称空间都拥有独立的网络设备、IP地址、路由表、端口号范围以及iptables规则等。这一特性在Linux虚拟化技术中占据了核心位置🌟,它不仅为构建轻量级虚拟化解决方案(如容器📦)提供了基础支持,也在传统的虚拟机技术中发挥作用,实现资源隔离和网络虚拟化。
网络名称空间在Linux虚拟化技术中的位置
|
17天前
|
网络协议 安全 Linux
Linux网络名称空间之独立网络资源管理
Linux网络名称空间是一种强大的虚拟化技术🛠️,它允许用户创建隔离的网络环境🌐,每个环境拥有独立的网络资源和配置。这项技术对于云计算☁️、容器化应用📦和网络安全🔒等领域至关重要。本文将详细介绍在Linux网络名称空间中可以拥有的独立网络资源,并指出应用开发人员在使用时应注意的重点。
|
17天前
|
安全 网络协议 Linux
Linux网络名称空间概述
Linux网络名称空间是操作系统级别的一种虚拟化技术🔄,它允许创建隔离的网络环境🌐,使得每个环境拥有自己独立的网络资源,如IP地址📍、路由表🗺️、防火墙规则🔥等。这种技术是Linux内核功能的一部分,为不同的用户空间进程提供了一种创建和使用独立网络协议栈的方式。本文旨在全方面、多维度解释Linux网络名称空间的概念、必要性和作用。
Linux网络名称空间概述
|
3天前
|
NoSQL Linux 程序员
【linux进程信号(一)】信号的概念以及产生信号的方式
【linux进程信号(一)】信号的概念以及产生信号的方式
|
3天前
|
Linux
【linux进程间通信(一)】匿名管道和命名管道
【linux进程间通信(一)】匿名管道和命名管道
|
3天前
|
Java Shell Linux
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
|
3天前
|
算法 Linux Shell
【linux进程(二)】如何创建子进程?--fork函数深度剖析
【linux进程(二)】如何创建子进程?--fork函数深度剖析
|
3天前
|
存储 Linux Shell
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
|
4天前
|
消息中间件 Unix Linux
Linux的学习之路:17、进程间通信(1)
Linux的学习之路:17、进程间通信(1)
20 1
|
4天前
|
存储 安全 Linux
Linux的学习之路:9、冯诺依曼与进程(1)
Linux的学习之路:9、冯诺依曼与进程(1)
18 0