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

server/2025/2/13 12:46:59/

目录

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

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/server/167329.html

相关文章

SSH隧道+Nginx:绿色通道详解(SSH Tunnel+nginx: Green Channel Detailed Explanation)

SSH隧道Nginx&#xff1a;内网资源访问的绿色通道 问题背景 模拟生产环境&#xff0c;使用两层Nginx做反向代理&#xff0c;请求公网IP来访问内网服务器的网站。通过ssh隧道反向代理来实现&#xff0c;重点分析一下nginx反代的基础配置。 实验环境 1、启动内网服务器的tomca…

分布式 IO 模块:港口控制主柜的智能 “助手”

在繁忙的港口&#xff0c;每一个集装箱的装卸、每一艘货轮的停靠与离港&#xff0c;都离不开高效精准的控制系统。港口控制主柜作为整个港口作业的核心枢纽之一&#xff0c;其稳定运行至关重要。而明达技术自主研发推出的MR30分布式 IO 模块可作为从站&#xff0c;与 PLC&#…

elementuiPlus日期范围选择el-date-picker动态禁用时间选择

记录项目中的一个小需求&#xff1a;使用 elementuiPlus 日期选择组件时&#xff0c;需要动态禁用可选择的日期&#xff0c;禁止选中今天之后的日期&#xff0c;且选中的日期范围不饿能超过30天。 饿了么组件的 plus 版本去掉了v2版本的配置项 picker&#xff0c;改用 calenda…

【算法学习】DFS与BFS

目录 一&#xff0c;深度优先搜索 1&#xff0c;DFS 2&#xff0c;图的DFS遍历 (1)&#xff0c;递归实现&#xff08;隐士栈&#xff09; (2)&#xff0c;显示栈实现&#xff08;非递归&#xff09; 二&#xff0c;广度优先搜索 1&#xff0c;BFS 2&#xff0c;图的BF…

Hive的数据库操作和表操作

1、启动zookeeper以正确启动高可用集群 2、启动HiveServer2服务 3、连接HiveServer2服务 4、创建数据库hive_db 5、调整日志级别忽略INFO日志 6、查看数据库hive_db的属性信息 7、修改数据库hive_db的属性信息&#xff0c;并查看数据库hive_db的属性信息 8、删除数据库hive_db&…

dify.ai 怎么配置链接火山引擎等云厂商的deepseek模型

要将 dify.ai 配置链接到火山引擎等云厂商的 DeepSeek 模型. 申请火山引擎的key&#xff0c;创建endpoint 添加模型 测试模型

1.攻防世界 unserialize3(wakeup()魔术方法、反序列化工作原理)

进入题目页面如下 直接开审 <?php // 定义一个名为 xctf 的类 class xctf {// 声明一个公共属性 $flag&#xff0c;初始值为字符串 111public $flag 111;// 定义一个魔术方法 __wakeup()// 当对象被反序列化时&#xff0c;__wakeup() 方法会自动调用public function __wa…

本地部署【LLM-deepseek】大模型 ollama+deepseek/conda(python)+openwebui/docker+openwebui

通过ollama本地部署deepseek 总共两步 1.模型部署 2.[web页面] 参考官网 ollama:模型部署 https://ollama.com/ open-webui:web页面 https://github.com/open-webui/open-webui 设备参考 Mac M 芯片 windows未知 蒸馏模型版本:deepseek-r1:14b 运行情况macminim2 24256 本地…