目录
环境变量:
env:显示所有的环境变量:
echo +$环境变量名表示查看环境变量的值
理解环境变量:
getenv:显示环境变量的值
export
set命令:显示所有变量
unset取消变量:
pwd:当前路径
自主实现pwd环境变量:
问题:环境变量是怎么被子进程继承的(思考题目)
命令行参数:
main函数的第三个参数:
environ
putenv
stat
我们提出一个问题:
现象:
现象:
echo的问题:
程序地址空间
感性理解虚拟地址空间
操作系统如何画饼呢?
环境变量:
一般是指在 操作系统 中用来指定操作系统运行环境的一些参数
我们使用指令来具体理解环境变量。
env:显示所有的环境变量:
这就是所有的环境变量。
echo +$环境变量名表示查看环境变量的值
例如:
PATH上一节课我们已经讲过,PATH是一个环境变量,它的值等于Linux系统指令所在的路径。
我们再举几个例子:
假如我们想要显示家目录:
假如我们想要显示登录账户的名字:
理解环境变量:
我们输入指令env,显示所有的环境变量:
我们可以在这些环境变量中找到名字为USER的环境变量:
这里表示当前登录的用户名。
接下来,我们切换到root用户:
我们输入env指令:
当我们切换用户的时候,我们的USER环境变量发生了变化。
环境变量是可以随着我们运行环境的变化而发生更改的。
接下来,我们写一串代码帮助理解:
我们首先创建一个源文件
我们再写出mycmd.c的源文件对应的解决方案文件Makefile
我们使用vim对Makefile进行文本编辑:
我们对这串指令进行分析:
表示mycmd的依赖对象是mycmd.c
这是对应的依赖方法:gcc表示我们要进行编译 -o表示我们要进行编译链接形成可执行程序,$@表示左依赖列表,也就是mycmd,@^表示的是右依赖列表,也就是mycmd.c,表示我们要把mycmd.c经过编译链接形成对应的可执行程序mycmd
.PHONY有两层含义:含义1表示我们的clean是一个为目标,没有对应的依赖对象,含义2:.PHONY修饰的目标不会关心文件的修改时间,总是被执行,clean对应的依赖方法是强制删除文件mycmd.c
getenv:显示环境变量的值
该函数的返回值列表如图:
我们举一个例子:
我们通过echo得到了环境变量USER的值,我们通过getenv函数也能得到相同的结果。
我们对mycmd.c源文件进行文本编辑:
我们调用getenv函数,函数的参数是字符串"USER",char*类型的who用来接收返回时,然后我们以字符串的形式打印who
我们输入make,默认执行的是Makefile的第一个解决方法:
接下来,我们通过./mycmd来调用该程序:
所以我们的who指向的bbb,我们可以得出结论:
getenv函数的作用:
name是环境变量的名字,返回的结果是环境变量的值,假如调用失败时,我们返回空指针。
接下来,我们切换到用户root
我们调用该可执行程序:
我们并没有对mycmd.c的源文件的文本进行修改,但是USER发生了变化,所以我们打印的结果也就发生了变化。
所以这里的USER环境变量的作用就是标识当前使用的Linux用户。
我们再切换会原来的bbb用户
对mycmd.c进行文本修改:
我们调用strcmp,假如who等于root时,我们显示用户名,陷入who不是root时,我们提示权限不足。
我们当前的账户名为bbb
我们make之后调用该可执行程序:
显示我们权限不足,我们切换成为root用户:
所以我们可以通过判定环境变量USER的值来决定那些用户有权限,而哪些用户没有权限。
这里的环境变量起到了身份认证的作用,当我们想要执行一个操作时,我们先检测环境变量USER的值,假如USER拥有这个权限,我们再执行这个操作。
所以sudo的本质就是相当于把我们的环境变量USER的值由原来的bbb切换成为root。
我们也可以自己设置一些环境变量,例如:
我们设置变量myval的值为1234
我们调用echo
我们显示了该变量的值,接下来,我们调用env,从env中找这个myval的变量。
我们并没有找到myval的值,原因是什么?
原因是env显示的是所有的环境变量,而echo既可以显示普通的变量,也可以显示环境变量,我们这里的myval很明显是一个普通变量,所以env无法显示。
我们可以把这里的myval理解为局部变量,把env显示的环境变量理解为全局变量。
那我们该如何链接环境变量的全局性和这个局部变量呢?
我们进行解释:
首先对mycmd.c进行文本编辑。
我们调用getenv函数 看是否能够得到myenv的值。
所以myval值并不是一个环境变量。
接下来,我们介绍export指令
export
export的作用是定义环境变量;假如一个变量已经存在,export+该变量能够将该变量导成环境变量。
例如:
我们把myval导成环境变量。
这个时候,在env中就能找到该环境变量了。
接下来我们调用mycmd,因为我们的myval变量变成了全局变量。
所以可以显示该变量的值。
重点:bash是一个系统进程,在该进程内部我们创建了环境变量和普通变量,mycmd是bash创建的子进程,子进程可以继承父进程的环境变量,但是无法继承普通变量,所以环境变量具有全局属性,我们有了环境变量,就可以做身份识别,查找指令路径等工作了
本地变量只在bash进程中有效,无法在被子进程mycmd继承。
set命令:显示所有变量
我们创建一个本地变量,我们通过env无法显示,这个时候,我们可以使用set命令
set既显示所有的环境变量,也显示所有的本地变量
假如我们想要把youval导成环境变量,我们可以用export
unset取消变量:
set和env都找不到这个变量。
export定义环境变量的写法:
pwd:当前路径
我们提出一个问题:
为什么我们可以调用这个指令?
我们知道,ls是系统给的指令,环境变量PATH中对应的就是系统路径,所以我们知道路径,就可以直接调用该指令,但是mycmd呢?我们知道,调用一个指令要么知道绝对路径,要么知道相对路径,最标准的写法应该是这样:
因为我们有环境变量PWD
我们输入env查看环境变量。
我们可以找到环境变量PWD
这里表示的就是当前路径,因为我们的ls是bash的子进程,子进程继承了父进程的环境变量,也就继承了PWD,所以我们知道了当前的路径,所以我们可以这样写:
我们退到上级目录:
环境变量PWD也发生了改变。
原因:环境变量是在bash中维护,当当前路径发生改变时,bash就会修改环境变量PWD的值。
所以这里实质的原因是:ls也是一个进程,是bash的子进程,PWD是bash的环境变量,环境变量可以由子进程来继承,所以ls继承了PWD,所以ls知道了当前的路径,所以也就知道了mycmd的位制。
自主实现pwd环境变量:
非常简单,因为我们有了PWD环境变量,我们直接调用getenv就能实现PWD
我们也可以把mycmd的路径添加到环境变量PATH中。这样不需要./就可以调用我们自己实现的PWD。
问题:环境变量是怎么被子进程继承的(思考题目)
命令行参数:
main函数的参数列表叫做命令行参数。
我们思考一个问题:main函数的参数可以有几个。
main函数最多有三个参数,但是我们正常见的main函数一般只有两个参数。
我们对这两个参数进行解释:
参数argc是一个整型,argv是一个指针数组 ,argv指向的数组的元素个数是argc个。
这两个参数是谁传给main函数的?
答:我们可以理解为父进程或者系统传递给main函数。
我们对mycmd进行文本编辑:
当在命令行中输入的内容不断增多时,数组的内容也会自动变多。
我们可以这样理解:
例如我们在Linux中可以这样调用指令:
所以ls是一个程序,本质上也就是main函数,main函数的第一个参数我们不需要输入,随着第二个参数输入,第一个参数自动匹配,这里的-a -l -d表示我们向argv[]指针数组中传递的内容。
命令行参数的本质是依次把程序名和选项传递给数组argv
一共有几个选项,我们的argc就是多少,我们的argv数组的元素个数就是多少。
我们画图加强理解:
例如我们调用这样一个指令:
然后把这些字符传递给argv数组,ls传向第一个元素位置,a传到第二个元素位置等等。
这个数组的最后元素位置存放的是空指针。
从把我们写的指令进行解析,到把每一个字符放到argv数组中,都是由我们的命令行解释器,也就是shell完成的。
但是传这些选项的意义又是什么呢?
我们对mycmd.c进行编辑:
我们进行调用:
所以我们可以通过命令行中的选项来决定我们要调用什么的功能。
这和我们的这些指令是等价的:
main函数的第三个参数:
env是一个环境变量参数,是一个指针数组。
数组env存储的是指向环境变量的指针。
环境变量数组的末尾也是空指针。
所以环境变量就能被我们的进程拿到了,我们就可以使用环境变量了。
接下来,我们对观点进行证明:
如果我们的观点正确的话,我们应该可以打印出许多环境变量。
main函数最多三个参数
第一个参数argc是第二个参数数组argv的元素的个数,第二个参数是指针数组argv,我们可以通过输入指令(字符串)来决定我们调用什么程序的什么选项,这些程序和选项就被存放到数组argv中,第三个参数是指针数组env,数组中存放的是环境变量,我们通过该数组可以访问环境变量。
所以我们通过指令+选项+环境变量最终调用成功了一个进程。
所以解决上述一个问题:shell是怎么把环境变量传递给子进程的。
通过命令行参数,我们在shell中输入指令本质上就是调用main函数,我们把shell的环境变量以数组指针的形式传递给子进程,子进程就能够继承到环境变量了。
正常情况下,假如我们的main函数并没有传递环境变量数组,我们怎么拿到环境变量。
我们可以通过getenv的形式得到环境变量。
这里表示打印系统指令所在路径:
environ
environ是一个二级指针,指向的是环境变量表:
我们使用environ进行尝试:
environ[i]表示*(environ+i),表示指向环境变量的指针,我们根据该指针就能打印出对应的环境变量。
显示了所有的环境变量。
获取环境变量的三种方式:
最重要的是第一个getenv
putenv
表示更改或添加一个环境变量的值
stat
表示查看一个文件的所有属性:
例如:
我们提出一个问题:
这是我们当前的目录,假如我们重新登录我们的用户:
为什么pwd发生了变化。
因为我们的shell命令在我们重新登入用户时,检测我们的用户是bbb,用户的家目录是/home/bbb,然后我们通过某些cd命令就到了该路径下。
现象:
这两个到的路径是相同的,原因是cd ~检测到我们要到的路径是家目录,我们直接找到HOME环境变量,跳到HOME所在的路径处。
现象:
我们在main函数进行传参时,传递的env[]数组实际上传递的是environ,为什么呢?因为函数传参要发生降维,例如我们要把整型a传递给main函数,我们要传递a的地址,否则我们只能完成值拷贝,env是指针数组,发生降维,降维之后的结果就是二级指针,也就是environ。
echo的问题:
echo是一个进程,是shell的子进程,echo可以继承环境变量,但是无法继承本地变量,为什么这里还能显示本地变量val的值。
程序地址空间
我们之前讲的c++地址空间,这些空间的实质是什么?是内存吗?
不是内存,我们用现象进行解释:
把全局的mycmd替换成为mytest
我们首先理解一个理念:
只要是一个进程,就要被操作系统管理,只要被操作系统管理,在创建子进程时,就要拷贝父进程的内核数据结构,例如父进程有pcb,在创建子进程时拷贝父进程的pcb。
我们对mytest.c进行文本编辑:
我们创建一个变量global_value,接下来,我们创建子进程,当id<0表示fork失败,当id为0时,表示进行的是子进程,当id不为0时,表示进行的是父进程,我们在子进程运行到一定时间时修改global_value的值。
当我们修改了global_value的值,该变量的地址没有修改,两个地址相同的变量值却不同证明了这里的地址并不是真实的内存。
换言之:我们c语言学到的所有的地址,空间等等都不是真实的内存。
所以这里的地址以及我们c语言学的地址都是虚拟地址(线性地址)
感性理解虚拟地址空间
有一个大富翁,他有十亿美金,他有三个私生子,这些私生子都不知道彼此的存在,大富翁为了鼓励这些私生子努力工作,分别与三个孩子单独会面并且许诺他们在自己死后把全部的资产继承给这个孩子,所以所有的私生子都非常努力的工作,而且所有的私生子都认为十亿美金迟早是自己的。
但是私生子也需要花钱,于是每一个私生子就问大富翁要钱,每次只要1w美金。
所以,这十个亿的美金只是大富翁给所有私生子画的一个大饼。
我们以系统的方式进行理解:
大富翁就是操作系统,十亿美金就是内存,这三个私生子就是进程,所有的进程都认为自己会独占系统资源(实际上,这只是操作系统给进程画的饼)。
所有私生子要的1w美金只是进程申请的资源和空间。
给每一个儿子画的大饼就是进程地址空间。
操作系统如何画饼呢?
首先,画饼的本质是老板给员工的脑海了构建一个蓝图,如图所示:
假如有500个员工,老板和员工a许诺你以后要当部门总监,之后又对员工a许诺你以后要当总经理,这不就是乱套了吗?
所以不仅员工要被管理,老板画的饼也要被管理,老板画的饼叫做地址空间。
如何管理呢?
先描述,再组织。
所有的进程都以结构体的形式先描述,然后再以某种数据结构的形式再进行组织。