[Linux]进程地址空间

news2025/1/14 1:06:00

🥁作者华丞臧.
📕​​​​专栏:【LINUX】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站


文章目录

  • 前言
  • 一、进程地址空间
    • 1.1 程序地址空间回顾
    • 1.2 地址空间的存在
    • 1.3 进程地址空间
    • 1.4 程序如何变成进程?
    • 1.5 如何理解进程的独立性
    • 1.6 为什么要有虚拟进程地址空间?


前言

在之前的学习中,我们通常知道代码和数据存放在程序地址空间不同的几个区域,如:代码区、常量区、静态区、堆区、栈区等;那么我们说的这个地址空间是内存吗?

一、进程地址空间

1.1 程序地址空间回顾

在这里插入图片描述
前面粗略地学习过程序地址空间,更准确地应该称为进程地址空间;进程地址空间不是C/C++是操作系统上概念,并且进程地址空间不是内存。

//进程地址空间的验证
#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[])
{
    char *m1 = (char*)malloc(sizeof(char));
    char *m2 = (char*)malloc(sizeof(char));
    char *m3 = (char*)malloc(sizeof(char));
    char *m4 = (char*)malloc(sizeof(char));
    printf("code addr           : %p\n", main);
    printf("init global addr    : %p\n", &g_val);
    printf("uninit global  addr : %p\n", &un_g_val);
    printf("heap addr           : %p\n", m1);
    printf("heap addr           : %p\n", m2);
    printf("heap addr           : %p\n", m3);
    printf("heap addr           : %p\n", 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);
    printf("argv addr           : %p\n", &argv);
    
    int i = 0;
    for(;env[i]; ++i)
    {
        printf("env addr           : %p\n", &env[i]);
    }

    return 0;
}

堆区向地址增大的方向增长,栈区向地址减小的方向增长;一般在C函数中定义的变量,通常在栈上保存,那么先定义的一定是地址较高的。
在这里插入图片描述

如何理解static变量?

函数内定义的变量用static修饰,本质是编译器会把该变量编译进全局数据区。

1.2 地址空间的存在

首先来看下面这一段代码:

//code1.c
#include <stdio.h>
#include <unistd.h>

int g_val = 100;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        int i = 0;
        //child
        while(1)
        {
            printf("我是子进程:%d,ppid:%d,g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
            ++i;
            if(i == 5)
            {
                printf("我是子进程,全局变量已被修改\n");
                g_val = 200;
            }
        }
    }
    else if(id > 0)
    {
        //parent
        while(1)
        {
            printf("我是父进程:%d,ppid:%d,g_val:%d,&g_val:%p\n", getpid(), getppid(), g_val, &g_val);
            sleep(3);
        }
    }
    return 0;
}

当父子进程没有修改全局变量时,如下图:我们发现父子进程的全局变量数据一模一样并且地址相同,那么可以确定父子进程是共享该数据的。
在这里插入图片描述

当子进程修改全局数据时,如下图所示:我们发现子进程的全局数据修改并没有影响父进程的全局数据,但是父子进程全局数据的地址是相同的,显然同一块空间不可能存放两个不同的值,所以我们可以肯定在C/C++中使用的地址都不是物理地址
在这里插入图片描述

通过上述代码实现,我们可以感觉到实际上物理内存和我们的操作系统之间隔了一层地址,这个地址是虚拟地址,是一种线性地址逻辑地址。

为什么操作系统不让用户直接访问物理内存呢?

内存是一个硬件,不能拦截用户访问,只能被动的进行读取和写入,让用户直接访问存在风险。

1.3 进程地址空间

进程地址空间不是物理内存,它是一种虚拟地址,介于操作系统和物理内存之间;那么该如何理解呢?

首先我们需要知道虚拟地址并不是真正存在的物理空间,而是逻辑地址是操作系统给进程画的蓝图;简单来说就是操作系统给进程画饼说你可以独占所有资源,进程可以随意使用,而且进程之间并不知道对方存在。
32位的机器上进程地址空间大小为4G,而实际上我们自己编写出的进程不会使用4G的进程地址空间,而是每次都申请进程需要的空间大小。

