序言:
在上期我们已经对进程PCB以及进程状态进行了详细的解释说明。今天,我将带领大家学习的是关于进程
(一)孤儿进程
首先,在正式学习进程环境变量之前。我们先学习一下孤儿进程的相关知识,这个知识点在上篇博文中漏掉了,在本期中我给大家补上。
1、基本介绍
- 孤儿进程(Orphan Process)是指在父进程结束后,子进程仍然在继续运行的情况;
- 当父进程先于子进程退出时,子进程将成为一个孤儿进程;
- 操作系统会将孤儿进程的父进程设置为init进程(通常是进程ID为1的进程),init进程会接管孤儿进程,并负责回收它们的资源。
孤儿进程的产生是由于父进程提前退出,导致子进程失去了父进程的引用,但子进程本身仍然在运行。
这种情况可能出现在以下几种情况下:
- 父进程调用了
fork
创建子进程后,快速退出,不等待子进程的结束。 - 父进程意外终止,例如发生了异常或被强制终止。
2、代码演示
下面是一个简单的代码演示,展示了如何创建一个孤儿进程:
#include <stdio.h> #include <unistd.h> int main() { pid_t id = fork(); if(id == 0) { //child while(1) { printf("我是子进程: pid: %d, ppid: %d\n", getpid(), getppid()); sleep(1); } } else{ //parent int cnt = 10; while(1) { printf("我是父进程: pid: %d, ppid: %d\n", getpid(), getppid()); sleep(1); if(cnt-- <= 0) break; } } return 0; }
- 通过管道过滤查看进程相应的状态
while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v grep; sleep 1;echo"---------------"; done
- 具体过程如下:
其次,在上诉杀进程我们是使用的【kill】进程号的方式。今天,我在交大家一种方式,那就是使用【killall】。
【分析】
- 在上述代码中,父进程创建了一个子进程,并分别打印出父进程和子进程的进程ID;
- 父进程在运行10秒立即退出,而子进程会一直在一直程序中运行;
- 当父进程退出时,子进程变成了一个孤儿进程,但仍然会继续运行。
【分析】
- 孤儿进程的资源回收是由操作系统的init进程负责的;
- 所以在上述代码中,如果查看进程列表,可以看到子进程的父进程ID为1,表示由init进程接管。
(二)环境变量
1、基本概念
- 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数;
- 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找;
- 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性
2、和环境变量相关的命令
- 1. echo: 显示某个环境变量值
- 2. export: 设置一个新的环境变量
- 3. env: 显示所有环境变量
- 4. unset: 清除环境变量
- 5. set: 显示本地定义的shell变量和环境变量
3、常见环境变量
- PATH : 指定命令的搜索路径
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
- SHELL : 当前Shell,它的值通常是/bin/bash。
我们可以通过【env】查看:
1️⃣、测试PATH
在Linux下,需要加上【 ./
】来运行自己写的程序,而系统自带的程序不需要加这个前缀。
这是因为在Linux系统中,执行命令时会按照环境变量 $PATH
中定义的路径来查找可执行文件。当你直接输入一个命令时,系统会按照 $PATH
中定义的目录逐一搜索,找到匹配的可执行文件并执行。而当前目录(.
)并不在 $PATH
中,默认情况下系统不会搜索当前目录。
如果你在当前目录下有一个可执行文件,而没有指定路径或者给出了一个相对路径,系统就无法找到该可执行文件,因此需要显式地使用 ./
表示当前目录来执行程序。
例如,假设你有一个名为 mytest
的可执行文件,在当前目录下执行它时,应该使用 【./
mytest
】命令。如果当前目录不在 $PATH
中,直接输入 mytest
是无法找到和执行该程序的。
- 因此不难看出上述是一个错误的示范 ;
- 那么正确的应该怎么做呢,即不仅当前自己指令不需要加【./】,系统中的也不需要加。
首先,我们需要先关掉【xshell】重新启动,当我们重新启动之后,一切都会恢复为默认的。
- 相比之下,系统自带的程序通常被安装在
$PATH
中定义的某个目录下,因此可以直接通过命令名来执行,而无需加上./;
- 系统自带的程序的路径已经在
$PATH
中设置好,所以可以直接使用命令名来运行这些程序。
【注意】
为了安全考虑,当前目录(.
)一般不会包含在 $PATH
中,以避免执行恶意文件。因此,为了执行自己写的程序,需要显式地使用 ./
前缀来指定当前目录。
2️⃣、测试HOME
在 Linux 中,可以使用
echo
命令结合环境变量HOME
来测试用户的家目录。
(1)用root和普通用户,分别执行 echo $HOME ,对比差异
💨首先以普通用户输入以下命令并按回车键执行:
echo $HOME
- 这条命令会输出环境变量
HOME
的值,即当前用户的家目录。
如果一切正常,你将在终端中看到输出结果为当前用户的家目录的路径。
输出演示:
💨 而当我们使用【root】身份后在执行相应的指令时,会是什么效果呢?
具体如下:
【分析】
- 当使用 root 用户执行
echo $HOME
命令时,输出的结果是【/root】。这是因为在大多数 Linux 系统中,root 用户的主目录被设置为/root
。 - 而当使用普通用户执行
echo $HOME
命令时,输出的结果是该普通用户的主目录路径;
【小结】
- root 用户的主目录是
/root
,而普通用户的主目录位于/home/username
,其中 "username" 是用户的实际用户名; - 这是因为在 Linux 系统中,不同用户有各自的独立主目录,用于存储其个人文件和配置信息。
(2)用root和普通用户,分别执行 执行 cd ~; pwd 对比差异
首先,还在以普通身份执行相应的指令。结果如下:
其次,以 root 身份执行相应的指令。结果如下:
【小结】
【~
】符号和 【$HOME
】环境变量都表示当前用户的主目录路径。它们在大多数情况下是等价的,可以互换使用。
3️⃣、shell环境变量
Shell 环境变量是在操作系统中定义的一组全局变量,可以被不同的进程和程序共享和访问。下面是有关 Shell 环境变量的详细说明:
1、查看环境变量:
- 在 Shell 中可以使用
env
命令来查看当前的环境变量列表。
- 输出结果列出当前环境中的所有变量及其对应的值。
2、定义环境变量:
- 可以使用
export
命令来定义一个环境变量。一旦定义,该变量就能够被当前 Shell 进程以及其子进程所访问。
export myenv="hello"
紧接着去环境变量中查看:
3、引用环境变量:
- 使用美元符号
$
作为前缀来引用环境变量。
【小结】
- Shell 环境变量在系统中起着重要的作用,它们可以为不同的进程提供全局的共享配置信息;
- 通过定义和引用环境变量,我们可以方便地进行参数传递、配置管理等相关的工作。
4、环境变量的组织方式
环境变量可以组织为键值对的集合。这种方式通常被用于存储一组相关的配置信息。
- 环境变量表是操作系统中存储环境变量的数据结构,它通常是一个键值对的集合;
- 在 C 语言中,可以通过
environ
或getenv()
函数来获取环境变量表。
- 每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以 ’ \0’ 结尾的环境字符串
💨以下是一个示例,展示如何访问环境变量表中的键值对:
#include <stdio.h> #include <stdlib.h> extern char** environ; int main() { char** env = environ; // 遍历环境变量表并打印每个键值对 while (*env != NULL) { printf("%s\n", *env); env++; } return 0; }
【分析】
- libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。
- 在上述示例中,
environ
是一个全局变量,它会被操作系统设置为指向环境变量表的指针数组; - 通过迭代访问
environ
数组中的元素,可以逐个打印出环境变量表中的键值对。
输出显示:
【注意】
environ
在不同的操作系统或编译器中可能有所不同。在某些系统中,可能需要使用_environ
或者其他类似的变量名来访问环境变量表。
💨 除了上诉方式可以获取系统的环境变量表之外,还有一种方式也可以做到:
- 命令行第三个参数
#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; }
【分析】
- 以前大家写C语言的时候,碰到的最多的main中只有两个参数,其实不然。main其实可以接收三个参数。
💨 接下来,我具体给大家分析:
- 这段代码是一个C语言程序的入口函数
main
,它接受三个参数:argc
、argv[]
和env[]
。
下面对每个参数进行详细解释:
int main(int argc, char *argv[], char *env[]) { // ... }
int argc
:代表命令行参数的数量。argc
是一个整数类型的变量,表示在命令行中传递给程序的参数数量(包括程序名称本身)。argc
的值至少为 1。char *argv[]
:表示命令行参数的字符串数组。argv
是一个指针数组,其中每个指针指向一个字符串,该字符串是一个命令行参数的副本。argv[0]
存储的是程序的名称,而argv[1]
、argv[2]
等存储的是其他参数值。char *env[]
:表示程序运行时的环境变量表。env
是一个指针数组,其中每个指针指向一个字符串,该字符串是一个环境变量的键值对(形如 "键=值")。通过遍历env
数组,可以访问和操作环境变量表中的所有环境变量。
通过使用这些参数,我们可以在程序中获取并处理命令行参数以及环境变量,以满足不同的需求。例如,使用 argc
和 argv
可以根据命令行参数的数量和内容采取不同的操作,而使用 env
可以访问和操作环境变量表中的各种环境变量。
【小结】
总之通过环境变量表,我们可以访问和操作系统中定义的各种环境变量,这对于程序的配置和参数传递非常有用。
(三)通过系统调用获取或设置环境变量
- putenv
- getenv
在 Linux 中,可以使用 getenv
函数来获取环境变量的值,但是无法直接通过系统调用设置环境变量。要设置环境变量,可以使用 putenv
系统调用。
1、getenv
在 Linux 中,
getenv
函数用于获取环境变量的值。它位于<cstdlib>
头文件中,并且是 C/C++ 标准库提供的函数。
- getenv 函数的原型如下:
char* getenv(const char* name);
- 接下来,我们在 man 手册中查看一下这个函数:
【分析】
- 该函数接受一个 const char* 类型的参数 name,表示要获取的环境变量的名称;
- 如果成功找到对应的环境变量,则返回一个指向字符串值的指针;
- 如果未找到对应的环境变量,则返回 NULL。
💨 接下来,给大家简单的看以下程序来获取 "USER" 的环境变量:
【分析】
- 这段代码的作用是获取名为
"USER"
的环境变量的值,并将其输出到标准输出; - 如果获取环境变量失败,会输出相应的错误信息。
输出显示:
💨 以下是使用 getenv
函数获取环境变量值的示例代码:
输出显示:
【分析】
- 在这个示例中,我们尝试获取环境变量
HOME
的值。首先,声明一个const char*
类型的变量varName
,并将其赋值为要获取的环境变量的名称; - 然后,调用
getenv
函数,传入varName
,获取环境变量的值,将结果存储在char*
类型的变量varValue
中。 - 接下来,我们通过判断
varValue
是否为NULL
来确定是否成功获取到环境变量的值。如果成功,我们输出环境变量的名称和对应的值;如果未找到对应的环境变量,我们输出未找到的提示。
【注意】
getenv
函数返回的指针指向了一个静态分配的字符串缓冲区,因此不应该尝试修改或释放该指针指向的内存。
接下来,再给大家看一段代码:
输出展示:
【分析】
- 该段代码的作用是判断当前执行程序的用户是否为
"zp"
,并根据判断结果输出相应的信息; - 但是如果当前用户是
"zp"
,则输出执行完成的提示信息;如果当前用户不是"zp"
,则输出非法用户的提示信息,指示无法执行。
2、putenv
- putev的函数原型如下:
int putenv(char* string);
- 接下来,我们在 man 手册中查看一下这个函数:
【分析】
- 该系统调用用于设置环境变量的值,类似于
setenv
。它接受一个字符串参数string
,格式为"name=value"
,用于指定环境变量的名称和值; putenv
直接接受一个字符串参数,而不需要设置是否覆盖已存在的环境变量。- 调用成功时,
putenv
返回 0;调用失败时,返回 -1,并设置errno
变量以指示错误的原因。
💨 以下是一个使用 putenv
设置环境变量的示例代码:
输出显示:
【分析】
- 我们首先使用
snprintf
将环境变量的名称和值格式化为一个字符串"name=value"
,然后将该字符串传递给putenv
。
【注意】
putenv
函数接受的参数必须是指向静态分配的字符串或者全局变量的指针,因为它会直接使用传递的指针,而不会对其进行拷贝;- 因此这意味着传递栈上的局部变量给
putenv
是不安全的。
常用getenv和putenv函数来访问特定的环境变量。
(四)环境变量通常是具有全局属性的
环境变量通常具有全局属性,可以被子进程继承下去
在上述将shell环境时,我们手动的定义了环境变量 【myenv】,接下来我以这个为例进行代码演示:
#include <stdio.h> #include <stdlib.h> int main() { char * env = getenv("myenv"); if(env){ printf("%s\n", env); } return 0; }
输出显示:
接下来,我们在手动的定义一个环境变量:
紧接着我们修改一下源代码,看程序输出结果:
输出显示:
【分析】
- 我们可以发现带【export】的环境变量可以显示出来;
- 直接查看,发现没有结果,说明该环境变量根本不存在
【小结】
- 我们可以发现【hello1】此时依旧可以打出来,只是没有放在环境变量表里面而已。
- 对于这种,我们有个专门的称呼叫做 shell本地变量
那么此时我想把它输出来有方法吗?具体如下:
实验
下面是一个简单的代码示例,说明子进程继承父进程的环境变量:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { // 父进程定义一个环境变量 const char* my_var = "Hello World"; setenv("MY_VAR", my_var, 1); // 创建子进程 pid_t pid = fork(); if (pid == -1) { // 创建子进程失败 perror("fork"); return 1; } else if (pid == 0) { // 子进程 const char* child_var = getenv("MY_VAR"); printf("子进程中的 MY_VAR 值为: %s\n", child_var); } else { // 父进程 printf("父进程中的 MY_VAR 值为: %s\n", my_var); } return 0; }
输出:
【分析】
- 通过
setenv
函数在父进程中定义了一个名为MY_VAR
的环境变量,并设置其值为"Hello World"
。然后使用fork
函数创建了一个子进程。 - 在子进程中,使用
getenv
函数获取MY_VAR
环境变量的值,并打印出来。在父进程中,直接打印父进程中定义的my_var
环境变量的值。
【小结】
可以看到,子进程成功继承了父进程的环境变量,并且可以在子进程中访问和使用
- 这是因为在创建子进程时,操作系统会将父进程的环境变量复制给子进程的环境,使子进程可以直接访问父进程的环境变量,从而实现了继承。
- 这种继承机制使得父子进程可以共享环境变量的值,方便了进程间的数据传递和共享配置信息。
总结
以上就是关于进程环境变量的全部知识内容了,感谢大家的观看与支持!!!