LIBRARY_PATH)自动定位动态/静态库的位置,无需手动指定路径即可成功生成可执行程序 。在环境变量展开介绍前,我们先来认识一下命令行参数,那命令行参数又是什么呢?
命令行参数则是用户在启动程序时传递给程序的参数,这些参数在程序运行时可用。它们通常是特定于程序的,用于指定程序运行时的一些选项或参数。
例如我们常写的main函数,他其实是可以有参数的
int main(int argc, char *argv[]) {
// ...
}int argc:参数个数,包括程序名。
char *argv[]:参数值数组,argv[0] 是程序名,后续元素是用户传递的参数。
argv是一个以NULL结尾的指针数组,结构如下:
char *argv[] = { "./a.out", "arg1", "arg2", NULL }; // 示例argv[0]:程序名称(如"./a.out")。argv[1..argc-1]:用户输入的参数。argv[argc] = NULL:标识参数数组结束 。以下是一个简单的示例,展示如何使用命令行参数:
#include <stdio.h>
int main(int argc, char *argv[]) {
for (int i = 0; i < argc; i++) {
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}代码解析:
argc 是命令行参数的个数。
argv 是一个指向字符数组的指针数组,每个元素对应一个命令行参数。
argv 数组,可以打印出所有的命令行参数。
运行示例:
假设将上述代码保存为 code.c,编译并运行:
ltx@hcss-ecs-d90d:~/lesson4$ ls -l
total 8
-rw-rw-r-- 1 ltx ltx 766 Jul 15 15:18 code.c
-rw-rw-r-- 1 ltx ltx 58 Jul 15 15:21 Makefile
ltx@hcss-ecs-d90d:~/lesson4$ make
gcc -o code code.c
ltx@hcss-ecs-d90d:~/lesson4$ ./code
argv[0]: ./code
ltx@hcss-ecs-d90d:~/lesson4$ ./code a
argv[0]: ./code
argv[1]: a
ltx@hcss-ecs-d90d:~/lesson4$ ./code a b
argv[0]: ./code
argv[1]: a
argv[2]: b
ltx@hcss-ecs-d90d:~/lesson4$ ./code a b c
argv[0]: ./code
argv[1]: a
argv[2]: b
argv[3]: c
ltx@hcss-ecs-d90d:~/lesson4$ ./code a b c d
argv[0]: ./code
argv[1]: a
argv[2]: b
argv[3]: c
argv[4]: d执行make命令,根据Makefile中的规则编译代码。这里调用了gcc编译器,将code.c文件编译成一个可执行文件code。
从运行结果可以看出,程序正确地接收并输出了命令行参数。argv[0]始终是程序本身的名称,后面的argv[1]到argv[n]依次是提供的命令行参数。参数的个数由argc表示,在每次运行中,argc的值等于输出的argv索引最大值加1。
其实我们的进程在启动时默认就有一个命令行参数表
exec()系统调用将参数表复制到子进程的栈空间,子进程的main()函数通过argc(参数数量)和argv(参数表指针)访问 。
那命令行参数有什么用呢?
我们再来看一段代码:
#include <stdio.h>
#include <string.h>
// main有参数吗?有
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage: %s [-a|-b|-c]\n", argv[0]);
return 1;
}
const char *arg = argv[1];
if(strcmp(arg, "-a")==0)
printf("这是功能1\n");
else if(strcmp(arg, "-b")==0)
printf("这是功能2\n");
else if(strcmp(arg, "-c")==0)
printf("这是功能3\n");
else
printf("Usage: %s [-a|-b|-c]\n", argv[0]);
return 0;
}运行结果:
ltx@hcss-ecs-d90d:~/lesson4$ make
gcc -o code code.c
ltx@hcss-ecs-d90d:~/lesson4$ ./code
Usage: ./code [-a|-b|-c]
ltx@hcss-ecs-d90d:~/lesson4$ ./code -a
这是功能1
ltx@hcss-ecs-d90d:~/lesson4$ ./code -b
这是功能2
ltx@hcss-ecs-d90d:~/lesson4$ ./code -c
这是功能3
ltx@hcss-ecs-d90d:~/lesson4$ ./code -abc
Usage: ./code [-a|-b|-c]if(argc != 2)确保用户必须提供一个选项。如果参数数量不符,打印用法并退出(返回非零值表示错误)。strcmp比较argv[1]与预定义字符串(如"-a"),匹配成功则执行对应功能。这种设计允许程序通过不同选项激活不同子功能,体现了“单一程序,多种行为”的灵活模式。else分支处理无效选项(如用户输入-d),防止程序崩溃。此代码的核心在于命令行参数作为程序行为的控制开关,通过参数值动态决定执行路径。这是Linux系统工具(如ls、grep)的基础实现原理。
命令行参数是操作系统与程序间传递配置信息的核心机制,尤其在Linux环境中,它实现了程序的模块化和可配置性。以下是其功能与原理的详细分析。
-a、-b)。这是Linux指令(如ls -l vs ls -a)多样性的基础。grep "pattern" file.txt中,"pattern"和"file.txt"通过argv传递。argc检查参数数量,避免无效输入导致的未定义行为(如示例中的if(argc != 2))。argv[0]用于打印用法(如printf("Usage: %s ...", argv[0])),提升用户体验。./program -a)后,Shell(如Bash)按空格分割字符串,生成argv数组:["", "-a", NULL]。exec()系统调用(如execv)启动程序,并将argv数组复制到进程内存栈中。argc和argv传递给main函数。argv(如使用strcmp或库函数如getopt),执行对应逻辑。argv和environ(环境变量表)在进程地址空间中相邻存储,均由父进程(Shell)ls是Linux核心命令,用于列出目录内容。其选项(如-l、-a)通过main函数的命令行参数实现,本质与我们的示例代码相同。
所有Linux命令的选项本质都是通过main的argv实现
main函数没有参数?main函数更为简洁和直接。
argc和argv只会增加不必要的复杂性。
main函数的这两种常见形式(带参数和不带参数)。编译器通常会支持这两种形式,所以开发者可以选择最适合自己需求的形式。
main函数。
总之,是否使用带参数的main函数取决于程序的具体需求。如果程序不需要命令行参数,使用无参数的main函数是完全合理且常见的做法。
ltx@hcss-ecs-d90d:~/lesson4$ ls
code code.c Makefile
ltx@hcss-ecs-d90d:~/lesson4$ pwd
/home/ltx/lesson4
ltx@hcss-ecs-d90d:~/lesson4$ ./code
Usage: ./code [-a|-b|-c]思考:为什么我们的代码编译成可执行程序后需要加路径 ./ (当前目录下),而系统的指令不需要带路径呢?
我们来试一下,如果我们的可执行程序不带路径会怎么样呢?
ltx@hcss-ecs-d90d:~/lesson4$ code
Command 'code' not found, but can be installed with:
snap install code
Please ask your administrator.我们发现,系统说他找不到这个指令
那如果我们在使用系统的指令的时候带上路径会有什么不一样吗?
ltx@hcss-ecs-d90d:~/lesson4$ which ls
/usr/bin/ls
ltx@hcss-ecs-d90d:~/lesson4$ /usr/bin/ls
code code.c Makefile
ltx@hcss-ecs-d90d:~/lesson4$ ls
code code.c Makefile可以看到,系统的指令带不带路径都能找到,我们的程序不带路径就找不到,这是为什么呢?
在Linux系统中,当你运行一个可执行程序时,系统会根据环境变量PATH中定义的目录来查找该程序。而我们的可执行程序code不在PATH中定义的目录里,所以在运行时需要指定其相对路径./code。否则,系统会在PATH指定的目录中查找,找不到就会提示命令未找到。
PATH环境变量PATH是一个包含多个目录路径的变量,用于告诉操作系统在哪些目录中查找可执行文件。例如,当你在命令行中输入ls时,系统会在PATH指定的目录中依次查找ls命令。
运行以下命令可以查看当前的PATH环境变量:
echo $PATH输出示例:
ltx@hcss-ecs-d90d:~/lesson4$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin为什么系统命令不需要带路径?
系统命令通常安装在PATH中定义的目录里,如/usr/bin、/bin等。因此,当你运行ls时,系统会在PATH中的目录查找并找到/usr/bin/ls。
为什么我们的程序不带路径就找不到?
我们的程序code位于当前目录./下,而./通常不在PATH环境变量中。因此,当你运行code时,系统会在PATH指定的目录中查找,但找不到,所以提示命令未找到。
为什么默认不包含当前目录?
PATH 包含当前目录(.),攻击者可在公共目录放置恶意程序(如伪造 ls),用户进入该目录后输入 ls 可能触发恶意程序 。test 程序覆盖系统 test 命令)。如何让我们的程序不带路径就能运行?
有以下几种方法:
方法 | 命令示例 | 优点 | 缺点 |
|---|---|---|---|
复制到系统路径 | sudo cp code /usr/local/bin | 永久生效,所有用户可用 | 需 root 权限,污染系统路径 |
修改 PATH 环境变量 | export PATH=$PATH:/home/ltx/lesson4 | 无需权限,快速生效 | 仅当前终端会话有效 |
永久生效配置 | 在 \~/.bashrc 添加: export PATH=$PATH:/home/ltx/lesson4 | 用户级持久化 | 需重启终端或执行 source \~/.bashrc |
示例:
ltx@hcss-ecs-d90d:~/lesson4$ export PATH=$PATH:/home/ltx/lesson4
ltx@hcss-ecs-d90d:~/lesson4$ code
Usage: code [-a|-b|-c]
ltx@hcss-ecs-d90d:~/lesson4$ code -a
这是功能1为什么系统命令可以带路径运行?
系统命令的路径通常在PATH中,因此无论是否带路径,系统都能找到它们。例如,/usr/bin/ls和ls都可以运行,因为/usr/bin在PATH中。
总结

