【Linux操作系统】:Linux进程概念(2)

目录

Z(zombie)-僵尸进程

1. 僵尸进程概念

2. 僵尸进程的危害

3. 如何查看僵尸进程

4. 如何清理僵尸进程

4. 僵尸进程的模拟实现

孤儿进程 

1. 孤儿进程概念

2. 模拟实现孤儿进程

3. 孤儿进程 vs 僵尸进程

 4. 总结

进程优先级

1. 查看进程优先级

2. PRI(Priority)和 NI(Nice)

3. 为啥要有优先级 

4. 关键寄存器的作用

环境变量

常见的环境变量 

查看环境变量

PATH环境变量

环境变量的组织方式

环境变量相关的命令

1. 查看环境变量

2. 设置环境变量 

3. 删除环境变量 

通过代码如何获取环境变量

本地变量和环境变量以及内键命令


Z(zombie)-僵尸进程

1. 僵尸进程概念

故事

   张三每天都有跑步的习惯,这一天他和往常一样跑步,跑了两三圈,突然跑在它前面的一个人倒在地上不动了,作为热心市民张三赶紧报警并且拨打120。很快120就来了,但是没过几分钟就走了,这个人已经噶了(去世了),并交给了警察。警察这个时候会怎样做呢?警察立刻封锁了现场,并且叫来法医,法医确认了这个人的死亡原因,并且收集了所有信息。然后告诉家属信息。这个时候再把这个人抬走。

请问从这个人倒下到他被抬走,他是死亡状态吗?

不是,他是死亡了,但是他不是死亡状态,这个时候他是僵死状态,直到他被抬走他才是死亡状态。

 僵尸进程的产生原因?

  • 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
  • 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
  • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

 当子进程终止时,会向父进程发送 SIGCHLD 信号,此时:

  • 正常情况:父进程调用 wait() 或 waitpid() 系统调用,回收子进程的退出状态。

  • 僵尸进程产生:父进程未处理 SIGCHLD 信号,也未回收子进程的退出状态。

2. 僵尸进程的危害

  1. 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态。
  2. 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护。
  3. 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
  4. 僵尸进程会导致内存泄漏
  5. 资源占用:占用有限的 PID 资源(PID 耗尽时无法创建新进程)。
  6. 系统稳定性:大量僵尸进程可能干扰系统监控和管理工具。

3. 如何查看僵尸进程

方法 1:使用 ps 命令

ps aux | grep 'Z'

4. 如何清理僵尸进程

解决方法

  1. 重启操作系统(建议不推荐)
  2. 将僵尸进程的父进程杀掉,将最终使用的子进程变为孤儿进程,从而交由init进程处理其退出信息
  3. 利用信号SIGCHLD,子进程状态改变后会向其父进程发送SIGCHLD信号。父进程在接受到该信号后,在信号处理函数中调用wait()或者waitpid()

方法 1:终止父进程

僵尸进程会随父进程终止而被系统回收(由 init 进程接管)。

1. 找到僵尸进程的父进程 PID(PPID):

ps -eo pid,ppid,state,cmd | grep 'Z'

 

 父进程为72197

 2. 终止父进程:

kill -9 72197   # 强制终止父进程

 

方法 2:手动发送 SIGCHLD 信号 

若父进程设计不良未处理 SIGCHLD,可手动触发父进程回收子进程:

kill -s SIGCHLD PPID   # 向父进程发送 SIGCHLD 信号

 

4. 僵尸进程的模拟实现

在代码当中创建出来一个子进程,模拟让子进程先于父进程退出,通过fork函数的返回值,让父子进程走不同的分支逻辑。

  1. 子进程直接退出。
  2. 父进程别退出(while (1))。

观察:子进程目前的运行状态

代码如下:

