[操作系统] 深入进程地址空间



程序地址空间回顾
在C语言学习的时,对程序的函数、变量、代码等数据的存储有一个大致的轮廓。在语言层面上存储的地方叫做程序地址空间,不同类型的数据有着不同的存储地址。
下图为程序地址空间的存储分布和和特性:

使用以下代码来验证一下各个类型的是数据存储是否如图所示:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_unval;
int g_val = 100; int main(int argc, char *argv[], char *env[])
{const char *str = "helloworld";printf("code addr: %p\n", main);printf("init global addr: %p\n", &g_val);printf("uninit global addr: %p\n", &g_unval);static int test = 10;char *heap_mem = (char*)malloc(10);char *heap_mem1 = (char*)malloc(10);char *heap_mem2 = (char*)malloc(10);char *heap_mem3 = (char*)malloc(10);printf("heap addr: %p\n", heap_mem);printf("heap addr: %p\n", heap_mem1);printf("heap addr: %p\n", heap_mem2);printf("heap addr: %p\n", heap_mem3);printf("test static addr: %p\n", &test);printf("stack addr: %p\n", &heap_mem);printf("stack addr: %p\n", &heap_mem1);printf("stack addr: %p\n", &heap_mem2);printf("stack addr: %p\n", &heap_mem3);printf("read only string addr: %p\n", str);for(int i = 0; i < argc; i++){printf("argv[%d]: %p\n", i, argv[i]);}for(int i = 0; env[i]; i++){printf("env[%d]: %p\n", i, env[i]);}return 0;
}
结果如下:
$ ./a.out
code addr: 0x40055d // 正文代码 main()
init global addr: 0x601034 // 未初始化全局变量
uninit global addr: 0x601040 // 初始化的全局变量
heap addr: 0x1791010 // 堆:向上增长 ↑
heap addr: 0x1791030
heap addr: 0x1791050
heap addr: 0x1791070
test static addr: 0x601038 // static int 类型
stack addr: 0x7ffd0f9a4368 // 栈:向下增长 ↓
stack addr: 0x7ffd0f9a4360
stack addr: 0x7ffd0f9a4358
stack addr: 0x7ffd0f9a4350
read only string addr: 0x400800 // const char *str
argv[0]: 0x7ffd0f9a4811
env[0]: 0x7ffd0f9a4819
env[1]: 0x7ffd0f9a482
env[2]: 0x7ffd0f9a4845
env[3]: 0x7ffd0f9a4850
env[4]: 0x7ffd0f9a4860
env[5]: 0x7ffd0f9a486e
通过地址结果的验证可以明确:
- 堆向上增长,随着创建申请空间,空间地址逐渐变大。
- 栈向下增长,随着创建变量,变量空间地址逐渐变小。
const char*的最字符串常量地址与正文代码的地址相近,说明在编译的时候会将该类型硬编到正文代码中,所以形成了代码只可读。- 函数内部的
static类型的变量地址与初始化数据中全局变量地址相近,因为static类型的变量在编译时就会在初始化数据区域,所以就会作为全局变量。则static是全局属性。