PATH来查找命令。
code不在PATH中,因此需要带路径运行。
PATH或移动程序到PATH中的目录来让程序不带路径运行。
环境变量的本质是操作系统与应用程序间的动态配置桥梁,其存储设计融合了内存管理、持久化策略和访问效率的平衡。
环境变量的存储分为运行时内存存储和持久化磁盘存储两级,通过操作系统内核协同运作。
存储层级 | 物理载体 | 数据结构 | 生命周期 | 典型操作 |
|---|---|---|---|---|
运行时存储 | 进程内存空间 | 指针数组(char **environ) | 进程存活期间 | getenv(), setenv() |
持久化存储 | 磁盘配置文件(如.bashrc) | 文本键值对 | 永久生效 | source \~/.bashrc |
运行时内存存储机制
数据结构:以 NULL 结尾的连续指针数组(char *envp[]),每个指针指向 KEY=VALUE 格式的字符串。
// 内存布局示例
char *envp[] = {
"PATH=/usr/bin",
"HOME=/home/ltx",
NULL // 结束标记
};存储位置:位于进程用户空间栈的顶部,通过 task_struct->mm->env_start 定位(Linux内核)。
访问效率:O(n) 遍历复杂度,但通过哈希表缓存(如Bash的全局环境变量表)优化高频访问。
持久化磁盘存储机制
存储形式:纯文本键值对(如 export PATH=$PATH:/new/path)。
加载过程:

