【Linux】—— 进程的环境变量

简介: 【Linux】—— 进程的环境变量

序言:

在上期我们已经对进程PCB以及进程状态进行了详细的解释说明。今天,我将带领大家学习的是关于进程


(一)孤儿进程

首先,在正式学习进程环境变量之前。我们先学习一下孤儿进程的相关知识,这个知识点在上篇博文中漏掉了,在本期中我给大家补上。

1、基本介绍

  • 孤儿进程(Orphan Process)是指在父进程结束后,子进程仍然在继续运行的情况;
  • 当父进程先于子进程退出时,子进程将成为一个孤儿进程;
  • 操作系统会将孤儿进程的父进程设置为init进程(通常是进程ID为1的进程),init进程会接管孤儿进程,并负责回收它们的资源。

孤儿进程的产生是由于父进程提前退出,导致子进程失去了父进程的引用,但子进程本身仍然在运行。

这种情况可能出现在以下几种情况下:

  1. 父进程调用了fork创建子进程后,快速退出,不等待子进程的结束。
  2. 父进程意外终止,例如发生了异常被强制终止

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、基本概念

  1. 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数;
  2. 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找;
  3. 环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性

2、和环境变量相关的命令

  • 1. echo: 显示某个环境变量值
  • 2. export: 设置一个新的环境变量
  • 3. env: 显示所有环境变量
  • 4. unset: 清除环境变量
  • 5. set: 显示本地定义的shell变量和环境变量

3、常见环境变量

  1. PATH : 指定命令的搜索路径
  2. HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  3. 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
  1. 这条命令会输出环境变量 HOME 的值,即当前用户的家目录。

如果一切正常,你将在终端中看到输出结果为当前用户的家目录的路径。

输出演示:

💨 而当我们使用【root】身份后在执行相应的指令时,会是什么效果呢?

具体如下:

 

【分析】

  1. 当使用 root 用户执行 echo $HOME 命令时,输出的结果是【/root】。这是因为在大多数 Linux 系统中,root 用户的主目录被设置为 /root
  2. 而当使用普通用户执行 echo $HOME 命令时,输出的结果是该普通用户的主目录路径;

【小结】

  1. root 用户的主目录是 /root,而普通用户的主目录位于 /home/username,其中 "username" 是用户的实际用户名;
  2. 这是因为在 Linux 系统中,不同用户有各自的独立主目录,用于存储其个人文件和配置信息。

(2)用root和普通用户,分别执行 执行 cd ~; pwd 对比差异

首先,还在以普通身份执行相应的指令。结果如下:

其次,以 root 身份执行相应的指令。结果如下:

 

【小结】

~】符号和 【$HOME】环境变量都表示当前用户的主目录路径。它们在大多数情况下是等价的,可以互换使用。


3️⃣、shell环境变量

Shell 环境变量是在操作系统中定义的一组全局变量,可以被不同的进程和程序共享和访问。下面是有关 Shell 环境变量的详细说明:

1、查看环境变量:

  • 在 Shell 中可以使用 env 命令来查看当前的环境变量列表。

  • 输出结果列出当前环境中的所有变量及其对应的值。

2、定义环境变量:

  • 可以使用 export 命令来定义一个环境变量。一旦定义,该变量就能够被当前 Shell 进程以及其子进程所访问。
export myenv="hello"

紧接着去环境变量中查看:


3、引用环境变量:

  • 使用美元符号 $ 作为前缀来引用环境变量。

【小结】

  1. Shell 环境变量在系统中起着重要的作用,它们可以为不同的进程提供全局的共享配置信息;
  2. 通过定义和引用环境变量,我们可以方便地进行参数传递、配置管理等相关的工作。

4、环境变量的组织方式

环境变量可以组织为键值对的集合。这种方式通常被用于存储一组相关的配置信息。

  1. 环境变量表是操作系统中存储环境变量的数据结构,它通常是一个键值对的集合;
  2. 在 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;
}

【分析】

  1. libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。
  2. 在上述示例中,environ 是一个全局变量,它会被操作系统设置为指向环境变量表的指针数组;
  3. 通过迭代访问 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,它接受三个参数:argcargv[]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 可以访问和操作环境变量表中的各种环境变量。

小结

总之通过环境变量表,我们可以访问和操作系统中定义的各种环境变量,这对于程序的配置和参数传递非常有用。


 

(三)通过系统调用获取或设置环境变量

  1. putenv
  2. getenv

在 Linux 中,可以使用 getenv 函数来获取环境变量的值,但是无法直接通过系统调用设置环境变量。要设置环境变量,可以使用  putenv 系统调用。


1、getenv

在 Linux 中,getenv 函数用于获取环境变量的值。它位于 <cstdlib> 头文件中,并且是 C/C++ 标准库提供的函数。

  • getenv 函数的原型如下:

char* getenv(const char* name);

  • 接下来,我们在 man 手册中查看一下这个函数:

【分析】

  1. 该函数接受一个 const char* 类型的参数 name,表示要获取的环境变量的名称;
  2. 如果成功找到对应的环境变量,则返回一个指向字符串值的指针;
  3. 如果未找到对应的环境变量,则返回 NULL。

💨 接下来,给大家简单的看以下程序来获取  "USER" 的环境变量:

【分析】

  1. 这段代码的作用是获取名为 "USER" 的环境变量的值,并将其输出到标准输出;
  2. 如果获取环境变量失败,会输出相应的错误信息。

输出显示:

 

