【Linux:程序地址空间--原来操作系统也喜欢画大饼】
目录
1 代码感受
2 进程地址空间
3 扩展
1 代码感受
在正式讲程序地址空间前我们先来看一段简单的代码来分析分析:
1 #include<iostream>2 #include<unistd.h>3 using namespace std;4 5 int g_val=100;6 7 int main()8 {9 pid_t id=fork();10 if(id==0)11 {12 //child13 while(true)14 {15 cout<<"我是一个子进程,我的pid是:"<<getpid()<<",我的ppid是:"<<getppid()<<",g_val:"<<g_val<<";&g_val:"<<&g_val<<endl;16 g_val=200;17 sleep(1);18 }19 }20 else21 {22 //parent 23 while(true)24 {25 cout<<"我是一个父进程,我的pid是:"<<getpid()<<",我的ppid是:"<<getppid()<<",g_val:"<<g_val<<";&g_val:"<<&g_val<<endl;26 sleep(1);27 }28 }29 return 0;30 }31
大家可以自己先分析一下结果。
我们来运行一下结果:
大家看前面几行可能就会立马发现问题:我们定义的g_val是全局变量,当子进程修改g_val的值时我们发现父进程的g_val是不受影响的,那么说明父子进程所用的g_val并不是同一个变量(这个很好理解,之前的我们说过父子进程是相互独立的,互相不受干扰的),但是问题出现在最后一列,我们惊奇的发现居然父子进程的g_val变量的地址居然是相同的,前面不是说父子进程的g_val不是同一个变量吗?这里为啥打印出来的地址会是相同的呢?
这里就说明我们打印出来的地址并不是真正的物理地址,我们语言层面打印出的地址叫做虚拟地址或者线性地址。我们在用C/C++语言所看到的地址,全部都是虚拟地址!而物理地址,用户一概看不到,由OS统一管理 。OS必须负责将虚拟地址转化成物理地址 。
2 进程地址空间
首先我们来讲一个故事:从前有一个企业家很有钱,他的家产大概有一亿美金左右的样子。他有4个私生子,并且这四个私生子互相并不知道对方的存在。第一个私生子是个学霸,在国内顶尖学校上学,这个富豪便对他说,你要好好读书,将来我这一亿美金全部都是你的;第二个私生子是一个三线演员,富豪便对他说,我帮你打开你红的渠道,你不要辜负了我对你的期望,好好努力,将来我这一亿美金都是你的;第3个私生子是个女儿,当的是小学老师,富豪便对他说,你也不用太过努力工作,我就你这一个女儿,等我老了这一亿美金就是你的了;第四个私生子是一个初中的小混混,富豪对他说,你只要好好听我的话,这一亿美金就是你的了。
富豪给每个私生子都做出了承诺要将一亿美金给他们,但是实际富豪并没有那么多的钱给每个私生子一亿美金,而这一亿美金就是富豪给私生子们画的一张大饼,但是它的私生子们却信以为真。
那这个故事与我们讲的知识有什么关系呢?其实操作系统就是那个富豪,私生子们就是一个一个的进程,而那一亿美金就是进程地址空间。
PS:我们在生活中要尽量少画饼。
操作系统给进程画了一张大饼,操作系统的资源是有限的,所以他就得要好好的把这张饼给管理起来,不让这些进程乱来,而如何管理呢?
那就要先描述,再组织,Linux中用的是一种叫做mm_struct的内核数据结构来管理的。
我们来用一张图带大家来看看程序地址空间:
这张图相信大家多多少少也不会陌生,在C语言的学习中我们也见到了很多次。
那么程序地址空间如何编码的呢?(32位的平台下虚拟地址空间大概是4GB)
ps:下面图每个小空格代表着一个字节。
所以从这里我们也不难看出为啥虚拟地址也叫做线性地址。那么我们究竟是如何管理虚拟地址空间的每个区的呢?
我们可以用下面这种方式来描述管理:
struct mm_struct
{
long code _start;
long code _end;
long init _start;
long init _end;
…………
long brk _start;
long brk _end;
long stack _start;
long stack _end;
}
而_start和_end限定的区域就是叫做虚拟地址(线性地址)
那么问题来了,既然上面我们讲了那么多虚拟地址,真正的物理地址又在哪里呢?
我们画一个图方便大家理解:
通过这张图大家并不难发现,我们在语言层面上的地址是地址空间的虚拟地址,而虚拟地址要与物理地址建立映射,就需要一张页表(页表的工作原理我们将放到后面来讲)。
我们在学习C语言时大家在书上看到这样的一句代码:const char* str="hello world";
这时书上会告诉大家这句str指向的内容是只读的,不可修改的,但是这时为什么呢?这时我们就可以自己来分析分析:str指向的内容是在常量字符区,当常量字符区通过页表与物理地址建立映射时在页表中就将该数据设置为只读,当我们后续有修改操作时就会直接报错。
有了上面的基础我们就可以来解释解释为啥开头我们的g_val是同一个地址,但是指向的内容却不相同的问题了:
当不修改数据时就不会发生写时拷贝,父子进程指向的是同一块物理空间(为了节约资源);当要修改数据时就会发生写时拷贝,父子进程指向的是不同的物理空间,但是虚拟地址空间是相等的。
我们再来回答为啥fork会有两个返回值的问题就很容易了,就是因为父子进程的返回值是不同的,所以肯定会发生写时拷贝将不同的返回值用相同的虚拟地址来进行返回,虽然虚拟地址是相同的,但是他们通过页表建立映射的关系却是不一样的。
到目前为止,程序地址空间的基本内容已经ok,接下来给出一些扩展。
3 扩展
首先引出一个问题:假如没有程序地址空间,OS是如何工作的?
我们知道如果没有了地址空间,那么cpu将直接跟物理地址打交道,这样做的后果是什么?
我们不难知道假如cpu直接跟物理地址打交道的话那么当我们从cpu中读到非法地址时那就坏了,通过非法地址将我们程序中其他变量的值给修改了那不就扯淡了吗。所以我们要通过一层屏障来保护数据,而这一层保护就是通过程序地址空间来进行的,当我们访问的数据非法时通过页表的映射就会拒绝你的非法操作。
所以我们得出了程序地址空间的第一个好处:防止地址随意访问,保护物理内存和其他进程。
在向大家提出一个小问题:当我们在堆上new空间时OS是立马就把空间给你,还是等你需要的时候再给你?
这个问题大家应该都能够答对,与我们想得一样,OS会在我们需要该空间的时候再去在堆上申请。
而页表暂时没有与物理内存建立映射关系称作页表中断,当我们需要空间的时候再与·物理内存建立映射。大家从这张图看出来没有,当我们通过页表建立映射时将进程管理与内存管理给解耦合了。我进程管理不需要关心你是怎样在内存上申请空间的,内存管理也不需要关心进程是如何管理起来的,这样下来维护成本就会变得更低,维护效率会更加高效一些。
所以我们得出了程序地址空间的第二个好处:将进程管理与内存管理进行解耦合。
再提出一个问题:程序在被编译的时候没有被加载到内存,那么程序内有没有地址呢?
答案是有的。源代码再被编译的时候就是按照虚拟地址空间的方式将对应的代码和数据进行编制,编译器也会遵守虚拟地址的规则。
当我们把程序加载到内存,程序里保存的地址(虚拟地址,并不是程序本身在内存中的物理地址)就会被cpu读取,cpu通过虚拟地址找到对应的虚拟地址空间,然后虚拟地址空间又通过页表映射到物理内存中,这样就将程序的整个运转给联系起来了。
所以我们得出了程序地址空间的第三个好处:可以让进程以统一的视角看待自己的代码和数据。
相关文章:

【Linux:程序地址空间--原来操作系统也喜欢画大饼】
目录 1 代码感受 2 进程地址空间 3 扩展 1 代码感受 在正式讲程序地址空间前我们先来看一段简单的代码来分析分析: 1 #include<iostream>2 #include<unistd.h>3 using namespace std;4 5 int g_val100;6 7 int main()8 {9 pid_t idfork();10 if(i…...

Python实现简单信号滤波实战
在有些项目中需要对信号进行滤波处理,尤其是在医疗的设备中如心跳、脉搏等设备的采样后进行处理。滤波的目的就是除去某些频率的信号如噪声。常见的包括有低通滤波、高通滤波、带通滤波。 低通滤波指的是去除高于某一阈值频率的信号;高通滤波去除低于某…...

Java(110):非对称加密RSA的使用(KeyPair生成密钥)
Java(110):非对称加密RSA的使用(KeyPair生成密钥) RSA 算法是一种非对称加解密算法。服务方生成一对 RSA 密钥,即公钥 私钥,将公钥提供给调用方,调用方使用公钥对数据进行加密后,服务方根据私钥进行解密。 1、RSA生…...
(Mybatis 学习【1】)整合 Mybatis 开发流程
Mybatis 整合流程 ① 添加MyBatis的依赖 ② 创建数据库表 ③ 编写pojo实体类 ④ 编写映射文件UserMapper.xml ⑤ 编写核心文件mybatis-config.xml ⑥ 编写测试类** 编写 pojo 实体类 (设计相应的数据库) Data AllArgsConstructor NoArgsConstructor public class…...

一文搞懂Kerberos
Kerberos一词来源于古希腊神话中的Cerberus——守护地狱之门的三头犬,Kerberos是为TCP/IP 网络设计的可信第三方鉴别协议,最初是在麻省理工学院(MIT)为Athena 项目而开发的。Kerberos服务起着可信仲裁者的作用,可提供安全的网络鉴别ÿ…...