嵌入式系统特例:U-Boot将环境变量存储在独立Flash扇区,包含CRC校验头和标志位:
struct env_data {
uint32_t crc; // 校验码
uint8_t flags; // 状态标志
char data[4096]; // 键值对数据
};KEY=VALUE 格式,禁止空格(如 PATH=/usr/bin:/local/bin)。extern char **environ 全局变量访问。env_hash),将 O(n) 查找降至 O(1)。PATH 值)。继承机制
fork() 时复制:子进程获得父进程环境变量表的只读副本,通过指针共享原始数据。exec() 时重建:新程序加载时,内核将环境变量表指针压入新进程栈顶,初始化 environ。作用域隔离
setenv("TEMPVAR", "123"); // 修改当前进程环境
pid_t pid = fork();
if (pid == 0) {
// 子进程 TEMPVAR 仍为原值
setenv("TEMPVAR", "456"); // 仅修改子进程副本
}以下是一些常见环境变量的详解:
1. PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
2. HOME
/home/username
3. SHELL
/bin/bash
4. USER
username
5. LOGNAME
username
6. PWD
/home/username/documents
7. oldPWD
/home/username/downloads
8. LANG
zh_CN.UTF-8
9. TZ
Asia/Shanghai
10. DISPLAY
:0
11. EDITOR
vim、nano
12. PAGER
less、more
13. LD_LIBRARY_PATH
/usr/local/lib:/opt/myapp/lib
14. CLASSPATH
/.:/usr/lib/java:.
15. HISTFILE
~/.bash_history
16. HISTSIZE
1000
17. MAIL
/var/mail/username
18. TERM
xterm、linux
19. SSH_AGENT_PID
1234
20. SSH_AUTH_SOCK
/tmp/ssh-abc123/agent.1234
21. VIRTUAL_ENV
/home/username/envs/myenv
22. XDG_CONFIG_HOME
~/.config
23. XDG_DATA_HOME
~/.local/share
24. XDG_CACHE_HOME
~/.cache
25. XDG_RUNTIME_DIR
/run/user/1000
26. XDG_DESKTOP_DIR
~/.desktop
27. XDG_DOCUMENTS_DIR
~/documents
28. XDG_DOWNLOAD_DIR
~/downloads
29. XDG_MUSIC_DIR
~/music
30. XDG_PICTURES_DIR
~/pictures
31. XDG_VIDEOS_DIR
~/videos
总结
环境变量在操作系统和应用程序中起着重要的作用。它们帮助配置运行时环境,指定资源位置,并影响程序的行为。了解这些常见环境变量的用途可以帮助用户更好地管理和使用系统资源,提高工作效率。
echo $PATH。这个命令会输出当前系统中环境变量 PATH 的值,它包含了系统查找可执行文件的多个目录路径,这些路径之间用冒号分隔。
export MY_VAR="my_value"。这会创建一个新的环境变量 MY_VAR,并将其值设置为 "my_value"。如果该变量已经存在,这个命令会修改其值。
env,然后回车,系统会列出当前 shell 环境中所有的环境变量及其对应的值,包括系统默认的环境变量和用户自定义的环境变量。
unset MY_VAR。这个命令会清除之前设置的 MY_VAR 环境变量,之后在当前 shell 会话中再使用 echo $MY_VAR 就不会输出该变量的值了。
set,它会列出当前 shell 环境中所有的变量,包括环境变量和 shell 的局部变量。这些变量可能包括用户定义的变量、shell 内置变量以及环境变量等。
示例:
ltx@hcss-ecs-d90d:~/lesson4$ echo $PATH #输出环境变量PATH的值
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
ltx@hcss-ecs-d90d:~/lesson4$ export MY_VAR="my_value" #设置一个新的环境变量
ltx@hcss-ecs-d90d:~/lesson4$ env #显示所有当前环境变量
SHELL=/bin/bash
HISTSIZE=1000
HISTTIMEFORMAT=%F %T ltx
PWD=/home/ltx/lesson4
LOGNAME=ltx
XDG_SESSION_TYPE=tty
MOTD_SHOWN=pam
HOME=/home/ltx
LANG=en_US.UTF-8
MY_VAR=my_value #我们刚才设置的
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=00: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:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=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:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=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:*.webp=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:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
SSH_CONNECTION=59.62.147.11 3278 192.168.0.214 22
LESSCLOSE=/usr/bin/lesspipe %s %s
XDG_SESSION_CLASS=user
TERM=xterm
LESSOPEN=| /usr/bin/lesspipe %s
USER=ltx
DISPLAY=localhost:10.0
SHLVL=1
XDG_SESSION_ID=343
XDG_RUNTIME_DIR=/run/user/1000
SSH_CLIENT=59.62.147.11 3278 22
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
SSH_TTY=/dev/pts/0
OLDPWD=/home/ltx
_=/usr/bin/env
ltx@hcss-ecs-d90d:~/lesson4$ unset MY_VAR #清除之前设置的 MY_VAR 环境变量
ltx@hcss-ecs-d90d:~/lesson4$ env #再次显示所有当前环境变量,发现刚才设置的MY_VAR环境变量清除了
SHELL=/bin/bash
HISTSIZE=1000
HISTTIMEFORMAT=%F %T ltx
PWD=/home/ltx/lesson4
LOGNAME=ltx
XDG_SESSION_TYPE=tty
MOTD_SHOWN=pam
HOME=/home/ltx
LANG=en_US.UTF-8
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=00: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:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=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:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=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:*.webp=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:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
SSH_CONNECTION=59.62.147.11 3278 192.168.0.214 22
LESSCLOSE=/usr/bin/lesspipe %s %s
XDG_SESSION_CLASS=user
TERM=xterm
LESSOPEN=| /usr/bin/lesspipe %s
USER=ltx
DISPLAY=localhost:10.0
SHLVL=1
XDG_SESSION_ID=343
XDG_RUNTIME_DIR=/run/user/1000
SSH_CLIENT=59.62.147.11 3278 22
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
SSH_TTY=/dev/pts/0
OLDPWD=/home/ltx
_=/usr/bin/env核心数据结构
环境变量通过 KEY=VALUE格式的字符串数组 组织,以NULL指针作为结束标志:
// C语言中环境表的存储结构
char *envp[] = {
"HOME=/home/ltx",
"PATH=/usr/bin:/bin",
"LANG=en_US.UTF-8",
NULL // 结束标识
};KEY=VALUE格式的独立字符串NULL指针标识边界
char **environ全局变量extern char**environWindows_environ (CRT库)_getenv()嵌入式系统独立Flash扇区(带CRC校验)U-Boot的env save命令 1. 进程启动时的传递机制
通过execve()系统调用实现父子进程传递:
// Linux内核系统调用
int execve(
const char *filename,
char *const argv[], // 参数表
char *const envp[] // 环境变量表
);
2. 作用域控制原理
变量类型 | 作用域 | 生命周期 | 底层机制 |
|---|---|---|---|
环境变量 | 进程及所有子进程 | 进程会话期间 | 通过fork()继承environ指针 |
Shell变量 | 仅当前Shell进程 | Shell会话期间 | 存储于Shell进程堆内存 |
关键区别:环境变量通过export命令将Shell变量写入environ数组
3. 系统级环境变量
系统级环境变量是在系统范围内对所有用户和进程都有效的变量。这些变量通常在系统启动时由初始化脚本设置,存储在/etc/environment、/etc/profile或/etc/profile.d/等文件中。例如:
PATH:指定系统查找可执行文件的路径。
LANG:指定系统的语言和区域设置。
4. 用户级环境变量
用户级环境变量是针对特定用户的变量,只对当前用户有效。这些变量通常在用户的 shell 配置文件中设置,如~/.bashrc、~/.bash_profile或~/.zshrc等。例如:
EDITOR:指定用户默认的文本编辑器。
SSH_AGENT_PID:指定 ssh-agent 进程的 PID。
5. 进程级环境变量
进程级环境变量是进程在运行时继承自父进程的变量。当一个进程创建子进程时,子进程会继承父进程的环境变量。子进程可以修改这些变量,但这些修改只对子进程及其后续创建的子进程有效,不会影响父进程或其他进程。例如:
export MY_VAR="my_value"后,当前终端及其子进程都会继承这个变量。
6. 作用域
环境变量的作用域决定了它们的可见性和影响范围:
在这之前我们要在再来了解一个命令行参数,前文中我们知道了main函数可以有参数,那最多有几个参数呢?
其实main函数最多有三个命令行参数,第三个参数就是envp
envp(environment pointers)
类型:char *envp[] 或 char **envp
功能:接收环境变量表(Environment Variables Table),存储当前进程运行所需的环境配置信息。
示例
int main(int argc, char *argv[], char *envp[]) {
for (int i = 0; envp[i] != NULL; i++) {
printf("envp[%d]: %s\n", i, envp[i]);
}
return 0;
}运行结果:
ltx@hcss-ecs-d90d:~/lesson4$ make
gcc -o code code.c
ltx@hcss-ecs-d90d:~/lesson4$ ./code
envp[0]: SHELL=/bin/bash
envp[1]: HISTSIZE=1000
envp[2]: HISTTIMEFORMAT=%F %T ltx
envp[3]: PWD=/home/ltx/lesson4
envp[4]: LOGNAME=ltx
envp[5]: XDG_SESSION_TYPE=tty
envp[6]: MOTD_SHOWN=pam
envp[7]: HOME=/home/ltx
envp[8]: LANG=en_US.UTF-8
envp[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=00: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:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=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:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=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:*.webp=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:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
envp[10]: SSH_CONNECTION=59.62.147.11 3278 192.168.0.214 22
envp[11]: LESSCLOSE=/usr/bin/lesspipe %s %s
envp[12]: XDG_SESSION_CLASS=user
envp[13]: TERM=xterm
envp[14]: LESSOPEN=| /usr/bin/lesspipe %s
envp[15]: USER=ltx
envp[16]: DISPLAY=localhost:10.0
envp[17]: SHLVL=1
envp[18]: XDG_SESSION_ID=343
envp[19]: XDG_RUNTIME_DIR=/run/user/1000
envp[20]: SSH_CLIENT=59.62.147.11 3278 22
envp[21]: XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
envp[22]: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
envp[23]: DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
envp[24]: SSH_TTY=/dev/pts/0
envp[25]: OLDPWD=/home/ltx
envp[26]: _=./codeenviron的关系等价性:envp与全局变量extern char **environ指向同一内存地址,两种访问方式等价:
// 方式1:通过main参数
int main(int argc, char *argv[], char *envp[]) {
for (int i=0; envp[i]!=NULL; i++) printf("%s\n", envp[i]);
}
// 方式2:通过全局变量
#include <stdio.h>
extern char **environ;
int main() {
for (int i=0; environ[i]!=NULL; i++) printf("%s\n", environ[i]);
}实现差异:environ由C运行时库(如glibc)定义,envp由操作系统内核通过execve()系统调用传递
步骤 | 说明 | 底层实现 |
|---|---|---|
1. Shell预处理 | 解析用户命令,合并继承的环境变量与临时设置(如VAR=value command) | Bash通过env_hash表管理变量 |
2. 调用execve() | Shell通过系统调用加载程序,传递环境表: int execve(path, argv, envp) | Linux内核复制envp到新进程栈顶 |
3. 程序启动初始化 | C运行时库(crt0)从栈中读取envp,初始化全局变量environ | crt0.o代码片段:environ = __envp; |
4. 传递给main() | 若main声明包含envp参数,则将其地址压栈 | x86_64调用约定:rdi=argc, rsi=argv, rdx=envp |
PATH、HOME等)。exec族函数指定新环境表(如execle("/bin/ls", "ls", NULL, new_envp)),覆盖默认继承_start:从操作系统到用户代码的桥梁_start 的本质与定位main 函数被视为程序的起点,但实际执行流程中,_start 才是操作系统加载程序后执行的第一条指令。这一机制由操作系统与编译器共同约定,确保程序初始化逻辑的统一性_start 是一个由汇编实现的符号(Symbol),通常由链接器(如 GNU ld)通过默认链接脚本指定。其内存地址被写入可执行文件的ELF头部(e_entry字段),操作系统内核加载程序时直接跳转至该地址main 函数的层级关系

