个人主页:小则又沐风个人专栏:数据结构竞赛专栏C语言CLinux座右铭路虽远行则将至事虽难做则必成目录前言命令行参数和环境变量命令行参数环境变量查找环境变量export: 设置⼀个新的环境变量查看所有的环境变量getenv();环境变量的继承程序的地址空间虚拟地址的管理前言在前面我们知道了怎么创建进程,并且知道了怎么拿到进程退出的信息和拿到这个信息的重要性今天我们来结束进程的篇章,今天会补充一下进程的概念的知识(包括环境变量和虚拟地址空间)然后讲解进程替换的内容,模拟实现一下xshell命令行参数和环境变量• 环境变量(environmentvariables)⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数• 如我们在编写C/C代码的时候在链接的时候从来不知道我们的所链接的动态静态库在哪 ⾥但是照样可以链接成功⽣成可执⾏程序原因就是有相关环境变量帮助编译器进⾏查找。• 环境变量通常具有某些特殊⽤途还有在系统当中通常具有全局特性命令行参数我们在日常使用指令的时候比如ls我们会又在后面有选项的,我们知道的是Linux的底层使用C语言实现的,那么这个东西是怎么实现的呢?我们的main函数有参数吗?有的有的#includeiostream using namespace std; int main(int argc, char const *argv[]) { for(int i0;iargc;i) { printf(argv[i] is %s\n,argv[i]); } return 0; }这就是我们main函数的参数,我们来看看是什么情况:我们可以发现我们像使用ls的方法也可以使用我们自己的程序那么这个main函数的参数就是用来表示选项的那么我们就可以想到了我们命令行的参数就是传入到了这个argv的字符串的表中,然后将一个个的选项分隔开一个个来执行他的功能所以我们也可以简单模拟实现一下这个功能#include iostream #include string.h using namespace std; int main(int argc, char const *argv[]) { if (argc ! 3) { // printf(%d\n,argc); printf(用法错误,这个程序需要两个选项\n); return 1; } if (strcmp(argv[1], -a) 0) { printf(这是a功能\n); printf(hhhh\n); } else if (strcmp(argv[1], -b) 0) { printf(这是b功能\n); printf(xxxxx\n); } if (strcmp(argv[2], -a) 0) { printf(这是a功能\n); printf(hhhh\n); } else if (strcmp(argv[2], -b) 0) { printf(这是b功能\n); printf(xxxxx\n); } return 0; }我们执行一下看看这大概就是指令的参数的内部核心了吧需要注意的是我们的第一个运行的程序也算是一个指令的选项的,也就是说我们不掺入参数的话,我们默认的argc是1那么我们来解决一个问题,为什么我们自己编写的程序必须带./呢?环境变量需要知道的是我们在执行一个程序的时候必须找到这个程序.那么我们就可以解释了这个./不就是代表的就是当前路径吗?但是常用的指令为什么不需要带路径呢?为什么我们的就是not found了这就是因为我们的可执行程序又不是在这个路径下找的,在什么路径下找程序是规定了的我们可以查看一下那么系统就会在这些的路径下来查找指令了,如果我们把text的路径添加到这里的话我们的text也可以直接运行了果然是这样的那么我们来重启一下我们的机器怎么又显示找不到了下面我们来介绍一下环境变量是怎么生成的环境变量是从我们的配置文件中生成的,从这些的配置文件中获得环境变量刚才我们知识修改了环境变量并没有修改配置文件,当我们的机器重启之后当然我们刚才配置的环境就消失了除了这个PATH还有什么环境变量呢??我们来打印一下环境变量#includeiostream using namespace std; int main(int argc, char *argv[], char *env[]) { for(int i0;env[i];i) { printf(%s\n,env[i]); } return 0; }在这里我们进一步了解一些main函数的参数,第三个参数是一个环境变量的表我们看看打印出的环境变量是什么SHELL/bin/bash COLORTERMtruecolor TERM_PROGRAM_VERSION1.108.2 PWD/home/jiao/study/5.24 LOGNAMEjiao XDG_SESSION_TYPEtty VSCODE_GIT_ASKPASS_NODE/home/jiao/.vscode-server/cli/servers/Stable-c9d77990917f3102ada88be140d28b038d1dd7c7/server/node HOME/home/jiao LANGen_US.UTF-8 LS_COLORSrs0:di01;34:ln01;36:mh00:pi40;33:so01;35:do01;35:bd40;33;01:cd40;33;01:or40;31;01:mi00:su37;41:sg30;43:ca00:tw30;42:ow34;42:st37;44:ex01;32:*.tar01;31:*.tgz01;31:*.arc01;31:*.arj01;31:*.taz01;31:*.lha01;31:*.lz401;31:*.lzh01;31:*.lzma01;31:*.tlz01;31:*.txz01;31:*.tzo01;31:*.t7z01;31:*.zip01;31:*.z01;31:*.dz01;31:*.gz01;31:*.lrz01;31:*.lz01;31:*.lzo01;31:*.xz01;31:*.zst01;31:*.tzst01;31:*.bz201;31:*.bz01;31:*.tbz01;31:*.tbz201;31:*.tz01;31:*.deb01;31:*.rpm01;31:*.jar01;31:*.war01;31:*.ear01;31:*.sar01;31:*.rar01;31:*.alz01;31:*.ace01;31:*.zoo01;31:*.cpio01;31:*.7z01;31:*.rz01;31:*.cab01;31:*.wim01;31:*.swm01;31:*.dwm01;31:*.esd01;31:*.avif01;35:*.jpg01;35:*.jpeg01;35:*.mjpg01;35:*.mjpeg01;35:*.gif01;35:*.bmp01;35:*.pbm01;35:*.pgm01;35:*.ppm01;35:*.tga01;35:*.xbm01;35:*.xpm01;35:*.tif01;35:*.tiff01;35:*.png01;35:*.svg01;35:*.svgz01;35:*.mng01;35:*.pcx01;35:*.mov01;35:*.mpg01;35:*.mpeg01;35:*.m2v01;35:*.mkv01;35:*.webm01;35:*.webp01;35:*.ogm01;35:*.mp401;35:*.m4v01;35:*.mp4v01;35:*.vob01;35:*.qt01;35:*.nuv01;35:*.wmv01;35:*.asf01;35:*.rm01;35:*.rmvb01;35:*.flc01;35:*.avi01;35:*.fli01;35:*.flv01;35:*.gl01;35:*.dl01;35:*.xcf01;35:*.xwd01;35:*.yuv01;35:*.cgm01;35:*.emf01;35:*.ogv01;35:*.ogx01;35:*.aac00;36:*.au00;36:*.flac00;36:*.m4a00;36:*.mid00;36:*.midi00;36:*.mka00;36:*.mp300;36:*.mpc00;36:*.ogg00;36:*.ra00;36:*.wav00;36:*.oga00;36:*.opus00;36:*.spx00;36:*.xspf00;36:*~00;90:*#00;90:*.bak00;90:*.crdownload00;90:*.dpkg-dist00;90:*.dpkg-new00;90:*.dpkg-old00;90:*.dpkg-tmp00;90:*.old00;90:*.orig00;90:*.part00;90:*.rej00;90:*.rpmnew00;90:*.rpmorig00;90:*.rpmsave00;90:*.swp00;90:*.tmp00;90:*.ucf-dist00;90:*.ucf-new00;90:*.ucf-old00;90: SSL_CERT_DIR/usr/lib/ssl/certs GIT_ASKPASS/home/jiao/.vscode-server/cli/servers/Stable-c9d77990917f3102ada88be140d28b038d1dd7c7/server/extensions/git/dist/askpass.sh PROMPT_COMMAND__vsc_prompt_cmd_original SSH_CONNECTION39.144.28.73 7013 10.2.0.7 22 VSCODE_GIT_ASKPASS_EXTRA_ARGS VSCODE_PYTHON_AUTOACTIVATE_GUARD1 LESSCLOSE/usr/bin/lesspipe %s %s XDG_SESSION_CLASSuser TERMxterm-256color LESSOPEN| /usr/bin/lesspipe %s USERjiao VSCODE_GIT_IPC_HANDLE/run/user/1003/vscode-git-0081e76109.sock GOPROXYhttps://mirrors.tencent.com/go,direct SHLVL1 XDG_SESSION_ID5078 XDG_RUNTIME_DIR/run/user/1003 SSL_CERT_FILE/usr/lib/ssl/cert.pem SSH_CLIENT39.144.28.73 7013 22 VSCODE_GIT_ASKPASS_MAIN/home/jiao/.vscode-server/cli/servers/Stable-c9d77990917f3102ada88be140d28b038d1dd7c7/server/extensions/git/dist/askpass-main.js XDG_DATA_DIRS/usr/local/share:/usr/share:/var/lib/snapd/desktop BROWSER/home/jiao/.vscode-server/cli/servers/Stable-c9d77990917f3102ada88be140d28b038d1dd7c7/server/bin/helpers/browser.sh PATH/home/jiao/.vscode-server/data/User/globalStorage/github.copilot-chat/debugCommand:/home/jiao/.vscode-server/data/User/globalStorage/github.copilot-chat/copilotCli:/home/jiao/.vscode-server/cli/servers/Stable-c9d77990917f3102ada88be140d28b038d1dd7c7/server/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin DBUS_SESSION_BUS_ADDRESSunix:path/run/user/1003/bus TERM_PROGRAMvscode VSCODE_IPC_HOOK_CLI/run/user/1003/vscode-ipc-0c32845d-e511-48b7-b310-062b475cc7ba.sock _./env OLDPWD/home/jiao可以看到在这个环境变量表中我们发现了许多重要的变量PWD/home/jiao/study/5.24HOME/home/jiaoUSERjiaoOLDPWD/home/jiaoLOGNAMEjiao这些环境变量有没有联想到一些指令HOME不是我们的家目录吗?LOGNAME不是我们的登录用户吗?whoamiOLDPWD这个不是我们的cd -吗所以在执行我们的程序的时候我们不仅仅需要一张命令行参数表还需要一张环境变量表,在这个环境变量表中我们可以知道在哪里找程序什么的等基础的信息那么我们现在来学习一下怎么查找环境变量查找环境变量echo $查找的环境变量export: 设置⼀个新的环境变量假设我们需要设置一下我们的PATH路径这样就把我们的PATH路径新增了我们的当前路径了查看所有的环境变量env上面的都是在命令行端获得环境变量那么我们怎么在代码上获得环境变量呢?getenv();#includeiostream #includestdlib.h using namespace std; int main() { printf(%s\n,getenv(PATH)); return 0; }环境变量的继承子进程的环境变量是来自于哪里?我们知道的是我们的进程都是来自于bash的,那么我们的程序的环境变量是来自于父进程bash吗?我们来看看#include iostream #includestdlib.h using namespace std; int main() { char *Pgetenv(MYENV); if(P!NULL) { coutMYENVPendl; } return 0; }现在我们bash的环境变量并没有这个MYENV的变量那么我们一定不会再这个程序中输出数据果不其然那么我们设置一个环境变量呢?果然这个进程也会有了这个环境变量了那么我们可以确认的就是子进程的环境变量是来自于父进程的那么我们子进程环境变量修改了话会影响父进程的环境变量吗?不会的,这就像是我们之前说的当我们子进程需要修改子父进程的公共数据的时候就会发生写实拷贝程序的地址空间我们在学习C语言的时候一定我们见到过这个图片但是在当时我们根本不了解这是什么东西,今天我们就来计息的了解一下这个图在之前我们知道一些的知识是:我们的数据进行了分类分别放在了这些一个个的区间中.但是这些空间就是一个个真实的物理空间了吗?我们来看这个代码:还记得在之前我们在创建进程的时候遗留下的问题了吗?为什么一个相同的变量可以拥有两个数据?我们下面的知识就会解决这个问题#include iostream #include stdlib.h #include unistd.h #include sys/types.h #include sys/wait.h using namespace std; int main() { int id fork(); int g_value 10; if (id -1) { perror(fork error\n); } else if (id 0) { // child printf(i am child my pid is %d the g_value is %d\n, getpid(), g_value); } else { // father g_value 100; printf(i am father my pid is %d the g_value is %d\n, getpid(), g_value); } return 0; }我们运行起来是什么样子的结果呢?我们竟然发现这两个的g_value的值不一样那么我们来看看这个g_valus的地址呢?#include iostream #include stdlib.h #include unistd.h #include sys/types.h #include sys/wait.h using namespace std; int main() { int id fork(); int g_value 10; if (id -1) { perror(fork error\n); } else if (id 0) { // child printf(i am child my pid is %d the g_value is %d my g_value is %p\n, getpid(), g_value, g_value); } else { // father g_value 100; printf(i am father my pid is %d the g_value is %d my g_value is %p\n, getpid(), g_value,g_value); } return 0; }这太不可思议了,一个地址一样的变量居然有了两个值.这到底是怎么回事这是因为我们在这里得到的地址不是实际的物理地址,而是一个虚拟地址那么什么是虚拟地址?其实在上面的那个图中就是我们用户能够得到的地址,这些地址都是虚拟的地址那么我们就会感到了不解了,这不就是给我们一个假的地址呗,那么我们怎么才能得到一个真的地址呢?我们在得到了一个虚拟地址之后我们就可以拿着这个虚拟的地址去和页表这个表中的地址进行映射,得到一个物理地址,那么这样我们就可以得到真实的地址了.那么怎么解释上述的情况呢?这是因为我们的子进程的task_struct都是拷贝父进程的,那么其他的数据当然也是啊那么每一个变量的虚拟地址空间也是一样的,但是只要这个变量需要发生写实拷贝的话,那么这个数据就会在物理空间上存在两份,一人一个,那么各自就可以拿着相同的虚拟地址拿到不同的物理地址了虚拟地址的管理在上面我们知道的是在我们的进程的结构体中存储的其实是一个虚拟的地址,那么这个表是怎么进行对一个个的空间进行控制的?他是怎么分割一个个的空间的?难道开辟一个个的空间吗?但是我们知道的是这个空间就需要4G的内存,一个进程就足够呛了的.别说同时多个进程了那么这个虚拟地址空间是怎么进行空间资源的分配和管理的?我们来看下面的例子:你是一名小学生,你的同桌是一个十分美丽的小女孩,但是呢你是一个鼻涕都不停的小屁孩,你的同桌嫌弃你,就在一个课间的时候说:张三这是一个分界线,谁越界谁就是狗!边说着在你两个的课桌的上面平均画上了一个线.在这个时候小美做了什么事情???就是这一个简单的限不就是把张三的活动的空间约束到了一半吗假设总个课桌的长度是100那么张三的空间就是[0,,50)小美就是[50,100];这就是实现了空间的分配了.所以一个简单的生活例子我们知道了,要想对整个空间进行若干份的分配的话,我们只需要表示清楚各个部分的开始和结束就可以了那么我们的操作系统也是这么做的,所以mm_struct横空出世假设你是操作系统的设计者你怎么写呢?不就是这样的吗?struct mm_struct{int code_begin0;//代码区int code_end100;//代码区int data_begin101;//数据区int data_end 201;/数据区....}ok 就是这样但是这时候会出现一个小问题,我们的堆区是一块一块的,这是怎么弄得?我们需要就申请不需要就自己释放,这个一个的mm_struct是做不到的把?没错我们的mm_struct不能很好的进行对堆区的管理,所以有一个个小的mm_struct的结构体对一个个的区进行细致的管理.也就是这个图中的vm_area_struct这个结构会对对样的区的内存进行管理,管理的方式和上述的一样,那么我们的mm_struct的工作就是管理一个个的vm_area_struct了一般是用红黑树加双向链表进行管理的