从0开始linux(13)——进程(4)进程空间地址(1)

欢迎来到博主的专栏:从0开始linux
博主ID:代码小豪

文章目录

    • 进程空间地址

还记得博主在之前介绍子进程时说过的话吗?子进程与父进程共享代码,而数据却不共享;这很好理解,因为子进程和父进程是不同的进程,根据进程的独立性,子进程和父进程不能相互影响,所以数据是不能互通的,但是这个观点我们好像一直没有去证实过,现在我们来写一个如下的代码,以证明博主所言非虚。

#include<stdio.h>
#include<unistd.h>
int gval=100;//全局变量
int main()
{
    pid_t ID=fork();
    if(ID==0)
    {
		//子进程执行
        gval++;//子进程中修改数据,看看会不会影响父进程
        printf("i'm subprocess PID:%d PPID:%d gval=%d &gval=%p\n",getpid(),getppid(),gval,&gval);
    }
    else if(ID>0)
    {
        //父进程执行
        printf("i'm parent process PID:%d PPID:%d gval=%d &gval=%p\n",getpid(),getppid(),gval,&gval);
    }
    sleep(1);//让进程休眠一秒在关闭,避免看不到子进程的打印信息                                                                                                                                          
    return 0;                                                                              
}  

接着我们运行,查看打印结果。
在这里插入图片描述

可以看到,子进程的gval被修改了,并不会影响到父进程,因为它们的数据不共享。换句话说,父进程中的gval和子进程的gval压根就不是同一个变量。但是我们往后看,咦?父进程和子进程的gval的地址竟然是一致的。
在这里插入图片描述
两个相同地址的变量,其数据一定是共享的,如果一个地址的数据竟然能读出两种结果,这就好像两个人竟然有一样的身份证号一样不可思议!因为我们知道,内存中的数据实际是0和1组成的,而变量显示的数据其实是读取内存空间块的结果,这就说明,在同一个内存空间块中,不会读取出两种数据。

但是事实就摆在眼前,相同地址的变量竟然被读取出了不同的数据,但是实际上,相同地址的变量可以被读取出不同的数据,而在同一个内存块中,不会读取出两种结果。这并非互相矛盾,而是因为:变量的地址都是假的!!!

这里就有人说了,怎么可能地址是假的,我都用指针修改过无数次数据了,地址不可能是假的,别急,我们慢慢来说。

进程空间地址

相信大家在学c\c++的时候都听过这么一句话,全局变量和静态变量位于静态区,局部变量在栈区,而动态管理的内存开辟在堆区,好吧,我们说了这么久这些东西,但是似乎还未真正的去认识它们。

首先,这些分区其实和语言并没有太大关系,而是和进程相关。这些变量都是在进程运行的过程不断被生成或者销毁的,因此这些不同的分区,实际上就是进程空间地址。
在这里插入图片描述
我们从低地址到高地址开始介绍,首先,最底下部分存储的正文代码,比如说函数的地址。往上则是静态区,其中,初始化与未初始化的变量又存在于不同的分区中,堆区主要存储动态内存,随着生成在堆区的数据不断变多,堆区会向上扩展。栈区则是存储局部变量,或者函数栈帧的分区,大部分都是一些临时的数据存储于此。最顶部的数据则是命令行参数和环境变量了,这两也是进程重要的数据。

那么如何证明呢?很简单,我们将对应的变量的地址打印出来呗,看看高低。

#include<stdio.h>
#include<stdlib.h>

int val=100;//声明了的全局变量
int unval;//未声明
int main()
{
    int a=100;
    int* pb=malloc(sizeof(int));
    printf("stack addr:%p\n",&a);//栈区
    printf("heap addr:%p\n",pb);//堆区
    printf("static addr:%p\n",&unval);//静态区的未初始化数据
    printf("static addr:%p\n",&val);//静态区的初始化数据
    printf("code addr%p\n",main);//代码正文区
    return 0;                                                                                                                                                                                          
}

在这里插入图片描述
既然这个进程中的地址是假的,那么我们就将其称为“虚拟地址”吧。

