当前位置: 首页 > news >正文

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

程序地址空间回顾

在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

通过地址结果的验证可以明确:

  1. 堆向上增长,随着创建申请空间,空间地址逐渐变大。
  2. 栈向下增长,随着创建变量,变量空间地址逐渐变小。
  3. const char*的最字符串常量地址与正文代码的地址相近,说明在编译的时候会将该类型硬编到正文代码中,所以形成了代码只可读。
  4. 函数内部的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修改时操作系统会进行以下操作:

  1. 在物理地址空间中会重新开辟一块int大小的空间,在此空间内存储修改后的地址。
  2. 在页表中查询子进程虚拟地址空间中g_val虚拟地址,然后将新开辟的物理地址与虚拟地址重新建立映射关系。
  3. 此时,因为继承的关系,父进程与子进程中的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值进行确定,那么当需要对这个区域大小进行调整的时候,区域调整就是对起始和结束的整数范围进行调整。

根据各个区域的特性,例如堆向上增长,栈向下增长,将其对应的startend进行+或者,以此来进行区域的调整。

小结:虚拟地址空间是什么

操作系统需要对进程中的虚拟地址空间进行管理,虚拟地址空间是内核中的一种数据结构mm_struct大部分属性都是各个区域的开始和结束地址的int值。

先描述,在组织。作为数据结构,操作系统不仅会对进程进行管理,也会对mm_struct进行管理,用链表进行管理。但是实际上通过PCB也可以直接访问到mm_struct

相关文章:

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

程序地址空间回顾 在C语言学习的时&#xff0c;对程序的函数、变量、代码等数据的存储有一个大致的轮廓。在语言层面上存储的地方叫做程序地址空间&#xff0c;不同类型的数据有着不同的存储地址。 下图为程序地址空间的存储分布和和特性&#xff1a; 使用以下代码来验证一下…...

CVE-2025-0411 7-zip 漏洞复现

文章目录 免责申明漏洞描述影响版本漏洞poc漏洞复现修复建议 免责申明 本文章仅供学习与交流&#xff0c;请勿用于非法用途&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任 漏洞描述 此漏洞 &#xff08;CVSS SCORE 7.0&#xff09; 允许远程攻击者绕…...

leetcode151-反转字符串中的单词

leetcode 151 思路 时间复杂度&#xff1a;O(n) 空间复杂度&#xff1a;O(n) 首先将字符串转为数组&#xff0c;这样可以方便进行操作&#xff0c;然后定义一个新的数组来存放从后到前的单词&#xff0c;由于arr中转换以后可能会出现有些项是空格的情况&#xff0c;所以需要判…...

若依 v-hasPermi 自定义指令失效场景

今天使用若依跟往常一样使用v-hasPermi 自定义指令的时候发现这个指令失效了&#xff0c;原因是和v-if指令一块使用&#xff0c;具体代码如下&#xff1a; <el-buttonsize"mini"type"text"icon"el-icon-edit-outline"v-hasPermi"[evalu…...

vue3中自定一个组件并且能够用v-model对自定义组件进行数据的双向绑定

1. 基础用法 在 Vue3 中&#xff0c;v-model 在组件上的使用有了更灵活的方式。默认情况下&#xff0c;v-model 使用 modelValue 作为 prop&#xff0c;update:modelValue 作为事件。 1.1 基本示例 <!-- CustomInput.vue --> <template><input:value"mo…...

使用 Python 和 Tesseract 实现验证码识别

验证码识别是一个常见且实用的技术需求&#xff0c;尤其是在自动化测试和数据采集场景中。通过开源 OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;工具 Tesseract&#xff0c;结合 Python 的强大生态&#xff0c;我们可以高效实现验证码识…...

谈一谈前端构建工具的本地代理配置(Webpack与Vite)

在Web前端开发中&#xff0c;我们在本地写代码经常遇到的一件事情就是代理配置。代理配置说简单也简单&#xff0c;配置一次基本就一劳永逸&#xff0c;但有时候配置不对&#xff0c;无论如何也连不上后端&#xff0c;就成了非常头疼的一件事。在这本文中&#xff0c;我们讨论一…...

CentOS7非root用户离线安装Docker及常见问题总结、各种操作系统docker桌面程序下载地址

环境说明 1、安装用户有sudo权限 2、本文讲docker组件安装&#xff0c;不是桌面程序安装 3、本文讲离线安装&#xff0c;不是在线安装 4、目标机器是内网机器&#xff0c;与外部网络不连通 下载 1、下载离线安装包&#xff0c;并上传到$HOME/basic-tool 目录 下载地址&am…...

Alibaba Spring Cloud 十三 Nacos,Gateway,Nginx 部署架构与负载均衡方案