#include <stdio.h>      // 标准输入输出函数
#include <stdlib.h>     // 标准库函数,如exit, EXIT_SUCCESS
#include <unistd.h>     // 提供系统调用,如fork(), sleep(), getpid()
#include <sys/types.h>  // 定义进程标识类型pid_t
#include <sys/wait.h>   // 提供wait()相关函数
int main()
{
    // 使用fork()创建子进程,id保存返回值
    pid_t id = fork();
    
    // fork失败处理:id < 0表示创建进程失败
    if(id < 0)
    {
        perror("fork"); // 打印错误信息
        return 1;       // 非正常退出
    }   
    // 父进程分支:id > 0时,当前代码在父进程运行
    else if (id > 0)
    {
        // 打印父进程PID并进入长时间休眠
        printf("parent[%d] is sleeping...\n", getpid());
        sleep(100);     // 父进程休眠100秒,期间未处理子进程状态
    }       
    // 子进程分支:id == 0时,当前代码在子进程运行
    else
    {
        // 打印子进程PID后短暂休眠并退出
        printf("child[%d] is begin Z...\n", getpid());
        sleep(2);       // 子进程休眠2秒
        
        exit(EXIT_SUCCESS); // 子进程正常退出,状态码0
                            // 父进程未调用wait,子进程将成僵尸进程(Z状态)
    }
    return 0; // 父进程最终正常退出(但子进程在此前可能已成僵尸)
}

/* 代码现象说明:
 * 1. 子进程在2秒后退出,父进程持续休眠100秒。
 * 2. 由于父进程未调用wait()回收子进程,子进程在退出后会保持僵尸状态(Z+),
 *    直到父进程结束或处理子进程状态。
 * 3. 可通过`ps aux | grep 程序名`在运行期间观察僵尸进程状态。
 */

编译运行如下图所示: 

僵尸进程会自行消失吗?

  • 不会,除非父进程终止或主动回收。

init 进程会回收僵尸进程吗?

  • ,但仅当僵尸进程的父进程终止后,init 才会接管并回收。

孤儿进程 

1. 孤儿进程概念

  • 状态:父进程提前终止(如崩溃、被终止),但其子进程仍在运行。

  • 系统处理:孤儿进程会被操作系统内核的 init 进程(PID=1)接管,成为新的父进程。

  • 特点

    • 孤儿进程本身仍在运行,只是父进程被替换为 init

    • 不会对系统造成危害,最终会由 init 进程正确回收资源。

父进程先退出,子进程被称为“孤儿进程”。

父进程结束或者异常终止,但是子进程继续运行。此时子进程的PPID被设置为1,即init进程。init进程接管了该子进程,并等待它结束,在父进程退出之后,子进程退出之前,该子进程属于孤儿进程 

孤儿进程的产生原因?

当父进程终止时,其子进程若仍在运行,则会成为孤儿进程。常见场景:

  • 父进程意外崩溃(如段错误)。

  • 父进程未正确等待子进程结束(如未调用 wait()/waitpid())。

  • 父进程被强制终止(如 kill -9)。

2. 模拟实现孤儿进程

孤儿进程:

  1. 模拟产生一个孤儿进程:父进程创建一个子进程,父进程先于子进程退出,子进程就是孤儿进程
    1. 子进程代码当中写一个死循环
    2. 父进程直接退出 
#include <stdio.h>    // 标准输入输出库(printf、perror)
#include <unistd.h>   // Unix 标准库(fork、sleep、getpid)
#include <stdlib.h>   // 通用工具库(exit)

int main()
{
    // 创建子进程
    // fork() 返回值:
    //   -1 : 创建失败
    //   0  : 子进程分支
    //   >0 : 父进程分支(返回值为子进程PID)
    pid_t id = fork();

    if (id < 0)
    {
        // fork 创建失败处理
        perror("fork");  // 打印错误信息(例如达到进程数上限)
        return 1;        // 非正常退出
    }
    else if (id == 0)
    {
        // 子进程执行分支
        while (1)  // 无限循环
        {
            // 每秒打印子进程的 PID
            printf("I am child, pid : %d\n", getpid());
            sleep(1);  // 睡眠1秒(避免过度占用CPU)
        }
    }
    else
    {
        // 父进程执行分支
        printf("I am father, pid : %d\n", getpid());
        sleep(10);    // 父进程休眠10秒
        
        // 父进程主动退出(不等待子进程)
        exit(0);      // 正常退出
        // 此时子进程将成为孤儿进程(由init进程接管)
    }

    return 0;
}

 

