目录
一.环境变量
1.1基本概念
1.2命令行参数
1.3一个例子,一个环境变量
1.4环境变量的组织方式
1.5查看环境变量的方法
编辑
1.6从存储的角度理解环境变量
1.7环境变量最开始是从系统的配置文件里来的
1.8认识更多的环境变量
HOME
HISTSIZE
HOSTNANE
SSH_TTY
LS_COLORS
OLDPWD
1.9获得环境变量的方法
1.10代码获取环境变量
方法一:
方法二:
写一个只能自己执行的程序
方法三:
1.11补充
二.程序地址空间
2.1地址空间回顾
2.2虚拟地址
2.3进程地址空间
如何理解虚拟地址空间
如何理解区域划分
编辑
内核中:
2.4虚拟内存管理
2.5为什么要有虚拟地址空间
编辑 一些问题
一.环境变量
1.1基本概念
• 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
• 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪 里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
• 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
1.2命令行参数
我们写的c语言代码一定要有main函数作为我们的入口函数,但其实main函数也是被调用的,它也有参数:
形成可执行文件之后:
我们发现main函数的参数其实就是我们在命令行输入的字符串与字符串的个数,以空格为分界符。argv就是字符串数组。
来看这样一个代码:
所以我们就可以使用这个东西来实现不同的子功能
是不是跟ls 所带的选型差不多:
所以,main的命令行参数,是实现程序不同子功能的方法 (指令选项的实现原理)
1.3一个例子,一个环境变量
我们知道,要执行一个程序就必须先找到这个命令的位置。比如上面的代码我就必须加上“./”来指明程序所在的位置。而我们在使用ls的时候就不需要指明位置了,要知道我们自己写的代码翻译为二进制之后和我们系统预装的二进制文件是没有任何区别的。那么有一个方式可以让我们的代码也不需要我们自己指明位置就可以执行,像是ls不用写成/usr/bin/ls。
把我们的命令拷贝到/usr/bin/目录下:
但是为什么系统就可以认识/usr/bin/目录呢?因为一个环境变量:PATH(系统中默认的搜索指令的默认搜索路径)
1.4环境变量的组织方式
每个程序都会收到一张环境表,环境变量表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串
1.5查看环境变量的方法
echo $NAME //NAME:你的环境变量名称
env //查看所有的
以冒号作为分隔符,分别对应路径 ,我们输入的指令就会从这里的路径里去查找
那么此时就有一个想法,我们是不是也可以把我们自己写的代码放到这里面去,那么我们在执行代码时就不需要写路径了:
注意看,PATH=$PATH:+(你要改的路径),如果直接PATH=(你要改的路径)就是直接替换
1.6从存储的角度理解环境变量
bash内部有两张表!
在每一个用户登录系统的时候,系统都会创建一个bash,bash会帮我们形成一张表(环境变量表):
当我们输入指令的时候,bash会最先拿到我们的指令,然后会构建命令行参数表,再去我们的环境变量表里找到PATH。
1.7环境变量最开始是从系统的配置文件里来的
在每个用户的家目录下们都会存在两个隐藏文件:
.bash_profile会加载.bashrc,而.bashrc会加载系统/etc/bashrc。
而我们上面说的如果更改了PATH,重新开启系统系统会恢复成默认的路径,那这个默认可以改吗?
如图在.bash_profile文件里的PATH后面加上我们自己的路径:
后面再去重新登录的话,默认的PATH就有了我们的路径
1.8认识更多的环境变量
HOME
我们知道cd ~的意思就是回到家目录,那我们写的~代表的就是家目录?
当你输入cd ~时,shell会自动将~替换为$HOME的值,然后执行cd命令,将你切换到家目录。
HISTSIZE
我们在命令行使用上下键可以看到以前的历史命令,HISTSIZE则是我们记录历史命令的最大值。
HOSTNANE
当前主机的主机名字
SSH_TTY
这个哪一个终端设备
LS_COLORS
配色方案
其实是一行太长了
OLDPWD
这个会记录我们上一次的位置,所以我们使用cd -就可以回到上一次的工作目录
1.9获得环境变量的方法
1. echo:显示某个环境变量值
2. export:设置一个新的环境变量
3. env:显示所有环境变量
4. unset:清除环境变量
5. set:显示本地定义的shell变量和环境变量
export创建:
unset取消:
1.10代码获取环境变量
方法一:
我们还可以使用我们自己的程序获取环境变量(main函数的第三个参数):
所以环境变量可以被子进程继承 ,这也对应了上面说的全局特性
方法二:
getenv:
写一个只能自己执行的程序
我们自己就可以执行:
即使是root也不行:
所以环境变量可以被子进程继承,我们就可以结合环境变量进行个性化操作
方法三:
二级指针environ,指向全局变量表:
1.11补充
我们可以直接在命令行里输入
这就是本地环境变量
我们直接使用env是查不到的,要使用set
bash会记录两套变量环境变量和本地变量,本地变量不会被子进程继承,只在bash内部使用。
我们在命令行输入指令bash会创建子进程,而我们上面说的export可以创建环境变量,并且环境变量会进入到环境变量表里,这时就有问题了。bash创建子进程,但是子进程反过来修改了bash内部的环境变量表?
export是一个内键命令build-in command,比较特殊,它不需要创建子进程,而是让bash亲自执行,bash自己调用函数,或系统调用完成。
二.程序地址空间
2.1地址空间回顾
进程地址空间(虚拟地址空间):是一个系统的概念不是语言层的概念(不是物理内存!!!)
用代码可以验证一下:
上面说这不是内存,怎么体现呢?
2.2虚拟地址
一对父子进程:
子进程的gval在改变,父进程的不变,它们的地址一样??这怎么可能?所以只有一种结论,这不是真正的地址,这是虚拟地址!!
所以:
• 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
• 但地址值是一样的,说明,该地址绝对不是物理地址!
• 在Linux地址下,这种地址叫做虚拟地址
• 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理
OS必须负责将虚拟地址转化成物理地址 。
2.3进程地址空间
所以之前说‘程序的地址空间’是不准确的,准确的应该说成进程地址空间 ,那该如何理解呢?看 图:
• 上面的图就足矣说明问题,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映 射到了不同的物理地址!
如何理解虚拟地址空间
我们可以把虚拟地址空间理解为操作系统给进程画的大饼,让每一个进程都以为自己有4GB的物理地址空间,每一个进程都认为自己在独占物理内存。
大饼:虚拟地址空间
员工:进程
老板:操作系统
要对大饼也进行管理,就需要先描述再组织的思想,所以虚拟地址空间本质上也是一个数据结构
如何理解区域划分
对区域的调整实际上就值是对整数的调整:
内核中:
2.4虚拟内存管理
描述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_struct到mm_struct,进程的地址空间的分布情况:
那既然每一个进程都会有自己独立的mm_struct,操作系统肯定是要将这么多进程的mm_struct组织起来的!虚拟空间的组织方式有两种:
1. 当虚拟区较少时采取单链表,由mmap指针指向这个链表;
2. 当虚拟区间多时采取红黑树进行管理,由mm_rb指向这棵树。
linux内核使用 vm_area_struct 结构来表示一个独立的虚拟内存区域(VMA),由于每个不同质的虚 拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。上面提到的两种组织发那个式使用的就是vm_area_struct结构来连接各个VMA,方便进程快速访问。
所以在task_struct里面有一个指向mm_struct的指针mm,在mm_struct里面还有一个指针指向vm_area_struct。vm_area_struct 是记录了每一个区域的结构体,链表的节点有多个这样的结构体组成,这也就解释了堆区不连续的问题。
2.5为什么要有虚拟地址空间
这个问题其实可以转化为:如果程序直接可以操作物理内存会造成什么问题?
在早期的计算机中,要运行一个程序,会把这些程序全都装入内存,程序都是直接运行在内存上的, 也就是说程序中访问的内存地址都是实际的物理内存地址。当计算机同时运行多个程序时,必须保证这些程序用到的内存总量要小于计算机实际物理内存的大小。
那当程序同时运行多个程序时,操作系统是如何为这些程序分配内存的呢?例如某台计算机总的内存大小是128M,现在同时运行两个程序A和B,A需占用内存10M,B需占用内存110M。计算机在给程序分配内存时会采取这样的方法:先将内存中的前10M分配给程序A,接着再从内存中剩余的118M中划分 出110M分配给程序B。
这种分配方法可以保证程序A和程序B都能运行,但是这种简单的内存分配策略问题很多。
• 安全风险
◦ 每个进程都可以访问任意的内存空间,这也就意味着任意一个进程都能够去读写系统相关内存区域,如果是一个木马病毒,那么他就能随意的修改内存空间,让设备直接瘫痪。
• 地址不确定
◦ 众所周知,编译完成后的程序是存放在硬盘上的,当运行的时候,需要将程序搬到内存当中去运行,如果直接使用物理地址的话,我们无法确定内存现在使用到哪里了,也就是说拷贝的实际内存地址每一次运行都是不确定的,比如:第一次执行a.out时候,内存当中一个进程都没有运行,所以搬移到内存地址是0x00000000,但是第二次的时候,内存已经有10个进程在运行了,那执行a.out的时候,内存地址就不一定了
• 效率低下
◦ 如果直接使用物理内存的话,一个进程就是作为一个整体(内存块)操作的,如果出现物理内存不够用的时候,我们一般的办法是将不常用的进程拷贝到磁盘的交换分区中,好腾出内存,但是如果是物理地址的话,就需要将整个进程一起拷走,这样,在内存和磁盘之间拷贝时间太长,效率较低。
存在这么多问题,有了虚拟地址空间和分页机制就能解决了吗?当然!
• 地址空间和页表是OS创建并维护的!是不是也就意味着,凡是想使用地址空间和页表进行映射, 也一定要在OS的监管之下来进行访问!!也顺便 ,包括各个进程以及内核的相关有效数据! 保护了物理内存中的所有的合法数据
• 因为有地址空间的存在和表表的映射的存在,我们的物理内存中可以对未来的数据进行任意位置 的加载!物理内存的分配和进程的管理就可以做到没有关系。 进程管理模块和内存管理模块就完 成了解耦合
◦ 因为有地址空间的存在,所以我们在C、C++语言上new,malloc空间的时候,其实是在地址空间上申请的,物理内存可以甚至一个字节都不给你。而当你真正进行对物理地址空间访问的时候,才执行内存的相关管理算法,帮你申请内存,构建页表映射关系(延迟分配),这是由操作系统自动完成,用户包括进程完全0感知!!
• 因为页表的映射的存在,程序在物理内存中理论上就可以任意位置加载。它可以将地址空间上的 虚拟地址和物理地址进行映射,在进程视角所有的内存分布都可以是有序的。
一些问题
我们可以不加载代码和数据,只有task_struct,mm_struct,页表
因为存在缺页中断,当查找页表的映射关系时并没有关于物理空间地址的映射,只有虚拟地址,OS此时会自动的加载代码和数据
创建进程先有 task_struct,mm_struct,页表还是先加载代码和数据
根据前面的,先有 task_struct,mm_struct,页表
如何理解进程挂起
进程挂起其实就是内存空间不足的时候,会把暂时不用的进程的代码和数据重新返回磁盘的swap分区。这个过程实际就是页表的物理内存地址全部清空,把物理内存的代码和数据放到磁盘