优先级与环境变量的艺术:驾驭 Linux 系统的核心

前言
在 Linux 系统中,进程和环境变量是操作系统的核心组成部分。进程优先级决定了资源分配的先后顺序,直接影响系统性能;而环境变量则提供了一种灵活的方式,用于传递系统配置信息以及控制程序的运行环境。本篇文章将深入探讨进程优先级的概念、调节方法及其原理,以及环境变量的定义、特性和实际应用,帮助读者更好地理解 Linux 系统的核心机制,从而在实际工作中更高效地调试和优化系统。
一、什么是进程优先级?
优先级的定义:
优先级决定了多个进程竞争 CPU 时,哪个进程优先获得 CPU 时间片。
区别于权限,权限决定能不能执行某个任务,而优先级决定谁先执行。
优先级的作用:
通过优先级机制,操作系统能够公平地分配 CPU 资源,防止某些进程长期得不到 CPU 的问题(即进程饥饿问题)。
例如:
高优先级进程(实时任务)可以更快地执行。
低优先级进程可能被推迟,但不会被饿死(通过调度算法保证)。
小结:
CPU 资源分配的先后顺序由优先级决定。
Linux 的调度器根据优先级动态调度进程,提高系统的整体性能。
二、Linux 系统中的优先级
2.1 查看进程优先级
示例代码:
#include <stdio.h>
#include <unistd.h>
int main() {
    while (1) {
        printf("I am a process, PID:%d, PPID:%d\n", getpid(), getppid());
        sleep(1);
    }
    return 0;
}
运行步骤:
编译运行程序:
gcc process.c -o process
./process
打开另一个终端,查看进程的优先级:
ps -al | head -1 ; ps -al | grep process
输出字段解析:
UID:表示进程的执行者身份。
PID:当前进程的 ID。
PPID:当前进程的父进程 ID。
PRI:进程的优先级值,值越小优先级越高。
NI:进程的 nice 值,调整优先级的修正值。
2.2 PRI 和 NI 的关系
PRI(Priority):
表示进程的实际优先级,值越小优先级越高。
Linux 调度器根据 PRI 值决定哪个进程优先执行。
NI(Nice 值):
是进程优先级的修正值,用户可以通过调整 nice 值来影响 PRI 值。
nice 值范围:
[-20, 19],共 40 个级别。
默认值是 0。
nice 值的影响:
负数:提高优先级(PRI 值减小)。
正数:降低优先级(PRI 值增大)。
关系公式:
PRI(new) = PRI(old) + nice
PRI(old):系统默认优先级,起始值为 80。
nice 值:用户设置的调整值。
举例:
默认情况下(nice=0),PRI 值为 80。
如果 nice=-10,则:
PRI = 80 + (-10) = 70  (优先级提高)
2.3 修改进程优先级
查看进程优先级(top 命令):
使用 top 实时监控系统进程:
top
找到目标进程的 PID。
修改优先级:
在 top 中,按下 r 键。
输入目标进程的 PID。
输入新的 nice 值(范围:-20 至 19)。
命令行方式修改:
启动新进程并设置 nice 值:
nice -n <nice值> ./process
示例:
nice -n -10 ./process
调整运行中的进程:
renice <nice值> -p <PID>
示例:
renice -5 -p 12345
注意:
只有 root 用户 可以设置负的 nice 值(提高优先级)。
普通用户只能降低优先级。
2.4 进程优先级的实现原理
进程控制块(PCB):
每个进程在内核中对应一个 task_struct 结构体(即进程控制块)。
task_struct 中包含进程的优先级、状态等信息。
优先级队列:
系统根据进程的 PRI 值将其加入对应的优先级队列。
修改优先级的本质:
当调整进程的 nice 值时,内核会将该进程从当前队列移出,并重新加入新的优先级队列。
2.5 进程的特性
竞争性:系统进程数目众多,而 CPU 资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。
并行:多个进程在多个 CPU 下分别同时运行,这称之为并行。
并发:多个进程在一个 CPU 下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
并发情况下, 操作系统会根据每个进程的时间片进行进程间的切换,只要当前正在被 CPU 调度的进程的时间片到了,无论有没有执行结束,都会被操作系统从 CPU 上拿下来,放到 waiting 数组中去排队,等待 CPU 的下一次调度,我们把这种过程叫做进程基于时间片轮转的调度算法。
两个问题:
1.为什么函数返回值,会被外部拿到呢?
函数调用过程中,栈会保存必要的信息,包括返回地址、参数和局部变量等。
返回值通过寄存器或栈上的特定位置传递给调用者:
如果返回值是通过栈返回,调用者会在栈上预留一块区域,函数直接将返回值写入这块区域。
在调用完成后,调用者通过栈指针或其他方法从指定位置获取返回值。
2.系统如何得知进程当前执行到哪一行代码了?
CPU 中的程序计数器寄存器 ( PC 或者 eip ) 保存着当前正在执行指令的地址。每次指令执行完毕,程序计数器会自动递增(或根据控制流指令,如 jmp、call、ret 等更新),以指向下一条需要执行的指令。
补充知识:寄存器
CPU 中的寄存器有很多种,例如:通用寄存器 eax、ebx、ecx、edx;和栈帧有关的寄存器 ebp、esp、eip;和状态有关的寄存器 status。
寄存器扮演什么角色?提高效率,进程的高频数据会放入寄存器中。
寄存器也有对数据保存的能力,计算机在运行时一些重要的数据一定要保存在 CPU 内部,即寄存器中,因为寄存器离 CPU 很近且存储效率高,所以 CPU 内的寄存器保存的是进程相关的数据,可以随时被 CPU 访问或者修改,这种和进程有关的数据也被叫做进程的上下文。
进程在 CPU 上离开时要将自己的上下文数据保存好,甚至带走,这样做的目的是为了未来“回来”的时候进行恢复。进程在被切换的时候有两步:**1. 保存上下文,2. 恢复上下文。**进程的上下文数据量并不大,一般可以直接保存到进程的 PCB 对象中,当 CPU 再次调度该进程的时候,将这些上下文数据再恢复到寄存器里面即可。
三、环境变量
环境变量是操作系统用于存储系统范围内或用户特定信息的键值对,通常用于配置系统和应用程序运行时的行为。通过设置和读取环境变量,操作系统、开发工具和应用程序能够更灵活地响应不同的运行环境。
上图是 Windows 系统下的环境变量,本质上就是一组键值对,接下来将演示 Linux 系统下常见的环境变量。
3.1 PATH——搜索可执行文件的目录列表
1. 为什么自己写的程序需要加 ./ 才能执行?
默认搜索路径中没有当前目录:当前目录(.)通常并不包含在 PATH 环境变量中。这是为了避免安全隐患(防止恶意程序通过伪造指令干扰系统)。
加 ./ 的作用:加 ./ 明确告诉系统,程序位于当前目录下。例如:
./my_program
表示运行当前目录下名为 my_program 的程序。
2. 为什么执行指令时不需要加路径?
指令(如 ls、cat)的本质:它们是存放在系统目录中的可执行程序,通常位于 /usr/bin/ 或 /bin/ 等目录下。
PATH 环境变量:Linux 系统通过 PATH 环境变量来指定一系列目录,当你在终端输入指令时,系统会按照 PATH 中定义的目录顺序,依次查找该指令对应的可执行程序。
查找过程:
输入 ls 时,系统会在 PATH 的所有目录中搜索 ls 对应的程序(如 /usr/bin/ls),一旦找到,就直接执行。
如果未找到,系统会报错 command not found。
3. 如何查看和修改 PATH?
1. 查看 PATH:
echo $PATH
输出示例:
表示系统会依次在 /usr/local/bin、/usr/bin、/bin 等目录中查找命令。
2.临时添加路径到 PATH:
PATH=$PATH:/home/xny/linux/lesson13
/home/xny/linux/lesson13 是当前工作目录。
$PATH 表示保留原有的搜索路径,不覆盖。
该修改仅对当前终端有效,关闭终端后会恢复默认。
此时我们当前的工作目录也被添加了进去,此时在该目录下的可执行程序在执行的时候就可以不加 ./ 了。
如图所示,mycmd可以直接当作命令使用了。
3.2 HOME——表示当前用户的主目录路径
1. 什么是 HOME?
HOME 是 Linux 系统中的一个环境变量,它表示当前用户的主目录路径。主目录是每个用户的个人工作空间,用于存储用户的配置文件、文档、下载文件等。不同的用户拥有各自的主目录,彼此隔离。
普通用户的 HOME 路径:通常位于 /home/username。
超级用户(root)的 HOME 路径:默认为 /root。
2. 作为默认工作目录
用户登录系统时,Shell 默认进入 HOME 目录。
输入 cd(不带参数)或 cd ~ 时,都会切换到 HOME 目录。
3. 查看 HOME 环境变量的值
echo $HOME
示例输出:
我们可以通过 env 指令查看当前 bash 从操作系统中继承下来的所有环境变量。
除了使用 env 指令,我们还可以通过 getenv 这个系统调用接口来获取某个环境变量的值。
3.3 USER——存储当前登录用户的用户名
USER 是一个系统环境变量,用来存储当前登录用户的用户名。Linux 系统依赖此变量来标识当前用户,并根据其权限级别(如普通用户或 root 用户)提供不同的访问权限。
1. 示例代码 1:打印当前用户
#include <stdio.h>
#include <stdlib.h>
int main()
{
    printf("USER: %s\n", getenv("USER"));
    return 0;
}
解释:
getenv("USER"):
调用 C 标准库函数 getenv 获取环境变量 USER 的值,即当前登录用户的用户名。
打印结果:
不同用户运行此程序时,会得到各自的用户名。例如:
USER: root
USER: john
2. 示例代码 2:判断用户权限
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    char user[32];
    strcpy(user, getenv("USER"));
    if(strcmp("root", user) == 0)
    {
        printf("root用户,不受权限约束\n");
    }
    else
    {
        printf("普通用户,受权限约束\n");
    }
    printf("USER: %s\n", getenv("USER"));
    return 0;
}
功能:
读取环境变量 USER:
使用 getenv("USER") 获取当前登录用户名。
将用户名存储在 user 字符数组中。
判断是否为 root 用户:
使用 strcmp 函数对比用户名是否为 root。
如果是 root,则打印 “root用户,不受权限约束”。
如果不是,则打印 “普通用户,受权限约束”。
打印当前用户:
再次打印当前用户的用户名。
运行结果示例:
如果 root 用户运行程序:
root用户,不受权限约束
USER: root
如果普通用户(如 john)运行程序:
普通用户,受权限约束
USER: john
3. USER 与权限认证
系统权限判断:
Linux 系统会使用当前用户的 USER 变量与文件的拥有者和权限信息进行对比,判断当前用户是否有权访问或修改文件。
权限模型:
每个文件或目录都有三类权限:
拥有者(Owner):文件的所有者。
所属组(Group):文件所属的用户组。
其他用户(Other):不属于以上两类的用户。
权限分为三种:
读(r)、写(w)、执行(x)。
USER 的实际作用:
当用户执行某些操作(如访问文件、运行程序)时,系统会获取 USER 变量的值,判断当前用户是否有对应权限。例如:
如果是 root 用户,通常不受权限限制。
如果是普通用户,则需要对比权限位。
四、命令行参数
4.1 什么是命令行参数?
命令行参数是指在运行程序时通过命令行传递给程序的参数。它们使程序可以接受外部输入,从而改变程序的行为或处理不同的数据。
在 C/C++ 中,命令行参数通过 main 函数的参数 argc 和 argv 获取:
argc(argument count):参数的数量。
argv(argument vector):一个字符串数组,每个元素是一个参数的值。
4.2 main 函数的形式
int main(int argc, char *argv[])
argc:表示参数的个数,包括程序本身的名称。
如果程序运行时没有额外参数,则 argc 值为 1。
argv:一个指针数组,存储每个参数的字符串。
argv[0]:始终是程序的名称(包含路径)。
argv[1] 到 argv[argc-1]:表示传递给程序的实际参数。
int main(int argc, char* argv[])
{
    int i = 0;
    for(; i < argc; i++)
    {
        printf("argv[%d]->%s\n",i, argv[i]);
    }
    return 0;
}
运行示例:
命令行参数通过以下方式传递给程序:
当我们在命令行输入 ./mycode -a -b -c 时,bash 会将这整行输入当作一个字符串。
然后,bash 使用空格作为分隔符,将字符串拆分成单独的部分。
拆分后的部分作为字符串,存储在一个字符串数组中,这个数组的首地址传递给 argv,其大小传递给 argc。
argv 是一个指针数组,其最后一个元素会自动被设置为 NULL,用来标识参数结束。这种设计的好处是方便程序在处理参数时快速检测结束标志,而不用依赖 argc。
4.3 命令行参数的作用
命令行参数的一个重要作用是为程序、指令或工具提供灵活的选项支持,以便实现不同的功能或行为。
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{
    if (argc != 2) {  // 参数数量校验
        printf("./mycode [-a|-b|-c|-d]\n");
        return 0;
    }
    if (strcmp(argv[1], "-a") == 0) {  // 判断第一个参数是否为 "-a"
        printf("功能1\n");
    } else if (strcmp(argv[1], "-b") == 0) {
        printf("功能2\n");
    } else if (strcmp(argv[1], "-c") == 0) {
        printf("功能3\n");
    } else if (strcmp(argv[1], "-d") == 0) {
        printf("功能4\n");
    } else {
        printf("功能5\n");
    }
    return 0;
}
运行示例:
命令行参数有一个重要的作用,它可以为指令、工具、软件等提供命令行选项的支持。
4.4 main函数的第三个参数
除了上面提到的 argc 和 argv 两个参数,main 函数还有第三个参数 env,它也是一个指针数组,存放的是该进程的环境变量。
#include <stdio.h>
int main(int argc, char* argv[], char* env[])
{
    int i = 0;
    for (; env[i]; i++) {
        printf("env[%d] -> %s\n", i, env[i]);
    }
    return 0;
}
在 C/C++ 中,程序通常会使用两张核心向量表:
命令行参数表(argc 和 argv)
用于接收用户通过命令行传递的参数。
提供了程序运行时的输入灵活性和动态控制能力。
环境变量表(env)
用于存储操作系统中的全局配置信息。
包含用户信息、语言设置、路径变量等,且由父进程传递给子进程。
运行指令与程序的关系:
命令行参数:
每个命令行指令都会拆解为多个部分,分别存储在 argv 数组中。
程序运行时可以通过 argv 和 argc 获取这些参数,并根据参数执行不同的功能。
环境变量继承:
当我们在 bash 中运行程序时,bash 作为父进程会将其环境变量传递给程序(子进程)。
这使得程序可以通过读取环境变量动态适应运行环境。
4.5 环境变量的继承
验证子进程继承环境变量
设置环境变量 在 Bash 中执行以下命令:
export MY_VALUE=12345678
通过 export 将本地变量转化为环境变量。
然后再查看当前的环境变量:
下面执行子进程 ./mycmd,可以发现它里面也有 MY_VALUE 这个环境变量,说明子进程 mycmd 继承了父进程 bash 的环境变量。
正是因为子进程可以继承父进程的环境变量,所以我们在 bash 输入的所有指令都要遵守权限,因为输入的所有指令都可以看做是 bash 的子进程,都继承了 bash 的环境变量。通过下面这条指令可以删除环境变量。
unset MY_VALUE
五、本地变量与内建命令
本地变量:就是在命令行中直接定义的变量。
本地变量是通过 bash 命令行直接定义的,仅在当前 bash 会话中有效。
本地变量不能被子进程继承。
环境变量:
环境变量是可以被子进程继承的变量。
通过 export 将本地变量转化为环境变量。
查看所有变量:使用 set 查看:
set
5.1 为什么 echo 能打印本地变量
我们可以通过 echo 指令打印出本地变量,之前说过 echo 作为指令,本质上也是一个可执行程序,既然是可执行程序,那就会创建进程,但是我们又说了子进程是无法继承父进程的本地变量,那为什么 echo 可以打印出父进程 bash 的本地变量呢?
本地变量的作用域:仅在当前 bash 会话中有效。
echo 是内建命令:直接由 bash 执行,不创建子进程,因此可以访问 bash 的本地变量。
内建命令与常规命令
bash 中的指令可以分为两类:
常规命令:
通过创建子进程执行的外部程序。
示例:
ls:使用 fork 创建子进程,然后执行 execve 加载外部程序。
内建命令:
由 bash 自身直接实现,不会创建子进程。
示例:
echo、cd、export、set 等。
echo 就是一个内建命令,执行 echo 命令的时候并不会创建子进程.
同样的,cd 也是一个内建命令,用于切换当前工作目录。其本质是调用系统接口 chdir(),直接改变当前进程的工作目录。
除了上面提到的两种通过代码获取环境变量的方法(命令行第三个参数、getenv系统接口)外,还可以通过全局变量 environ获取
libc 提供的全局变量 environ,指向环境变量表。
需要 extern 显式声明后使用:
#include <stdio.h>
int main(int argc, char *argv[])
{
extern char **environ;
int i = 0;
for(; environ[i]; i++)
{
printf("%s\n", environ[i]);
}
return 0;
}
结语
通过对进程优先级和环境变量的深入学习,我们可以发现,它们在 Linux 系统中扮演着不可或缺的角色。进程优先级确保了多任务系统中资源的公平竞争和高效利用,而环境变量为系统和用户程序的交互提供了灵活性和动态配置的能力。掌握它们不仅能帮助我们优化系统性能,还能提升在日常开发和运维中的效率。希望通过本篇文章,能够让您对 Linux 系统的运行机制有更深入的认识,助力您的学习与实践。
————————————————
                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/suye050331/article/details/144250600
阅读剩余
THE END