目录
3、使用extren char **environ来获取环境变量。编辑
一、环境变量
基本概念
- 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数
- 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但 是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
- 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
常见的环境变量
- PATH : 指定命令的搜索路径
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
- SHELL : 当前Shell,它的值通常是/bin/bash。
查看环境变量的方法
我们可以使用echo来查看环境变量:
echo $NAME //NAME为待查看的环境变量名称
查看环境变量PATH:
测试PATH
为何我们在执行可执行文件的时候需要加./?
在这里我们知道,如果我们使用gcc或者g++编译器生成了可执行程序(例如a.out)的时候,如果想要执行a.out那么需要加“./”,也就是./a.out来执行程序。当然在前面的Linux学习中我们知道Linux下皆是文件,那么同样是文件,为何我们所执行的一些指令,例如ls、ll、clear的时候不需要加./呢。我们知道./的作用是找到这个文件,也就是在当前目录找到这个文件。
那么其他指令难道不需要去做找到它们这个步骤吗?
其实不然,他们也需要,但是他们的路径已经在环境变量PATH中了,也就是说当我们执行它们的时候,系统会去查看环境变量,然后默认从环境变量的路径中从左到右的各个路径下以此访问,所以只要这些指令的路径位于PATH的某个路径下,就可以不用加./了。
那如何才能让我们所生成的可执行程序也可以直接执行而不用使用./呢?
方法一:
将我们的可执行程序拷贝到环境变量PATH的某一路径下,这个方法我们不做演示,因为这样的或容易破坏我们本来的环境变量,不建议使用。
方法二:
将我们所要执行的可执行程序的路径加到环境变量PATH的后面。
测试HOME
显示用户的主工作目录,当然当用户不同时HOME的值也是不一样的。
普通用户
超级用户(root)
测试SHELL
查看当前所用的命令行解释器的种类
相关指令
echo:显示某个环境变量的值
export:可以改变环境变量的值,也就是可以给环境变量修改路径。
env:显示所有环境变量
main函数命令行参数的解析及意义
main函数的第二个参数是一个指针数组,每个指针指向的都是所接收的字符串的地址。它指向的是一个环境变量的表,表中以NULL结尾。
char *argv[]的理解。
使用char *argv[]自定义实现特定的功能。
通过函数来获取环境变量
推荐使用第一种(能准确的获取我们需要获取的环境变量),不是很推荐第三种。
同上,char *env[]也是一个指针数组,同样也指向一个环境变量的表,表中存放的是环境变量的地址,也是以NULL结尾。
1、使用getenv函数来获取环境变量PATH的值
指定环境变量名直接获取。
2、使用char *env[]来获取环境变量。
3、使用extren char **environ来获取环境变量。![](https://i-blog.csdnimg.cn/blog_migrate/fe28fcf120b7356efd6f970d886ea807.png)
地址空间
程序地址空间
这个空间分布其实我们在C语言阶段就有所研究,现在我们用代码来验证一下:
运行结果:
可知与布局图是相同的。
接下来我们看一段代码:
#include<stdio.h>
#include<unistd.h>
int global_value = 100;
int main()
{
pid_t id = fork();
if (id < 0)
{
printf("fork error\n");
return 1;
}
else if (id == 0)
{
int cnt = 0;
while (1)
{
printf("我是子进程, pid, %d,ppid:%d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value);
sleep(1);
cnt++;
if (cnt == 10)
{
global_value = 300;
printf("子进程已经更改全局变量########################\n");
}
}
}
else
{
while (1)
{
printf("我是父进程, pid, %d,ppid:%d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value);
sleep(2);
}
}
sleep(1);
return 0;
}
运行结果:
我们惊奇的发现,我们把子进程中global_val的值已经更改了,但是它与父进程中g_val的地址依然相同。如果他们对应的是我们熟知的物理地址,显然是不可能的,因此这里其实他们对应的是虚拟地址。
进程地址空间
之前说的那张布局图‘程序的地址空间’是不准确的,准确的应该说成进程虚拟地址空间 ,每个进程都会有自己的地址空间,认为自己独占物理内存。操作系统在描述进程地址空间时,是以结构体的形式描述的,在linux中这种结构体是 struct mm_struct 。它在内核中是一个数据结构类型,具体进程的地址空间变量,mm_struct存在于进程PCB中。
进程地址空间就类似于一把尺子,尺子的刻度由0x00000000到0xffffffff,尺子按照刻度被划分为各个区域,例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个边界刻度,例如代码区的开始刻度与结束刻度,如下图所示:
在结构体mm_struct当中,各个边界刻度之间的每一个刻度都代表一个虚拟地址,这些虚拟地址通过页表映射与物理内存建立联系。由于虚拟地址是由0x00000000到0xffffffff线性增长的,所以虚拟地址又叫做线性地址。
写时拷贝
当一个进程被创建的时候,随之创建的有进程控制块PCB(task_struct),进程地址空间(mm_struct)也会被随之创建。OS是可以通过task_strcut找到mm_strcut的,因为task_strcut中有一个结构体指针存储的是mm_strcut的地址。
当子进程被创建的时候,需要注意的是父子进程的数据和代码是共享的,当父子进程有其中一个需要修改数据的时候,需要将该数据在物理内存中拷贝一份儿,保留之前未被修改的数据。比如我们前面所提到的子进程将全局变量global_val的值改为了300,此时已经在物理内存中重新拷贝了一份数据,对我们重新拷贝出来的那一份数据进行修改。这体现了进程的独立性。
为什么数据不在创建子进程的时候就进行数据的拷贝?
子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延时分配),这样可以高效的使用内存空间。
为什么要有地址空间?
1.通过添加一层软件层,完成有效的对进程操作内存进行风险管理(权限管理),本质目的是为了,保护物理内存以及各个进程的数据安全!
2.将内存申请和内存使用的概念在时间上划分清楚,通过虚拟地址空间,来屏蔽底层申请内存的过程,达到进程读写内存和OS进行内存管理操作,进行软件上面的分离!
3.站在CPU和应用层的角度,进程统一可以看做统一使用4GB页表空间,而且每个空间区域的相对位置,是比较确定的!OS最终这样设计的目的,达到一个目标:每个进程都认为自己是独占系统资源的!->进程是有独立性的!!!
页表
我们通过页表将我们进程地址空间的虚拟地址和物理内存中的物理地址一一映射,页表还可以帮我们去检测我们的虚拟地址是否符合映射的标准,如果不符合可以不映射。