【Linux篇】环境变量与地址空间

【Linux篇】环境变量与地址空间
📌 个人主页:孙同学_
🔧 文章专栏:Liunx
💡 关注我,分享经验,助你少走弯路!

一.环境变量

1. 环境变量

1.1基本概念

  • 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
  • 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
  • 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

命令行参数

在这里插入图片描述


在这里插入图片描述


我们会发现argv是一个变长数组,会把我们输入的内容呈现出来。实际上argv是一个指针数组。当我们在命令行中输入一个./code以空格作为分隔符,其实我们输入的时一个长字符串,我们把它叫做命令行或者命令行命令,其实就是一个字符串。当我们执行c语言程序时,这个字符串就会被切分成以空格作为分隔符,切成好几份,所以它把第一个字符串的地址填在argv[0]里面,依次类推。其中数组的有效元素个数就是argc

在这里插入图片描述

所以有人帮我们把命令行当中我们输入的字符串打散成这种以空格作为分隔符的上图这个样子,这个样子就叫做命令行参数,命令行参数依次变成一个字串,放到一个叫argv的数组里,一共有argc个有效元素,最后这个argv把有效元素放完之后,必须以NULL结尾。
指令选项实现原理:main函数的命令行参数,是实现程序不同子功能的方法。
进程有一张argv表,用来支持实现选项功能!

  1. 我们会发现当执行系统命令比如ls时不需要带./,而执行我们自己的程序时就需要带./,这是为什么呢?
  • 要执行程序我们先得找到它,./表示在当前路径下,而系统命令不需要是因为存在环境变量,来帮助我们找到目标二进制文件
  • ls是在/usr/bin/路径下的,我们会发现当前路径下我们输入code是不会运行的,而当我们把code拷贝到/usr/bin路径下就会执行了
  • 执行命令时系统为什么会在/usr/bin路径下去查呢?答案是系统中存在环境变量(PATH),来帮助系统找到目标二进制文件
  • 环境变量(PATH):系统中搜索指令的默认搜索路径

1.2 常见环境变量

  • PATH: 指定命令的搜索路径
  • HOME: 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  • SHELL: 当前Shell,它的值通常是/bin/bash

1.3 查看环境变量方法

  • echo $NAME : //NAME(你的环境变量名称)查看单个环境变量

env:查看所有环境变量

在这里插入图片描述


环境变量的构成:名字+内容

🌵1.如何理解环境变量?存储的角度

bash内部有两张表,一个是环境变量表,另一个是命令行参数表。

在这里插入图片描述

🌵2. 环境变量最开始从哪里来的呢?

  • 系统相关的配置文件中来的

在每个用户的家目录里都会有.bash_profile.bash_rc这两个配置文件

在这里插入图片描述

1.4 和环境变量相关的命令

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

1.5 环境变量的组织方式

在这里插入图片描述


每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串

1.6 通过代码如何获取环境变量

方法二:getenv(),它会根据环境变量的名字来获取指定环境变量的内容

在这里插入图片描述


在这里插入图片描述

方法一:main()函数

在这里插入图片描述


在这里插入图片描述


上面获取的环境变量是父进程(bash),环境变量可以被子进程继承

✏️ 如果我们想写一个程序,只能我执行,其他人一律不执行,该如何设计呢?根据我们刚刚对环境变量的认识,现在只有一个人知道登陆用户是谁,那就是bash.
➀我们写一个只有sp能运行的程序

在这里插入图片描述


运行

在这里插入图片描述


可以正常运行
➁我们拿root账号来运行一下

在这里插入图片描述


不能运行
所以这个程序只能由sp运行
所以获取环境变量的第二种做法叫做getenv ,环境变量可以被子进程继承是因为我们可以把环境变量相关的信息让子进程继承下去,子进程就可以和环境变量来做个性化操作,比如定制一个只能自己执行的程序

方法三:使用全局变量environ

在这里插入图片描述


我们可以看到它的参数类型为char **,因为环境变量表是一个char * 的,所以char **environ应该指向第一个元素

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

