(14)Linux 地址空间的理解
前言:本章核心主题为 "进程地址空间"。
一、Linux 进程地址空间
程序地址空间是内存吗?不是!程序地址空间不是内存!
其实,我们称之为程序地址空间都不准确,应该叫 进程地址空间,这是一个系统级的概念!
我们来写个代码验证一下 Linux 进程地址空间!
代码:Linux 进程地址空间
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int un_g_val;
int g_val = 100;int main(int argc, char* argv[], char* env[])
{printf("code addr : %p\n", main);printf("init global addr : %p\n", &g_val);printf("uninit global addr : %p\n", &un_g_val);char* m1 = (char*)malloc(100);printf("heap addr : %p\n", m1);printf("stack addr : %p\n", &m1);int i = 0;for (i = 0; i < argc; i++) {printf("argv addr : %p\n", argv[i]); }for (i = 0; env[i]; i++) {printf("env addr : %p\n", env[i]);}
}
运行结果如下:
可以看到,从低到高,从下到上,整体地址是依次增大的。
请注意,堆和栈之间能观察到有非常大的地址镂空。
下面我们来验证一下堆和栈的 "挤压式" 增长方向的问题,在刚才的代码中我们加上如下代码:
/* 堆上申请四块空间 */
char* m1 = (char*)malloc(100);
char* m2 = (char*)malloc(100);
char* m3 = (char*)malloc(100);
char* m4 = (char*)malloc(100);printf("heap addr : %p\n", m1);
printf("heap addr : %p\n", m2);
printf("heap addr : %p\n", m3);
printf("heap addr : %p\n", m4);
现在我们再验证一下栈区,m1,m2,m3,m4 依次入栈,我们取地址将其分别打印出来:
printf("stack addr : %p\n", &m1);
printf("stack addr : %p\n", &m2);
printf("stack addr : %p\n", &m3);
printf("stack addr : %p\n", &m4);
我们发现,堆区向地址增大方向增长,栈区向地址减少方向增长。
我们一般在 C 函数中定义的变量,通常在栈上保存,那么先定义的一定是地址比较高的,
后定义的地址一定是比较低的。因为先定义的先入栈,后定义的后入栈。
我们还是写代码去观察分析:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_val = 100;
int main(void)
{pid_t id = fork();if (id == 0) {// childwhile (1) {printf("我是子进程: %d, ppid: %d, g_val: %d, &g_val: %p\n\n", getpid(), getppid(), g_val, &g_val);sleep(1);}}else {// fatherwhile (1) {printf("我是父进程: %d, ppid: %d, g_val: %d, &g_val: %p\n\n", getpid(), getppid(), g_val, &g_val);sleep(2);}}
}
运行结果如下:
我们发现,父进程和子进程的地址其实都是一样的
结论:当父子进程没有人修改全局数据的时候,父子是共享该数据的。
如果此时尝试写入,比如我们让子进程有一个修改的操作。
我们在子进程那定义一个 flag
, sleep(1)
执行五次,即五秒之后给它改值:
嘻嘻,少截了一点。
修改前:父子都是100;修改后:父:100,子:200;
发现:父子进程读取同一个变量(因为地址一样),但是后续没有人修改的情况下,父子进程读取到的内容却不一样。
父子进程打出来的地址是一样的,值却不一样!?
既然如此,那我就告诉你真相 —— 我们在 C/C++ 中使用的地址,绝对不是物理地址!
我们先抛出概念:我们在 C/C++ 中使用的地址,是 虚拟地址。
虚拟地址在我们 Linux 下也称为 线性地址,有些教材中也称之为 逻辑地址。这三个概念实际上是不一样的,但是在 Linux 下它是一样的(这和其本身的空间布局有关系)。
我们再抛出一个问题:为什么我的操作系统不让我直接看到物理内存呢?
如果能让你直接看到物理内存,或者让你访问物理内存,岂不是会出乱子。
内存就是一个硬件,不能阻拦你访问!只能被动地进行读取和写入!
1、讲解:进程地址空间
每一个进程在启动的时侯都会让操作系统给它创建一个地址空间,该地址空间就是 进程地址空间
操作系统为了管理一个进程,给该进程维护一个 task_struct
叫做进程控制块。
首先,每一个进程都会有一个自己的进程地址空间。
操作系统要不要管理这些进程地址空间呢?当然是要管理了,我们还是引出前几章提出的:
先描述,再组织。
所谓的进程地址空间,其实是内核的一个数据结构!叫做 mm_struct
。
在上一章,我们谈论过进程的概念,竞争和独立、并行和并发,我们要需要谈论其中的 独立性。
进程具备独立性,简单来说就是一个进程挂掉或崩溃是不会波及其他进程的。
- 进程相关的数据结构是独立的,进程的代码和数据是独立的。
说得好,但是独立性又和地址空间有什么关系呢?
举例:
有一个富豪,它有10亿资产,由于
年轻时比较浪,所以他有四个私生子
这四个私生子并不知道彼此的存在,
私生子A是个医生,私生子B是个企业家
私生子C是个街头混混,私生子D是个学生
富豪分别对小A,B,C,D说:
(1),小A啊,你要是努力做个医生,以后我的
10亿美金都是你的了
(2),小B啊,要是你把你的公司运作的很好
以后我的10亿美金就是你的了
(3),小C啊…小D啊…
老板给他的四个儿子画的大饼,我们就称之为 "进程地址空间"。
所以,进程地址空间并不是物理上存在的概念,而是在逻辑上抽象的一个虚拟的空间。
财阀老板给四个私生子画饼,就是为了维护这四个私生子互相之间的独立性,
如果让私生子知道自己并不是唯一,那以后分割财产必然会造成矛盾,
对他来说自然就不是一件好事。
所以,进程地址空间,就是就是给进程画的大饼。
进程地址空间 → 逻辑上抽象的概念 → 让每个进程都认为自己独占系统的所有资源
概念:操作系统通过软件的方式,给进程提供一个软件视角,认为自己是独占系统的所有资源(内存)。
2、理解:区域和页表
什么叫做区域?就像小学时候我们画的三八线一样。
我们把一张桌子分为两个区域,对桌子进行区域划分:
比如,既然要标出区域,定义一个桌面区域,其实用两个变量就可以表示了:
struct destop_area {int start; // 区域起始位置int end; // 区域结束位置
};struct destop_area A = {1,50};
struct destop_area B = {50, 100};
然而,后来出现了抢地盘。都想让自己占的更大一些
抢地盘对桌面区域进行划分,调整区域的大小只需要让 end 加上 "调整值" 就行。
这就是区域的概念,我们只需要定义 start 和 end 就可以表示了。
每个区域范围都是可以有对应的编号的,比如以厘米为单位,我的修正带就放在了 50cm。
我们的 mm_struct
里面不就是区域范围吗?所以 mm_struct
就可以靠 start 和 end 定义:
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;...
}
程序加载到内存,由程序变成进程后,由操作系统给每个进程构建的一个页表结构,就是 页表。
我们来看看内核代码,就是用一个 start 一个 end 来呈现区域空间。
每个区域都有一个 start 和 end,它们之间就有了地址,地址我们称之为虚拟地址,
然后这些虚拟地址经过页表,就能映射到内存中了。
3、写时拷贝
思考:程序是如何变成进程的?
程序被编译出来,没有被加载的时候,程序内部有地址吗?当然有!
有没有区域?也有!
区分:我们程序内部的地址和内存的地址是没有关系的。
编译程序的时候,我们就认为程序是按照 0 ~ FFFF 进行编址的。
虚拟地址空间,不仅仅是操作系统会考虑,编译器也会考虑。
每个进程都会创建一个 task_struct,每一个进程都会维护一个 mm_struct,自己有对应的区域,当我们的程序加载到内存时,程序有自己的加载到物理内存的物理地址,虚拟地址和物理地址建立映射关系,进程访问某个区域当中的地址时,经过页表找到对应的代码和数据。当找到代码和数据后,代码加入到对应的 CPU 中,代码中的地址在加载中就已经转化成了线性地址/虚拟地址,所以 CPU 可以继续照着这个逻辑向后运行。
所以刚才我们代码测试,打印看到的虚拟地址值是一样的,并且内容也是一样的。在没有人写入的时候,虚拟地址到物理地址之间映射的页表是一样的,所以指向的代码和数据都是一样的。
我们的操作系统当我们的父子对数据进行修改时,操作系统会给修改的一方重新开辟一块空间,并且把原始数据拷贝到新空间当中,这种行为就是 写时拷贝!
当父子有任何一个进程尝试修改对应变量时,有一个人想修改,就会触发写时拷贝,让他去拷贝新的物理内存,这只需要重新构建也表的映射关系,虚拟地址是不发生任何变化的,所以最终你看的结果是虚拟地址不变,而内容不同。
现在再看,一点都不神奇了。
通过页表,将父子进程的数据就可以通过写时拷贝的方式,进行了分离。
这就做到父子进程具有独立性,父子进程不互相影响。
4、回顾:fork 有两个返回值的问题
我们在讲解进程的第一个章节就提出过一个问题,关于 fork
为什么有两个返回值的问题。
当时我们还提出了两个问题,局限于当时还没有讲到进程地址空间,所以没有办法深入讲解。
代码:验证 fork
返回值的问题,我们把 id
给打印出来:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main(void) {pid_t id = fork();printf("Hello, World! id: %d\n", id);sleep(1);
}
fork 有两个返回值,pid_t id
,同一个变量为什么会有两个返回值?
本章我们就可以理解了,因为当它 return
的时候,pid_t id
是属于父进程的栈空间中定义的。
fork
内部 return
会被执行两次,return
的本质就是通过寄存器将返回值写入到接收返回值的变量中。当我们的 id = fork()
时,谁先返回,谁就要发生 写时拷贝。所以,同一个变量会有不同的返回值,本质是因为大家的虚拟地址是一样的,但大家的物理地址是不一样的。
5、探讨:为什么要有虚拟地址空间?
如果我们没有虚拟地址空间,直接让进程访问物理内存是不安全的。
有了虚拟地址空间,就是给访问内存添加了一层软硬关键层,可以对转化过程进行审核,非法的访问就可以被直接拦截了,可以 保护内存。
还能够将 进程管理 和 Linux 内存管理,通过地址空间进行功能模块的解耦。
让进程或者程序可以以一种统一的视角看待内存!
有了虚拟地址空间,还可以让进程或者程序可以 以统一的视角看待内存。方便以统一的方式来编译和加载所有的可执行程序。如此一来,就可以简化进程本身的设计和实现。
参考:柠檬叶子:https://foxny.blog.csdn.net/article/details/128889095
相关文章:
(14)Linux 地址空间的理解
前言:本章核心主题为 "进程地址空间"。 一、Linux 进程地址空间 程序地址空间是内存吗?不是!程序地址空间不是内存! 其实,我们称之为程序地址空间都不准确,应该叫 进程地址空间,这…...
Java中的设计模式
设计模式是软件开发中常见问题的可重用解决方案。在Java中,设计模式有助于提高代码的可维护性、可读性和可扩展性。以下是一篇关于Java中设计模式的文章,以帮助您更好地理解这些模式。 一、设计模式简介 设计模式是经过验证的解决方案,用于…...
Hadoop(2):常见的MapReduce[在Ubuntu中运行!]
1 以词频统计为例子介绍 mapreduce怎么写出来的 弄清楚MapReduce的各个过程: 将文件输入后,返回的<k1,v1>代表的含义是:k1表示偏移量,即v1的第一个字母在文件中的索引(从0开始数的);v1表…...
Unity | 快速修复Animation missing错误
目录 一、背景 二、效果 三、解决办法 一、背景 最近在做2D 骨骼动画相关的Demo,我自己使用Unity引擎进行骨骼绑定并创建了anim后,一切正常,anim也能播放。但是昨天我修改Obj及子物体的名称(由中文改为英文,如&…...
ssm基于web的志愿者管理系统的设计与实现+vue论文
摘 要 使用旧方法对志愿者管理系统的信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在志愿者管理系统的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能及时纠正等问题。这次开发的志愿者…...
C++运算符重载(插入and提取)
介绍 本文主要介绍 插入(>>) and 提取(<<)的运算符重载 1.插入(>>) 提取(<<)只能是友元函数 2.插入关键词istream 例子:istream& operator>>(istream& in, sumber&Left) 3.提取关键词ostream 例子:ostream&a…...
C#高级 08Json操作
1.概念 Json是存储和交换文本信息的语法。类似于XML。Json比XML更小、更快、更易解析。Json与XML一样是一种数据格式。Json是一种轻量级的数据交换格式。它基于ECMAScript的一个子集。Json采取完全独立于语言的文本格式, 但是也使用了类似于C语言的习惯。这些特性使…...
封装uniapp签字板
新开发的业务涉及到签字功能,由于是动态的表单,无法确定它会出现在哪里,不得已封装模块。 其中涉及到一个难点就是this的指向性问题, 第二个是微信小程序写法, 我这个写法里用了u-view的写法,可以自己修改组…...
Mybatis行为配置之Ⅳ—日志
专栏精选 引入Mybatis Mybatis的快速入门 Mybatis的增删改查扩展功能说明 mapper映射的参数和结果 Mybatis复杂类型的结果映射 Mybatis基于注解的结果映射 Mybatis枚举类型处理和类型处理器 再谈动态SQL Mybatis配置入门 Mybatis行为配置之Ⅰ—缓存 Mybatis行为配置…...
Java设计模式-外观模式
目录 一、影院管理项目 二、外观模式 (一)基本介绍 (二)原理类图 (三)解决影院管理 (四)注意事项和细节 (五)外观模式在MyBatis框架应用的源码分析 一…...
js+css实现颜色选择器
<!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>颜色选择器</title><style>.color-box {width: 50px;height: 50px;border: 1px solid #000;cursor: pointer;}</style> </head> <body><…...
Go语言中的包管理工具之Go Modules的使用
GoLang 中常用的包管理的方式 常用的有三种 Go PathGo VendorGo Modules 关于 Go Modules 1 ) 概述 Go的包管理,经过社区和官方的共同努力下,最终在百家争鸣后Go官方在 2018.8 推出了go 1.11版本中的Go Modules,并且很快成为一统江湖的包…...
【c/c++】指针例图基础详解
文章目录 指针变量内存指针详解例1例2练习&答案解析 指针变量内存 int main(){// 各类型变量占字节数printf("char: %d\n",sizeof(char)); // 1printf("short: %d\n",sizeof(short)); // 2printf("int: %d\n",sizeof(int)); // 4pri…...
TCP/IP的网络层(即IP层)之IP地址和网络掩码,在视频监控系统中的配置和应用
在给客户讲解我们的AS-V1000视频监控平台的时候,有的客户经常会配置错误IP地址的掩码和网关,导致出现一些网路问题。而在视频监控系统中,IP地址和子网掩码是用于标识网络中设备的重要标识符。IP地址被用来唯一地标识一个网络设备,…...
代码随想录刷题 | Day1
今日学习目标 一、基础 数组 array类 模板类vector 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索引的方式获取到下标下对应的数据。 需要两点注意的是 数组下标都是从0开始的。 数组内存空间的地址是连续的 而且大家如果使用C的话&…...
查看IOS游戏FPS
摘要 本篇技术博客将介绍如何使用克魔助手工具来查看iOS游戏的帧率(FPS)。通过克魔助手,开发者可以轻松监测游戏性能,以提升用户体验和游戏质量。 引言 在iOS游戏开发过程中,了解游戏的帧率对于优化游戏性能至关重要…...
挑战Python100题(7)
100+ Python challenging programming exercises 7 Question 61 Print a unicode string "hello world". Hints: Use ustrings format to define unicode string. 打印一个unicode字符串“helloworld”。 提示:使用u“字符串”格式定义unicode字符串。 Solution…...
HarmonyOS自学-Day4(TodoList案例)
目录 文章声明⭐⭐⭐让我们开始今天的学习吧!TodoList小案例 文章声明⭐⭐⭐ 该文章为我(有编程语言基础,非编程小白)的 HarmonyOS自学笔记,此类文章笔记我会默认大家都学过前端相关的知识知识来源为 HarmonyOS官方文…...
LTPI协议的理解——2、LTPI实现的底层架构
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 LTPI协议的理解——2、LTPI实现的底层架构 前言一、体系结构三、实现细节四、物理接口信号传输方法总结 前言 前面讲了LTPI的定义和大概结构,接下来继续理解LTPI…...
CentOS 8.2 安装 Mysql 5.7.26(单机)
Mysql二进制包: mysql-5.7.26-linux-glibc2.12-x86_64.tar.gz 1、卸载旧环境 rpm -qa|grep mysql rpm -qa|grep mariadb rpm -e XXX.rpm --nodeps # 强制卸载rpm包 rm -rf /etc/my.cnf rm -rf /etc/mysql rm -rf /usr/local/mysql 2、安装依赖包 yum -y install libaio yum…...
Vue Tinymce富文本组件自定义带下拉框的操作按钮
想实现如下效果 首先在init方法中的props,toolbar属性增加一个自定义按钮 增加一个setup方法 代码 setup: function(editor) { editor.ui.registry.addSplitButton(myDateButton, {text: 日期时间,onAction: (_) > editor.insertContent(getJsMonthDay(getNowDat…...
YOLOv5算法进阶改进(10)— 更换主干网络之MobileViTv3 | 轻量化Backbone
前言:Hello大家好,我是小哥谈。MobileViTv3是一种改进的模型架构,用于图像分类任务。它是在MobileViTv1和MobileViTv2的基础上进行改进的,通过引入新的模块和优化网络结构来提高性能。本节课就给大家介绍一下如何在主干网络中引入MobileViTv3网络结构,希望大家学习之后能够…...
Java UDP
接收方 创建DatagramSocket实例并指定端口。创建DatagramPacket实例接收信息。调用DatagramSocket的receive()方法将接收信息并传递给DatagramPacket。通过DatagramPacket的getData()方法获取信息内容,getLength()方法获取长度。 package io.github.jast90.udp;im…...
Halcon阈值处理的几种分割方法threshold/auto_threshold/binary_threshold/dyn_threshold
Halcon阈值处理的几种分割方法 文章目录 Halcon阈值处理的几种分割方法1. 全局阈值2. 基于直方图的自动阈值分割方法3. 自动全局阈值分割方法4. 局部阈值分割方法5. var_threshold算子6 . char_threshold 算子7. dual_threshold算子 在场景中选择物体或特征是图像测量或识别的重…...
FB混合C语言编译
这是群友分享的方法,这里只是作为记录和分享。 有了这个功能,可以很方便的拷贝一下C或者C代码直接用到FB上。 既然是混合C语言编译,当然得有C的代码。比如随便去网上找两个排序:冒泡排序和选择排序,代码如下…...
【机器学习】深度学习概论(二)
五、受限玻尔兹曼机(Restricted Boltzmann Machine,RBM) 5.1 RBM介绍 示例代码: Python 编写了一个简单的 RBM 实现,并用一些假数据训练了它。然后,他展示了如何用 RBM 来解释用户的电影偏好,以…...
词法语法语义分析程序设计及实现,包含出错提示和错误恢复
词法说明 (1)关键字 main, int, char, if, else, for, while, void (2)运算符 - * / < < > > ! (3)界符 ; ( ) { } (4)标识符 ID letter(letter|digit)* (5)整型常数 NUM digit digit* (6)空格 ‘ ‘ ‘\n’ ‘\r’ ‘\t’ 空格用来分隔ID,NUM,运算符,界…...
Linux的capability深入分析
from:https://www.cnblogs.com/iamfy/archive/2012/09/20/2694977.html 一)概述: 1)从2.1版开始,Linux内核有了能力(capability)的概念,即它打破了UNIX/LINUX操作系统中超级用户/普通用户的概念,由普通用户也可以做只有超级用户可以完成的工作. 2)capability可以作用在进程上…...
【自然语言处理】类似GPT的模型
除了GPT (Generative Pre-trained Transformer) 之外,还有一些其他的好用的类似工具可以用来生成文本。以下是几个受欢迎的工具: BERT (Bidirectional Encoder Representations from Transformers): BERT 是一个预训练的深度双向 Transformer 模型&#…...
【Unity】【FBX】如何将FBX模型导入Unity
【背景】 网上能够找到不少不错的FBX模型资源,大大加速游戏开发时间。如何将这些FBX导入Unity呢? 【步骤】 打开Unity项目文件,进入场景。 点击Projects面板,右键选择Import New Assets 选中FBX文件后导入。Assets文件夹中就会…...
运营一个网站的费用/网站申请
题库来源:安全生产模拟考试一点通公众号小程序 美容师(初级)模拟试题考前必练!安全生产模拟考试一点通每个月更新美容师(初级)模拟考试题库题目及答案!多做几遍,其实通过美容师&…...
猪八戒网做网站/seo百科大全
目前很多制造型企业开始进行数字化转型,嫁接了很多提高生产效率的设备,比如MES、ERP、SPC等系统。 这些数据能帮助企业看到当下正在发什么,比如在制品多少?库存多少?损耗多少?但是不能告诉企业为什么会形成…...
word文档做网站/营业推广
ES6为Array增加了find(),findIndex函数。 find()函数用来查找目标元素,找到就返回该元素,找不到返回undefined。 findIndex()函数也是查找目标元素,找到就返回元素的位置,找不到就返回-1。 他们的都是一个查找回调函数…...
曹县住房和城乡建设局网站/百度学术论文查重官网
作者:Cary G.Gray and David R. Cheriton 1989 译者:phylipsbmy 2011-5-7 出处:http://duanple.blog.163.com/blog/static/70971767201141111440789/ [ 序:所谓租约(leases),其实就是一个合同,即服务端给予…...
宝安专业网站设计多少钱/百度竞价推广价格
转自:http://blog.csdn.net/joker0910/article/details/7171626 内核使用了大量不同的宏来标记具有不同作用的函数和数据结构。如宏__init 、__devinit 等。这些宏在include/linux/init.h 头文件中定义。编译器通过这些宏可以把代码优化放到合适的内存位置ÿ…...
做么网站有黄/足球排名最新排名世界
管理使用者和设立权限的命令命令说明命令说明chmod用来改变权限useradd用来增加用户su用来修改用户5.1 chmod命令chmod命令用来改变许可权限。读取、写入和执行是许可权限中的三个主要设置。因为用户在他们的账号被创建时就被编入一个组群,所以还可以指定那些组群可…...