操作系统如何画饼呢?画饼本质是在你的大脑中描绘蓝图,而在操作系统中描述某种对象通常使用数据结构。
结论:进程地址空间本质是内核的一种数据结构。
每个进程都有一个自己的进程地址空间,操作系统通过其数据结构管理这些进程地址空间,管理的本质是先描述再组织,因此进程地址空间被操作系统中的struct mm_struct描述再被链表组织,此时进程地址空间就能被操作系统管理。实际上,进程PCB中有指向进程地址空间的指针即PCB中有特定的指针指向进程的mm_struct。

//mm_struct实际上是描述进程地址空间是对其空间各个区域大小的描述
//类似下面这种方式
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;
}

进程地址空间的好处:

  1. 虚拟地址空间会让进程认为自己独占操作系统的资源,进程之间并不知道对方的存在;
  2. 所有进程都可以用统一的方式进程描述,因为虚拟地址空间是一样的,方便操作系统管理进程。

1.4 程序如何变成进程?

所谓进程地址空间其实就是操作系统通过软件的方式,给进程提供一个软件视角,认为自己会独占系统所有资源(主要指内存)。
那么程序如何变成进程的呢?操作系统又是如何通过进程地址空间运行进程的呢?

首先,程序本质是磁盘上的文件,即编译出来的可执行文件;程序内部肯定有地址,也肯定划分了区域,理由是一个代码经过编译形成可执行文件需要四个阶段:预编译、编译、汇编、链接,其中汇编会将程序中的全局符号及其地址汇总成一个一个的符号表,各个符号表在链接时合并。
其次在LInux中,通过指令可以查看可执行文件的内容,如下图:

在这里插入图片描述

虚拟地址空间不仅仅是操作系统会考虑,编译器也会考虑,编译程序的时候就认为程序是按照0000~FFFF进行编址的。程序内部的地址可以看做是虚拟地址空间,和内存的地址没有关系。

  1. 内存地址是物理地址;
  2. 进程地址空间是虚拟地址;
  3. 代码和数据是加载到内存中的;
  4. 操作系统管理进程通过PCB来管理;
  5. 操作系统管理进程地址空间通过mm_struct来管理;

因此操作系统要执行进程代码获得进程的数据,可以通进程PCB找到进程mm_struct,通过mm_struct操作系统可以执行进程的代码也可读取进程的数据,进程地址空间通过页表和实际内存地址相关联,通过页表可以找到进程在内存中的代码和数据。

在Linux当中,映射进程地址空间和内存地址的表称为页表。

在这里插入图片描述

1.5 如何理解进程的独立性

在前面的测试当中,fork创建子进程,父子进程同时运行,同样地址的全局变量,子进程改变全局变量,父进程不会改变。
在这里插入图片描述
这体现了进程的独立性,即多进程运行,独享个中资源,运行期间进程互不干扰。

当我们使用fork创建子进程时,子进程会继承父进程的一切包括进程控制块PCB(task_struct),也包括mm_struct和页表,因此子进程也会继承mm_struct和页表;那么子进程就有了自己的PCB并且该PCB与父进程相同。
这也就是为什么我们看到开始时,父子进程全局变量的值和地址都相同。
在这里插入图片描述

