【Linux系统】进程地址空间

news2025/4/16 7:53:41

命令行参数

int main (int argc, char* argv[])

  • 命令行参数列表
    • argc:参数的个数
    • argv:参数的清单
int main (int argc, char* argv[])
{
    printf("argc: %d\n",argc);
    for(int i = 0; i < argc; i++)
    {
        printf("argv[%d] : %s \n", i, argv[i]);
    }
    return 0;
}

命令行参数是在命令行中输入命令时,跟在命令后面的附加信息。这些参数用于向程序传递特定的指令、选项或数据,以改变程序的默认行为或提供必要的输入。

同一个程序,可以根据命令行参数、根据选项的不同,表现出不同的功能。比如:指令中选项的实现。

环境变量

基本概念

  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
  • 如:我们在编写C/C++代码的时候,在链接的时操作候,从来不知道我们所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
  • 在Linux操作系统中,环境变量是存储系统和用户相关信息的动态命名值。它们本质上是一些字符串,可以被系统进程、shell脚本以及各种应用程序访问。这些变量在系统启动时或者用户登录时被初始化,并且在整个系统运行过程中起到关键的配置作用。

重要的环境变量及其作用

1. PATH

PATH是一个非常重要的环境变量,它定义了系统在执行命令时查找可执行文件的路径列表。在终端输入一个命令时,系统会按照PATH变量中指定的目录顺序依次查找对应的可执行文件

  • 为什么系统知道,命令在/usr/bin路径下?
    • 因为PATH环境变量,告诉了shell应该在哪一个路径下查找命令。
    • 若想运行程序前不带路径,把其路径添加到PATH中。
  • 进程启动会记录是谁启动的这个进程。
    • 在启动进程的时候,系统怎么知道操作者是谁?并如何知道其id写入进程pcb中?
    • 环境变量(开始是在系统的配置文件中的)
    • 登陆→启动shell进程→读取用户和系统用户相关的环境变量的配置文件→形成自己的环境变量表→子进程。
2. HOME

HOME变量指定用户的主目录,用于存储用户个人文件、配置文件等。

3. LANG和LC_ALL

用于设置系统的语言环境和本地化相关信息。

  • LANG定义了系统默认的语言、字符编码等本地化设置。

  • LC_ALL是一个优先级更高的环境变量,当设置了LC_ALL时,它会覆盖LANG和其他相关的本地化环境变量。这些变量可以决定系统如何显示日期、时间、文本编码等信息。

  • 示例
    若设置LANG=en_US.UTF - 8,表示系统将以美国英语和UTF - 8编码来处理文本相关内容。

4. SHELL

SHELL环境变量指定了用户所使用的shell程序。
常见的shell有bash、zsh、csh等,用于确定当用户打开一个新的终端时,系统将启动哪种shell来解释用户输入的命令。
其值通常是**/bin/bash**。

和环境变量相关的命令

  1. echo: 显示某个环境变量值
  2. export: 设置一个新的环境变量
  3. env: 显示所有环境变量
  4. unset: 清除环境变量
  5. set: 显示本地定义的shell变量和环境变量

环境变量的查看方法

  1. echo $NAME
  2. printenv :查看所有的环境变量
    printenv NAME:查看指定环境变量
  3. env | grep NAME

环境变量的修改方法(临时修改)

  • 使用export命令:
    对于临时修改环境变量,可以用export命令。

  • 例如:要临时将PATH变量添加一个新的目录/new/dir,可以输入export PATH=$PATH:/new/dir
    这种修改只在当前终端会话有效,当关闭终端后,修改就会消失

环境变量的组织方式

每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’ 结尾的环境字符串
在这里插入图片描述

通过代码如何获取环境变量
  • 命令行第三个参数
#include <stdio.h>

int main(int argc, char *argv[], char *env[])
{
    int i = 0;
    for(; env[i]; i++){
        printf("%s\n", env[i]);
    }
    return 0;
}
  • 通过第三方变量environ获取
#include <stdio.h>