那么为什么进程放着好好的真实地址(物理地址)不用,要用虚拟地址呢?我们先来搞清楚这个虚拟地址到底是什么东东。

我们的物理地址,其实就是存储器当中的内存空间(即硬件),而虚拟地址,则是一个程序(即软件)。在前面不是提到了,一个进程是由task_struct管理的,而在这个task_struct当中有一个成员,名为mm_struct,实际上这个mm_struct就是管理进程虚拟空间的成员。空口无凭,博主在这里放上linux的源码。

struct task_struct
{
	//省略前面的内容
	struct mm_struct *mm,*active_mm;
};

在这里插入图片描述
这个mm_struct本质上就是上图所说的进程空间,即栈区,堆区等等的分区,都在这个mm_struct当中。

我们可以设想一下,这个mm_struct到底是怎么管理进程空间的,比如栈区中的地址,堆区中的地址,到底是什么样的数据,什么样的方法将它们记录下来的。

最容易想到就是用指针来指向每个地址,毕竟指针就是这样用的(笑)。这个方法实际想想就不太可能,以32位的计算机为例,想要将32位计算机的内存物理地址一 一存储起来,就需要创建2^32个指针(因为32计算机的地址是由32个二进制表示的)。而每个指针都有8字节,那么就需要内存4*2*1024*1024*1024(即4*2^32)个字节,这就要16G的内存空间。那么如果要内存分出16个g来管理进程,那么这个进程还要不要运行了?所以关于如何管理进程的内存空间,肯定有更高效的方法。

既然我们连地址都虚拟了,那不如更彻底一点,我们将整个空间都虚拟了,程序说它有一个变量要放在堆区间当中,要操作系统为其开辟一个在堆区的空间,那么操作系统答应下来,但是实际上,这个变量最终还是要放在物理地址(存储器中),而不是放在mm_struct。那么操作系统也就选择放开自我,既然进程想要一个堆区间,那么就满足它,给它一个对区间,这个堆区间是一个连续的区间,只需要用两个指针,来指向堆区间的起始地址,与结束地址就好了!!!
在这里插入图片描述
当进程中的变量要想存储在堆区间,那么就要有对应的地址,操作系统会在heap_start和heap_end之中选一个地址给它,如果堆区间的地址都用完了,就让heap_end往上指,那么新的空间不就又出来了吗?
在这里插入图片描述
这可不是博主在吹牛,而是代码真是这样写的,我们来看看mm_struct的代码。在这里插入图片描述
比如start_brk就是堆区的起始位置,start_stack是栈区的起始位置,这里博主就不挨个说明了,虽然这些数据的类型是unsigned long,但是实际上,这些数据存储的是地址,因为unsigned long和指针类型的变量,存储的数据位数是相同的。

这就说明了,我们在进程中创建的变量,函数以及各种与有地址的东西,它们的地址值都是假的,是操作系统给的虚拟地址,通过mm_struct来管理。

但是不管这些有的没的,进程中的一个变量不管用的是什么虚拟地址,它最终还是放在存储器中,也就是说这个变量一定要有真实的地址值。那么虚拟地址与真实地址的关系是什么?

在虚拟地址与物理地址之间存在一个东西链接着它们,那就是页表。

这个页表会记录一些进程信息,比如说虚拟地址与真实地址之间的映射。
在这里插入图片描述
但是由此,我们好像还没解释完最初的那个东西,即为什么子进程中变量的地址,会与父进程变量的地址一致,这其实也与页表有关,我们已知gval在静态区中。那么在页表中的映射则是这样
在这里插入图片描述
而创建子进程时,操作系统为了省事,直接就将父进程的页表给了子进程用。因此父进程与子进程的gval的虚拟地址是一致,但是真实地址则不是。
在这里插入图片描述
但是并非所有的数据的真实地址都要改变,因为我们在之前的文章提到了,父进程与子进程的代码是互通的,只有数据不互通,因此父进程与子进程的代码的真实地址则不用变。

