Linux进程地址空间

简介: Linux进程地址空间

零、前言


本章主要讲解学习进程地址空间的知识


一、程序内存空间


在学习C/C++中我们知道了程序内存的空间开辟以及内存分区的基本概念


  • 示图:


各分区作用:

内核空间:用户代码无法读写


命令行参数环境变量:储存命令行参数环境变量


栈区:存放运行函数而分配的局部变量、函数参数、返回数据、 返回地址等,栈区地址向下生长


共享区:储存文件映射,匿名映射,动态库


堆区:存放动态分配的变量,堆区地址向上生长


数据段(初始化数据/未初始化数据区):存放全局变量、静态数据


代码区:存放函数体(类成员函数和全局函数)的二进制代码


代码验证示例:


#include<stdio.h>
#include<stdlib.h>
int g_unval;
int g_val=1;
int main(int argc,char* argv[],char* env[])//命令行参数以及环境变量
{
    printf("code addr:%p\n",main);//代码区
    char* str="hello world";
    printf("read only addr:%p\n",str);//只读常量区:str是常量字符串的地址
    printf("init addr:%p\n",&g_val);//初始化数据区
    printf("uninit addr:%p\n",&g_unval);//未初始化数据区
    int* p1=(int*)malloc(10);
    int* p2=(int*)malloc(10);
    printf("heap addr:%p\n",p1);//堆区
    printf("heap addr:%p\n",p2);//堆区向上生长
    printf("stack addr:%p\n",&str);//栈区
    printf("stack addr:%p\n",&p1);//栈区向下生长
    for(int i=0;argv[i];i++)
        printf("args addr:%p\n",argv[i]);//命令行参数区 
    for(int i=0;i<2;i++)
        printf("env addr:%p\n",env[i]);//环境变量区
    return 0;
}


二、进程地址空间


1、引入及概念


对于上述的程序地址空间,其实它的真实面貌为进程地址空间,对于进程地址空间本质上来说是一个虚拟地址空间,并非真实的物理空间


  • 示例:


#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
    int i=10;
    pid_t id=fork();//创建子进程
    if(id<0)
    {
        perror("fork fail\n");
        return 1;
    }
    else if(id==0)
    {
        //child
        int cnt=0;
        while(1)
        {
            printf("I am child:  i:%3d &i:%p pid:%d\n",i,&i,getpid());
            sleep(1);
            if(cnt==3)//修改i的值
            {
                i=100;
            }
            cnt++;
        }
    }
    else
    {
        //father
        while(1)
        {
            printf("I am father: i:%3d &i:%p pid:%d\n",i,&i,getpid());
            sleep(1);
        }    
    }
    return 0;
}


分析:

我们知道,父进程创建子进程时,子进程以父进程为模板构建进程,代码父子共享,数据各有一份(谁进行写入谁发生拷贝)


而我们发现子进程数据发生修改时,子进程数据的地址和父进程的地址一样,没有发生改变


对于变量内容不一样,但地址值是一样的,说明该地址绝对不是物理地址,因为是物理地址根本不会有这种事发生


2、进程地址空间


概念:

在Linux地址下,这种地址叫做 虚拟地址,我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理,OS必须负责将 虚拟地址 转化成 物理地址


进程地址空间本质是进程看待内存的方式,抽象出来的一个概念,对于每个进程来说,系统会给他们创建对应的PCB进程块结构体,同时也相应的分配了对应的mm_struct进程地址空间(PCB中储存了该进程对应的进程地址空间的地址),也就是每个进程都认为自己独占内存资源


对于进程来说,进程控制块以及进程地址空间以及相应的资源,随进程的创建而创建,随进程的退出而回收


进程地址空间的内容:

进程地址空间是由0x00000000到0xffffffff的线性地址空间,按照刻度被划分为各个区域,例如代码区、堆区、栈区等


在结构体mm_struct当中,便记录了各个边界刻度(对于堆向上增长以及栈向下增长实际就是改变mm_struct当中堆和栈的边界刻度)


示图:


注:在结构体mm_struct中各个刻度之间的每一个刻度都代表一个虚拟地址,这些虚拟地址通过页表映射与物理内存建立联系


程序执行流程:

程序运行,进程被加载到CPU上,系统在内核为进程创建PCB记录进程属性,分配进程空间地址,由页表构建虚拟地址与物理地址的映射关系,程序查找或者修改数据会通过PCB找到对应的进程地址空间,再由进程地址空间上的虚拟地址由页表找到物理空间上分配的数据


示图:


对于父子进程变量地址相同数据不同:

父进程创建子进程时,子进程以父进程为模板构建进程,代码数据父子共享,当子进程进行修改数据时,由页表发现该数据是父子进程共享的,所以系统会找到另一个物理空间进行拷贝数据,拷贝数据后再修改数据,达到数据各有一份互不干扰的目的


注:这种在需要进行数据修改时再进行拷贝的技术称为写时拷贝


示图:


3、相关问题


为什么数据要进行写时拷贝

进程需要保证独立性,多进程运行,需要独享各种资源,多进程运行期间互不干扰,数据写实拷贝让子进程的修改不影响到父进程


为什么不在创建子进程的时候就进行数据的拷贝

子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延时分配),这样可以高效的使用内存空间


如果fork函数在子进程创建的同时即创建对应的数据结构还要拷贝数据的话,会降低fork的效率


fork就是在向系统获取资源,如果再拷贝的话,即获取更多的资源,容易造成fork失败


代码会不会进行写时拷贝

90%的情况下是不会的,但这并不代表代码不能进行写时拷贝,例如在进行进程替换的时候,则需要进行代码的写时拷贝


为什么要有进程地址空间

保护物理内存,不让程序直接进行访问物理地址,方便进行合法性校验,控制以及管理了访问的权限

如果可以直接访问,那么看到的地址就是物理地址,对于野指针,越界访问等问题则不能进行很好的控制,不能保证程序的独立性;当通过物理地址暴露,恶意程序通过物理地址进行读取或者修改数据,无法保证信息和数据安全;控制以及管理了访问的权限,以常量区不能的常属性来说,当常量定义出来的时候不就是修改数据了么,但是再次修改时,通过页表访问时,页表发现是常量区数据则拒绝修改的访问,以此保护了数据的常属性


将内存管理与进程管理进行解耦

如果直接使用物理地址,那么进程一创建就需要立即将数据写到物理内存中,当进程退出就需要将数据立即释放,也就是说内存的管理需要特别关注进程的状态,这之间具有强相关性(耦合度高);具有进程地址空间后,进程管理只需管理PCB以及进程地址空间,而内存管理只需管理物理地址空间,也就是内存管理只需要通过智能指针知道内存区域那些是有效的哪些是无效的就能管理好内存,实现了进程管理与内存管理的解耦


让程序以同样的方式看待代码和数据

可执行程序实际上也被分为了各个区域,例如初始化区、未初始化区等(便于程序编译时进行查找和链接)。当该可执行程序运行起来时,操作系统则将对应的数据加载到对应内存当中即可,同时分区有利于执行的效率,大大提高了操作系统的工作效率。


相关文章
|
17天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
43 1
|
5天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
47 13
|
12天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
20天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
1月前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
138 4
linux进程管理万字详解!!!
|
25天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
78 8
|
1月前
|
网络协议 Linux 虚拟化
如何在 Linux 系统中查看进程的详细信息?
如何在 Linux 系统中查看进程的详细信息?
72 1
|
1月前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
1月前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
71 4