2. 环境变量的特性

2.1 环境变量具有全局特性

  • 环境变量通常具有全局属性,可以被子进程继承下去

2.2 补充两个概念

  1. 我们的环境变量是在谁的上下文里面呢?bash
    export
    命令是一个内建命令built-in command,不需要创建子进程,而让bash自己亲自执行,或者系统调度完成。

bash会记录两套变量:一个是环境变量,一个是本地变量

在这里插入图片描述


可以通过set命令查到所有的本地变量,本地变量不会被子进程继承,只在bash内部使用

二.程序地址空间

1. 程序地址空间回顾

我们在以前学习c/c++的时候,就听说过c/c++程序默认内存地址空间是代码区,字符常量区,初始化数据区,未初始化数据区,堆区,栈区,共享区等。

证明这个地址是虚拟地址

在这里插入图片描述

下面我们把整个程序的内存空间布局以代码的方式打出来

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述
在这里插入图片描述


结论:

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

OS必须负责将 虚拟地址 转化成 物理地址

2. 进程地址空间

一个进程一个虚拟地址空间,虚拟地址空间的宽度是一字节,32位下是2^32个地址 * 1字节 = 4GB(0-3GB用户空间[拿到地址就能直接访问],3-4GB内核空间),64位下是2^64个地址。

一个进程一套页表,页表是用来做虚拟地址和物理地址映射的。

在这里插入图片描述


一个int类型有4个字节,但我们的虚拟进程空间的宽度位1个字节,如何处理呢?因为我们有类型,实际上我们在访问任何一个变量时,只要知道起始地址+偏移量就访问到了,系统访问的是最小的那个地址。

有父进程,也就会有子进程,子进程的很多东西都是拷贝父进程的,他把父进程task_struct里面的属性给自己拷贝一份,把个别的属性自己一更改,一个进程,一个虚拟地址空间,一个进程,一套页表,所以我们的子进程也有自己的虚拟地址空间和页表

在这里插入图片描述


以前我们说过,子进程的PCB和一些物理属性都是拷贝自父进程的,同样的,页表也是拷贝自父进程的,相当于发生了浅拷贝,所以子进程和父进程就有相同的虚拟地址空间,我们也就理解了为什么全局变量为什么默认地被父子进程共享,因为他们的虚拟地址空间到物理空间的映射关系是一样的,相当于它们指向同一块物理内存。变量如此,代码也是如此。

上面我们演示的子进程的gval++,父进程的gval 不变是怎么回事呢?
原因是子进程的gval++的时候,操作系统会在物理内存空间上重新开辟一块空间,把老变量gval的内容拷贝到新空间,此时就得到了一个新的变量或者物理地址,然后操作会把这个新的物理空间地址给给子进程的页表,构建全新的映射关系,这种机制称为写实拷贝

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

3. 虚拟内存管理 - 第一讲

描述linux下每一个虚拟地址空间的所有信息的结构体是mm_struct,每一个进程都只有一个mm_struct结构体,每个进程的task_struct结构体中都只一个指针指向mm_struct

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

mm_struct结构是对整个用户空间的描述。每一个进程都会有自己独立的mm_struct,这样每一个进程都会有自己独立的地址空间才能互不干扰。下面是task_structmm_struct,进程的地址空间的分布情况:

在这里插入图片描述


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

