【linux】:进程地址空间

简介: 【linux】:进程地址空间

前言



本篇文章接着上一篇文章继续讲解进程,主要讲述了进程在运行过程中是如何在内存中被读取的以及为什么要有虚拟地址的存在,CPU在运行过程中是拿到程序的虚拟地址还是真实的物理内存。


一、进程地址空间



下面我们先写一个程序用来引出问题:

8909342fdeb64084b7b237ec2e343fbe.png

先写一个.c程序和makefile文件

ecf9001189c946d49b83e5f646816de4.png我们通过创建一个子进程的方式,让子进程中的全局变量val改变而父进程不会改变,这样的结果在我们的预期中应该是子进程修改了全局变量导致父进程中的这个全局变量的值也发生改变,那么我们运行起来看看结果:

3e1ddd65675a4c5b90b07addd9bd8252.png

我们发现全局变量val的地址是一样的,但是值却不一样了,子进程修改了全局变量,但是在父进程中为什么全局变量没有被改变呢?这是因为进程具有独立性,进程 = 内核数据结构+代码数据 也就是说每个进程的内核数据结构和代码数据也是独立的。那么val经过写时拷贝应该有两个不一样的地址才对,为什么打印出来的地址是一样呢?我们假设这是物理地址,有没有可能读取同一个变量的地址竟然会读到不同的数值呢?这是不可能的,既然是物理地址那就是唯一的,所以这一定不是物理地址!这就引入了我们今天所学习的虚拟地址,也叫线性地址。


进程地址空间本质就是一个内核数据结构,struct mm_struct{},下面我们画图描述一下:

8fd5de37dc4648968455d256ab54edee.png


如上图,首先一个进程中存放的是虚拟地址,在虚拟地址中又分为内核空间和用户空间,在用户空间中从低地址到高地址分别有正文代码,初始化数据,未初始化数据,堆,共享区,栈,命令行参数环境变量,而这些区域是如何划分的呢?其实很简单,只需要一个结构体即可:

d02219f90a19422c9033d193c44ce073.png


就比如正文代码和初始化数据的划分只需要修改结构体中的start和end即可,像栈区的扩大堆区的缩小原理都是修改相对应区域的区间即可。那么地址空间又如何存储数据的呢?CPU和内存是用总线连接的,比如32位系统就用32根线连接,而这些线只能用0和1表示代表二进制,一共有2的32次方种排列方法。

9bcceb7d0cac4e2890fcba90b96aaa76.png


如上图所示,进程地址空间就是一个线性范围,每个数字都是一个地址,而地址空间本质就是线性的。那么我们既然用的是虚拟地址又如何将数据保存到真实的物理内存呢?8d5f56c01b034ed9b40c119527711963.png



虚拟地址通过页表+MMU映射到物理地址,MMU就是内存管理单元。也就是说进程先通过虚拟地址再由页表和MMU将虚拟地址映射找到其物理地址,这样我们就能解释为什么相同的地址却有不同的值,如下图:

dc2a88d064e1400aa042ba289b056d92.png


父进程中大部分数据被子进程继承后两个进程都是一样虚拟地址,既然虚拟地址一样那么通过页表肯定也映射到相同的物理内存中了,当子进程要修改val 的时候是不能直接修改的,因为进程具有独立性如果子进程修改成功就会影响父进程,所以在子进程要修改的时候发生了写时拷贝,如下图:

661f55fd97f542f88ce0686c90485961.png


由于发生写时拷贝只改变页表中的V值不改变K值所以虚拟地址是一样的而val不一样。

那么如果没有虚拟地址空间,操作系统是如何工作的呢?

e646d5a5a39a49f78d24ad2dff10f641.png



如果没有虚拟地址那么直接访问物理内存,这样的方式是不安全的,一旦我们写的代码有问题或者出现野指针的情况,不小心将其他进程的数据修改了,那么就会出现很大的问题,虚拟地址可以保证在出现野指针等情况时直接拒绝映射到物理内存,这样你就访问不到物理内存,也就不会随意访问到其他进程的数据。所以虚拟地址空间的第一个作用是:防止地址随意访问,保护物理内存与其他进程。就比如这样的代码:char* p = "hello world"这样的代码我们是不能以p[0] = 'H'这样的方式修改的,因为hello world是保存在常量区的,对应的页表中的权限为r也就是只读权限所以我们不可以去修改。


那么当我们向操作系统申请内存,操作系统立马把内存给我们还是等我们用的时候再给呢?由于操作系统不允许任何的资源浪费,所以一定是在需要的时候才给空间。当然在我们申请内存成功后和使用之前,会有一段小小的时间窗口,在这个时间内即使空间没有被使用,别人也用不了这块空间,这叫做空间的闲置状态。由于页表的存在所以我们并不关心我们的代码放在物理内存的哪个位置只要存放在物理内存就可以了,这样就有了虚拟地址的第二个优点:将进程管理和内存管理进行解耦合。 也就是说我们不用再像没有虚拟地址的时候那样直接存放在物理内存导致进程管理和内存管理在一起相互管理,有了虚拟地址就可以分开管理进程和内存。


当我们的程序再被编译的时候,没有被加载到内存,那么我们的程序内部有没有地址呢?答案是有地址。因为在编译的时候就有了相应的代码段和数据段,所以源代码在被编译的时候,就是按照虚拟地址空间的方式进行对代码和数据早就已经编好了对应的编制。那么CPU中读到的数据中涵盖的这个地址是虚拟的地址还是物理的地址呢?答案是虚拟的,因为程序在编译的时候就包含了虚拟地址,虚拟地址加载到内存后就具备了物理地址,然后通过映射就可以找到物理地址,而当程序加载到CPU中的时候CPU是看不到物理地址的,因为物理地址是由页表映射而来的,所以CPU只能看到虚拟地址。 这就引出了虚拟地址的第三个优点:可以让进程以统一的视角,看待自己的代码和数据。


总结



本篇文章的重点是如何理解虚拟内存,我们通过画图的方式展示了进程地址空间,并且提到了虚拟内存的三个优点:1.将进程管理和内存管理进行解耦合 2.防止地址随意访问,保护物理内存与其他进程 3.可以让进程以统一的视角,看待自己的代码和数据


目录
相关文章
|
16天前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
3月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
132 1
|
1月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
76 34
|
11天前
|
Linux
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
27 7
|
10天前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
32 5
|
10天前
|
Linux 应用服务中间件 nginx
Linux 进程管理基础
Linux 进程是操作系统中运行程序的实例,彼此隔离以确保安全性和稳定性。常用命令查看和管理进程:`ps` 显示当前终端会话相关进程;`ps aux` 和 `ps -ef` 显示所有进程信息;`ps -u username` 查看特定用户进程;`ps -e | grep <进程名>` 查找特定进程;`ps -p <PID>` 查看指定 PID 的进程详情。终止进程可用 `kill <PID>` 或 `pkill <进程名>`,强制终止加 `-9` 选项。
20 3
|
1月前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
68 16
|
2月前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
185 20
|
3月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
137 13
|
3月前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具