实际上刚刚创建页表的时候,父子进程虚拟地址与真实地址的映射关系都是一致的,包括变量(比如gval),但是,如果某个进程,将这个变量的值改变了,此时系统为了保持进程的独立性,即不让一个进程的运行影响到另一个进程,于是就会将被修改的变量的地址,移动到其他地方,这种方法我们称为:写时拷贝。

换句话说就是,如果我们不在子进程中修改gval的值,那么实际上父子进程将会共用一个gval,包括它的真实地址,当gval被修改时,操作系统就会办gval重新找个真实地址,但是gval虚拟地址则不会改变。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java线程安全集合之COW

概述 java.util.concurrent.CopyOnWriteArrayList写时复制顺序表&#xff0c;一种采用写时复制技术&#xff08;COW&#xff09;实现的线程安全的顺序表&#xff0c;可代替java.util.ArrayList用于并发环境中。写时复制&#xff0c;在写入时&#xff0c;会复制顺序表的新副本&…

K8S调度不平衡问题分析过程和解决方案

不平衡问题排查 问题描述&#xff1a; 1、业务部署大量pod(据反馈&#xff0c;基本为任务型进程)过程中&#xff0c;k8s node内存使用率表现不均衡&#xff0c;范围从80%到百分之几&#xff1b; 2、单个node内存使用率超过95%&#xff0c;仍未发生pod驱逐&#xff0c;存在node…

Janus:开创统一的多模态理解和生成框架

Janus是DeepSeek开源的多模式自回归框架&#xff0c;统一了多模态理解和生成&#xff0c;既可以理解图片内容又可以生成图片。 1.简介 Janus 是一种新颖的自回归框架&#xff0c;它将多模态理解和生成统一起来。它通过将视觉编码解耦为单独的路径来解决以前方法的局限性&…

jmeter发送post请求

在jmeter中&#xff0c;有两种常用的请求方式&#xff0c;get和post.它们两者的区别在于get请求的参数一般是放在路径中&#xff0c;可以使用用户自定义变量和函数助手等方式进行参数化&#xff0c;而post请求的参数不能随url发送&#xff0c;而是作为请求体提交给服务器。而在…

OpenWRT 和 Padavan 路由器配置网络打印机 实现远程打印

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 之前有给大家介绍过 Armbian 安装 CUPS 作为打印服务器&#xff0c;像是 N1 盒子、玩客云&#xff0c;甚至是随身 WiFi 都可以通过 CUPS 来进行打印。但是有些朋友不想专门为打印机添置一个设备&#xff0…

Spring AI 1.0.0 M1版本新特性!

Spring AI 1.0.0 M1版本新特性介绍 前言一、在1.0.0 M1版本中&#xff0c;主要有以下新特性&#xff1a;1.ChatModel2.ChatClient3.多模态的支持4.模型评估RequestResponseAdvisor接口MessageChatMemoryAdvisorPromptChatMemoryAdvisorQuestionAnswerAdvisor动态过滤表达式 Vec…

爬虫逆向-js进阶(续写,搭建网站)

1.搭建简单网站1 from flask import Flask,render_template import requests import json app Flask(name)# **location**的温度是**temp**度&#xff0c;天气状况&#xff1a;**desc**app.route(/) # 绑定处理函数 def index_url():location 101010100data get_weather(lo…

黑马JavaWeb-day02

什么是JavaScript&#xff1f; JavaScript&#xff1a;简称Js,是一门跨平台、面向对象的脚本语言。是用来控制网页行为的&#xff0c;它能使网页可交互 JavaScript和Java是完全不同的语言&#xff0c;无论是概念还是设计。但是基础语法类似。 JavaScript JavaScript引入方式…

第三方软件测试中心有什么特点?江苏软件测试中心推荐

随着软件市场的激烈竞争&#xff0c;软件企业越来越多&#xff0c;为了更好的服务用户以及专心于产品开发工作&#xff0c;将软件测试外包给第三方软件测试中心已经成为了行业发展趋势。第三方软件测试中心顾名思义就是区别于软件开发方和需求方的第三方存在&#xff0c;是专门…

使用 MongoDB 构建 AI:利用实时客户数据优化产品生命周期

