目录
(5)set:查看本地定义的shell变量(本地变量)和环境变量
残留问题:本地变量不会被子进程继承下去,为什么本地变量的子进程echo能使用本地变量local_val呢?——内建命令
1.问题1:main函数可以带参数吗?最多可以带多少? -可以,实际是3个
3.命令行可以带第三个参数!: char *env[](环境变量)
为什么要写时拷贝?创建子进程的时候,就把数据分开,不行吗? ?
fork有两个返回值,pid_ t id ,同一个变量,怎么会有不同的值?
一.环境变量
1.环境变量概念
2.常见环境变量
——详情见大标题二
3.查看环境变量方法
4.和环境变量相关的命令
(1)echo: 显示某个环境变量值
[zsh@ecs-78471 ~]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/zsh/.local/bin:/home/zsh/bin
(2)export :导出环境变量
(3)env: 显示所有环境变量
[zsh@ecs-78471 ~]$ env
XDG_SESSION_ID=3849
HOSTNAME=ecs-78471
TERM=xterm
SHELL=/bin/bash
HISTSIZE=10000
SSH_CLIENT=218.6.151.253 54910 22
SSH_TTY=/dev/pts/0
USER=zsh
…………………………………………
(4)unset :删除环境变量
(5)set:查看本地定义的shell变量(本地变量)和环境变量
[zsh@ecs-78471 ~]$ aaaa=100 aaaa是本地变量
[zsh@ecs-78471 ~]$ env | grep aaaa 环境变量查不到aaaa
[zsh@ecs-78471 ~]$ export bbbb=50 bbbb是本地变量
[zsh@ecs-78471 ~]$ env | grep bbbb
bbbb=50 环境变量查的到bbbb
[zsh@ecs-78471 ~]$ set | grep aaaa
aaaa=100 set既可以查到本地变量aaaa
[zsh@ecs-78471 ~]$ set | grep bbbb
bbbb=50 set也可以查到环境变量bbbb
5.PATH-搜索可执行程序的环境变量
一个问题?基本指令也是程序,为什么我们的代码程序运行要带路径,而系统的指令不用带路径? ?比如使用指令ls,pwd时直接使用即可,使用自己的myproc 可执行程序时(gcc -o mproc.c myproc) 需要./myproc.c
答:系统中是存在相关的环境变量,保存了程序的搜索路径的! 比如:执行 ls 这个可执行程序时,系统会在PATH中一个一个搜索,在特定路径(系统所有命令都在usr/bin路径下)下可以找到ls,就可以执行
系统中搜索可执行程序的环境变量叫做 PATH !
查看环境变量指令:echo $PATH
如何让自己的程序不带路径也可以执行?:
——①把自己的程序拷贝进环境变量中,就可以直接myproc执行程序了,拷贝的过程就是安装软件,但是不建议这样做,本身我们的软件就没什么意义,会污染系统,删除=卸载
——②把myproc自己的文件加入环境变量PATH中,export PATH=$PATH:路径(相当于把PATH中的路径改成PATH和myproc的路径)
错误示范:如果直接 export PATH=路径 ,会覆盖环境变量PATH的原有路径:这里pwd还能用,ls,top什么的就不能用了
二.介绍常见的环境变量
[zsh@ecs-78471 ~]$ env
XDG_SESSION_ID=3849
HOSTNAME=ecs-78471
TERM=xterm
SHELL=/bin/bash SHELL:显示shell所在路径
HISTSIZE=10000 HISTSIZE:历史能够记录自己敲过的命令条数
SSH_CLIENT=218.6.151.253 54910 22 SSH_CLIENT:ip地址
SSH_TTY=/dev/pts/0
USER=zsh USER:用户名
LD_LIBRARY_PATH=:/home/zsh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
下面是ls的配色方案:
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/zsh MAIL:系统中的邮箱路径
PATH:命令的搜索路径:
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/zsh/.local/bin:/home/zsh/bin
PWD=/home/zsh PWD:当前用户所处路径
LANG=en_US.UTF-8 LANG:支持的编码格式,现在支持的是UTF8
HISTCONTROL=ignoredups
SHLVL=1
HOME=/home/zsh HOME:代表不同用户的家目录
LOGNAME=zsh LOGNAME:登录的人是谁
SSH_CONNECTION=218.6.151.253 54910 192.168.0.183 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/1002
HISTTIMEFORMAT=%F %T zsh
_=/usr/bin/env
HOSTNAME——显示主机名
echo $HOSTNAME——显示主机名
SHELL
[zsh@ecs-78471 ~]$ echo $SHELL 显示shell所在路径
/bin/bash
HISTSIZE
[zsh@ecs-78471 ~]$ echo $HISTSIZE 历史能够记录自己敲过的命令条数
10000
[zsh@ecs-78471 ~]$ history 可以查看历史记录的命令
……
[zsh@ecs-78471 ~]$ history | wc -l 查看历史记录命令的总条数
1440
HOME
[zsh@ecs-78471 ~]$ echo $HOME
/home/zsh
三.环境变量和局部(普通)变量
环境变量:系统当中用做特殊用途的系统变量。
env:查看所有的环境变量
命令行变量分两种:
1.普通变量(在env查不到)
2.环境变量(全局)(在env能查到)
环境变量具有全局属性:环境变量是会被子进程继承下去的! !
所谓得本地变量,本质就是在bash内部定义的变量,不会被子进程继承下去!
定义 qi_104是本地变量,用set能找到qi_104这个本地变量,env就找不到本地变量,用myenv显示环境变量是空,当export qi_ 104 把本地变量导成环境变量后,myenv显示环境变量就是 qi_ 104
残留问题:本地变量不会被子进程继承下去,为什么本地变量的子进程echo能使用本地变量local_val呢?——内建命令
Linux下大部分命令都是通过子进程的方式执行的!
但是,还有一部分命令,不通过子进程的方式执行,而是由bash自己执行(调用自己的对应的函数来完成特定的功能),我们把这种命令叫做内建命令!
四.环境变量的C、C++获取方式
——————————————————————————————先讲讲main函数参数
1.问题1:main函数可以带参数吗?最多可以带多少? -可以,实际是3个
(1)先说main函数的前两个参数
main函数的前两个参数分别是下图所示:这两个参数我们称为:命令行参数
(2)argv[]中放什么呢?
我们给main函数传递的前两个参数 argc,char* argv[] 称为 命令行参数,传递的是命令行中输入的程序名和选项!比如命令行输入了./myproc -a -b -c,argc对应就是4,argv[ ] 中传入了这4个字符串,argv[0] = "./myproc",argv[1] = "-a",argv[2] = "-b",argv[3] = "-c",argv[4] = "NULL",指针数组以NULL结尾。
给命令行参数传程序名和选项意义是什么?我们通过实现一个命令行版的计算器来理解:
2.实现一个命令行版的计算器
我们要实现的功能:
执行 ./myproc -a 10 20 要实现 10+20=30;
执行 ./myproc -s 10 20 要实现 10-20=-10
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
if (argc != 4)
{
如果用户输入不对,打印使用手册,-a加法,-s(subtract)减法,-m乘法,-d除法:
printf("Usage: %s [-a|-s|-m|-d] one_data two_data\n", argv[0]);
return 0;
}
int x = atoi(argv[2]); atoi:把字符串转为整数
int y = atoi(argv[3]);
if (strcmp("-a", argv[1]) == 0) 如果输入-a,就是加法
{
printf("%d+%d=%d\n", x, y, x + y);
}
else if (strcmp("-s", argv[1]) == 0) 如果输入-s,就是减法
{
printf("%d-%d=%d\n", x, y, x - y);
}
else if (strcmp("-m", argv[1]) == 0) 如果输入-m,就是乘法
{
printf("%d*%d=%d\n", x, y, x * y);
}
else if (strcmp("-d", argv[1]) == 0 && y != 0) 如果输入-d,就是除法
{
printf("%d/%d=%d\n", x, y, x / y);
}
else
{
输入错误说明不会用,还是打印使用手册
printf("Usage: %s [-a|-s|-m|-d] one_data two_data\n", argv[0]);
}
return 0;
}
给命令行参数传程序名和选项意义是什么?
答:同一个程序,通过传递不同的参数,让同一个程序有不同的执行逻辑 / 执行结果。
这就解释了指令中那么多选项的由来和起作用的方式!!Linux系统中,会根据不通的选项,让不同的命令,可以有不同的表现!
这就解释了我们平时输入的指令传入了哪里
3.命令行可以带第三个参数!: char *env[](环境变量)
指针数组env[]和argv[]差不多,只不过传的是所有的指向环境变量的地址,指针数组也是以NULL结尾的
每个进程是会被传入环境变量参数的! ! 环境变量传给env[] :
C语言获取环境变量的第一种方法
1 #include<stdio.h>
2 int main(int argc,char* argv[],char* env[])
3 {存环境变量的指针数组以NULL结尾,所以到NULL时for循环结束:
4 for(int i=0;env[i];i++)
5 {
6 printf("env[%d]:%s\n",i,env[i]);
7 }
8 return 0;
9 }
[zsh@ecs-78471 10_18_复习环境变量]$ ./test
env[0]:XDG_SESSION_ID=3849
env[1]:HOSTNAME=ecs-78471
env[2]:TERM=xterm
env[3]:SHELL=/bin/bash
env[4]:HISTSIZE=10000
env[5]:SSH_CLIENT=218.6.151.253 54910 22
env[6]:SSH_TTY=/dev/pts/0
env[7]:USER=zsh
env[8]:LD_LIBRARY_PATH=:/home/zsh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
env[9]:LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
env[10]:MAIL=/var/spool/mail/zsh
env[11]:PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/zsh/.local/bin:/home/zsh/bin
env[12]:PWD=/home/zsh/linux/104new/10_18_复习环境变量
env[13]:LANG=en_US.UTF-8
env[14]:bbbb=50
env[15]:HISTCONTROL=ignoredups
env[16]:SHLVL=1
env[17]:HOME=/home/zsh
env[18]:LOGNAME=zsh
env[19]:SSH_CONNECTION=218.6.151.253 54910 192.168.0.183 22
env[20]:LESSOPEN=||/usr/bin/lesspipe.sh %s
env[21]:XDG_RUNTIME_DIR=/run/user/1002
env[22]:HISTTIMEFORMAT=%F %T zsh
env[23]:_=./test
env[24]:OLDPWD=/home/zsh/linux/104new
c语言中函数无参,也是可以传参的,只不过实参没用上
但是如果是int fun(void) 就不可以传参。
这就解释了我们可以直接给main()函数传参的原因
#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;
}
——————————————————————————————————————————
4.通过代码获取环境变量的三种方式
①C语言获取环境变量的第一种方法
#include <stdio.h>
int main(int argc, char* argv[], char* env[])
{
for (int i = 0; env[i]; i++) {
printf("%s\n", env[i]);
}
return 0;
}
②通过第三方变量environ获取
c语言给我们提供了一个全局变量environ
③getenv——获取环境变量的接口
通过环境变量名直接获得环境变量的内容
a.我为什么要获取环境变量? ? 万一以后要用(一定有特殊用途!)
b:环境变量是谁给我的呢?? 目前谈不清,但是我们可以观察到!
环境变量为什么具有全局性!
五.程序地址空间
1.初步认识
程序地址空间,不是内存!
程序地址空间=进程地址空间,是操作系统上的概念!
堆,栈相对而生,
堆区向地址增大方向增长
栈区向地址减少方向增长
我们一般在C函数中定义的变量,通常在栈上保存,那么先定义的一定是地址比较高的!
如何理解static变量——函数内定义的变量用static修饰,本质是编译器会把该变量编译进全局数据区!
2.感知地址空间的存在!
函数内定义的变量用static修饰,本质是编译器会把该变量编译进全局数据区! 父子进程共享全局变量。
fork创建子进程,父子进程同时运行,若在子进程中第5秒改了全局变量g_val的值,会发现
父子进程读取同一个变量(因为地址一样!),但是子进程修改的全局变量后,父子进程读取到的
内容却不一样! ! ! !
结论:——我们在C/C++中使用的地址,绝对不是物理地址
如果是物理地址,这种现象不可能产生
3.让每一个进程都认为自己是独占系统中的所有资源
每一个进程在启动的时候,都会让操作系统给他创建一个地址空间,该地址空间就是进程地址空间
每一个进程 都会有一个自已的进程地址空间! !
操作系统要不要管理这些进程地址空间呢??
先描述,在组织
进程地址空间,其实是内核的一个数据结构,struct mm_ struct (稍后看! )
举例子:
①我们往银行存10亿,但是银行可能没10亿,但银行给你画的饼就是10个亿。
②美国一个富翁有3个私生子,富翁给每个私生子画的大饼:说自己有10个亿以后都是你的财产,富翁: OS
三个私生子:进程
富翁给三个私生子画的大饼:进程地址空间
让每一个进程都认为自己是独占系统中的所有资源的! !
4.虚拟地址空间=进程地址空间 mm_struct
所谓的进程/虚拟地址空间:其实就是OS通过软件的方式,给进程提供一个软件视角,认为自己会独占系统的所有资源(内存),每个进程会维护一个mm_struct,虚拟地址和物理内存通过页表建立映射关系,(上学时,一个班级中,老师点名需要一张名单,这个名单就是虚拟地址空间)
linux下:逻辑地址=虚拟地址=线性地址
什么叫做区域
每个区域范围,都是可以有对应的编号的,在虚拟地址空间 mm_struct 中有存储各个数据区的起始地址和结束地址,也就是区域
5.写时拷贝
写时拷贝正好可以回答fork 现象:三.2 子进程修改的全局变量后,父子进程读取到的内容却不一样
(下面这两张图是一样的)
因为进程具有独立性! !做到互不影响。当子进程修改g_val时,为了做到子进程不影响父进程,会发生写时拷贝:g_val会再拷贝一份,子进程中的映射关系会改变,指向新的g_val,但是g_val的虚拟地址(相对地址)还是原来的地址,和父进程的g_val虚拟地址(相对地址)一样,但是他们的物理地址不一样,100改成200时,只会改变新的g_val(写时拷贝本身就是有OS的内存管理模块完成的!所以我们感知不到)
为什么要写时拷贝?创建子进程的时候,就把数据分开,不行吗? ?
答:1.父进程的数据,子进程不一定全用,即便使用,也不一定全部写入——会有浪费空间的嫌疑
2. 最理想的情况,只有会被父子修改的数据,进行分离拷贝。不需要修改的共享即可——但是从技术角度实现复杂,不可能实现
3.如果fork的时候,就无脑拷贝数据给子进程,会增加fork的成本(内存和时间)
所以最终采用写时拷贝:
①写时拷贝只会拷父子修改的,变相的,就是拷贝数据的最小成本
②拷贝的成本依旧存在
写时拷贝本质是延迟拷贝策略!只有真正使用的时候,才给你!
你想要,但是不立马使用的空间,先不给你,那么也就意味着可以先给别人!
变相的提高内存的使用率!
fork有两个返回值,pid_ t id ,同一个变量,怎么会有不同的值?
pid_ t id是属于父进程栈空间中定义的变量,fork内部,return 会被执行两次,return 的本质,就是通过寄存器将返回值写入到接受返回值的变量中! !
当id=fork()的时候,谁先返回,谁就要发生写时拷贝,所以,同一个变量,会有不同的内容值,本质是因为大家的虚拟地址是一样的,但是大家对应的物理地址是不一样的! !
6.为什么要有虚拟地址空间?
访问内存添加了一层软硬件层,可以对转化过程进行审核,非法的访问,就可以直接拦截了
1.保护内存!
2.进程管理-Linux内存管理
通过地址空间,进行功能模块的解耦! !
3.让进程或者程序可以以一种统一的视角看待内存!
方便以统一的方式来编译和加载所有的可执行程序
简化进程本身的设计与实现!