int main(int argc, char *argv[])
{
    extern char **environ;
    int i = 0;
    for(; environ[i]; i++){
        printf("%s\n", environ[i]);
    }
    return 0;
}

libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用extern声明。

通过系统调用获取或设置环境变量

  • putenv,后面讲解
  • getenv,本次讲解
#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("%s\n", getenv("PATH"));
    return 0;
}

常用getenv和putenv函数来访问特定的环境变量。

环境变量通常是具有全局属性的

  • 环境变量通常具有全局属性,可以被子进程继承下去
#include <stdio.h>
#include <stdlib.h>

int main()
{
    char *env = getenv("MYENV");
    if(env){
        printf("%s\n", env);
    }
    return 0;
}

直接查看,发现没有结果,说明该环境变量根本不存在

  • 导出环境变量
    export MYENV="hello world"
  • 再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!想想为什么?

环境变量的继承与作用范围

当一个进程创建子进程时,子进程会继承父进程的环境变量(具有主从属性)。

环境变量的作用范围主要取决于其是系统级还是用户级设置:

  • 系统级环境变量对所有用户和进程都有效。
  • 用户级环境变量对特定用户进程有效。

环境变量可以被所有bash之后的进程全部看到。

系统的配置信息,尤其是具有“指导性”的配置信息,也是系统配置信息的一种表现。进程具有独立性,环境变量可用来进程间传递数据(尤其是只读数据)。

程序地址空间

程序地址空间回顾

在这里插入图片描述

虚拟地址

来段代码感受一下

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

int g_val = 0;

