命令行参数、环境变量、进程地址空间及 2.6 内核调度队列解读

news/2025/2/14 0:59:42/

目录

一、命令行参数环境变量探秘

1.1 命令行参数的本质作用

1.2 环境变量实战指南

🌵关键环境变量解析

🌵测试PATH:

🌵测试HOME:

🌵环境变量的组织方式:

🌵环境变量操作命令速查表

1.3 解析Linux环境变量继承机制

二、程序地址空间深度解析

2.1 虚拟地址空间示意图

2.2 虚拟地址实验分析

2.3 进程地址空间

三、Linux2.6内核进程调度队列

3.1 O(1)调度器核心结构

3.2 活动队列

3.3 过期队列

3.4 active指针和expired指针

3.5 调度流程解析


一、命令行参数环境变量探秘

1.1 命令行参数的本质作用

        命令行参数是在程序运行时传递给程序的选项,用于定制程序的功能。它们通过 main 函数的参数传递,通常包括 argc(参数数量)和 argv(参数数组)。例如,运行一个程序时,可以使用 ./program -a -b 传递参数 -a-b,程序可以根据这些参数执行不同的操作。

        在Linux系统中,命令行参数是程序启动时接收外部配置的核心机制。通过以下代码示例可以看到参数传递的完整过程:

#include <stdio.h>int main(int argc, char* argv[], char* env[]) 
{printf("参数个数: %d\n", argc);for(int i=0; argv[i]; i++){printf("argv[%d]: %s\n", i, argv[i]);}return 0;
}

当执行./demo -a -b hello时输出:

设计原理

  • 参数个数自动计算(argc)

  • 参数指针数组连续存储(argv)

  • 环境变量指针数组结尾以NULL标识


1.2 环境变量实战指南

🌵关键环境变量解析

        环境变量是操作系统用来存储和传递系统环境信息的一种机制,相当于全局变量,可供系统中的各个程序、进程在运行时访问和使用。常见的环境变量包括:

变量名功能说明示例值
PATH可执行文件搜索路径/usr/bin:/usr/local/bin
HOME用户主目录/home/user
SHELL默认shell程序路径/bin/bash

        环境变量可以通过 echo $NAME 查看,其中 NAME 环境变量的名称。例如,echo $PATH 可以显示当前的命令搜索路径。


🌵测试PATH:

1. 创建 hello.c 文件

#include <stdio.h>int main()
{printf("hello world!\n");return 0;
}

2. 对比 ./hello 执行和直接 hello 执行

gcc hello.c -o hello

生成可执行文件 hello

执行方式对比:

  • ./hello:直接运行当前目录下的可执行文件。

  • hello:尝试在 PATH 指定的路径中查找 hello 命令。

测试结果:

通常情况下,直接输入 hello 会报错,提示 command not found,而 ./hello 可以正常运行。

3. 为什么有些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行?

  • 系统命令路径已包含在 PATH 中:例如 lscd 等命令所在的路径(如 /bin/usr/bin)默认包含在 PATH 环境变量中。

  • 当前目录不在 PATH 中:系统默认不会在当前目录查找可执行文件,除非 PATH 包含 .(当前目录)。