💨 以下是使用 getenv 函数获取环境变量值的示例代码:

输出显示:

【分析】

  1. 在这个示例中,我们尝试获取环境变量 HOME 的值。首先,声明一个 const char* 类型的变量 varName,并将其赋值为要获取的环境变量的名称;
  2. 然后,调用 getenv 函数,传入 varName,获取环境变量的值,将结果存储在 char* 类型的变量 varValue 中。
  3. 接下来,我们通过判断 varValue 是否为 NULL 来确定是否成功获取到环境变量的值。如果成功,我们输出环境变量的名称和对应的值;如果未找到对应的环境变量,我们输出未找到的提示。

【注意】

getenv 函数返回的指针指向了一个静态分配的字符串缓冲区,因此不应该尝试修改或释放该指针指向的内存。

接下来,再给大家看一段代码:


输出展示:

 

【分析】

  1. 该段代码的作用是判断当前执行程序的用户是否为 "zp",并根据判断结果输出相应的信息;
  2. 但是如果当前用户是 "zp",则输出执行完成的提示信息;如果当前用户不是 "zp",则输出非法用户的提示信息,指示无法执行。

2、putenv

  • putev的函数原型如下:

int putenv(char* string);

  • 接下来,我们在 man 手册中查看一下这个函数:

【分析】

  1. 该系统调用用于设置环境变量的值,类似于 setenv。它接受一个字符串参数 string,格式为 "name=value",用于指定环境变量的名称和值;
  2. putenv 直接接受一个字符串参数,而不需要设置是否覆盖已存在的环境变量。
  3. 调用成功时,putenv 返回 0;调用失败时,返回 -1,并设置 errno 变量以指示错误的原因。

💨  以下是一个使用 putenv 设置环境变量的示例代码:

输出显示:

 

 

【分析】

  1. 我们首先使用 snprintf 将环境变量的名称和值格式化为一个字符串"name=value",然后将该字符串传递给 putenv

【注意】

  1. putenv 函数接受的参数必须是指向静态分配的字符串或者全局变量的指针,因为它会直接使用传递的指针,而不会对其进行拷贝;
  2. 因此这意味着传递栈上的局部变量给 putenv 是不安全的。

常用getenvputenv函数来访问特定的环境变量。


(四)环境变量通常是具有全局属性的

环境变量通常具有全局属性,可以被子进程继承下去

在上述将shell环境时,我们手动的定义了环境变量 【myenv】,接下来我以这个为例进行代码演示:

#include <stdio.h>
#include <stdlib.h>
int main()
{
 char * env = getenv("myenv");
 if(env){
 printf("%s\n", env);
 }
 return 0;
}

输出显示:

 

接下来,我们在手动的定义一个环境变量:

 

紧接着我们修改一下源代码,看程序输出结果:

输出显示:

 

【分析】

  1. 我们可以发现带【export】的环境变量可以显示出来;
  2. 直接查看,发现没有结果,说明该环境变量根本不存在

【小结】

  1. 我们可以发现【hello1】此时依旧可以打出来,只是没有放在环境变量表里面而已。
  2. 对于这种,我们有个专门的称呼叫做 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;
}

输出:

【分析】

  1. 通过 setenv 函数在父进程中定义了一个名为 MY_VAR 的环境变量,并设置其值为 "Hello World"。然后使用 fork 函数创建了一个子进程。
  2. 在子进程中,使用 getenv 函数获取 MY_VAR 环境变量的值,并打印出来。在父进程中,直接打印父进程中定义的 my_var 环境变量的值。

【小结】

可以看到,子进程成功继承了父进程的环境变量,并且可以在子进程中访问和使用

  1. 这是因为在创建子进程时,操作系统会将父进程的环境变量复制给子进程的环境,使子进程可以直接访问父进程的环境变量,从而实现了继承。
  2. 这种继承机制使得父子进程可以共享环境变量的值,方便了进程间的数据传递和共享配置信息。

总结

以上就是关于进程环境变量的全部知识内容了,感谢大家的观看与支持!!!

 

相关文章
|
3天前
|
存储 负载均衡 Linux
【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(下)
【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(下)
|
3天前
|
消息中间件 Unix Linux
【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(上)
【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(上)
|
3天前
|
缓存 Linux 调度
【Linux 系统】进程控制 -- 详解
【Linux 系统】进程控制 -- 详解
|
3天前
|
存储 Linux 数据安全/隐私保护
Linux进程间通信
Linux进程间通信
17 6
|
3天前
|
存储 Unix Linux
【Linux 系统】进程信号 -- 详解(下)
【Linux 系统】进程信号 -- 详解(下)
|
3天前
|
NoSQL Linux Shell
【Linux 系统】进程信号 -- 详解(上)
【Linux 系统】进程信号 -- 详解(上)
|
3天前
|
消息中间件 存储 安全
【Linux 系统】进程间通信(共享内存、消息队列、信号量)(下)
【Linux 系统】进程间通信(共享内存、消息队列、信号量)(下)
|
3天前
|
消息中间件 算法 Linux
【Linux 系统】进程间通信(共享内存、消息队列、信号量)(上)
【Linux 系统】进程间通信(共享内存、消息队列、信号量)(上)
|
3天前
|
存储 算法 Linux
【Linux】程序地址空间 -- 详解 & Linux 2.6 内核进程调度队列 -- 了解
【Linux】程序地址空间 -- 详解 & Linux 2.6 内核进程调度队列 -- 了解
|
3天前
|
存储 缓存 Linux
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解