目录
1.环境变量 1.基本概念 2.常见环境变量 3.查看环境变量的方法 4.测试PATH 5.测试HOME 6.和环境变量相关的命令 7.环境变量的组织方式 8.通过代码如何获取环境变量 9.通过系统调用获取或设置环境变量 10.环境变量通常是具有全局属性
2.进程地址空间 0.这里的地址空间,是物理内存吗? 1.为什么不能直接访问物理内存? 2.分页&虚拟地址空间 3.扩展内容1 4.为什么要有地址空间?
3.重新理解什么是挂起?
1.环境变量
1.基本概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
如:在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序 原因就是有相关环境变量帮助编译器进行查找 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性 环境变量默认存放在:~/.bash_profile
2.常见环境变量
PATH : 指定命令的搜索路径 HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)SHELL : 当前Shell,它的值通常是/bin/bash
3.查看环境变量的方法
echo $NAME // NAME:你的环境变量名称
4.测试PATH
创建hello.c文件 对比./hello执行和之间hello执行 为什么有些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行? 将我们的程序所在路径加入环境变量PATH当中, export PATH=$PATH:hello程序所在路径
有效期限:临时改变 ,只能在当前的终端窗口中有效,当前窗口关闭后就会恢复原有的path配置
5.测试HOME
用root和普通用户,分别执行 echo $HOME,对比差异 执行 cd ~; pwd,对应 ~ 和 HOME 的关系
6.和环境变量相关的命令
echo :显示某个环境变量值export :设置一个新的环境变量env :显示所有环境变量unset :清除环境变量set :显示本地定义的shell变量和环境变量
7.环境变量的组织方式
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串
8.通过代码如何获取环境变量
int main ( int argc, char * argv[ ] , char * env[ ] )
{
int i = 0 ;
for ( ; env[ i] ; i++ )
{
printf ( "%s\n" , env[ i] ) ;
}
return 0 ;
}
通过第三方变量environ获取
libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用extern声明
int main ( int argc, char * argv[ ] )
{
extern char * * environ;
int i = 0 ;
for ( ; environ[ i] ; i++ )
{
printf ( "%s\n" , environ[ i] ) ;
}
return 0 ;
}
9.通过系统调用获取或设置环境变量
int main ( )
{
printf ( "%s\n" , getenv ( "PATH" ) ) ;
return 0 ;
}
常用 getenv和putenv函数来访问特定的环境变量
10.环境变量通常是具有全局属性
子进程的环境变量是从父进程继承来的 --> 环境变量具有全局属性
int main ( )
{
char * env = getenv ( "MYENV" ) ;
if ( env)
{
printf ( "%s\n" , env) ;
}
return 0 ;
}
直接查看,发现没有结果,说明该环境变量根本不存在
导出环境变量 – export MYENV=“hello world” 再次运行程序,发现结果有了! 说明:环境变量是可以被子进程继承下去的! 如果只进行 MYENV=“helloworld”,不调用export导出,再用我们的程序查看,会有什么结果?为什么?
2.进程地址空间
0.这里的地址空间,是物理内存吗?
int g_val = 0 ;
int main ( )
{
pid_t id = fork ( ) ;
if ( id < 0 )
{
perror ( "fork" ) ;
return 0 ;
}
else if ( id == 0 )
{
g_val = 100 ;
printf ( "child[%d]: %d : %p\n" , getpid ( ) , g_val, & g_val) ;
}
else
{
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
综上发现,父子进程,输出地址是一致的,但是变量内容不一样!可得出如下结论:
变量内容不一样,所以父子进程输出的变量绝对不是同一个变量 但地址值是一样的,说明,该地址绝对不是物理地址!
在用C/C++所看到的地址,全部都是虚拟地址!
物理地址,用户一概看不到,由OS统一管理,OS必须负责将虚拟地址转化成物理地址 发生了写时拷贝
1.为什么不能直接访问物理内存?
内存本身是随时可以被读写的
比如有一个指针,成了野指针,通过它,访问了别的进程的数据,并且篡改了其数据 这样特别不安全!
2.分页&虚拟地址空间
上图可看出,同一个变量,地址相同,其实是虚拟地址相同 ,内容不同其实是被映射到了不同的物理地址 地址空间 是一种内核数据结构(mm_struct ),它里面至少要有:各个区域的划分地址空间和页表(用户级)是每一个进程都私有一份
只要保证,每一个进程的页表,映射的是物理内存的不同区域 ,就能做到,进程之间不会互相干扰,保证进程的独立性
3.扩展内容1
当程序在编译的时候,形成可执行程序的时候,没有被加载到内存中的时候,程序的内部,有地址吗?
地址空间不要仅仅理解成为是OS内部要遵守的,其实编译器也要遵守
即编译器编译代码的时候,就已经给用户形成了各个区域,如代码区,数据区等 并且,采用和Linux内核中一样的编址方式,给每一个变量,每一行代码都进行了编址 故,程序在编译的时候,每一个字段早已经具有了一个虚拟地址 程序内部的地址,依旧用的是编译器编译好的虚拟地址,当程序加载到内存的时候,每行代码,每个变量便具有了一个外部的物理地址 那么,当CPU读到指令的时候,指令内部也有地址 ,这个地址是物理地址还是虚拟地址?
4.为什么要有地址空间?
凡是非法的访问或者映射,OS都会识别到,并且终止这个进程 --> 有效的保护了物理内存
因为地址空间和页表是OS创建并维护的,也就意味着凡是想使用地址空间和页表进行映射,也一定要在OS的监管之下来进行访问 也便保护了物理内存中的所有合法数据,包括各个进程以及内核的相关有效数据 因为有地址空间&页表映射的存在,物理内存可以对未来的数据进行任意位置加载
正因此,物理内存的分配就可以和进程的管理,做到没有关系
即:(内存管理模块 vs 进程管理模块) --> 完成了解耦合 所以,在C/C++中,new/malloc空间的时候,本质是在哪里申请的?
但倘若申请了空间,如果不立马使用,是不是造成了空间的浪费?
本质上,(因为有地址空间的存在,所以上层申请空间,其实是在地址空间上申请的,物理内存甚至可以一个字节都不给你!而当你真正进行对物理地址空间访问的时候,才执行内存的相关管理算法,帮你申请内存,构建页表映射关系),然后再让你进行内存的访问
这一整套流程是操作系统自动完成的,用户/进程,都完全0感知 此举为延迟分配内存的策略,提高了整机的效率
因为在物理内存中,理论上可以任意位置加载,所以物理内存中的几乎所有的数据和代码在内存中都是乱序的
但是,因为页表的存在,它可以将地址空间上的虚拟地址和物理地址进行映射,所以在进程的视角,所有的内存分布都是有序的!
可以理解成,地址空间是OS给进程画的大饼
进程要访问物理内存中的数据和代码,可能目前并没有在物理内存中,同样的,也可以让不同的进程映射到不同的物理内存,这样是不是便很容易做到,进程独立性的实现?
因为有地址空间的存在,每一个进程都认为自己拥有4GB空间(x32),并且各个区域是有序的,进而可以通过页表映射到不同的区域,来实现进程的独立性。每一个进程都不知道,也不需要知道其他进程的存在!
3.重新理解什么是挂起?
加载本质就是创建进程,那么是不是必须非得立马把所有的程序的代码和数据加载的内存中,并创建内核数据结构建立映射关系?
不是 在最极端的情况下,甚至只有内核数据结构被创建出来
页表映射的时候,映射的可不仅仅是内存,磁盘中的位置,也可以映射 理论上,可以实现对程序的分批加载 既然可以分批加载,可以分批换出吗?
甚至,这个进程短时间不会再执行了,比如阻塞 进程的数据和代码被换出了,就叫做挂起