在微服务体系中&#xff0c;Nacos 主要承担“服务注册与发现、配置中心”的职能&#xff0c;Gateway&#xff08;如 Spring Cloud Gateway&#xff09;通常负责“路由转发、过滤、安全鉴权、灰度流量控制”等功能&#xff0c;而 Nginx 则常被用作“边缘反向代理”或“统一流量入…...

+-*/运算符优先级计算模板

acwing3302 知识点一&#xff1a;有关unordered_map的优先级 头文件<unordered_map>,然后进行符号优先级定义 定义方式unordered_map<char,int>pr{ {,1},{-,1},{*,2},{/,2}};其余没定义的默认为0 知识点二&#xff1a;头文件<cctype>中的isdigit()是判断…...

GPT 结束语设计 以nanogpt为例

GPT 结束语设计 以nanogpt为例 目录 GPT 结束语设计 以nanogpt为例 1、简述 2、分词设计 3、结束语断点 1、简述 在手搓gpt的时候&#xff0c;可能会遇到一些性能问题&#xff0c;即关于是否需要全部输出或者怎么节约资源。 在输出语句被max_new_tokens 限制&#xff0c…...

FastDFS的安装及使用

分布式存储发展历程 前段时间 618 活动火热进行&#xff0c;正是购物的好时机。当我们访问这些电 商网站的时候&#xff0c;每一个商品都会有各式各样的图片展示介绍&#xff0c;这些图 片一张两张可以随便丢在服务器的某个文件夹中&#xff0c;可是电商网站如此 大体量的…...

C++ lambda表达式

目录 1.lambda表达式 1.1什么是Lambda表达式&#xff1f; 1.2Lambda表达式的语法 1.3捕捉列表 1.4函数对象与lambda表达式 1.lambda表达式 1.1什么是Lambda表达式&#xff1f; Lambda表达式是C11标准引入的一种匿名函数&#xff0c;它允许你在需要函数的地方直接编写代码…...

react页面定时器调用一组多个接口,如果接口请求返回令牌失效,清除定时器不再触发这一组请求

为了实现一个React页面使用定时器调用一组多个接口&#xff0c;并在任意一个接口请求返回令牌失效时清除定时器且不再触发这一组请求&#xff0c;可以遵循以下步骤&#xff1a; 1. 定义API调用函数&#xff1a;创建一个函数来处理一组API调用。每个API调用都应该检查响应状态以…...

Python的泛型(Generic)与协变(Covariant)

今天咱们聊聊Python类型标注中的泛型(Generic),与协变(Covariant)。 不了解类型标注的小伙伴,可以先看一看我的上一篇文章 “Python类型检查” Python 类型检查-CSDN博客 例子 这次我开个宠物商店。看下面代码。 class Animal:passclass Dog(Animal):passclass Cat(A…...

Python Typing: 实战应用指南

文章目录 1. 什么是 Python Typing&#xff1f;2. 实战案例&#xff1a;构建一个用户管理系统2.1 项目描述2.2 代码实现 3. 类型检查工具&#xff1a;MyPy4. 常见的 typing 用法5. 总结 在 Python 中&#xff0c;静态类型检查越来越受到开发者的重视。typing 模块提供了一种方式…...

OpenEuler学习笔记(六):OpenEuler与其他Linux服务器的区别是什么?

OpenEuler是一款基于Linux内核的开源服务器操作系统&#xff0c;与其他Linux服务器操作系统&#xff08;如CentOS、Ubuntu Server等&#xff09;存在多方面的区别&#xff0c;主要体现在以下几个方面&#xff1a; 一、社区与支持 OpenEuler&#xff1a;由华为发起并开源&…...

如何使用CRM数据分析和洞察来支持业务决策和市场营销?

如何使用CRM数据分析和洞察来支持业务决策和市场营销&#xff1f; 大家好&#xff01;今天咱们聊聊一个特别重要的话题——如何利用客户关系管理&#xff08;CRM&#xff09;系统中的数据进行分析与洞察能够帮助我们做出更好的业务决策以及提升市场营销效果。其实啊&#xff0…...

MyBatis和JPA区别详解

文章目录 MyBatis和JPA区别详解一、引言二、设计理念与使用方式1、MyBatis&#xff1a;半自动化的ORM框架1.1、代码示例 2、JPA&#xff1a;全自动的ORM框架2.1、代码示例 三、性能优化与适用场景1、MyBatis&#xff1a;灵活的SQL控制1.1、适用场景 2、JPA&#xff1a;开发效率…...

SVN客户端使用手册

目录 一、简介 二、SVN的安装与卸载 1. 安装&#xff08;公司内部一般会提供安装包和汉化包&#xff0c;直接到公司内部网盘下载即可&#xff0c;如果找不到可以看下面的教程&#xff09; 2. 查看SVN版本 ​编辑 3. SVN卸载 三、SVN的基本操作 1. 检出 2. 清除认证数据 3. 提交…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...