关键结论:main 仅是用户代码的语法入口,而 _start 是操作系统层面的实际入口点
_start 与 main 的关系
_start 是 main 的前置调用:_start 先于 main 执行,完成必要的初始化工作后才调用 main。
main 是 _start 的一部分:从逻辑上讲,main 是程序逻辑的起点,而 _start 是程序执行的真正起点。
_start 的底层实现机制初始化操作的核心步骤
_start 函数需完成硬件环境的基础配置,具体包括:
栈指针(SP)设置:为函数调用栈分配内存,通常指向进程地址空间的高地址区域(如 Linux 用户栈起始于 0x7fffffff0000)
寄存器清零:清除通用寄存器中的随机值,避免干扰后续逻辑(如 x86 的 xor ebp, ebp)
参数传递:从内核传递的栈中提取 argc 和 argv,为调用 __libc_start_main 做准备:
; x86_64 架构示例(AT&T语法)
_start:
xor %rbp, %rbp ; 清空帧指针
mov (%rsp), %rdi ; argc → rdi
lea 8(%rsp), %rsi ; argv → rsi
call __libc_start_main ; 调用C库初始化函数跳转至C运行时库
__libc_start_main(Glibc实现)是 _start 的核心调用对象,其职责包括:
功能 | 实现细节 | 影响范围 |
|---|---|---|
全局变量初始化 | 执行 .data 段赋值与 .bss 段清零 | 静态存储期变量 |
C++全局对象构造 | 调用 _init() 和 __do_global_ctors_aux | C++跨平台兼容性 |
线程局部存储(TLS) | 设置线程局部变量模板 | 多线程程序基础 |
安全机制启动 | 栈保护(Stack Guard)与地址随机化(ASLR)校验 | 防御内存攻击 |
环境变量处理 | 解析 envp 并设置 environ 全局变量 | 进程环境配置 |
完成初始化后,该函数最终调用 main(argc, argv, envp),并将返回值传递给 exit()
getenv 函数getenv 是一个标准库函数,用于获取环境变量的值。
函数原型
#include <stdlib.h>
char *getenv(const char *name);参数
name:要获取值的环境变量的名称。
返回值
NULL。
示例代码
#include <stdio.h>
#include <stdlib.h>
int main() {
char *path = getenv("PATH");
if (path != NULL) {
printf("PATH: %s\n", path);
} else {
printf("PATH environment variable not found.\n");
}
char *home = getenv("HOME");
if (home != NULL) {
printf("HOME: %s\n", home);
} else {
printf("HOME environment variable not found.\n");
}
char *nonexistent = getenv("NONEXISTENT_VAR");
if (nonexistent == NULL) {
printf("NONEXISTENT_VAR not found.\n");
}
return 0;
}输出示例
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
HOME: /home/ltx
NONEXISTENT_VAR not found.putenv 函数putenv 是一个标准库函数,用于设置或修改环境变量。
函数原型
#include <stdlib.h>
int putenv(char *string);参数
string:一个形如 "NAME=VALUE" 的字符串,表示要设置的环境变量名称和值。
返回值
0。
示例代码
#include <stdio.h>
#include <stdlib.h>
int main() {
// 设置新的环境变量
char *new_var = "MY_VAR=my_value";
if (putenv(new_var) == 0) {
printf("Set MY_VAR successfully.\n");
} else {
perror("Failed to set MY_VAR");
}
// 修改现有环境变量
char *new_path = "PATH=/new/path";
if (putenv(new_path) == 0) {
printf("Modified PATH successfully.\n");
} else {
perror("Failed to modify PATH");
}
// 获取并打印环境变量
char *my_var = getenv("MY_VAR");
if (my_var != NULL) {
printf("MY_VAR: %s\n", my_var);
}
char *path = getenv("PATH");
if (path != NULL) {
printf("PATH: %s\n", path);
}
return 0;
}输出示例
Set MY_VAR successfully.
Modified PATH successfully.
MY_VAR: my_value
PATH: /new/path注意事项
putenv 的参数:
string 必须是形如 "NAME=VALUE" 的格式。
NAME 已经存在,则其值会被更新为 VALUE。
NAME 不存在,则会创建新的环境变量。
putenv 会将参数字符串 string 保存在环境变量中,因此该字符串必须在调用后保持有效。通常,建议使用静态分配或动态分配的内存来存储参数字符串。
getenv 和 putenv 在多线程环境中是线程安全的,但需要注意在修改环境变量时的并发问题。
setenv 和 unsetenv,它们提供了更灵活的接口来设置和删除环境变量。这些函数的使用方式与 putenv 类似,但提供了更明确的参数分离。
总结
getenv:用于获取环境变量的值。
putenv:用于设置或修改环境变量。
这两个函数是操作环境变量的标准库函数,提供了简单易用的接口。在实际应用中,可以根据需要使用这些函数来获取和设置环境变量,以适应不同的运行环境和需求。