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

【Linux系统】进程地址空间

命令行参数

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){ //childprintf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{ //parentprintf("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{ //parentsleep(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. 提供内存保护和访问控制

相关文章:

【Linux系统】进程地址空间

命令行参数 int main (int argc, char* argv[]) 命令行参数列表 argc&#xff1a;参数的个数argv&#xff1a;参数的清单 int main (int argc, char* argv[]) {printf("argc: %d\n",argc);for(int i 0; i < argc; i){printf("argv[%d] : %s \n", i…...

记录学习的第二十六天

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

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

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

测试 认识bug

一、软件测试生命周期与测试模型 1. 软件&#xff08;开发&#xff09;生命周期&#xff1a;包括需求分析、计划、设计、编码、测试、运行维护阶段。需求分析是起始点&#xff0c;明确用户需求&#xff0c;后续阶段依此展开 。例如开发电商软件&#xff0c;需求分析阶段确定商品…...

Mysql主从复制有哪些方式

MySQL 主从复制主要有以下几种方式&#xff0c;根据不同的分类标准&#xff08;如同步机制、数据复制格式、拓扑结构等&#xff09;可以分为&#xff1a; 一、按同步机制分类 1. 异步复制 (Asynchronous Replication) 原理&#xff1a;主库提交事务后&#xff0c;立即返回给客…...

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

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

Spring Cloud 服务间调用深度解析

前言 在构建微服务架构时&#xff0c;服务间的高效通信是至关重要的。Spring Cloud 提供了一套完整的解决方案来实现服务间的调用、负载均衡、服务发现等功能。本文将深入探讨 Spring Cloud 中服务之间的调用机制&#xff0c;并通过源码片段和 Mermaid 图表帮助读者更好地理解…...

如何使用通义灵码玩转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…...

uniapp的通用页面及组件基本封装

1.基本布局页面 适用于自定义Navbar头部 <template><view class"bar" :style"{height : systemInfo.statusBarHeight px, background: param.barBgColor }"></view><view class"headBox" :style"{ height: param.h…...

YOLO11改进——融合BAM注意力机制增强图像分类与目标检测能力

深度学习在计算机视觉领域的应用取得了显著进展,尤其是在目标检测(Object Detection)和图像分类(Image Classification)任务中。YOLO(You Only Look Once)系列算法凭借其高效的单阶段检测框架和卓越的实时性能,成为目标检测领域的研究热点。然而,随着应用场景的复杂化…...

Spring Boot循环依赖全解析:原理、解决方案与最佳实践

&#x1f6a8; Spring Boot循环依赖全解析&#xff1a;原理、解决方案与最佳实践 #SpringBoot核心 #依赖注入 #设计模式 #性能优化 一、循环依赖的本质与危害 1.1 什么是循环依赖&#xff1f; 循环依赖指两个或多个Bean相互直接或间接引用&#xff0c;形成闭环依赖关系。 典…...

力扣 283 移动零的两种高效解法详解

目录 方法一&#xff1a;两次遍历法 方法二&#xff1a;单次遍历交换法 两种方法对比 在解决数组中的零移动到末尾的问题时&#xff0c;我们需要保持非零元素的顺序&#xff0c;并原地修改数组。以下是两种高效的解法及其详细分析。 方法一&#xff1a;两次遍历法 思路分析…...

「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…...

Java面试黄金宝典48

1. C++ 的拷贝构造函数,深拷贝和浅拷贝 定义 拷贝构造函数:在 C++ 里,拷贝构造函数属于特殊的构造函数,其功能是使用一个已存在的对象来初始化一个新对象。当对象以值传递的方式作为参数传给函数、函数返回对象、用一个对象初始化另一个对象时,拷贝构造函数会被调用。浅拷…...

QuickAPI 核心能力解析:构建数据服务化的三位一体生态

在企业数据资产化运营的进程中&#xff0c;如何打破数据开发与共享的效率瓶颈&#xff0c;实现从 “数据可用” 到 “数据好用” 的跨越&#xff1f;麦聪软件的 QuickAPI 给出了系统性答案。作为 SQL2API 理念的标杆产品&#xff0c;QuickAPI 通过SQL 编辑器、数据 API、数据市…...

ES和MySQL概念对比

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

Spring如何解决项目中的循环依赖问题?

目录 什么是循环依赖&#xff1f; 如何解决&#xff1f; 采用两级缓存解决 需要AOP的Bean的循环依赖问题&#xff1f; 三级缓存解决 什么是循环依赖&#xff1f; 循环依赖就是Spring在初始化Bean时两个不同的Bean你依赖我&#xff0c;我依赖你的情况 例如A依赖B&#xf…...

计算机系统---烤机(性能测评)

计算机烤机 一、烤机的定义与核心目的 烤机&#xff08;Burn-in Test&#xff09; 是通过对计算机硬件施加持续高负载&#xff0c;模拟极端运行环境&#xff0c;以验证硬件稳定性、性能极限、散热能力及潜在缺陷的测试方法。核心目标包括&#xff1a; 硬件稳定性验证&#x…...

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…...

[Java实战经验]链式编程与Builder模式

目录 链式编程Builder模式 链式编程 链式编程&#xff08;Fluent AP&#xff09;是一种编程风格&#xff0c;它通过在同一个对象上连续调用多个方法来执行一系列操作&#xff08;让方法返回对象本身&#xff08;return this&#xff09;&#xff09;。这种风格的编程使代码更加…...

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…...

使用Python+xml+shutil修改目标检测图片和对应xml标注文件

使用Pythonxmlshutil修改目标检测图片文件名和对应xml标注文件&#xff1a; import os import glob import xml.etree.ElementTree as et import shutildef change_labels(source_dir):name_id 18001file_list glob.glob(os.path.join(source_dir, "*.xml"))print…...