【解锁创意之门:环境变量引领你的编程奇思妙想】(下)

简介: 【解锁创意之门:环境变量引领你的编程奇思妙想】

【解锁创意之门:环境变量引领你的编程奇思妙想】(中):https://developer.aliyun.com/article/1425753


此时我们就看不到我们刚刚设置的环境变量myenv和本地变量hello了。还有一个问题,我们之前提到了如果我们修改了系统的某一个环境变量,我们只需要关掉我们的Xshell,然后再次启动,此时环境变量就被恢复了目前我们见到的环境变量都是内存级别的,所以bash进程退出了,那么这个环境变量也就没有了,那么下次启动bash环境变量是从哪里来的呢?答案是磁盘里文件当中。当你启动bash时,它会按照特定的顺序读取和执行这些文件,从而设置相应的环境变量。这确保了在每个用户登录时和每个Shell启动时都能够获得正确的环境变量。那我们来看看这个文件


bash_profile:这是一个Bash shell的配置文件,它在用户登录时执行一次。如果存在,通常位于用户的主目录下,文件名为.bash_profile。


那我们想自己导入一个环境变量呢?export MYVAL=youcanseeme


然后我们退出我们的Xshell,看看再次启动的时候是否有刚刚设置的环境变量。


此时我们就可以刚刚设置的环境变量。除了从bash_profile中导入我们的环境变量,其实更多的是从basrc中导入的。


而里面又是etc/bashrc中导入的,我们可以打开观看一下。由于里面都是脚本文件,我们这里就不多多介绍了。


总结:是什么为什么怎么办?

  • 1.环境变量是有系统提供的一组变量,每一个环境变量都有它的用途
  • 2.在不同的场景下,执行某些对应的工作或者任务时,是需要知道它的更多属性的,比如平时创建的文件的时候,它就知道此时文件的拥有者是谁。
  • 3.指令操作、代码操作、三种获取环境变量的方式和环境变量的特性。


4.程序地址空间


由于64位下的程序地址空间比较复杂,我们下面展示的都是kernel 2.6.32内核32位平台下


4.1.程序地址空间图



上面的图我们以及不陌生了,在前面的c语言专栏这张图就已经出现过了,今天我们再来看这张图,来验证一下程序地址空间的特性。


运行结果:


从上面的结果我们可以发现地址是和我们上面的程序地址空间图变化一致的。我们发现栈地址和堆地址之间相差非常大,可以确定它们之间出现非常大的镂空。下面我再来验证其他的特性:堆区向上(高地址)增长,而栈区(低地址)增长。我们来看一段代码看一下他们的增长方向。


我们来看一下运行结果:


此时就能验证上面的结果,即堆栈相对而生。我们看到程序地址空间图还要环境变量和命令行参数,我们再来看一下他们


这里我们给命令行带上-a -b -c参数,然后再来看一下结果:


可以得出命令行参数表(每个表表向的地址)和环境变量表(每个表表向的地址)都比栈大,且环境变量表最大。那如果我们这样写呢?


运行结果:


这里我们就有一个结论了,无论是表,还是表指向的项目,都在栈上。


同时根据上面的程序地址空间图,已初始化和未初始化是我们的全局变量,会在我们的进程运行期间,一直运行!如果我们再定义一个变量,然后将其用static修饰会怎样呢?


我们发现被static修饰的变量的地址会居于已初始化和未初始化之间,为什么呢?因为static修饰的变量会自动初始化成0,这也就是为什么static修饰的变量不会随着局部函数调用而销毁,因为此时在语言上已经变成全局的了。问题又来了,上面的程序地址空间图是我们的内存吗?我们来看一下代码


运行结果:


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


运行结果:


我们子进程将我们的数据修改之后,父子进程输出不同的值,这也很合理,因为此时发生了写时拷贝,父子进程是两个进程,两个进程之间相互独立,他们之间不能发生数据干扰,对于父子进程而言他们应该是访问的是不同的变量,但是此时变量的地址确实一样的。我们发现,父子进程,输出地址是一致的,但是变量内容不一样!能得出如下结论:


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


所以就可以得出结论:上面的程序地址空间图不是我们的物理内存,而是我们的进程地址空间。


5.进程地址空间


5.1.什么是进程地址空间


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


在多进程编程中,当创建子进程时,操作系统会使用写时复制(Copy-on-Write,COW)技术来优化内存的使用。写时复制是一种延迟内存拷贝的策略,它允许父子进程共享相同的物理内存页,直到其中一个进程尝试修改该页的内容时,才会真正进行拷贝。


当在子进程中修改数据时,由于发生了写操作,操作系统会复制相应的内存页,确保父子进程不再共享相同的物理内存页。因此,尽管父子进程最初访问相同的变量地址,但在写操作之后,它们实际上拥有各自的拷贝,此时只有相同的虚拟地址,而真正的物理地址是不相同的。


