Linux进程的命令行参数与环境变量:从应用到内核

📅 2026/8/20 12:54:54
Linux进程的命令行参数与环境变量:从应用到内核
前导本文讲述关于命令行参数以及环境变量的内容一、命令行参数的完整面貌1、main函数的不同写法(1)、无参形式:int main()适用场景简单的任务不涉及到终端的输入不涉及到终端的操作的读取。(2)、双参形式:int main(int argc,char *argv[])适用场景:涉及到对终端的文件和操作去进行读取(3)、三参形式int main(int arg,char *argv[],char *envp[])适用场景需要读取系统环境配置去改变行为。2、关于argc和argv1、输入指令后会被shell解析(通常是bash)将输入的指令通过空格、符号等将指令分隔成一个一个的“单词”int main(int argc,char *argv[]){for(int i0;iargc;i){printf(argv的下标:%d ,argv内容:%s\n,i,argv[i]);}return 0;}下标为0一般是程序自己的名字剩下的就是你传进去的命令被分成其他几个2、调用execve这个系统调用请求内核加载并执行你的程序这时argv会变成一个参数就会被传入此时我们的指令就会从shell到了内核。int execve(const char *filename, char *const argv[], char *const envp[]);3、内核会将argc、argv复制到新的进程上。当内核接受到execve的请求之后会去复用进程的PCB和释放旧的虚拟地址空间创建新的虚拟地址空间然后将argcargv复制到用户栈顶部最后跳转到新程序入口执行。总结而言就是argc/argv是操作系统和我们的程序的第一个桥梁。3、argv在指针数组内存的布局注意argv[argc]NULL,这是为了让指针遍历有一个终止值。4、额外知识argv[0]伪造:是因为pathname是要执行的程序名称同时argv[0]只是一个字符串此时呢内核并不会去检查一样不一样。二、环境变量程序运行时的全局变量1、环境变量是什么定义环境变量就是操作系统的一组“全局的设置开关”采用键值对的形式也就是名字值的形式能被子进程继承。部分环境变量:SHELL/bin/bashPWD/home/ubuntuLOGNAMEubuntuXDG_SESSION_TYPEttyHOME/home/ubuntu指令集env--------------------- 显示所有的环境变量echo $----------------- 显示特定的环境变量export 名内容--- 设置环境变量unset 名------------- 删除环境变量环境变量(全局变量)和普通的shell变量(本地变量)有什么区别核心区别就是能不能传给子进程。普通的shell,只能在这个进程里看到再另外创建一个新的子进程的话另外的子进程是看不到的。而环境变量则能把变量复制一份给子进程所以说子进程修改环境变量不会影响父进程。2、常见的环境变量:变量名作用示例PATH可执行文件搜索路径/usr/local/bin:/usr/bin:/binHOME当前用户主目录/home/aliceSHELL当前登录shell/bin/bashUSER当前用户名aliceLANG语言和编码en_US.UTF-8PWD当前工作目录/home/alice/projectsLD_PRELOAD动态链接预加载库/path/to/lib.so额外问题:为什么我们的文件需要加./PATH环境变量设置了可执行文件的默认路径也就是说当你输入一个指令时会去默认去PATH指定下的路径下去找如果有则会执行如果没有则不会执行当系统执行文件需要具体路径而自己生成的文件没有在PATH内部所以说需要加上./去指定当前路径以便找到所执行文件的路径。如何解决将我们文件的路径加入到PATH所指定的路径内。(1)export PATH$PATH:. 不推荐安全性质不高这个指令是将该目录包入PATH也就是说你如果cd到了其他目录此时PATH也会去包含不同的目录这里第一张图是默认的第二张图发现路径修改了。2可以将文件放入/usr/local/bin路径下: 推荐安全sudo cd code.exe /usr/local/bin然后可以看看PATH的路径是否改变可以看到是没有改变的所以更加安全一些。如果想要删除的话就是sudo rm /sur/local/bin/“你的文件名”;3、环境变量在内存中如何被组织每个进程都会有一个环境表也就是那个上文显示三参的main函数中第三个参数这个环境表里存着的都是地址指向的就是栈顶的环境变量。环境变量是在栈顶。获取环境变量的三种方法1envp获取环境变量int main(int arcg,char *argv[],char *envp[]){for(int i0;i5;i){printf(echo[%d]的内容:%s\n,i,envp[i]);}return 0;}这一段代码能够看出来这个是如何去管理的如下图(2)environ获取环境变量,这里是没有去用第三个那个参数的。extern char **environ; //手写显示声明说这个是外部变量int main(int arcg,char *argv[]){for(int i0;i5;i){printf(echo[%d]的内容:%s\n,i,environ[i]);}return 0;}从上边可以得到envp和environ其实指向的是同一个位置的内容可以用代码去证实一下。3getenv()精确找到环境变量#includestdio.h#includestdlib.hint main(){char *pathgetenv(PATH);printf(PATH的路径是:%s\n,path);return 0;}4、环境变量的全局属性与独立属性下边是一段代码当然在shell界面中我已经export HAHAhappy了也就是说此时HAHA已经是一个环境变量了。全局性int main(){pid_t idfork();if(id0){char *hahagetenv(HAHA);printf(子进程%s\n,haha);}else{char *hahagetenv(HAHA);printf(父进程%s\n,haha);}return 0;}可以发现子进程复制了父进程的环境变量表。独立性int main(){pid_t idfork();if(id0){setenv(HAHA,HAPPY,1); //修改环境变量的char *hahagetenv(HAHA);printf(子进程%s\n,haha);}else{sleep(1);char *hahagetenv(HAHA);printf(父进程%s\n,haha);}return 0;}可以发现子进程修改不会影响父进程说明了进程之间的环境变量表具有独立性。普通变量没有传递性当直接在shell上敲下HAHAHAPPY时此时就是一个普通的变量此时用证明全局性的代码可以发现得到的只有NULL说明了普通的变量只在当前shell有效没有传递性。总结一下fork创建子进程之后会复制父进程的地址空间此时环境列表也在其中当exec加载新程序的时候就会用当前进程的环境列表初始化environ也就完成了传递的过程。5、setenvputenvunsetenv#include stdlib.hint setenv(const char *name, const char *value, int overwrite);int putenv(char *string); // string为 namevalueint unsetenv(const char *name);这是C标准库提供的可以修改环境变量的库函数setenvsetenv我们上边已经有所使用着重讲讲第三个参数也就是那个int。int setenv(const char *name, const char *value, 0);表示不覆盖之前形成的内容int setenv(const char *name, const char *value, 1);表示覆盖之前的内容。putenvputenv这个不推荐使用因为这个不会复制字符串而是复制指针此时如果传入局部变量可能会涉及到野指针的问题。unsetenv删除环境变量输入你的name就会删除相应的环境变量。总结三个都是只能在当前进程影响不会影响到其他的进程都是返回0就代表着成功返回-1就是失败。三、深入底层去看环境变量在内存中栈区不只是存放着局部变量栈顶还存放着命令行参数和环境变量。int main(int argc,char *argv[],char *envp[]){printf(argv[0]的地址%p\n,argv[0]);printf(env[0]的地址%p\n,envp[0]);char stack_var;printf(stack_var 地址:%p\n,stack_var);char *heapmalloc(10);printf(heap地址:%p\n,heap);return 0;}可以看出argv和env的地址非常接近。下面做一个增加命令行参数以及环境变量的实验观察现象。可以发现这些地址都有所减小也就是向低地址方向移动同时可以自己实现一下增加环境变量也是地址会变小。结论如果只增加环境变量需要存放更多的环境字符串占用更多栈的高位空间此时整个参数环境块向低地址挤压如果只增加命令行参数需要存放更多的命令行参数整体块会向下挤压地址同样会变小。argv和env的字符串和指针属于同一块连续的栈区域不管是argv还是envp的内容增加都会增加字节数整块向低地址偏移。后续将更新进程控制等内容。