structmm_struct{/*...*/structvm_area_struct*mmap;/* 指向虚拟区间(VMA)链表 */structrb_root mm_rb;/* red_black树 */unsignedlong task_size;/*具有该结构体的进程的虚拟地址空间的⼤⼩*//*...*/// 代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。 unsignedlong start_code, end_code, start_data, end_data;unsignedlong start_brk, brk, start_stack;unsignedlong 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,方便进程快速访问。

structvm_area_struct{unsignedlong vm_start;//虚存区起始 unsignedlong vm_end;//虚存区结束 structvm_area_struct*vm_next,*vm_prev;//前后指针 structrb_node vm_rb;//红⿊树中的位置 unsignedlong rb_subtree_gap;structmm_struct*vm_mm;//所属的 mm_struct  pgprot_t vm_page_prot;unsignedlong vm_flags;//标志位 struct{structrb_node rb;unsignedlong rb_subtree_last;} shared;structlist_head anon_vma_chain;structanon_vma*anon_vma;conststructvm_operations_struct*vm_ops;//vma对应的实际操作 unsignedlong vm_pgoff;//⽂件映射偏移量 structfile* vm_file;//映射的⽂件 void* vm_private_data;//私有数据  atomic_long_t swap_readahead_info;#ifndefCONFIG_MMUstructvm_region*vm_region;/* NOMMU mapping region */#endif#ifdefCONFIG_NUMAstructmempolicy*vm_policy;/* NUMA policy for the VMA */#endifstructvm_userfaultfd_ctx vm_userfaultfd_ctx;} __randomize_layout;

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

在这里插入图片描述


在这里插入图片描述


虚拟地址空间的意义:

  • 将地址从无序变为有序
  • 将虚拟地址转化为物理地址(OS查找页表),也可以对你的地址和操作进行合法判定,进而保护物理内存

📌:页表项里面除了虚拟地址和物理地址外,还有一个条目,这个条目里面包含着r,w,x权限,实现对物理内存的保护

✏️再谈野指针问题

当这个指针指向的对应区域被释放了,即物理内存释放了,所以映射关系要去掉,当对一个已经释放了的内存访问时,页表中就不存在对应的虚拟物理映射关系,查页表时会失败,操作系统会知道,就把进程干掉了,所以有了野指针之后,进程有可能会崩溃。

✏️char *str = “hello world” ; *s = ‘H’;这段代码能编过吗?答案使能编过

上面的char *str = "hello world" ;我们都知道叫做字符串常量,我们用指针指向字符串常量时,我们在c语言中已经学过,字符串常量不能被修改。字符串是被编译到字符常量区的,也就是和正文部分是编到一块的,所以它是只读的,所以想把这个字符串常量修改成 *s = 'H';时,查页表时就会发现是只读的,而要时页表会转化失败,所以操作系统不让我们转。

✏️再谈为什么要有虚拟地址空间
让进程管理和内存管理进行一定程度的解耦合

🎯 澄清一些问题!

  1. 我们可以不加载代码和数据,只有task_struct,mm_struct,页表,程序也能运行,因为存在缺页中断
  2. 创建进程,先有task_struct,mm_struct等,还是先加载代码和数据?
    答案是先要有内核数据结构,然后才加载代码和数据
  3. 如何理解进程挂起(阻塞挂起)??
    先找到对应的进程,然后将页表清空,将物理内存里对应的代码和数据和换出到swap分区里。只保留页表中的左半部分,而把右半部分换出。

堆区细节性话题:堆区有自己的开始与结束,我们平时用堆区时可能malloc了好多次,申请了不同的堆空间,而每个堆都有起始地址,而定义的堆空间上只有一个起始和结束,那么如何确定其他的地址开始和结束呢?

在这里插入图片描述

vm_area_struct里面就有vm_startvm_end,会记录下你所需要的vm_startvm_end,一份堆区对应一个vm_area_struct

在这里插入图片描述

👍 如果对你有帮助,欢迎:

  • 点赞 ⭐️
  • 收藏 📌
  • 关注 🔔

Read more

Agent Skill黄金三层结构与五步法打造指南:让AI帮你自动生成若依框架代码!

Agent Skill黄金三层结构与五步法打造指南:让AI帮你自动生成若依框架代码!

文章介绍Agent Skill的设计原理与实现方法,重点讲解黄金三层结构(元数据层、指令层、资源层)和五步法打造技能包(定边界、显性化经验、工具与脚本、引入控制流、迭代与反馈)。通过若依代码生成器改造案例,展示如何将项目规范、代码模板打包成可复用Skill,让AI按预设规则自动生成符合规范的代码,提升开发效率并减少重复工作。 读完这篇文章,你将学会: ✅ 什么是 Agent Skill ✅ 设计技能的黄金三层结构 ✅ 五步法打造你的第一个技能包 ✅ 实践拆解:将若依代码生成器改为Agent Skill 前几天有小伙伴在 [Antigravity 进阶指南: 3 种方式复刻 Kiro Spec 模式]那篇文章下留言,想要那个示例里的Spec模式 Skill 包。 我想了想,与其直接给大家丢一个 Skill 文件,不如和大家聊聊什么时候需要创建以及怎么创建Agent Skill。 在让 AI 帮我们生成Skill之前,我们需要先理解 Skill

By Ne0inhk
【人工智能机器学习基础篇】——深入详解无监督学习之降维:PCA与t-SNE的关键概念与核心原理

【人工智能机器学习基础篇】——深入详解无监督学习之降维:PCA与t-SNE的关键概念与核心原理

深入详解无监督学习之降维:PCA与t-SNE的关键概念与核心原理         在当今数据驱动的世界中,数据维度的增多带来了计算复杂性和存储挑战,同时也可能导致模型性能下降,这一现象被称为“维度诅咒”(Curse of Dimensionality)。降维作为一种重要的特征提取和数据预处理技术,旨在通过减少数据的维度,保留其主要信息,从而简化数据处理过程,并提升模型的性能。本文将深入探讨两种广泛应用于无监督学习中的降维方法——主成分分析(PCA)和t-分布随机邻域嵌入(t-SNE),并详细解析其关键概念与核心原理。 目录 1. 降维概述 2. 主成分分析(PCA) * 核心概念 * 数学原理 * 关键步骤 * 优缺点分析 3. t-分布随机邻域嵌入(t-SNE) * 核心概念 * 数学原理 * 关键步骤 * 优缺点分析 4. PCA与t-SNE的对比分析 5. 适用场景与选择指南 6. 结论 1. 降维概述 什么是降维? 降维是将高维数据映射到低维空间的过

By Ne0inhk
【AI】免费的代价?Google AI Studio 使用指南与 Cherry Studio + MCP 实战教程

【AI】免费的代价?Google AI Studio 使用指南与 Cherry Studio + MCP 实战教程

🟢 定义速览:Google AI Studio 是使用谷歌相关模型的网页;Cherry Studio 是可以使用多种模型API Key的桌面软件。 文章目录 * 一、Google AI Studio * 1.1 和Gemini网页版的区别 * 1.2 免费层级的用量限制 * 1.3 Google AI Studio 隐私问题(重要) * 1.31 案例分享 * 1.32 Gemini API 附加服务条款 * 1.4 API Key的价格 * 1.5 Google AI Studio 使用说明 * 二、Cherry Studio * 2.1

By Ne0inhk
小白程序员必看:OpenClaw带你体验AI“真正干活”的全新革命!

小白程序员必看:OpenClaw带你体验AI“真正干活”的全新革命!

OpenClaw是一款运行在个人电脑上的AI助理,它具有“眼睛”和“双手”,能读邮件、看网页、操作文件、运行命令等,基于大模型理解意图并自主规划执行步骤。它不仅能处理职场任务如邮件管理、日程安排,还能处理生活事务如购物比价、旅游规划。最酷的是,它能“自己写技能”,实现自我编程。OpenClaw代表AI的新范式,成为用户的“数字分身”,提升效率,是给电脑装上AI协作者的绝佳选择。 一、你是否想过:AI 不该只是“回答问题” 我们用过太多 AI 工具:ChatGPT、Copilot、Claude……它们聪明、博学,但始终像个“话痨顾问”——你能问,它能答,却不能动手做事。 * 想让它“帮我查下航班值机状态”?它只能告诉你步骤。 * 想让它“把上周的会议纪要整理成文档”?它需要你复制粘贴内容。 * 想让它“半夜服务器崩了自动修复”?对不起,

By Ne0inhk