Linux进程理解【程序地址空间】

简介: Linux程序地址空间,包含虚拟地址空间、写时拷贝等内容,详细讲解,干货满满!

Linux进程理解【程序地址空间】

我们先来看看C/C++程序地址空间的分布图

如此多区域的划分是为了更好的使用和管理空间,但是真实的内存空间也是按照图上的地址空间这样划分的吗?其实并不是,那么这样划分的不是内存是什么呢?真正的内存又在哪呢?且带着这些问题,本文将对这些疑惑一一解答

1. 话题引入

我们定义一个变量,创建父子进程共同使用这个变量,并子进程中对其做出修改,来看看现象

int main()
{
   
   
  pid_t id = fork(); //获取返回值
  assert(id != -1);  //创建失败的情况
  int num = 100;

  if(id == 0) //子进程
  {
   
   
    num += 12; //子进程中改变num的值
    printf("我是子进程, 我的PID是: %d,PPID是: %d, num = %d, num的地址: %p\n", getpid(), getppid(), num, &num);
  }
  else if(id > 0) //父进程
  {
   
   
    printf("我是父进程, 我的PID是: %d,PPID是: %d, num = %d, num的地址: %p\n", getpid(), getppid(), num, &num);
  }

  return 0;
}

我们运行发现,在同一块地址空间,读到了不同的值

分析:

  • 物理地址是唯一的,如果这里表示的是物理地址的话,不可能在同一个变量的地址上,读取到两个不同的值,所以这个地址一定不是物理地址

结论:

  • 子进程修改变量值的时候,发生了写时拷贝机制
  • 语言层面用的地址,不是物理地址,而是被称为虚拟地址或者线性地址

2. 进程地址空间

所以上面空间分布图其实是虚拟空间的分布图,下面我们来看看真实的空间分布图

2.1 虚拟地址

下面我们结合两个小故事来帮助大家理解

小明今年初三,妈妈每个周末都会给他布置作业,临近期末考试,妈妈对小明说,如果他考进了年级前十下学期就把周末的作业免了,小明高兴极了,更加努力的学习,真的取得了年级前十。马上下学期开学了,到了周末妈妈又给小明布置了作业,小明有苦说不出,只能继续照做。

  • 故事中妈妈给小明画了一个免除作业的饼,类比一下,这个饼就是虚拟地址空间,而小明就是一个进程,妈妈就是操作系统,说来虚拟地址空间就是操作系统给进程画的饼,而妈妈在下学期周末又布置的作业就是内存了
  • 妈妈为了激励小明学习是要管理这张饼的,也就对应着操作系统需要管理击进程地址空间,也是用了先描述,再组织的方式,而虚拟地址空间本质就是操作系统中的一个内核数据结构 mm_struct

开学第一天小沸和小美被老师安排成了同桌,小沸是个有些邋遢的男生,而小美是个很爱干净的女生,小美对于小沸很是嫌弃,当即就在桌子上画清了三八线,对小沸说,桌子三分之二都是我的,你只能在线那边的三分之一,小沸听了虽然有些伤心,但还是笑眯眯的答应了。

  • 在小美画的三八线两侧两人井水不犯河水,这里区域划分的本质就是对线性区域指定startend来完成区域划分

在mm_struct结构体中,也是如此划分,通过对边界值的调整,还可以做到不同区域的增长

mm_struct
{
   
   
    //代码区划分
    unsigned long code_start;
    unsigned long code_end;

    //堆区划分
    unsigned long heap_start;
    unsigned long heap_end;

    //栈区划分
    unsigned long stack_start;
    unsigned long stack_end;

    // ......
}

虚拟地址空间,加上页表 + MMU机制,通过寻址的方式,进行物理内存的访问

如何理解这个地址空间是线性连续的呢?

  • 在使用visual studio进行调试时,打开内存会发现我们的地址空间中的地址是用16进制表示的,32位计算机下范围是[0x00000000, 0xFFFFFFFF],有4G的空间大小

  • 地址是多少不重要,关键是地址具有唯一性,不能发生冲突,每个地址值对应的就是一个字节。所以因为表示地址数字是连续的,所以这里说地址空间是线性连续的

2.2 写时拷贝

写时拷贝

  • 多个进程在访问同一个数据时,会指向同一块空间
  • 当发生数据改写行为时,再重新开辟空间进行改写,这就是写时拷贝机制
  • 写时拷贝其实是一种赌bo行为,OS会赌你不会对数据做出修改,来提高效率

了解了以上,我们就能对最开始的问题:为什么同一块地址空间,会读到了不同的值做出更好的解释了

  • 父子进程有着一模一样的mm_struct,变量对应的虚拟地址,通过页表 + MMU的转换,指向同一块内存空间
  • 子进程对变量的改写,为了不影响父进程,此时OS会触发写时拷贝机制,在内存中重新开辟一块空间拷贝变量值,再来对其进行改写,父子进程的mm_struct并不改变,改变的是物理内存空间

3. 知识扩展

==没有虚拟地址空间,操作系统是如何工作的==

  • 早期在没有虚拟地址空间的时候,是直接在物理地址上进行数据读写的,当我们多个进程加载到内存时,其中有个进程时访问地址的操作,然后给到CPU,CPU返回给这个进程一个不是本进程而是其他进程的地址,这样假如我们本进程要进行写入或者删除的操作,这样就会影响到了其他进程,这样就无法保证进程的独立性

于是大佬们就引入了虚拟地址空间,加上页表 + MMU机制,通过寻址的方式,进行物理内存的访问,体现了不菲的价值

  • 防止地址随意访问,保护物理内存和其他进程
  • 将进程管理和内存管理进行解耦合
  • 让进程以统一的视角看待代码和数据

==再来看看申请内存malloc的本质==

  • 我们向操作系统申请空间,操作系统是你在需要的时候才会给你,而不是立马给你,因为操作系统是不允许任何的浪费或者不高效的行为
  • 在你申请成功之后和在你使用之前,这块空间有一段的闲置状态,操作系统为了不让空间浪费,首先是在虚拟地址空间申请空间,然后将对应的虚拟地址放进页表,但是没有映射处物理地址,物理内存上也没有申请空间,这种行为就叫做缺页中断

==进阶理解虚拟地址空间==

  • 程序在被编译的时候,没有被加载到内存中,那么程序内部是有地址的
  • 源代码在被编译的时候,就是按照虚拟地址空间的方式进行的,对代码和数据就已经编好了对应的编制。不要认为虚拟地址这样的策略只会影响OS,它也会让编译器遵守对用的规则
  • CPU中读取到的数据对应的地址还是虚拟地址

Linux进程理解—程序地址空间,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正

相关实践学习
CentOS 8迁移Anolis OS 8
Anolis OS 8在做出差异性开发同时,在生态上和依赖管理上保持跟CentOS 8.x兼容,本文为您介绍如何通过AOMS迁移工具实现CentOS 8.x到Anolis OS 8的迁移。
目录
相关文章
|
6天前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
3月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
127 1
|
19天前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
66 34
|
2天前
|
Linux
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
14 7
|
23天前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
59 16
|
2月前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
161 20
|
3月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
129 13
|
3月前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
3月前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
4月前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。