【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进程这样的话,这样操作系统就有大块的空间处于这种状态,这种情况叫做缺页中断

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


相关文章
|
7天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
31 4
linux进程管理万字详解!!!
|
6天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
30 4
|
7天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
8天前
|
消息中间件 存储 Linux
|
15天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
16 1
|
26天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
18 1
|
1月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
27 0
Linux c/c++之IPC进程间通信
|
1月前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
22 0
Linux c/c++进程间通信(1)
|
1月前
|
Linux C++
Linux c/c++之进程的创建
这篇文章介绍了在Linux环境下使用C/C++创建进程的三种方式:system函数、fork函数以及exec族函数,并展示了它们的代码示例和运行结果。
30 0
Linux c/c++之进程的创建
|
1月前
|
Linux C++
Linux c/c++进程之僵尸进程和守护进程
这篇文章介绍了Linux系统中僵尸进程和守护进程的概念、产生原因、解决方法以及如何创建守护进程。
17 0
下一篇
无影云桌面