目录
4. 环境变量
4-1 基本概念
4-2 常⻅环境变量
4-3 查看环境变量⽅法
4-4 和环境变量相关的命令
4-5 环境变量的组织⽅式
4-6 通过代码如何获取环境变量
4-7 环境变量通常是具有全局属性的
5. 程序地址空间
5-1 研究平台
5-2 程序地址空间回顾
5-3 虚拟地址
5-4 进程地址空间
5-5 虚拟内存管理 - 第⼀讲
4. 环境变量
4-1 基本概念
环境变量(environment variables)⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数
如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪
⾥,但是照样可以链接成功,⽣成可执⾏程序,原因就是有相关环境变量帮助编译器进⾏查找。
环境变量通常具有某些特殊⽤途,还有在系统当中通常具有全局特性
4-2 常⻅环境变量
PATH : 指定命令的搜索路径
HOME : 指定⽤⼾的主⼯作⽬录(即⽤⼾登陆到Linux系统中时,默认的⽬录)
SHELL : 当前Shell,它的值通常是/bin/bash。
4-3 查看环境变量⽅法
echo $NAME //NAME:你的环境变量名称
测试PATH
1. 创建hello.c⽂件
#include <stdio.h>
int main()
{
printf("hello world!\n");
return 0;
}
2. 对⽐./hello执⾏和之间hello执⾏
3. 为什么有些指令可以直接执⾏,不需要带路径,⽽我们的⼆进制程序需要带路径才能执⾏?
4. 将我们的程序所在路径加⼊环境变量PATH当中, export PATH=$PATH:hello程序所在路径
5. 对⽐测试
6. 还有什么⽅法可以不⽤带路径,直接就可以运⾏呢?
测试HOME
1. ⽤root和普通⽤⼾,分别执⾏ echo $HOME ,对⽐差异
2. 执⾏ cd ~; pwd ,对应 ~ 和 HOME 的关系
4-4 和环境变量相关的命令
1. echo: 显⽰某个环境变量值
2. export: 设置⼀个新的环境变量
3. env: 显⽰所有环境变量
4. unset: 清除环境变量
5. set: 显⽰本地定义的shell变量和环境变量
4-5 环境变量的组织⽅式
每个程序都会收到⼀张环境表,环境表是⼀个字符指针数组,每个指针指向⼀个以’\0’结尾的环境
字符串
4-6 通过代码如何获取环境变量
命令⾏第三个参数
#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声明。
4-7 环境变量通常是具有全局属性的
环境变量通常具有全局属性,可以被⼦进程继承下去
5. 程序地址空间
5-1 研究平台
kernel 2.6.32
32位平台
5-2 程序地址空间回顾
我们在讲C语⾔的时候,⽼师给⼤家画过这样的空间布局图

可是我们对他并不理解!可以先对其进行各区域分布验证
5-3 虚拟地址
来段代码感受⼀下
#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 进程地址空间
所以之前说‘程序的地址空间’是不准确的,准确的应该说成 进程地址空间 ,那该如何理解呢?看
图:
分⻚&虚拟地址空间
发现,⽗⼦进程,输出地址是⼀致的,但是变量内容不⼀样!能得出如下结论:
说明:
上⾯的图就⾜矣说明问题,同⼀个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映
射到了不同的物理地址!
5-5 虚拟内存管理 - 第⼀讲
描述linux下进程的地址空间的所有的信息的结构体是 mm_struct (内存描述符)。每个进程只有⼀
个mm_struct结构,在每个进程的task_struct结构中,有⼀个指向该进程的结构。
可以说,mm_struct结构是对整个⽤⼾空间的描述。每⼀个进程都会有⾃⼰独⽴的mm_struct,这样
每⼀个进程都会有⾃⼰独⽴的地址空间才能互不⼲扰。
5-6 为什么要有虚拟地址空间
这个问题其实可以转化为:如果程序直接可以操作物理内存会造成什么问题?
在早期的计算机中,要运⾏⼀个程序,会把这些程序全都装⼊内存,程序都是直接运⾏在内存上的,
也就是说程序中访问的内存地址都是实际的物理内存地址。当计算机同时运⾏多个程序时,必须保证
这些程序⽤到的内存总量要⼩于计算机实际物理内存的⼤⼩。
那当程序同时运⾏多个程序时,操作系统是如何为这些程序分配内存的呢?例如某台计算机总的内存
⼤⼩是128M,现在同时运⾏两个程序A和B,A需占⽤内存10M,B需占⽤内存110。计算机在给程序分
配内存时会采取这样的⽅法:先将内存中的前10M分配给程序A,接着再从内存中剩余的118M中划分
出110M分配给程序B。
这种分配⽅法可以保证程序A和程序B都能运⾏,但是这种简单的内存分配策略问题很多。
•
安全⻛险
◦
每个进程都可以访问任意的内存空间,这也就意味着任意⼀个进程都能够去读写系统相关内
存区域,如果是⼀个⽊⻢病毒,那么他就能随意的修改内存空间,让设备直接瘫痪。
•
地址不确定
◦
众所周知,编译完成后的程序是存放在硬盘上的,当运⾏的时候,需要将程序搬到内存当中
去运⾏,如果直接使⽤物理地址的话,我们⽆法确定内存现在使⽤到哪⾥了,也就是说拷⻉
的实际内存地址每⼀次运⾏都是不确定的,⽐如:第⼀次执⾏a.out时候,内存当中⼀个进程
都没有运⾏,所以搬移到内存地址是0x00000000,但是第⼆次的时候,内存已经有10个进程
在运⾏了,那执⾏a.out的时候,内存地址就不⼀定了
•
效率低下
◦
如果直接使⽤物理内存的话,⼀个进程就是作为⼀个整体(内存块)操作的,如果出现物理
内存不够⽤的时候,我们⼀般的办法是将不常⽤的进程拷⻉到磁盘的交换分区中,好腾出内
存,但是如果是物理地址的话,就需要将整个进程⼀起拷⾛,这样,在内存和磁盘之间拷⻉
时间太⻓,效率较低。
存在这么多问题,有了虚拟地址空间和分⻚机制就能解决了吗?当然!