Go爬虫学习笔记(三)
day3 04|敏捷之道:大型Go项目的开发流程是怎样的? 瀑布模式 流程: 市场调研需求分析产品设计研发实现集成与测试项目交付与维护 适用场景: 需求在规划和设计阶段就已经确定了,而且在项目开发周期内&…...

CASTEP参数设置(2)
虚拟试验(分子模拟) 在表征材料以及材料的相关性质时,只要是采用已有的理论加以解释 但是通常来说,需要采用已有的理论来进行设计和探索,伴随着工业软件的发展,应当选用仿真技术来缩小探索范围 传统试验V…...

浅谈对Promise的理解以及在工作中的应用
浅谈对Promise的理解以及在工作中的应用Promise的概念背景知识JavaScript的同步和异步JavaScript事件循环回调函数进行异步操作解决方案:PromisePromise 在工作中的运用创建PromisePromise封装AJAXPromise链式操作Promise.all()Promise.race()async和await总结Promi…...

开源|快速入门和理解并模拟实现GPS户外机器人的定位与导航
户外机器人的定位导航相对于需要建图的场景来说,是比较简单容易实现的,因为可以借助第三方地图完成定位,并在第三方地图中完成路径规划和下发航点等操作,实现的难题在于如何控制机器人完成步行和转弯。 这些在不引进RTK高精度定位…...

Java多线程系列--synchronized的原理
原文网址:Java多线程系列--synchronized的原理_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Java的synchronized的原理。 反编译出字节码 Test.java public class Test {private static Object LOCK new Object();public static int main(String[] args) {synchro…...

QEMU启动ARM64 Linux内核
目录前言前置知识virt开发板ARM处理器家族简介安装qemu-system-aarch64安装交叉编译工具交叉编译ARM64 Linux内核交叉编译ARM64 Busybox使用busybox制作initramfs使用QEMU启动ARM64 Linux内核前言 本文介绍采用 qemu 模拟ARM-64bit开发板(针对ARM-32bit的有另一篇文…...

Linux->进程程序替换
目录 前言: 1 程序替换原理 2 单进程替换 3 替换函数 3.1 函数使用 4 程序去替换自己的另一个程序操作方式 5 实现自己的shell 前言: 通过我们之前对于子进程的应用,我相信大家一定是能够想到创建子进程的目的之一就是为了代劳父进程执…...

最强分布式锁工具:Redisson
1 Redisson概述1.1 什么是Redisson?Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, Sorted…...
Java9-17新特性
Java9-17新特性 一、接口的私有方法 Java8版本接口增加了两类成员: 公共的默认方法公共的静态方法 Java9版本接口又新增了一类成员: 私有的方法 为什么JDK1.9要允许接口定义私有方法呢?因为我们说接口是规范,规范时需要公开…...

电脑开机找不到启动设备怎么办?
电脑正常开机,却提示“找不到启动设备”,这时我们该怎么办呢?本文就为大家介绍几种针对该问题的解决方法,一起来看看吧!“找不到启动设备”是什么意思?可引导设备(又称启动设备)是一…...

使用langchain打造自己的大型语言模型(LLMs)
我们知道Openai的聊天机器人可以回答用户提出的绝大多数问题,它几乎无所不知,无所不能,但是由于有机器人所学习到的是截止到2021年9月以前的知识,所以当用户询问机器人关于2021年9月以后发送的事情时,它无法给出正确的答案&#x…...
assert()宏函数
assert()宏函数 assert是宏,而不是函数。在C的assert.h文件中 #include <assert.h> void assert( int expression );assert的作用是先计算表达式expression, 如果其值为假(即为0),那么它会打印出来assert的内容…...

Docker圣经:大白话说Docker底层原理,6W字实现Docker自由
说在前面: 现在拿到offer超级难,甚至连面试电话,一个都搞不到。 尼恩的技术社群(50)中,很多小伙伴凭借 “左手云原生右手大数据”的绝活,拿到了offer,并且是非常优质的offer&#…...

Redis+Caffeine多级(二级)缓存,让访问速度纵享丝滑
目录多级缓存的引入多级缓存的优势CaffeineRedis实现多级缓存V1.0版本V2.0版本V3.0版本多级缓存的引入 在高性能的服务架构设计中,缓存是一个不可或缺的环节。在实际的项目中,我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中࿰…...

C#和.net框架之第一弹
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录C# 简介一、微软平台的编程二、使用VS创建第一个c#程序1、第一步2、第二步3、第三步4、第四步5、第五步C# 简介 C# 是一个现代的、通用的、面向对象的编程语言&…...
模型参数、模型存储精度、参数与显存
模型参数量衡量单位 M:百万(Million) B:十亿(Billion) 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的,但是一个参数所表示多少字节不一定,需要看这个参数以什么…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...