虚拟地址
实际上程序的地址空间是内存吗?
地址空间不是内存地址,而是虚拟地址!
在语言层上,我们会叫做程序地址空间。但是在系统层面上,会将其称为进程地址空间或者虚拟地址空间。
可以通过以下代码来验证:
#include<stdio.h>
#include <unistd.h>int gval = 100;int main()
{pid_t id = fork();if (id == 0){while (1){printf("子: gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid());sleep(1);gval++;}}else{while (1){printf("父: gval: %d, &gval: %p, pid: %d, ppid: %d\n", gval, &gval, getpid(), getppid());sleep(1);}}return 0;
}
结果如图:

父子进程按照代码逻辑进行运行,随着每次睡眠过后子进程的全局变量就会+1。但是通过结果可以发现,父进程和子进程各自的全局变量地址都是0x601054。明明是同一个地址空间,为什么全局变量gval的值不同呢?
这就证明了,进程的地址空间一定不是内存地址,不是物理上的地址,而是虚拟地址!我们在程序中使用指针指向的地址,以及取变量地址等操作,实际上都是在访问虚拟地址。
物理地址一般不会向用户展示,操作系统会将虚拟地址转化成物理地址,虚拟地址由操作系统统一管理。

进程地址空间
基础概念

每个进程都有其虚拟地址空间mm_struct和页表存在于task_struct中,每个地址空间1字节。所以对于32位的机器,在虚拟地址空间中共有2^32个地址空间,64位机器则有2^32地址空间。
页表中存储的是虚拟地址和物理地址的映射关系。
程序在运行时实际上管理的是虚拟地址空间中的地址,当程序需要进行管理一个地址的时候,操作系统会将该地址在页表中进行查找,就可以得到与其对应映射的物理内存地址。然后操作系统会对物理内存地址的数据进行访问管理。
子进程会继承父进程的虚拟地址空间和页表。
如何通过一个字节地址访问多个字节大小的数据?

通过地址和类型偏移量确定整个数据。
假设存在一个int变量a,当我们通过虚拟地址空间的映射找到物理地址后,会通过int类型在结构体中的偏移量进行确定整个数据内容,因为所有的数据都是通过先描述后组织进行管理,通过对应的数据结构就可以确定数据的位置。
进程如何独立

子进程的虚拟地址空间和页表会继承父进程,那么进程之间是怎么独立的呢?
假设父进程存在一全局变量int g_val,在当前父进程虚拟地址在页表中已经与物理地址映射。然后创建子进程,当子进程中尝试对g_val修改时操作系统会进行以下操作:
- 在物理地址空间中会重新开辟一块
int大小的空间,在此空间内存储修改后的地址。 - 在页表中查询子进程虚拟地址空间中
g_val虚拟地址,然后将新开辟的物理地址与虚拟地址重新建立映射关系。 - 此时,因为继承的关系,父进程与子进程中的
g_val使用的是同一个虚拟地址,但由于子进程对g_val进行修改,所以同一个变量的虚拟地址映射的是不同的物理地址。
这就是写时拷贝的机制!!
所以发生写时拷贝后,子进程对于修改的数据会重新构建映射关系,而其他的数据、代码、变量等都是共享的物理资源,这也避免了重复拷贝的内存的浪费,减少创建时间。
**通过这种机制就形成了进程的独立! **

虚拟地址与进程地址空间关系
通过上文可知,对于32位的机器来说,每个进程的虚拟地址空间有2^32字节的大小,也就是4个G。但如果整个内存只有4G的话,那么一个进程就要把所有的内存空间全部占满吗?显然不可能。
如其名,虚拟地址空间并不是真正的内存空间。操作系统会让每个进程都认为他们可以独占物理内存,但是在实际使用的时候会根据真实的需求通过映射关系开辟内存空间。
虚拟地址空间如何从物理内存划分
由于进程不会独占物理内存,那么肯定有相对应的划分管理方法。
虚拟地址的本质:结构体对象,数据结构!
mm_struct中存储的起始地址和结束地址用int表示。- 每个区域的范围是
[start_address, end_address],这些地址用int记录下来。例如:
struct mm_struct {int code_start; // 代码段起始地址int code_end; // 代码段结束地址int heap_start; // 堆起始地址int heap_end; // 堆结束地址int stack_start; // 栈起始地址int stack_end; // 栈结束地址
};
虚拟地址通常是用 int(4字节,32位) 类型存储的,而每个 int 值就直接对应一个地址。虚拟地址空间中的地址可以用一个 int 值表示,因为 int 的取值范围足够覆盖整个虚拟地址空间的范围(0 ~ 232−12^{32} - 1232−1,即 4GB)。
在32为机器中虚拟地址由2^32字节空间,每个区域(栈,堆…)都有自己确定的区域,然后堆所有的区域进行编址。虚拟地址空间就是结构体mm_struct,里面存放的就是每个区域的起始地址和结束地址对应的int值。
区域调整
既然每个区域的大小是用int值进行确定,那么当需要对这个区域大小进行调整的时候,区域调整就是对起始和结束的整数范围进行调整。
根据各个区域的特性,例如堆向上增长,栈向下增长,将其对应的start和end进行+或者—,以此来进行区域的调整。
小结:虚拟地址空间是什么
操作系统需要对进程中的虚拟地址空间进行管理,虚拟地址空间是内核中的一种数据结构mm_struct,大部分属性都是各个区域的开始和结束地址的int值。
先描述,在组织。作为数据结构,操作系统不仅会对进程进行管理,也会对mm_struct进行管理,用链表进行管理。但是实际上通过PCB也可以直接访问到mm_struct。

相关文章:
[操作系统] 深入进程地址空间
程序地址空间回顾 在C语言学习的时,对程序的函数、变量、代码等数据的存储有一个大致的轮廓。在语言层面上存储的地方叫做程序地址空间,不同类型的数据有着不同的存储地址。 下图为程序地址空间的存储分布和和特性: 使用以下代码来验证一下…...
CVE-2025-0411 7-zip 漏洞复现
文章目录 免责申明漏洞描述影响版本漏洞poc漏洞复现修复建议 免责申明 本文章仅供学习与交流,请勿用于非法用途,均由使用者本人负责,文章作者不为此承担任何责任 漏洞描述 此漏洞 (CVSS SCORE 7.0) 允许远程攻击者绕…...
leetcode151-反转字符串中的单词
leetcode 151 思路 时间复杂度:O(n) 空间复杂度:O(n) 首先将字符串转为数组,这样可以方便进行操作,然后定义一个新的数组来存放从后到前的单词,由于arr中转换以后可能会出现有些项是空格的情况,所以需要判…...
若依 v-hasPermi 自定义指令失效场景
今天使用若依跟往常一样使用v-hasPermi 自定义指令的时候发现这个指令失效了,原因是和v-if指令一块使用,具体代码如下: <el-buttonsize"mini"type"text"icon"el-icon-edit-outline"v-hasPermi"[evalu…...
vue3中自定一个组件并且能够用v-model对自定义组件进行数据的双向绑定
1. 基础用法 在 Vue3 中,v-model 在组件上的使用有了更灵活的方式。默认情况下,v-model 使用 modelValue 作为 prop,update:modelValue 作为事件。 1.1 基本示例 <!-- CustomInput.vue --> <template><input:value"mo…...
使用 Python 和 Tesseract 实现验证码识别
验证码识别是一个常见且实用的技术需求,尤其是在自动化测试和数据采集场景中。通过开源 OCR(Optical Character Recognition,光学字符识别)工具 Tesseract,结合 Python 的强大生态,我们可以高效实现验证码识…...
谈一谈前端构建工具的本地代理配置(Webpack与Vite)
在Web前端开发中,我们在本地写代码经常遇到的一件事情就是代理配置。代理配置说简单也简单,配置一次基本就一劳永逸,但有时候配置不对,无论如何也连不上后端,就成了非常头疼的一件事。在这本文中,我们讨论一…...
CentOS7非root用户离线安装Docker及常见问题总结、各种操作系统docker桌面程序下载地址
环境说明 1、安装用户有sudo权限 2、本文讲docker组件安装,不是桌面程序安装 3、本文讲离线安装,不是在线安装 4、目标机器是内网机器,与外部网络不连通 下载 1、下载离线安装包,并上传到$HOME/basic-tool 目录 下载地址&am…...
Alibaba Spring Cloud 十三 Nacos,Gateway,Nginx 部署架构与负载均衡方案
在微服务体系中,Nacos 主要承担“服务注册与发现、配置中心”的职能,Gateway(如 Spring Cloud Gateway)通常负责“路由转发、过滤、安全鉴权、灰度流量控制”等功能,而 Nginx 则常被用作“边缘反向代理”或“统一流量入…...
+-*/运算符优先级计算模板
acwing3302 知识点一:有关unordered_map的优先级 头文件<unordered_map>,然后进行符号优先级定义 定义方式unordered_map<char,int>pr{ {,1},{-,1},{*,2},{/,2}};其余没定义的默认为0 知识点二:头文件<cctype>中的isdigit()是判断…...
GPT 结束语设计 以nanogpt为例
GPT 结束语设计 以nanogpt为例 目录 GPT 结束语设计 以nanogpt为例 1、简述 2、分词设计 3、结束语断点 1、简述 在手搓gpt的时候,可能会遇到一些性能问题,即关于是否需要全部输出或者怎么节约资源。 在输出语句被max_new_tokens 限制,…...
FastDFS的安装及使用
分布式存储发展历程 前段时间 618 活动火热进行,正是购物的好时机。当我们访问这些电 商网站的时候,每一个商品都会有各式各样的图片展示介绍,这些图 片一张两张可以随便丢在服务器的某个文件夹中,可是电商网站如此 大体量的…...
C++ lambda表达式
目录 1.lambda表达式 1.1什么是Lambda表达式? 1.2Lambda表达式的语法 1.3捕捉列表 1.4函数对象与lambda表达式 1.lambda表达式 1.1什么是Lambda表达式? Lambda表达式是C11标准引入的一种匿名函数,它允许你在需要函数的地方直接编写代码…...
react页面定时器调用一组多个接口,如果接口请求返回令牌失效,清除定时器不再触发这一组请求
为了实现一个React页面使用定时器调用一组多个接口,并在任意一个接口请求返回令牌失效时清除定时器且不再触发这一组请求,可以遵循以下步骤: 1. 定义API调用函数:创建一个函数来处理一组API调用。每个API调用都应该检查响应状态以…...
Python的泛型(Generic)与协变(Covariant)
今天咱们聊聊Python类型标注中的泛型(Generic),与协变(Covariant)。 不了解类型标注的小伙伴,可以先看一看我的上一篇文章 “Python类型检查” Python 类型检查-CSDN博客 例子 这次我开个宠物商店。看下面代码。 class Animal:passclass Dog(Animal):passclass Cat(A…...
Python Typing: 实战应用指南
文章目录 1. 什么是 Python Typing?2. 实战案例:构建一个用户管理系统2.1 项目描述2.2 代码实现 3. 类型检查工具:MyPy4. 常见的 typing 用法5. 总结 在 Python 中,静态类型检查越来越受到开发者的重视。typing 模块提供了一种方式…...
OpenEuler学习笔记(六):OpenEuler与其他Linux服务器的区别是什么?
OpenEuler是一款基于Linux内核的开源服务器操作系统,与其他Linux服务器操作系统(如CentOS、Ubuntu Server等)存在多方面的区别,主要体现在以下几个方面: 一、社区与支持 OpenEuler:由华为发起并开源&…...
如何使用CRM数据分析和洞察来支持业务决策和市场营销?
如何使用CRM数据分析和洞察来支持业务决策和市场营销? 大家好!今天咱们聊聊一个特别重要的话题——如何利用客户关系管理(CRM)系统中的数据进行分析与洞察能够帮助我们做出更好的业务决策以及提升市场营销效果。其实啊࿰…...
MyBatis和JPA区别详解
文章目录 MyBatis和JPA区别详解一、引言二、设计理念与使用方式1、MyBatis:半自动化的ORM框架1.1、代码示例 2、JPA:全自动的ORM框架2.1、代码示例 三、性能优化与适用场景1、MyBatis:灵活的SQL控制1.1、适用场景 2、JPA:开发效率…...
SVN客户端使用手册
目录 一、简介 二、SVN的安装与卸载 1. 安装(公司内部一般会提供安装包和汉化包,直接到公司内部网盘下载即可,如果找不到可以看下面的教程) 2. 查看SVN版本 编辑 3. SVN卸载 三、SVN的基本操作 1. 检出 2. 清除认证数据 3. 提交…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...
