在讲环境变量之前,我们先把上次遗留知识点给总结了(僵尸进程和孤儿进程)
1 Z(zombie)-僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程 。
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
我们来创建一个僵尸进程的栗子:
1 #include<iostream> 2 #include<unistd.h> 3 #include<stdio.h> 4 using namespace std; 5 6 int main() 7 { 8 pid_t ret= fork(); 9 if(ret==0) 10 { 11 while(1) 12 { 13 //child 14 printf("我是一个子进程,pid:%d,ppid;%d\n",getpid(),getppid()); 15 sleep(1); 16 return 1; 17 } 18 } 19 else 20 { 21 while(1) 22 { 23 //parent 24 printf("我是一个父进程,pid:%d,ppid;%d\n",getpid(),getppid()); 25 sleep(1); 26 } 27 } 28 return 0; 29 }
当我们查看该进程时:
不难发现子进程已经处于僵尸状态了,那这样子进程不就没法回收了吗?该进程就会一直占有CPU资源,那不就造成了内存泄露了吗,对的。另外僵尸进程是不能够用命令杀掉的(因为已经退出)。
我们总结下僵尸进程的危害:
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态!维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护!
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间! 最终会造成内存泄漏!
如何避免我们将放到后面来讲。
2 孤儿进程
父进程如果提前退出,那么子进程后退出,进入 Z 之后,那该如何处理呢?
父进程先退出,子进程就称之为 “ 孤儿进程 ”
孤儿进程被 1 号 init 进程领养 。
上面我们提到了子进程先退出就是僵尸进程,那么父进程先退出呢?
我们想想,此时父进程会是僵尸状态吗?
答案是不会的,父进程在此时会被他自己的父进程(bash)回收,而它的子进程则会交给1号进程领养,我们可以修改一下代码,让父进程先退出,然后运行:
这时子进程已经被1号进程给领养了,我们想要杀掉该进程就可以用
killall "文件名"
我们不难看出孤儿进程正常情况下是不会有内存泄漏的。
3 环境变量
3.1 基本概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。
环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
相信大家在学习Java的时候都应该配置过环境变量,这是大家学习Java的第一步,相信大家在配置环境变量是心里非常疑惑,为啥要配置环境变量呢?接下来我会慢慢为大家解答的。
首先为问大家一个问题:为啥我们Makefile生成了可执行文件后再运行要加上./ ?
不加./就找不到该文件了吗?答案是是的,只有加上了./我们才能够正确定位到我们想要找的位置,但是像我们使用的一些基本命令像:whoami pwd 等等为啥就不用了呢?
这就是我们今天要讲的主题:因为配置了环境变量。
我们可以使用 env 来查看环境变量:
[grm@VM-8-12-centos lesson7]$ env XDG_SESSION_ID=342794 HOSTNAME=VM-8-12-centos SHELL=/bin/bash TERM=xterm HISTSIZE=3000 SSH_CLIENT=117.172.173.113 7924 22 OLDPWD=/home/grm SSH_TTY=/dev/pts/0 USER=grm LD_LIBRARY_PATH=:/home/grm/.VimForCpp/vim/bundle/YCM.so/el7.x86_64 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: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin MAIL=/var/spool/mail/root PWD=/home/grm/lesson7 LANG=en_US.utf8 HOME=/home/grm SHLVL=2 LOGNAME=grm SSH_CONNECTION=117.172.173.113 7924 10.0.8.12 22 LESSOPEN=||/usr/bin/lesspipe.sh %s PROMPT_COMMAND=history -a; history -a; history -a; history -a; printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}" XDG_RUNTIME_DIR=/run/user/0 HISTTIMEFORMAT=%F %T _=/usr/bin/env
不难发现上面出现了很多配置了的环境变量,像:
PATH : 指定命令的搜索路径
HOME : 指定用户的主工作目录 ( 即用户登陆到 Linux 系统中时 , 默认的目录 )
SHELL : 当前 Shell, 它的值通常是 /bin/bash
还有很多,这里就不一一列举了,大家可以在上面找到。
假设我们要查询pwd在哪个目录下,可以用命令:
[grm@VM-8-12-centos lesson7]$ which pwd /usr/bin/pwd
我们查看一下环境变量的方法:
echo $NAME
例如:
[grm@VM-8-12-centos lesson7]$ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
我们很容易验证pwd的绝对路径:
[grm@VM-8-12-centos lesson7]$ ls /usr/bin/pwd /usr/bin/pwd
那我们是不是只要将我们可执行文件添加到环境变量中就可以不用加./了呢?
我们可以来试试:
添加环境变量的方法:
export PATH=$PATH:程序所在路径
这时我们直接运行hello程序依旧能够跑起来:
假如我们不小心将命令写成了这个样子:
export PATH=程序所在路径
这时我们系统自带的环境变量将被我们新加入的环境变量所覆盖,大家这时也不要担心,我们将XShell关闭后重新打开就好了。
3.2 测试HOME
[grm@VM-8-12-centos lesson7]$ cd ~ [grm@VM-8-12-centos ~]$ pwd /home/grm [grm@VM-8-12-centos ~]$ echo $HOME /home/grm
我们不难发现我们平常用的pwd指令本质上就是将其添加到了环境变量中。
3.3 和环境变量相关的命令
1. echo: 显示某个环境变量值
2. export: 设置一个新的环境变量
3. env: 显示所有环境变量
4. unset: 清除环境变量
5. set: 显示本地定义的 shell 变量和环境变量
我们看下面的命令:
[grm@VM-8-12-centos ~]$ val=20 [grm@VM-8-12-centos ~]$ echo val val [grm@VM-8-12-centos ~]$ echo $val 20
下面我们加入的val在环境变量中吗?
我们通过env命令查询后发现没有在环境变量中,这种叫做本地变量,只在Shell内部有效。
要想导入环境变量得用export命令,就像上面我们使用export导入环境变量一样。当我们使用set时就能够看见我们写入的本地变量和环境变量,不过这个命令很少用。
3.4 环境变量的组织方式
大家心中的main函数应该是无参的,因为我们平时写代码从来都不会些main函数的参数。但是实际上main函数1最多是有3个参数的,分别是:int argc, char *argv[], char *env[]
我们可以来看看命令行第3个参数究竟是什么?
我们创建一个测试文件,并向里面写入:
1 #include<iostream> 2 using namespace std; 3 E> 4 int main(int argc,char* argv[],char* env[]) 5 { 6 for(int i=0;env[i];i++) 7 { 8 cout<<env[i]<<endl; 9 } 10 return 0; 11 }
运行后发现:
这不就是我们刚才通过env查到的环境变量吗?对的,其实main函数中第三个参数是一个指针数组,指向的就是环境变量表。
我们也可以通过第三方变量environ获取:
#include <iostream> using namespace std; int main(int argc, char *argv[]) { extern char **environ; int i = 0; for(; environ[i]; i++){ printf("%s\n", environ[i]); } return 0; }
libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。
我们还可以通过系统调用获取或设置环境变量。这样做的好处是每次寻找环境变量不用每次都遍历环境变量表。
#include <iostream> #include <cstdlib.h> int main() { printf("%s\n", getenv("PATH")); return 0; }
常用getenv和putenv函数来访问特定的环境变量。
putenv我们放到后面来讲解。
那么char* argv[]又是什么鬼呢?
我们来看一段代码:
1 #include<iostream> 2 using namespace std; 3 4 int main(int argc, char* argv[],char* env[]) 5 { 6 for(int i=0;i<argc;i++) 7 { 8 cout<<argv[i]<<endl; 9 } 10 }
当我们这样运行时:
不难发现argv[]将我们在命令行上敲出来的选项都打印出来了,这也是我们输入一些命令时(例如:ls )附带一些选项时的原理,为什么当执行不同的选项时结果会是不同的,就是因为当执行不同的选项时将选项的结果都保存到了argv[]中,当执行时就会拿出保存的结果来执行,具体的执行方式我们将放到后面来讲。
3.5 环境变量通常是具有全局属性的
环境变量通常具有全局属性,可以被子进程继承下去.
#include <stdio.h> #include <stdlib.h> int main() { char * env = getenv("MYENV"); if(env){ printf("%s\n", env); } return 0; }
直接查看,发现没有结果,说明该环境变量根本不存在:
当我们导出环境变量export MYENV="hello world"
再次运行程序,发现结果有了!说明:环境变量是可以被子进程继承下去的!(这张表是由bash制作而成的)
总结:
环境变量本质就是内存级的一张表,这张表在用户登录系统的时候,进行给特定的用户形成属于自己的环境变量表。
环境变量中的每一个都有自己的应用场景,有的是按路径查找的,有的是进行身份验证的,有的是进行动态库查找的,有的是用来确定当前路径等等。
环境变量的相关数据是从相关的配置文件中读到的,每一个元素都是kv的。