int main()
{
    pid_t id = fork();
    if(id < 0){
        perror("fork");
        return 0;
    }
    else if(id == 0){ //child
        printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }else{ //parent
        printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    sleep(1);
    return 0;
}

输出

//与环境相关,观察现象即可
parent[2995]: 0 : 0x80497d8
child[2996]: 0 : 0x80497d8

我们发现,输出出来的变量值和地址是一模一样的,很好理解呀,因为子进程按照父进程为模版,父子并没有对变量进行任何修改。可是将代码稍加改动:

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

int g_val = 0;

int main()
{
    pid_t id = fork();
    if(id < 0){
        perror("fork");
        return 0;
    }
    else if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
        g_val=100;
        printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }else{ //parent
        sleep(3);
        printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    sleep(1);
    return 0;
}

输出结果:

//与环境相关,观察现象即可
child[3046]: 100 : 0x80497e8
parent[3045]: 0 : 0x80497e8

我们发现,父子进程,输出地址是一致的,但是变量内容不一样! 能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量,但地址值是一样的,说明,该地址绝对不是物理地址!
  • 在Linux地址下,这种地址叫做 虚拟地址
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址! 物理地址用户一概看不到,由OS统一管理
    OS必须负责将 虚拟地址 转化成 物理地址

5-4 进程地址空间

所以之前说 ‘程序的地址空间’ 是不准确的,准确的应该说成 进程地址空间 ,那该如何理解呢?看图:
在这里插入图片描述

说明:

  • 上面的图就足矣说明问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!

  • 如何理解虚拟地址空间?
    让每一个进程都认为自己独占系统物理内存,进程彼此之间不知道、不关心对方的存在,从而实现一定程度的隔离。所谓进程虚拟地址空间,本质上是一个内核数据结构对象。

  • 如何理解区域划分?
    只要告诉开始和结束即可。地址本质就是一个数字,可被保存在unsigned long空间范围内的地址可以随便用,不必详细记录范围内地址。

  • 虚拟内存管理方案:struct mm_struct + 页表

虚拟内存管理

描述linux下进程的地址空间的所有的信息的结构体是 mm_struct(内存描述符)。每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个指向该进程的结构。

struct task_struct
{
    /*...*/
    struct mm_struct *mm;  //对于普通的用户进程来说该字段指向他的虚拟地址空间的用户空间部分,对于内核线程来说这部分为NULL。
    struct mm_struct *active_mm; // 该字段是内核线程使用的。当该进程是内核线程时,它的mm字段为NULL,表示没有内存地址空间,可也并不是真正的没有,这是因为所有进程内核的映射都是一样的,内核线程可以使用任意进程的地址空间。
    /*...*/
}

可以说,mm_struct结构是对整个用户空间的描述。每一个进程都会有自己独立的mm_struct,这样每一个进程都会有自己独立的地址空间才能互不干扰。先来看看由task_structmm_struct,进程的地址空间的分布情况:
在这里插入图片描述

定位mm_struct文件所在位置和task_struct所在路径是一样的,不过他们所在文件是不一样的,mm_struct所在的文件是mm_types.h。

struct mm_struct
{
    struct vm_area_struct *mmap; /* 指向虚拟区间(VMA) 链表 */
    struct rb_root mm_rb; /* 红黑结构体树的虚拟地址空间 */
    unsigned long task_size; /*具有该结构体的进程的虚拟地址空间的大小*/
    /* 代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。*/
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long start_brk, brk, start_stack;
    unsigned long arg_start, arg_end, env_start, env_end;

    /*...*/
}

那既然每一个进程都会有自己独立的mm_struct,操作系统肯定是要将这么多进程的mm_struct组织起来的。虚拟空间的组织方式有两种:

  1. 当虚拟区间少的时候采取链表,由mmap指针指向这个链表;
  2. 当虚拟区间多的时候采取红黑树进行管理,由mm_rb指向这棵树。

linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域(VMA),由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域

上面提到的两种组织方式使用的就是vm_area_struct结构来连接各个VMA,方便进程快速访问。

struct vm_area_struct {
    struct mm_struct *vm_mm; /* 所属的 mm_struct */
    unsigned long vm_start; //虚存区起始
    unsigned long vm_end; //虚存区结束
    struct vm_area_struct *vm_next, *vm_prev; //前后指针
    struct rb_node vm_rb;
    pgprot_t vm_page_prot; //标志位
    unsigned long vm_flags;
    struct list_head anon_vma_chain;
    struct anon_vma *anon_vma;
    const struct vm_operations_struct *vm_ops; //vma对应的实际操作
    unsigned long vm_pgoff; //文件的映射偏移量
    struct file * vm_file; //映射的文件
    void * vm_private_data; //私有数据
    /*...*/
}

所以我们可以对上图在进行更细致的描述,如下图所示:

在这里插入图片描述

页表

页表是操作系统用于实现虚拟内存管理中虚拟地址到物理地址映射的一种数据结构。

在分页系统中,每个进程都有自己独立的页表,其主要功能是记录虚拟页(由进程的虚拟地址空间划分而来)与物理页(物理内存划分之后的单元)之间的映射关系,同时还包含了每个页面的访问权限等信息。

页表项

  1. 物理页号:指明虚拟页对应物理页在物理内存中的位置。
  2. 访问权限号:用于控制对该页面的访问方式,常见的权限包括可读(R)、可写(W)、可执行(E)等。
  3. 存在位:用于指示该虚拟页是否已经加载到物理内存中。
  4. 脏位:当一个页面被修改时,脏位会被置为1,操作系统可以根据脏位来判断是否需要将修改后的页面写回磁盘。

父进程在执行过程中,当需要创建子进程时,子进程会继承父进程的虚拟地址布局。

在初始阶段,子进程的页表项和父进程的页表指向相同的物理页面,并且这些页表项的权限设置为只读。(这使得子进程可以访问和父进程相同的代码。)

当子进程或父进程尝试对共享页面进行写入操作时,会触发写时复制机制。(引发一个缺页中断)

处理缺页中断时,会为发生写入操作的进程分配一个新的物理页面,将原来共享的页面复制并更新,使其指向新建物理页面,并将该页面权限改为可读写。

进程虚拟地址空间的作用

  1. 提供内存抽象与隔离
    • 抽象:为每个进程提供了一个简单、统一的内存视图,进程无需关心物理内存的复杂细节。
    • 隔离:每个进程的虚拟地址空间相互独立,有效防止进程之间干扰。
  2. 支持进程的独立运行和动态内存管理
  3. 便于程序链接和加载

页表的作用

  1. 实现虚拟地址到物理地址的映射
  2. 提供内存保护和访问控制

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

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

相关文章

记录学习的第二十六天

还是每日一题。 今天这道题有点难度&#xff0c;我看着题解抄的。 之后做了两道双指针问题。 这道题本来是想用纯暴力做的&#xff0c;结果出错了。&#x1f613;

python成功解决AttributeError: can‘t set attribute ‘lines‘

文章目录 报错信息与原因分析解决方法示例代码代码解释总结 报错信息与原因分析 在使用 matplotlib绘图时&#xff0c;若尝试使用 ax.lines []来清除图表中的线条&#xff0c;会遇到AttributeError: can’t set attribute错误。这是因为 ax.lines是一个只读属性&#xff0c;不…

如何建立可复用的项目管理模板

建立可复用的项目管理模板能够显著提高项目执行效率、减少重复劳动、确保项目管理标准化。在企业中&#xff0c;项目管理往往涉及多个步骤和多个团队&#xff0c;然而每次开始一个新项目时&#xff0c;如果都从头开始设计流程和文档&#xff0c;势必浪费大量的时间和精力。通过…

如何使用通义灵码玩转Docker - AI助手提升开发效率

一、引言 Docker 作为一种流行的虚拟化技术&#xff0c;能够帮助开发者快速搭建所需的运行环境。然而&#xff0c;对于初学者来说&#xff0c;掌握 Docker 的基本概念和使用方法可能会遇到一些挑战。本文将介绍如何利用通义灵码这一智能编码助手&#xff0c;帮助你更高效地学习…

GGML源码逐行调试(下)

目录 前言1. 简述2. 预分配计算图内存2.1 创建图内存分配器2.2 构建最坏情况的计算图2.3 预留计算图内存 3. 分词4. 模型推理与生成4.1 模型推理4.2 采样 结语下载链接参考 前言 学习 UP 主 比飞鸟贵重的多_HKL 的 GGML源码逐行调试 视频&#xff0c;记录下个人学习笔记&#x…

「2025AIGC终极形态」AI系统源码:文本→图像→音乐→视频生成

—从技术痛点到企业级部署&#xff0c;手把手实现全流程AI内容工厂 行业核心痛点&#xff1a;为什么需要多模态AIGC系统&#xff1f; 1. 工具割裂&#xff0c;效率低下 传统流程&#xff1a; 文案&#xff08;ChatGPT&#xff09;→ 配图&#xff08;Midjourney&#xff09;→…

使用CS Roofline Toolkit测量带宽

使用CS Roofline Toolkit测量带宽 工程下载&#xff1a;使用CS Roofline Toolkit测量带宽-案例工程文件&#xff0c;也可以按照下面的说明使用git clone下载 目录 使用CS Roofline Toolkit测量带宽0、Roofline模型理解1、CS Roofline Toolkit下载1.1、设置代理1.2、git clone下…

L1-4 拯救外星人

题目 你的外星人朋友不认得地球上的加减乘除符号&#xff0c;但是会算阶乘 —— 正整数 N 的阶乘记为 “N!”&#xff0c;是从 1 到 N 的连乘积。所以当他不知道“57”等于多少时&#xff0c;如果你告诉他等于“12!”&#xff0c;他就写出了“479001600”这个答案。 本题就请你…

现代c++获取linux系统名称

现代c获取linux系统名称 前言一、使用命令获取操作系统名称二、使用c代码获取操作系统名称三、验证四、总结 前言 本文介绍一种使用c获取当前操作系统名称的方法 一、使用命令获取操作系统名称 在linux系统中可以使用uname或者uname -s命令来获取当前操作系统名称&#xff0c…

力扣刷题HOT100——53.最大子数组和

给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组是数组中的一个连续部分。 示例 1&#xff1a; 输入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5,4] 输出&#xff1a;6…

ES和MySQL概念对比

基本概念 ES和MySQL都属于数据库&#xff0c;不过各有各的特性&#xff0c;大致使用方法与MySQL类似并无区别。 MySQL&#xff1a;擅长事务持有ACID的特性&#xff0c;确保数据的一致性和安全。 ES&#xff1a;持有倒排索引&#xff0c;适合海量数据搜索和分析。 ES和MySQL如何…

Android开发过程中遇到的SELINUX权限问题

1、selinux权限一般问题 问题详情 log输出如下所示&#xff1a; 01-01 00:00:12.210 1 1 I auditd : type1107 audit(0.0:33): uid0 auid4294967295 ses4294967295 subju:r:init:s0 msg‘avc: denied{ set } for propertypersist.sys.locale pid476 uid1000 gid1000 scontext…

Windows系统docker desktop安装(学习记录)

目前在学习docker&#xff0c;在网上扒了很多老师的教程&#xff0c;终于装好了&#xff0c;于是决定再装一遍做个记录&#xff0c;省的以后再这么麻烦 一&#xff1a;什么是docker Docker 是一个开源的应用容器引擎&#xff0c;它可以让开发者打包他们的应用以及依赖包到一个…

MIP-Splatting:全流程配置与自制数据集测试【ubuntu20.04】【2025最新版】

一、引言 在计算机视觉和神经渲染领域&#xff0c;3D场景重建与渲染一直是热门研究方向。近期&#xff0c;3D高斯散射&#xff08;3D Gaussian Splatting&#xff09;因其高效的渲染速度和优秀的视觉质量而受到广泛关注。然而&#xff0c;当处理大型复杂场景时&#xff0c;这种…

怎样完成本地模型知识库检索问答RAG

怎样完成本地模型知识库检索问答RAG 目录 怎样完成本地模型知识库检索问答RAG使用密集检索器和系数检索器混合方式完成知识库相似检索1. 导入必要的库2. 加载文档3. 文本分割4. 初始化嵌入模型5. 创建向量数据库6. 初始化大语言模型7. 构建问答链8. 提出问题并检索相关文档9. 合…

XCTF-web(三)

xff_referer 拦截数据包添加&#xff1a;X-Forwarded-For: 123.123.123.123 添加&#xff1a;Referer: https://www.google.com baby_web 提示&#xff1a;想想初始页面是哪个 查看/index.php simple_js 尝试万能密码&#xff0c;没有成功&#xff0c;在源码中找到如下&#xf…

Verilog的整数除法

1、可变系数除法实现----利用除法的本质 timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 2025/04/15 13:45:39 // Design Name: // Module Name: divide_1 // Project Name: // Target Devices: // Tool Versions: // Description: // // Depe…

React 把一系列 state 更新加入队列

把一系列 state 更新加入队列 设置组件 state 会把一次重新渲染加入队列。但有时你可能会希望在下次渲染加入队列之前对 state 的值执行多次操作。为此&#xff0c;了解 React 如何批量更新 state 会很有帮助。 开发环境&#xff1a;Reacttsantd 学习内容 什么是“批处理”以…

【大模型理论篇】Search-R1: 通过强化学习训练LLM推理与利⽤搜索引擎

最近基于强化学习框架来实现大模型在推理和检索能力增强的项目很多&#xff0c;也是Deep Research技术持续演进的缩影。之前我们讨论过《R1-Searcher:通过强化学习激励llm的搜索能⼒》&#xff0c;今天我们分析下Search-R1【1】。 1. 研究背景与问题 ⼤模型&#xff08;LLM&a…

Google政策大更新:影响金融,新闻,社交等所有类别App

Google Play 4月10日 迎来了2025年第一次大版本更新&#xff0c;新政主要涉及金融&#xff08;个人贷款&#xff09;&#xff0c;新闻两个行业。但澄清内容部分却使得所有行业都需进行一定的更新。下面&#xff0c;我们依次从金融&#xff08;个人贷款&#xff09;&#xff0c;…