3. 孤儿进程 vs 僵尸进程

特性孤儿进程僵尸进程
状态仍在运行已终止但未被回收
父进程被 init 接管原父进程未调用 wait()
资源占用占用内存和 CPU仅占用 PID,不占内存/CPU
危害无直接危害可能耗尽 PID 资源
处理方式由 init 自动回收需终止父进程或修复父进程逻辑

 4. 总结

  • 孤儿进程是父进程意外终止后由 init 接管的子进程,无害且会被自动回收

  • 僵尸进程是已终止但未被回收的进程,需手动处理。

  • 通过正确的进程管理(如 wait()、信号处理),可避免孤儿或僵尸进程的产生。

进程优先级

1. 优先级(Priority) vs 权限(Permission)

维度优先级权限
本质资源分配的先后顺序资源访问的合法性
核心问题谁先获得资源?是否有资格获得资源?
类比场景银行排队:VIP客户优先办理进入银行需要身份验证(权限)
实现方式进程调度算法(如优先级数值、时间片轮转)用户/组权限控制(如 chmodsudo
操作影响影响进程获取资源的顺序和频率决定进程能否访问文件、设备或执行操作

示例

  • 优先级场景:两个用户同时运行 CPU 密集型任务,优先级高的进程会获得更多 CPU 时间片。

  • 权限场景:普通用户无法修改系统文件(无写权限),也无法将进程优先级设为负数(需 root 权限)。

为什么会存在优先级?

优先级,排队就是在确定优先级

  • 是什么:得到某种资源的先后顺序
  • 为什么:本质是 cpu 或 操作系统 等等的资源不足
  • 怎么做:其实就是 pcb 中的一个 int 字段,数值越小,优先级越大
  1. cpu 资源分配、外设 IO 的先后顺序,就是指进程的优先权(priority)
  2. 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的 Linux 很有用,可以改善系统性能。
  3. 还可以把进程运行到指定的 CPU 上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。 

 总结如下:

  • 资源不足:CPU、内存、I/O 设备等资源有限,无法同时满足所有进程的需求。

  • 优化目标

    • 公平性:防止单个进程独占资源。

    • 效率性:关键任务优先执行,提升系统整体性能。

    • 实时性:确保高优先级任务(如音视频处理)及时响应。

1. 查看进程优先级

使用 ps 命令

ps -eo pid,ni,pri,comm   # 查看所有进程的 Nice 值(NI)和优先级(PRI)
ps -l <PID>              # 查看指定进程的优先级
ps -al

使用命令如下图所示: 

    参数含义如下: 

    • UID:代表执行者的身份,用户标识符。
    • PID:代表这个进程的代号。
    • PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号。
    • PRI:prority代表这个进程可被执行的优先级,其值越小越早被执行。
    • NI:代表这个进程的nice值。 

    2. PRI(Priority)和 NI(Nice)

    1. PRI(Priority)与 NI(Nice)的定义 

    参数作用默认值范围调整权限
    PRI进程的实际优先级,决定调度顺序8060~99用户无法直接修改
    NI用户可调整的优先级修正值(静态)0-20~19普通用户只能增大 NI

     注意:

    • nice调整最小是:-20,超过部分统一当成-20
    • nice调整最大是:19,超过部分统一当成19 

    2. PRI 与 NI 的关系 

    计算公式如下:

    PRI(new) = PRI(old) + NI
    • PRI(old):系统基准优先级(默认 80)。

    • NI:用户设置的修正值(-20~19)。

    示例如下:

    • 若设置 NI = -10,则 PRI(new) = 80 + (-10) = 70(优先级提高)。

    • 若设置 NI = 15,则 PRI(new) = 80 + 15 = 95(优先级降低)。

    3. 为什么限制 PRI 和 NI 的数值范围? 

    (1) 避免进程饥饿(Starvation)

    • 问题:若允许 PRI 无限降低(或 NI 无限小),高优先级进程可能长期独占 CPU,导致低优先级进程无法执行。

    • 解决:通过范围限制(PRI 最低 60,NI 最低 -20),确保所有进程都能被公平调度。

    (2) 保障系统稳定性

    • 资源平衡:限制 PRI 范围(60~99)让内核在调度时有足够的调整空间,避免极端优先级导致系统负载失衡。

    • 用户权限隔离:普通用户只能降低自身进程优先级(增大 NI),防止恶意抢占系统资源。

    修改 NI值

    用top命令更改已经存在进程的nice值

    top
    # 1. 进入top后按“r”
    # 2. 输入进程PID
    # 3. 输入nice值

    3. 为啥要有优先级 

    在操作系统中引入 优先级(Priority) 机制,本质是为了解决 资源有限性 和 任务多样性 之间的矛盾。

    1. 解决资源竞争问题(竞争性)

    • 资源有限:CPU 核心数远小于进程数(例如单核 CPU 需处理成百上千个进程)。

    • 任务多样性:不同进程的重要性、实时性、资源需求差异巨大(如后台日志处理 vs 用户交互界面)

    优先级的作用

    每个进程不是占用 CPU 就一直运行,每隔一段时间,进程就会被从 CPU 剥离下来,时间片
    Linux内核支持进程之间进行 CPU 资源抢占的(基于时间片的轮转式的抢占内核)

    • 资源分配策略:通过优先级标识进程的重要性,确保关键任务优先获取 CPU 时间片。

    • 避免低效轮转:若所有进程平等竞争,高价值任务可能因等待低价值任务而延迟完成。

    进程调度和资源管理

    在操作系统中,进程的 竞争性、独立性、并行、并发 是进程调度和资源管理的核心特性。它们共同构成了多任务系统的基础,

    1. 竞争性:系统进程数目众多,而CPU资源只有少量甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
    2. 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。
      1. 资源隔离:每个进程拥有独立的虚拟地址空间、文件描述符、信号处理等资源,互不干扰。
      2. 故障隔离:一个进程崩溃不会影响其他进程或操作系统核心。
    3. 并行:多个进程在多个 CPU 下分别,同时进行运行,这称之为并行。
      1. 多核同时执行:多个进程在多个 CPU 核心上 同时运行,物理上真正并行。

      2. 加速任务:适用于计算密集型任务(如科学计算、图像渲染)。

    4. 并发:多个进程在一个 CPU 下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。 
      1. 时间片轮转:单个 CPU 核心通过快速切换(时间片)模拟“同时运行”多个进程。

      2. 逻辑上的并行:进程在宏观上看似同时推进,微观上交替执行。

    关键对比:并行 vs 并发

    维度并行(Parallelism)并发(Concurrency)
    硬件要求多核 CPU 或分布式系统单核 CPU 即可实现
    执行方式物理上同时运行逻辑上交替运行
    目标加速任务(利用多核性能)提高资源利用率(避免 CPU 空闲)
    适用场景计算密集型任务(如数值模拟)I/O 密集型任务(如网络请求处理)

     函数内定义的栈临时变量如何返回给外部?

    (1) 临时变量的生命周期

    • 栈分配:函数内的局部变量(临时变量)通常存储在栈上,函数返回时栈帧被销毁,变量理论上失效。

    • 返回值传递:若需要将内部变量传递到外部,需通过 寄存器 或 内存 传递值(而非传递指针)。

    (2) 寄存器 eax 的作用

    • 返回值寄存器:在 x86 架构中,eax(或 rax 在 64 位)寄存器用于存储函数的返回值。

    • 值复制机制:函数返回时,临时变量的值会被复制到 eax 中,外部通过读取 eax 获取结果。

     示例如下:

    int func() 
    {
        int tmp = 42;  // 栈上的临时变量
        return tmp;    // 将 tmp 的值复制到 eax
    }
    // 外部通过 eax 获取 42

    (3) 为什么栈变量能安全返回?

    • 值传递:返回的是值的副本,而非栈内存地址。即使栈帧销毁,外部获取的是 eax 中的副本,与原始栈变量无关。

    • 寄存器中转:编译器自动生成代码,将返回值存入 eax,不依赖原栈内存。

    程序如何跟踪执行位置与函数跳转?

    (1) 程序计数器 eip(Instruction Pointer)

    • 核心作用eip 寄存器存储下一条待执行指令的地址,CPU 按 eip 的指引顺序执行代码。

    • 函数跳转

      • call 指令:调用函数时,eip 跳转到函数入口地址,并将返回地址(下一条指令地址)压入栈。

      • ret 指令:函数返回时,从栈中弹出返回地址到 eip,继续执行原流程。

    (2) 栈帧与寄存器协作

    • ebp 和 espebp(基址指针)标记当前栈帧起始,esp(栈指针)动态管理栈顶。

    • 函数调用链:每次函数调用会创建新栈帧,ebp 和 esp 协助维护栈结构,确保返回时能正确恢复上下文。

    多进程如何管理 CPU 寄存器的临时数据?

    (1) 寄存器与寄存器内容的区别

    • 寄存器是硬件资源:CPU 中仅有单组物理寄存器(如 eaxebxeip 等)。

    • 寄存器内容是进程上下文:每个进程运行时,寄存器的值是其运行状态的快照(如 eip 指向进程代码段地址)。

    (2) 进程上下文切换(Context Switch)

    • 触发时机:时钟中断、系统调用、进程主动让出 CPU 等。

    • 切换步骤

      1. 保存当前进程上下文:将寄存器的值保存到进程控制块(PCB,如 Linux 的 task_struct)。

      2. 加载下一进程上下文:从目标进程的 PCB 恢复寄存器值到物理寄存器。

      3. 更新 cr3(页表基址寄存器):切换虚拟内存空间。

    4. 关键寄存器的作用

    寄存器核心作用
    eip程序计数器,指向下一条指令地址
    esp栈指针,指向当前栈顶地址
    ebp基址指针,标记当前栈帧起始位置
    eax通用寄存器,常用于函数返回值
    cr3控制寄存器,存储当前进程页表的物理地址,用于虚拟内存切换
    cs/ds代码段/数据段寄存器,与保护模式下的内存分段机制相关(现代系统多用平坦模式)

    环境变量

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

    环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。

    为什么需要环境变量?

    环境变量(envirnment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。

    常见的环境变量 

    1. PATH:指定命令的搜索路径
    2. HOME:指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
    3. SHEIL:当前Shell,它的值通常是/bin/bash
    4. USER当前登录的用户名
    5. LANG系统语言设置

    注意事项

    • 环境变量名是大小写敏感的(例如 PATH 与 Path 不同)。

    • 通过 export 设置的环境变量仅在当前 Shell 会话有效,重启后失效。

    查看环境变量

     1. 查看所有环境变量

    这会列出所有当前设置的环境变量

    // 使用 printenv 命令:这会列出所有当前设置的环境变量。
    printenv
    
    // 使用 env 命令:这会列出所有当前设置的环境变量。
    env

    如下图所示: 

    2. 查看特定环境变量

    # 通过 printenv + 变量名:
    printenv VARIABLE_NAME
    
    # 通过 echo + $ 符号:
    echo $VARIABLE_NAME
    

     如下图所示:

    PATH环境变量

    PATH用于指定命令的搜索路径

     我们来做一个简单的小实验,创建一个test.cpp文件,并且编译形成一个可执行程序mybin,然后分别带路径和不带路径进行测试对比

    #include<iostream>
    int main()
    {
        std::cout<<"Hello world"<<std::endl;
        return 0;
    }
    

    编译后运行 

    带路径运行如下图:

     带路径运行如下图:

    我们分别带路径和不带路径运行,发现带路径就可以运行,而不带路径无法运行。 这个时候就有个问题?

    为什么有些指令可以直接执行(例如ls、cd等等),不需要带路径,而我们的二进制程序需要带路径才能执行? 

    如果我们的程序所在路径加入环境变量PATH当中会发生什么呢?

    # 只是临时修改,离开当前shell就失效了
    export PATH=$PATH:文件所在路径

     此时我们发现确实不用带路径就可以运行了 。这个时候我们又有问题了。为啥这个时候有可以不带路径了,我只是把mybin的路径加入到环境变量中而已? 

    环境变量的组织方式

    • 命令行启动的进程都是shell/bash的子进程,子进程的命令行和环境变量是父进程(bash)传递的。
    • 父进程的环境变量又是哪来的呢?——》环境变量是以脚本配置文件的形式存在的
    • 我们在启动shell的时候就会自动执行的一个可执行程序,就是环境变量的以脚本配置文件

    在C语言中我们学到了指针数组,就是存放指针的数组,那么存放的就是各种环境变量的首地址,通过找到环境变量的首地址就可以找到对于的环境变量。
    在这里插入图片描述
    每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串

    环境变量相关的命令

    在 Linux 系统中,管理环境变量的常用命令和操作如下:

    1. 查看环境变量

    1.1 查看所有环境变量

    printenv   # 显示所有环境变量
    env        # 显示所有环境变量(与 printenv 类似)
    set        # 显示所有环境变量 + Shell 局部变量(内容较多)

    1.2 查看特定环境变量 

    printenv VARIABLE_NAME  # 例如 printenv PATH
    echo $VARIABLE_NAME    # 例如 echo $HOME

    2. 设置环境变量 

    2.1 临时设置(仅在当前 Shell 有效)

    export VAR_NAME="value"     # 设置环境变量(子进程可继承)
    VAR_NAME="value"            # 设置局部变量(仅当前 Shell 有效)
    export PATH=$PATH:文件所在路径  # 指定命令的搜索路径

    2.2 永久设置(需写入配置文件) 

    用户级(仅对当前用户生效):

    # 修改.bashrc 文件
    echo 'export VAR_NAME="value"' >> ~/.bashrc     # 追加到 ~/.bashrc
    source ~/.bashrc   # 立即生效

    系统级(对所有用户生效):

    # 修改profile 文件
    sudo sh -c 'echo "export VAR_NAME=value" >> /etc/profile'  # 写入全局配置
    source /etc/profile   # 立即生效(需管理员权限)

    3. 删除环境变量 

    3.1 临时删除

    unset VAR_NAME   # 删除环境变量或局部变量

    3.2 永久删除

    从配置文件(如 ~/.bashrc 或 /etc/profile)中移除对应的 export 行,并执行 source 重新加载。

    source ~/.bashrc
    source /etc/profile

    通过代码如何获取环境变量

    1. 方法 1:通过 main 的第三个参数 envp(POSIX 标准)

    #include <stdio.h>
    
    int main(int argc, char* argv[], char* envp[]) 
    {
        // argc : 命令行参数的个数, 本质上就是argv数组的元素个数
        // argv :具体的命令行参数
        // envp : 环境变量的值
    
        // 遍历命令行参数
        printf("命令行参数:\n");
        for (int i = 0; i < argc; i++) 
        {
            printf("argv[%d]: %s\n", i, argv[i]);
        }
    
        // 遍历环境变量
        printf("\n环境变量:\n");
        for (int i = 0; envp[i] != NULL; i++) 
        {
            printf("envp[%d]: %s\n", i, envp[i]);
        }
    
        return 0;
    }
    

    编译运行如下图:

    说明:

    • envp 是一个以 NULL 结尾的字符串数组,每个元素的格式为 VAR_NAME=value

    • 这种方式是 POSIX 标准 的一部分,在 Linux/Unix 系统中可用,但 不属于 C 语言标准

     

    2. 方法 2:通过全局变量 environ(更通用的方式)

    另一种更通用的方式是使用全局变量 extern char** environ(需要包含 <unistd.h>):

    #include <stdio.h>
    #include <unistd.h> // 需要包含此头文件
    
    extern char** environ; // 声明全局变量
    
    int main(int argc, char* argv[]) 
    {
        // 遍历环境变量
        printf("环境变量:\n");
        for (int i = 0; environ[i] != NULL; i++) 
        {
            printf("%s\n", environ[i]);
        }
    
        return 0;
    }

     编译运行如下图:

    说明:

    • environ 是一个全局变量,与 envp 的内容完全一致。

    • 这是 POSIX 标准 的推荐方式,避免了直接依赖 main 的第三个参数。

     

    3. 方法 3:通过 getenv 函数获取特定环境变量(标准 C 库)

    如果只需要获取某个特定环境变量的值,可以使用 getenv 函数:

    #include <stdio.h>
    #include <stdlib.h> // 需要包含此头文件
    
    int main() 
    {
        // 获取特定环境变量
        char* path = getenv("PATH");
        if (path != NULL) 
        {
            printf("PATH: %s\n", path);
        } 
        else 
        {
            printf("PATH 未设置\n");
        }
    
        return 0;
    }

     编译运行如下图:

    说明:

    • getenv("VAR_NAME") 返回环境变量的值(字符串),若未找到则返回 NULL

    • 这是 C 标准库函数,跨平台兼容性更好。

     方法的对比与选择

    方法特点
    main 的第三个参数直接访问所有环境变量,但依赖 POSIX 实现,可移植性较差
    全局变量 environ更通用的 POSIX 方式,适合需要遍历所有环境变量的场景
    getenv 函数标准 C 库函数,适合获取单个环境变量,跨平台兼容性好

    注意事项如下:

    1. 安全性

    • 直接操作 envp 或 environ 可能修改环境变量(不推荐),而 getenv 是只读的。
    • 修改环境变量应使用 setenv/putenv/unsetenv 等函数。

    2. 可移植性main 的第三个参数 envp 在 Windows 中不可用,但 environ 和 getenv 是跨平台的。

    3. 格式解析:环境变量的格式为 VAR_NAME=value,需自行解析字符串(例如用 strtok 或 strchr)。

    本地变量和环境变量以及内键命令

    1. 本地变量(Local Variables)

    • 作用域:仅在当前 Shell 进程内有效,不会传递给子进程

    • 生命周期:随当前 Shell 会话结束而消失。

    • 赋值语法

    VAR_NAME="value"    # 赋值(等号两侧不能有空格)

    示例如下:

    # 定义本地变量
    my_var="Hello, Local Variable"
    
    # 输出变量
    echo $my_var         # 输出: Hello, Local Variable
    
    # 在子 Shell 中无法访问
    bash -c 'echo $my_var'  # 输出: 空值

     运行如下图:

    2. 环境变量(Environment Variables)

    • 作用域:可传递给当前 Shell 的 子进程

    • 生命周期:默认仅在当前 Shell 有效,持久化需写入配置文件。

    • 创建方法

    export VAR_NAME="value"   # 将本地变量转为环境变量

    3. 内建命令(Built-in Commands)

    • 实现方式:由 Shell 自身直接执行,不启动新进程,执行速度快。

    • 作用:用于操作 Shell 环境(如变量管理、流程控制等)。

     常见内建命令

    命令作用示例
    export将变量导出为环境变量export VAR="value"
    unset删除变量unset VAR
    readonly设置只读变量readonly VAR="value"
    cd切换目录cd /path
    source执行脚本并影响当前 Shell 环境source ~/.bashrc
    echo输出内容echo "Hello"
    set设置 Shell 选项或显示变量set -o vi
    alias创建命令别名alias ll='ls -alF'

    判断命令是否为内建 

    type command_name  # 例如 type cd 返回 "cd is a shell builtin"

    4. 关键区别总结

    特性本地变量环境变量内建命令
    作用域当前 Shell当前 Shell 及其子进程直接由 Shell 执行,无子进程
    生命周期随 Shell 结束消失默认随 Shell 结束消失立即生效,无独立生命周期
    创建方式VAR="value"export VAR="value"Shell 内置实现
    查看方式setprintenv 或 envtype command 查看
    典型用途脚本内部临时存储数据跨进程传递配置信息管理 Shell 环境或执行基础操作

    5. 注意事项

    1. 变量赋值规则:等号两侧不能有空格:VAR=value(正确),VAR = value(错误)。

    2. 环境变量继承:子进程会继承父进程的环境变量,但无法反向修改父进程环境。

    3. 配置文件优先级:用户级配置(如 ~/.bashrc)会覆盖系统级配置(如 /etc/profile)。

    4. 只读变量:使用 readonly VAR="value" 设置后不可修改或删除。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包

    打赏作者

    -元清-

    你的鼓励将是我创作的最大动力

    ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
    扫码支付:¥1
    获取中
    扫码支付

    您的余额不足,请更换扫码支付或充值

    打赏作者

    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值