文章目录
1.环境变量
1.1 什么是环境变量?
创建一个 mycode.cpp
和 makefile
文件,按照如图文件进行示例演示,清楚了解什么是环境变量
众所周知,我们需要进行 make mycode
进行自动化编译,然后运行 ./mycode
这一执行文件才能进行打印如图的代码,但是为什么这个可执行文件不能像 ls
、pwd
等命令一样直接使用,而是要加 ./
这一前缀进行地址查找呢?
[zzh_test@hcss-ecs-6aa4 env_test]$ which pwd
/usr/bin/pwd
[zzh_test@hcss-ecs-6aa4 env_test]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/zzh_test/.local/bin:/home/zzh_test/bin
$PATH
表示指定命令的搜索路径,这是一个环境变量,打印出来发现,系统的命令确实是存在于这里面,这就说明了,能够直接使用而不需要类似 ./
这样指定地址,是因为在系统里用环境变量提前定义好了路径,方便系统进行查找,因此如果执行文件想要像系统命令那样直接执行,那么就需要提前将自定义的路径加入 PATH
环境变量中,这样系统才找得到
[zzh_test@hcss-ecs-6aa4 env_test]$ pwd
/home/zzh_test/env_test
[zzh_test@hcss-ecs-6aa4 env_test]$ PATH=$PATH:/home/zzh_test/env_test
[zzh_test@hcss-ecs-6aa4 env_test]$ mycode
hello Linux10!
hello Linux9!
hello Linux8!
hello Linux7!
hello Linux6!
hello Linux5!
hello Linux4!
hello Linux3!
hello Linux2!
hello Linux1!
PATH
的修改方式是覆盖式的,因此前面要加上 $PATH:
,注意 =
两侧不能有空格
总结: 环境变量是系统提供的一组 name=value
形式的变量,不同的环境变量有不同的用户,通常具有全局性,每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以 \0
结尾的环境字符串
1.2 查看环境变量
除了 PATH
这个环境变量,还有 HOME
:指定用户的主工作目录(即用户登陆到 Linux
系统中时,默认的目录)、SHELL
:当前 Shell
,它的值通常是 /bin/bash
等等环境变量
[zzh_test@hcss-ecs-6aa4 ~]$ env
XDG_SESSION_ID=4167
HOSTNAME=hcss-ecs-6aa4
TERM=xterm
SHELL=/bin/bash
HISTSIZE=10000
SSH_CLIENT=113.87.225.199 59926 22
SSH_TTY=/dev/pts/0
USER=zzh_test
......
直接使用 env
命令查看就好了,具体各个环境变量的含义可以自行查询
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));
return 0;
}
也可以通过 getenv()
函数获取环境变量
1.3 命令行参数
查看 main
函数源代码,我们可以知道 main
函数其实是有参数的,依据这个例子理解为什么命令可以带参数
[zzh_test@hcss-ecs-6aa4 env_test]$ ./mycode
argv[0]->./mycode
[zzh_test@hcss-ecs-6aa4 env_test]$ ./mycode -a
argv[0]->./mycode
argv[1]->-a
[zzh_test@hcss-ecs-6aa4 env_test]$ ./mycode -a -b
argv[0]->./mycode
argv[1]->-a
argv[2]->-b
[zzh_test@hcss-ecs-6aa4 env_test]$ ./mycode -a -b -c
argv[0]->./mycode
argv[1]->-a
argv[2]->-b
argv[3]->-c
打印后可以发现其实命令和参数都是被存放在 char* argv[]
这个数组里的,按照顺序依次读取,这也说明了 main
函数其实也是被调用的(被 CRTStartup()
调用)
1.4 环境变量具有全局性
我们所运行的进程,都是子进程,bash
本身在启动的时候,会从操作系统的配置文件中读取环境变量信息,子进程会继承父进程交给我们的环境变量,因此我们在修改的 PATH
也是继承下来的,不小心删除了变量也没关系,毕竟是从系统继承下来的子进程,重启一下 bash
即可
1.5 本地变量
创建环境变量ENVTEST,但是只有在左边的进程能够看到,右边的进程查找不到,因为我们这里创建的是本地变量,即只能在创建的进程里被使用的环境变量,只在本 bash
内部有效,不会被子进程继承
解决方法也很简单,只要在前面加个 export
就能变成全局属性的环境变量了
1.6 内建命令
命令分为两种:
- 常规命令: 通过创建子进程完成,它们并非
Shell
自身的一部分,而是操作系统预装或用户安装的独立工具,比如:ls
、cp
、mv
等 - 内建命令:
bash
不创建子进程,而是由自己亲自执行,比如:cd
、echo
等
2.进程地址
2.1 概念引入
为了理解进程地址,我们将以如图所示的代码为例引入概念
观察输出的全局变量 g_val
,发现同一个变量,同一个地址,却读取到不同的数值,这种现象很诡异?!其实是有原因的,如果变量的地址是物理地址是不可能出现上面的情况,这里的的地址是 虚拟地址(线性地址)
,平时写代码时写入的都是虚拟地址
2.2 进程地址空间本质
显而易见,父进程和子进程都各自拥有自己独立的 PCB
,即 task_struct
,PCB
里有对应的指针指向进程地址空间。依托上面的例子,对于父进程来说,这个进程地址空间是内核为进程创建的一个结构体对象,在静态区创建了 g_val
,存在于进程地址空间,0x40405c
是个虚拟地址,该地址还存在于页表,然后系统会在实际存储找到空闲的物理空间供 g_val
实际存放,该物理地址也会存在于页表,与虚拟地址形成映射
对于子进程来说,它的代码数据都继承于父进程,页表也是复制的父进程的,一模一样,当修改共享的变量 g_val
时,系统会识别到该数据为共享的,此时会进行写时拷贝,重新再找一个空闲的空间给子进程的 g_val
,将新空间的物理地址传给页表,修改对应的值,此时的修改与虚拟地址无关,不会互相影响
在物理意义上,地址空间其实是 32
位的地址和数据总线,按照 0
和 1
发出高频低频电信号,这些总线排列组合形成的空间范围 [0,
2
32
2^{32}
232) 就是地址空间
因此这也就能说明为什么同一变量在不同的进程中,同一地址也能对应不同的值
2.3 区域划分
以一个有趣的例子为例,小学同桌之间闹矛盾会画一条三八线,比如小胖和小花是同桌,这个桌子相当于进程地址空间,即 destop_area
,每个人的范围即 area
,有 start
也有 end
,每个人的范围也是可以调大调小的
所谓的进程地址空间,本质是一个描述进程可视范围的大小,地址空间内一定要存在各种区域划分,对线性地址进行 start
,和 end
即可
地址空间本质是内核的一个数据结构对象,类似 PCB
一样,地址空间也是要被操作系统管理的:先描述,在组织。实际存在一个 mm_struct
作为 PCB
的进程地址空间,内部有大量变量记录着地址
2.4 为什么要有进程地址空间和页表
- 方便每个进程以统一的视角进行操作,符合规范化的操作流程,进程地址空间将不同进程的内存隔离开来,一个进程不能随意访问和修改其他进程的内存数据,彼此的数据不会混乱
- 增加进程虚拟地址空间可以让我们访问内存的时候,增加一个转换的过程,在这个转化的过程中,可以对我们的寻址请求进行审查,所以一旦异常访问,直接拦截,该请求不会到达物理内存,保护物理内存
✏️比如: 对于 char
字符串、字符等,一般在编译过后是不可修改的,在代码层面上只是报错不可修改,但是在系统层面上是怎么样的呢?实际上页表里存在对于权限的记录,像字符串这类变量会被标记为仅读,当对其进行申请修改时,系统会判断该程序出错,并终止进程。除此之外页表还有关于代码数据是否被加载到内存的判断,要知道,现代操作系统为了节省空间,通常采用惰性分批加载,即按需求加载或预加载
- 因为有地址空间和页表的存在,将进程管理模块,和内存管理模块进行解耦合。进程地址空间属于进程管理,内存属于内存管理,操作系统对于这两的权重是不同的,所以需要页表作为中间进行过渡,实现功能的同时又互不影响
总结:进程 = 内核数据结构(task_struct && mm_struct && 页表)+ 程序代码和数据