在《使用 MongoDB 构建 AI》系列博文中&#xff0c;我们看到越来越多的企业正在利用 AI 技术优化产品研发和用户支持流程。例如&#xff0c;我们介绍了以下案例&#xff1a; Ventecon 的 AI 助手帮助产品经理生成和优化新产品规范 Cognigy 的对话式 AI 帮助企业使用任意语言&a…

《MYSQL实战45讲 》 优化器如何选择索引?

SHOW VARIABLES LIKE long_query_time; set long_query_time0 优化器如何选择索引&#xff1f; 1.扫描的行数 估计出各个索引大致的要扫描的行数&#xff0c;行数越少&#xff0c;效率越高。 索引的基数也叫区分度&#xff0c;就是这个索引所在的字段上不同的值又多少个。优…

10.21 多进程间通信-信号、消息队列

作业&#xff1a;使用消息队列实现两个进程间通信 编程代码&#xff1a;使用父子进程实现通信 msgsnd.c #include <myhead.h> //定义自定义函数用于接收僵尸进程 void handler(int signo){if(signoSIGCHLD){while(waitpid(-1,NULL,WNOHANG)>0);} } //定义存储消息队…

[云] Deploying Your First Serverless Application

• Goal: • Hands-on lab to get started with Serverless • Agenda: • Deploying Your First Serverless Application • Assignment Introduction Create and test function in AWS Lambda • Lets create an addition function using AWS Lambda. • To create the addi…

pipeline开发笔记

pipeline开发笔记 jenkins常用插件Build Authorization Token Root配置GitLab的webhooks(钩子)配置构建触发器--示例 piblish over sshBlue OceanWorkspace Cleanup PluginGit插件PipelineLocalization: Chinese (Simplified) --中文显示Build Environment Plugin 显示构建过程…

vscode离线状态ssh连接不断输入密码登不上:配置commit_id

如题&#xff0c;vscode在一个离线服务器上&#xff0c;通过remote-ssh登录远程服务器&#xff0c;不断弹出密码框&#xff0c;总是进不去&#xff0c;后来了解到主要是不同vscode版本需要下载对应抑制commit-id的vscode-server-linux-x64.tar.gz包。 1&#xff09;vscode, 点…

Jupyter Notebook汉化(中文版)

原版jupyter notebook是英文的&#xff0c;想要将其改为中文 在jupyter notebook所在环境输入以下命令 pip install jupyterlab-language-pack-zh-CN打开jupyter notebook&#xff0c;在设置语言中将其设置为中文

提升小学语文教学效果的思维导图方法

众所周知&#xff0c;教学不仅仅是站在讲台上传授知识&#xff0c;它还包括了备课、评估学生学习成果以及不断调整教学方法等多个环节。在面对教学中的各种挑战时&#xff0c;思维导图可以成为解决这些问题的有力工具。思维导图是一种利用图形来组织和表达发散性思维的工具&…

【DBA Part01】国产Linux上安装Oracle进行数据迁移

内容如下&#xff1a; 1.1.生产环境RHEL/OEL Linux8Oracle11gR2安装配置 1.2.国产麒麟操作系统Oracle11gR2安装配置 1.3.国产麒麟操作系统Oracle11gR2 RAC集群安装配置 1.4.Oracle11gR2迁移到国产麒麟操作系统&#xff08;单机/RAC&#xff09; 本阶段课程项目需求说明&am…

Spring配置/管理bean-IOC(控制反转) 非常详细!基于XML及其注解!案例分析! 建议复习收藏!

目录 1.Spring配置/管理bean介绍 2.基于XML配置bean 2.1基于id来获取bean对象 2.2基于类型获取bean对象 2.3通过指定构造器配置bean对象 2.4通过p名称空间配置bean 2.5通过ref配置bean(实现依赖注入) 2.6注入内部Bean对象&#xff0c;依赖注入另一种方式 2.7 注入集合…

PCL 基于距离阈值去除错误对应关系

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 获取初始对应点对 2.1.2 基于距离的对应关系筛选函数 2.1.3 可视化函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实…