当有进程试图改变g_val时,该进程就进行写时拷贝,即:任何一方尝试写入,操作系统先进行数据拷贝,更改该进程的页表映射,然后再让进程进行修改。(这就是写时拷贝

在这里插入图片描述

1.6 为什么要有虚拟进程地址空间?

1. 保护内存,访问内存添加了一层软硬件层,可以对转化过程进行审核,非法访问,就可以直接拦截了。
2. 进程和进程代码数据的解耦,通过地址空间进行功能模块的解耦,即进程管理和内存管理解耦。
3. 让进程或者程序可以以一种统一的视角看待内存,方便以统一的方式来编译和加载所有的可执行程序,简化进程本身的设计与实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/178802.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

谁你的财神 谁是你的穷神

送穷神&#xff0c;迎灶神&#xff0c;下午提前准备迎接财神 我们说一个人穷&#xff0c;揭不开锅了&#xff0c;只能喝凉水了&#xff0c;到后来只能喝西北风 谁是我们的财神&#xff0c;信任我们的人&#xff0c;帮助我们的人&#xff0c;感谢过往贵人的资助 但是信任是不对…

【数据结构】算法复杂度

文章目录引入算法复杂度一.时间复杂度定义大O渐进表示法经典例题常量字符串二分查找冒泡排序递归1.阶乘2.斐波切纳数列二.空间复杂度定义经典例题冒泡排序递归1.阶乘2.斐波切纳数列引入 为什么要有算法复杂度&#xff1f; 当我们正在解决一个问题的时候&#xff0c;想出了多种思…

【计算机基础】操作系统

前言&#xff1a;本文适合用于基础了解和背诵&#xff0c;尽可能采用流畅且简短的语言回答操作系统相关问题并且辅以图片帮助记忆&#xff0c;不适合用作详细了解与深入学习 。 总述 操作系统部分主要分为以下几个重点 —— 操作系统基础 、 进程和线程、操作系统的内存管理、…

JAVA基础知识07常用API

目录 1. 常用API 1.1 简介 1.2 String类 1.2.1 字符串在开发中的应用场景 1.2.2 String 类的特点 1.2.3 String 类的常见构造方法 1.2.4 String 类的常见面试题 1.2.5 面试题题解 1.2.6 String 类用于比较的方法 1.2.7 String 字符串的遍历 1.2.8 String 字符串的截取…

QByteArray字节流和二进制与字符的关系以及tohex() toLatin1

QByteArray 存储的是字节&#xff0c;二进制形式&#xff0c;即ascii码的二进制编码。输出的时候&#xff0c;会输出二进制对应的字符 即一个映射: 二进制到ascii码的映射而.tohex()会将二进制转化为16进制字符&#xff0c;这里的16进制字符又是作为值域了&#xff0c;实际存…

JavaEE-文件和IO(二)

目录2.2 文件内容相关的操作三、文件操作案例3.1 案例一3.2 案例二3.3 案例三2.2 文件内容相关的操作 打开文件读文件写文件关闭文件 针对文件内容的读写&#xff0c;java标准库提供了一组类~ 首先按照文件的内容&#xff0c;分为两个系列 字节流对象&#xff0c;针对二进制…

手把手教你写web全栈入门项目—React+Koa+MongoDB(3w字教程,真的很详细,有代码)

手把手教你写web全栈入门项目—ReactKoaMongoDB 文章目录手把手教你写web全栈入门项目—ReactKoaMongoDB前言一、推荐基础二、所需环境三、软件四、项目源码五、文章结构六、遇到问题怎么办前端一、页面登录页首页二、目录结构三、技术选择四、开始项目1、页面组件1.1 目录1.2 …

JavaScript 所见所得文本编辑器 Froala Editor 4.0.17Crack

Froala Editor v4.0.17 清除格式工具现在可以从粘贴的内容中删除内联样式。 2023 年 1 月 24 日 - 9:07新版本 特征 清除格式工具现在可以从粘贴的内容中删除内联样式。 改进的“删除时保留格式”功能可保留已删除文本的格式并将其应用于替换文本。 选择图像时&#xff0c;用于…

day20|77. 组合。回溯的开始

回溯思路 void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&#xff1a;本层集合中元素&#xff08;树中节点孩子的数量就是集合的大小&#xff09;) {处理节点;backtracking(路径&#xff0c;选择列表); // 递归回溯&#xff0c;撤销处理结果} } 77. 组合…

91.使用注意力机制的seq2seq以及代码实现

1. 动机 2. 加入注意力 key和value是一样的 假设英语句子长为3的话&#xff0c;就会有3个key-value pair&#xff0c;key和vlaue是一个东西&#xff0c;每一个key-value pair对应第i个词的RNN的输出。之前的seq2seq只使用了最后的key-value&#xff0c;现在则是把所有的key-val…

JavaWeb语法八:网络原理初识

目录 1.局域网与广域网 1.1&#xff1a;局域网 1.2&#xff1a;广域网 2&#xff1a;网络基础知识 3.协议分层 3.1&#xff1a;分层的好处 3.2&#xff1a;TCP/IP五层&#xff08;或四层&#xff09;模式 4&#xff1a;封装和分用 4.1&#xff1a;封装 4.2&#xff1…

MyBatisPlus入门简介

目录 1. 入门案例 问题导入 1.1 SpringBoot整合MyBatisPlus入门程序 2. MyBatisPlus概述 问题导入 2.1 MyBatis介绍​​​​​​​ 1. 入门案例 问题导入 MyBatisPlus环境搭建的步骤&#xff1f; 1.1 SpringBoot整合MyBatisPlus入门程序 ①&#xff1a;创建新模块&am…

P3368 【模板】树状数组 2

【模板】树状数组 2 题目描述 如题&#xff0c;已知一个数列&#xff0c;你需要进行下面两种操作&#xff1a; 将某区间每一个数加上 xxx&#xff1b; 求出某一个数的值。 输入格式 第一行包含两个整数 NNN、MMM&#xff0c;分别表示该数列数字的个数和操作的总个数。 第…

[Ext JS] Grid Summary(汇总行)特性

Ext.grid.feature.Summary 是 Grid 的feature之一。 这个特性会在表格的最下方多一行汇总。 汇总行主要包含两个部分: 值的计算效果的渲染使用后的效果如下: 定义方式 定义的步骤如下: 在grid的配置中使用features 加入 summary 的特性类型 ftype: summary在columns的每一列…

千峰学习【Ajax】总结

1.同步和异步 2.Ajax状态码 3.创建对象&#xff0c;发送请求 <script>//1.创建XHR&#xff1a; new XMLHttpRequest():var xhr new XMLHttpRequest();// console.log(xhr);//2&#xff0c;配置open(请求方式&#xff0c;请求地址&#xff0c;是否异步(默认为异步)) loc…

ESP32( IDF平台)+MAX30102 配合Pyqt上位机实现PPG波形显示与心率计算

0 引言 年前买了一个MAX30102模块&#xff0c;在家无聊做了这个demo对一些相关的知识进行学习。 主要学习的内容&#xff1a; 光体积变化描记图&#xff08;Photoplethysmogram, PPG&#xff09;测量原理学习。ESP32 IDF平台的MAX30102驱动开发&#xff0c;主要是初始化配置…

8、快捷键的使用

文章目录8、快捷键的使用8.1 常用快捷键第1组&#xff1a;通用型第2组&#xff1a;提高编写速度&#xff08;上&#xff09;第3组&#xff1a;提高编写速度&#xff08;下&#xff09;第4组&#xff1a;类结构、查找和查看源代码第5组&#xff1a;查找、替换与关闭第6组&#x…

理光M340W激光打印机加粉清零

粉盒型号&#xff1a; M340L&#xff08;如图&#xff09;&#xff1a; 加粉及清零&#xff1a; 原装粉盒不用考虑加粉了&#xff0c;原装粉盒墨粉用完后建议更换品牌代用的墨粉盒&#xff0c;品牌代用的墨粉盒直接带加粉口及清零齿轮&#xff1b; 1、加粉&#xff0c;打开加粉…

通信原理简明教程 | 物联网通信技术简介

文章目录1 物联网通信技术概述1.1 物联网通信的产生和发展1.2 物联网通信系统2 RFID技术2.1 RFID系统的组成2.2 RFID系统的工作原理2.3 RFID的典型应用3 ZigBee技术3.1 ZigBee技术的特点及应用3.2 ZigBee协议3.3 ZigBee网络的拓扑结构4 蓝牙通信技术4.1 蓝牙协议4.2 蓝牙网络连…

缓存失效问题和分布式锁引进

缓存失效问题 先来解决大并发读情况下的缓存失效问题&#xff1b; 1、缓存穿透  缓存穿透是指查询一个一定不存在的数据&#xff0c;由于缓存是不命中&#xff0c;将去查询数据库&#xff0c;但是数 据库也无此记录&#xff0c;我们没有将这次查询的 null 写入缓存&#x…