这就是为什么观察到父子进程输出的地址相同但变量内容不同的原因。这种行为确保了父子进程在修改数据时互不干扰,因为它们实际上操作的是各自私有的内存拷贝。


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


现在我们再来看看什么是进程地址空间?


进程地址空间是指操作系统为每个运行中的进程分配的内存空间。每个进程都有自己独立的地址空间,这使得不同进程能够独立运行,而不会相互干扰。这也就是上面的富豪给自己每个孩子独立画出的大饼,使得每个孩子都认真学习,每个孩子将来都会继承亿万遗产。这样每个进程有自己的地址空间,这样做的好处是,每个进程都认为它是系统中唯一的运行程序,都能享有4G的大空间,它可以独立地访问和修改自己的内存空间,而不会影响其他进程的内存。


进程地址空间本质其实就是pcb中的数据结构,那如何理解上面进程地址空间的各个区域呢?


在操作系统中,进程地址空间的属性通常会通过一些整型变量描述。这些信息可以存储在进程控制块(PCB)数据结构中,被一个struct mmstruct所管理。我们来看一下源码。


区域划分的本质就是区域内的各个地址都可以使用!!!但是我们的地址空间,不具备对我们的代码和数据的保存能力!代码和数据没有存放在地址空间,而是在物理内存中存放的!此时就需要将地址空间的虚拟地址转化到物理内存中!此时操作系统就给我们提供了一张表 - 页表!虚拟地址是给进程的,进程是你的,所以这个虚拟地址是给用户的。


存放页表的地址通常是物理地址,而不是虚拟地址。页表是操作系统用来进行虚拟地址到物理地址映射的关键数据结构之一。在典型的虚拟内存系统中,每个进程都有自己的页表,这个页表负责将进程中的虚拟地址映射到物理地址。


因为页表本身是由操作系统管理的数据结构,需要在物理内存中保留,以确保对它的快速访问。如果页表本身存放在虚拟地址空间中,那么就需要在访问页表时进行额外的虚拟地址到物理地址的映射,这会导致循环依赖问题,因为访问页表的过程本身需要页表。


在多进程的环境中,不同进程的地址空间是相互独立的,互不干扰。这也是为什么在多进程编程中,父子进程可以拥有相同的变量地址,但在写时复制的过程中,它们的地址空间会逐渐分离,以确保彼此之间的数据不会相互影响。


5.2.为什么要有进程地址空间 + 页表


1.将物理内存从无序变成有序


  • 虚拟内存映射到物理内存: 进程通常拥有一个虚拟地址空间,该空间被划分为若干段,如代码段、数据段等。虚拟地址并不直接对应物理内存,而是通过页表进行映射关系的管理。
  • 分页技术: 将虚拟地址空间和物理内存划分为固定大小的页,以便更灵活地管理内存。这样,操作系统可以按需将虚拟页面映射到物理内存中,使得物理内存的使用变得有序,而非零散的分布。


2.解耦合进程管理和内存管理


  • 独立性:进程地址空间的引入使得进程的创建、撤销和切换变得相对独立于底层物理内存的管理。进程管理的任务(例如上下文切换、进程调度)与内存管理的任务(例如分配、回收内存)可以相对独立地进行。这使得操作系统更加模块化,易于扩展和维护。
  • 更好的资源隔离:每个进程都有自己的地址空间,一个进程的错误不会直接影响其他进程的内存。这提高了系统的稳定性和安全性。


3.保护内存的重要手段:


  • 地址空间隔离:不同的进程有不同的页表,因此它们的虚拟地址空间是相互隔离的。这种隔离可以防止一个进程直接访问另一个进程的数据,提高了系统的安全性。


5.3.malloc申请的内存会立即使用吗,本质是在虚拟地址申请的还是物理地址申请的呢


malloc函数是用于在C语言中动态分配内存的函数。当调用 malloc 时,它会返回一个指向分配的内存块的指针。这个内存块在逻辑上被视为可用的,但在物理上可能并没有被实际分配。


具体来说,malloc 会在进程的虚拟地址空间中分配一块指定大小的内存,并返回这块内存的起始地址。但是,这个时候并没有真正分配物理内存。物理内存的分配通常是在程序试图访问这块内存时进行的,这就是所谓的"延迟分配"。


当程序开始使用 malloc 返回的指针来写入数据时,操作系统会在需要的时候分配物理内存,并将虚拟地址空间中的页面与实际的物理内存页关联起来。这个过程被称为""缺页中断"。操作系统会负责将相应的页面加载到物理内存中,并将适当的页表项设置为指向这个物理内存页。


所以,malloc 返回的内存块在逻辑上是立即可用的,但在物理上并不一定立即分配。分配的物理内存是在程序首次访问这块内存时动态完成的。这种延迟分配的机制可以帮助操作系统更高效地管理内存,只为程序实际需要的部分分配物理内存,充分提高内存的使用率。