4. 将程序所在路径加入 PATH(export PATH=$PATH:程序所在路径

5. 对比测试 


🌵测试HOME:

 1. 用 root 和普通用户分别执行 echo $HOME,对比差异

  • 切换到 root 用户

对比结果:

  • 普通用户HOME 通常指向 /home/username

  • root 用户HOME 通常指向 /root

2. 执行 cd ~; pwd,对应 ~ 和 HOME 的关系 

  • 在普通用户下执行 cd ~ 并查看当前目录

  •  在 root 用户下执行 cd ~ 并查看当前目录

解释:

  • ~ 是一个快捷符号,表示当前用户的主目录,即 HOME 环境变量所指向的目录。

  • cd ~ 等价于 cd $HOME,都会将用户切换到主目录。

  • pwd 命令用于显示当前工作目录的完整路径。

3. 总结

  • HOME 环境变量:用于存储当前用户的主目录路径。

  • ~ 符号:表示当前用户的主目录,等价于 $HOME

  • 普通用户与 root 用户的差异

    • 普通用户的主目录通常位于 /home/username

    • root 用户的主目录通常位于 /root


🌵环境变量的组织方式:

        每个程序运行时都会收到一张环境表,它是一个字符指针数组,数组中的每个指针都指向一个以 \0 结尾的环境字符串。通过 extern char **environ 可以访问环境变量表。例如,以下代码可以打印所有环境变量

#include <stdio.h>int main()
{extern char **environ;int i = 0;for (; environ[i]; i++) {printf("%s\n", environ[i]);}return 0;
}

libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。


🌵环境变量操作命令速查表

命令功能说明示例
env显示所有环境变量env | grep PATH
export设置/显示环境变量export MYVAR="test"
unset删除环境变量unset MYVAR
printenv显示指定环境变量printenv LANG

1.3 解析Linux环境变量继承机制

环境变量通常是具有全局属性的,这意味着它们不仅对当前 shell 有效,还可以被子进程继承。

1. 环境变量的继承机制

        当在父进程中通过 export 导出一个环境变量时,该变量会被传递给所有后续的子进程。这是因为在 Linux 系统中,进程在创建子进程时会复制自身的环境变量表,从而使子进程能够访问父进程中导出的环境变量。 例如,以下代码演示了环境变量的继承:

// 子进程
#include <stdio.h>
#include <stdlib.h>int main()
{char *env = getenv("MYENV");if (env){printf("%s\n", env); // 输出 "hello world"}return 0;
}

2. 普通变量与环境变量的区别 

普通变量(未导出的变量)仅在当前 shell 中有效,不会被子进程继承。例如:

// 子进程
#include <stdio.h>
#include <stdlib.h>int main()
{char *env = getenv("MYENV");if (env){printf("%s\n", env); // 没有输出,因为 MYENV 未导出}return 0;
}

3. 导出和不导出环境变量的实验 

MYENV="helloworld"
./program

子进程无法访问 MYENV,因此 getenv("MYENV") 返回 NULL。 

export MYENV="helloworld"
./program

 子进程可以访问 MYENV,因此 getenv("MYENV") 返回 "helloworld"

结论:

  • 导出环境变量:子进程可以继承。

  • 普通变量(未导出):子进程无法继承。

4. 原因分析

  • 导出环境变量export 将变量标记为环境变量,使其在父进程及其子进程中都可用。

  • 普通变量:仅在当前 shell 中有效,不会传递给子进程。

二、程序地址空间深度解析

2.1 虚拟地址空间示意图

2.2 虚拟地址实验分析

通过父子进程地址实验揭示虚拟内存机制:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_val = 0;
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 0;}else if(id == 0){ //childprintf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{ //parentprintf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}

输出:

我们发现,输出出来的变量值和地址是一模一样的,很好理解呀,因为子进程按照父进程为模版,父子并没有对变量进行进行任何修改。可是将代码稍加改动:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_val = 0;
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 0;}else if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取g_val=100;printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{ //parentsleep(3);printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}

输出:

我们发现,父子进程,输出地址是一致的,但是变量内容却不一样!能得出如下结论:

  • 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量。
  • 但地址值是一样的,说明,该地址绝对不是物理地址。
  • 在Linux地址下,这种地址叫做 虚拟地址
  • 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理。

OS必须负责将 虚拟地址 转化成 物理地址

2.3 进程地址空间

        所以之前说‘程序的地址空间’是不准确的,准确的应该说成 “进程地址空间”,那该如何理解呢?看图:

地址转换流程

虚拟地址 -> MMU -> 页表查询 -> 物理地址

        上面这张图从分页与虚拟地址空间概念入手,生动展示了同一变量在不同情境下虽地址看似相同(实为虚拟地址一致),但内容却迥异,关键在于它们被映射至了不同物理地址。通俗来讲,恰似一张“虚拟蓝图”,它让诸多人(进程)以为身处同一地图坐标(虚拟地址),但彼此所指实地点(物理地址)却大相径庭,皆因背后有一套转换体系(分页机制)在运筹帷幄,各司其职,巧妙分配。


三、Linux2.6内核进程调度队列

3.1 O(1)调度器核心结构

Linux2.6内核中进程队列的数据结构:

关键组件说明

  1. 活动队列(active):存放时间片未耗尽的进程。

  2. 过期队列(expired):存放时间片已用完的进程。

  3. 优先级数组(140级):0-99实时优先级(不关心),100-139普通优先级(nice值的取值范围,可与之对应)。

  4. 位图索引(bitmap[5]):快速定位非空队列。

3.2 活动队列

1. 时间片还没有结束的所有进程都按照优先级放在该队列nr_active: 总共有多少个运行状态的进程。

2. queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!

3. 从该结构中,选择一个最合适的进程,过程是怎么的呢?

  • 从0下表开始遍历queue[140]
  • 找到第一个非空队列,该队列必定为优先级最高的队列
  • 拿到选中队列的第一个进程,开始运行,调度完成!
  • 遍历queue[140]时间复杂度是常数!但还是太低效了!

4. bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!

3.3 过期队列

  • 过期队列和活动队列结构一模一样。
  • 过期队列上放置的进程,都是时间片耗尽的进程。
  • 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算。

3.4 active指针和expired指针

  • active指针永远指向活动队列。
  • expired指针永远指向过期队列。
  • 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。
  • 没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!

3.5 调度流程解析

  1. 从最高优先级(数组索引0)开始扫描

  2. 通过bitmap快速定位第一个非空队列

  3. 取出队列首进程分配CPU时间片

  4. 时间片耗尽后移入过期队列

  5. 活动队列空时交换active与expired指针

性能优势

  • 时间复杂度恒定为O(1)

  • 优先级处理高效

  • 负载均衡优化

3.6 总结

        在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增加,我们称之为进程调度O(1)算法!


http://www.ppmy.cn/news/1571833.html

相关文章

新数据结构(7)——Object

Object类是所有类的父类&#xff0c;在 Java 中&#xff0c;每个类都直接或间接地继承自Object类&#xff0c;也就是说所有类都是object类的子类可以使用Object里的方法。 equals()和hashCode()是Java中Object类所包含的两个关键方法&#xff0c;下面将介绍两个方法。 和equa…

linux的三剑客和进程处理

Linux三剑客&#xff1a; grep&#xff1a;查找 sed&#xff1a;编辑 awk&#xff1a;分析 grep - 正则表达式 [rootlocalhost ~]# grep ^a hello.txt abc grep - 忽略大小写&#xff0c;还有一些场景需要查询出来对应字符串所在的行号&#xff0c;方便我们快速在文件中定位字…

联想电脑如何进入BIOS?

打开设置 下滑找到更新与安全 点击恢复和立即重新启动 选择疑难解答 选择UEFI固件设置 然后如果有重启点击重启 重启开机时一直点击FNF10进入BIOS界面

Spring Boot 中的事务管理:默认配置、失效场景及集中配置

Spring Boot 提供了强大的事务管理功能&#xff0c;基于 Spring 的 Transactional 注解。本文将详细介绍事务的默认配置、事务失效的常见场景、以及事务的几种集中配置方式&#xff0c;并给出相应的代码片段。 一、事务的默认配置 在 Spring Boot 中&#xff0c;默认情况下&am…

TCP/IP 协议图解 | TCP 协议详解 | IP 协议详解

注&#xff1a;本文为 “TCP/IP 协议” 相关文章合辑。 未整理去重。 TCP/IP 协议图解 退休的汤姆 于 2021-07-01 16:14:25 发布 TCP/IP 协议简介 TCP/IP 协议包含了一系列的协议&#xff0c;也叫 TCP/IP 协议族&#xff08;TCP/IP Protocol Suite&#xff0c;或 TCP/IP Pr…

安川伺服控制器MP系列优势特点及行业应用

在工业自动化领域&#xff0c;运动控制器的性能直接决定了设备的精度、效率和可靠性。作为全球领先的运动控制品牌&#xff0c;安川电机伺服控制器凭借其卓越的技术优势和广泛的应用场景&#xff0c;正在为智能制造注入强劲动力&#xff01; MP3100&#xff1a;主板型运动控制…

数据可视化:让数据讲故事的力量

数据可视化&#xff1a;让数据讲故事的力量 在大数据时代&#xff0c;数据无处不在。每天&#xff0c;我们都在生成和消耗大量的数据。但仅有数据本身并不足够&#xff0c;我们需要一种方式将这些数据转化为有意义的信息&#xff0c;这就是数据可视化的力量所在。通过数据可视…

AI前端开发:赋能开发者,提升解决实际问题的能力

近年来&#xff0c;人工智能技术飞速发展&#xff0c;深刻地改变着各行各业。在软件开发领域&#xff0c;AI写代码工具的出现更是引发了一场革命&#xff0c;尤其是前端开发领域&#xff0c;AI的应用正在显著提升开发者的解决实际问题的能力。本文将探讨AI前端开发如何提升效率…