相关文章
|
机器学习/深度学习 人工智能 Java
学会用AI:释放创意,解放双手,工作再多也不慌
随着人工智能(AI)技术日渐成熟,AI在软件开发领域的应用也更加广泛。以前我们谈到AI时,常常会想到复杂的算法和深奥的理论,但如今,AI正在悄然改变着程序员的日常工作方式。从AI代码生成模型到AI编程助手应用,它们不仅仅是一小部分,更是未来程序开发的新趋势。
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
“解锁未来之钥:探索神经网络——那些正在改变世界的智能引擎背后的奥秘,让你见识前所未有的科技魅力”
【10月更文挑战第11天】神经网络是一种模拟人脑结构与功能的计算模型,通过学习数据模式进行预测或决策。基本单元为神经元,连接成层处理信息。本文介绍了神经网络的工作原理、结构及应用,并提供了一个使用Python和Keras构建简单神经网络解决二分类问题的示例。
30 3
|
1月前
|
数据采集 人工智能 测试技术
还在死磕AI咒语?北大-百川搞了个自动提示工程系统PAS
【10月更文挑战第4天】北京大学和百川智能研究人员开发了一种名为PAS的即插即用自动提示工程(APE)系统,利用高质量数据集训练的大型语言模型(LLMs),在基准测试中取得了显著成果,平均提升了6.09个百分点。PAS仅需9000个数据点即可实现顶尖性能,并能自主生成提示增强数据,提高了灵活性和效率。尽管存在训练数据质量和提示多样性等方面的潜在局限性,PAS仍为解决提示工程挑战提供了有前景的方法,有望提升LLM的可用性和有效性。论文详见:https://arxiv.org/abs/2407.06027。
41 3
|
3月前
|
Python
Python 控制结构:开启震撼编程之旅,犹如舞台上的精彩戏剧,让你的代码活起来!
【8月更文挑战第22天】Python的控制结构是编程的核心,包括条件判断(if-elif-else)和循环(for、while)。例如,可以用if-elif-else判断学生成绩等级,for循环计算1至10的总和,while循环实现猜数字游戏。此外,列表推导式等高级特性让操作更简洁高效。掌握这些结构能显著提升编程效率和代码质量。
53 1
|
3月前
|
UED 存储 自然语言处理
【语言无界·体验无疆】解锁Vaadin应用全球化秘籍:从代码到文化,让你的应用畅游世界每一个角落!
【8月更文挑战第31天】《国际化与本地化实战:构建多语言支持的Vaadin应用》详细介绍了如何使用Vaadin框架实现应用的国际化和本地化,提升用户体验和市场竞争力。文章涵盖资源文件的创建与管理、消息绑定与动态加载、日期和数字格式化及文化敏感性处理等方面,通过具体示例代码和最佳实践,帮助开发者构建适应不同语言和地区设置的Vaadin应用。通过这些步骤,您的应用将更加灵活,满足全球用户需求。
56 0
|
3月前
|
机器学习/深度学习 监控 自动驾驶
|
3月前
|
Python
编程之禅的奇幻之旅:探寻代码世界与生活万象的惊世共鸣,颠覆你的认知!
【8月更文挑战第7天】编程不仅是技术活,更融汇艺术与哲学。它启示我们在生活里追求简洁高效,如Python列表推导式的优雅;教会我们面对挑战时冷静分析,正如调试代码;体现分工合作的重要性,像模块化设计;并鼓励持续优化,提升效能。编程所蕴含的生活智慧,能引导我们创造更美好、有序的人生。
49 1
|
3月前
|
IDE 开发工具 计算机视觉
解锁Ruby图像处理新技能!从零基础到大师级,你的照片即将焕发不可思议的魔力!
【8月更文挑战第31天】Ruby不仅在Web开发和脚本编写中表现出色,还能通过强大的库如RMagick实现图像处理。RMagick是ImageMagick的Ruby接口,支持几乎所有图像格式。只需简单安装,即可进行图像加载、显示、滤镜应用、大小调整及裁剪等操作。
28 0
|
3月前
|
数据处理 Python
解锁Python多线程编程魔法,告别漫长等待!让数据下载如飞,感受科技带来的速度与激情!
【8月更文挑战第22天】Python以简洁的语法和强大的库支持在多个领域大放异彩。尽管存在全局解释器锁(GIL),Python仍提供多线程支持,尤其适用于I/O密集型任务。通过一个多线程下载数据的例子,展示了如何使用`threading`模块创建多线程程序,并与单线程版本进行了性能对比。实验表明,多线程能显著减少总等待时间,但在CPU密集型任务上GIL可能会限制其性能提升。此案例帮助理解Python多线程的优势及其适用场景。
39 0
|
5月前
|
机器学习/深度学习 算法 安全
解锁AIGC:软件开发的未来已来
【6月更文挑战第9天】
88 10