目录
[1. 进程切换](#1. 进程切换)
[1.1 如何切换](#1.1 如何切换)
[1.1.1 如何理解切换?](#1.1.1 如何理解切换?)
[1.1.2 数据保存位置](#1.1.2 数据保存位置)
[1.2 Linux2.6内核进程O(1)调度队列](#1.2 Linux2.6内核进程O(1)调度队列)
[1.2.1 运行队列(runqueue)](#1.2.1 运行队列(runqueue))
[1.2.2 O(1)进程调度算法核心逻辑](#1.2.2 O(1)进程调度算法核心逻辑)
[1.2.3 题外话(lock)](#1.2.3 题外话(lock))
[2. 环境变量](#2. 环境变量)
[2.1 基本概念](#2.1 基本概念)
[2.2 常见环境变量](#2.2 常见环境变量)
[2.3 查看环境变量方法](#2.3 查看环境变量方法)
[2.4 和环境变量相关的命令](#2.4 和环境变量相关的命令)
[2.5 环境变量的组织方式](#2.5 环境变量的组织方式)
[2.6 命令行参数](#2.6 命令行参数)
[2.6.1 main函数有参数吗?](#2.6.1 main函数有参数吗?)
[2.7 PATH环境变量](#2.7 PATH环境变量)
[2.8 如何理解环境变量?](#2.8 如何理解环境变量?)
[2.9 获取环境变量的方法](#2.9 获取环境变量的方法)
[2.10 本地变量和和环境变量](#2.10 本地变量和和环境变量)
[2.10.1 内建命令 buil-in command](#2.10.1 内建命令 buil-in command)
[2.10.2 创建、删除和查看本地变量](#2.10.2 创建、删除和查看本地变量)
1. 进程切换
CPU上下⽂切换:其实际含义是任务切换,或者CPU寄存器切换。当多任务内核决定运⾏另外的任务时,它保存正在运⾏任务的当前状态,也就是CPU寄存器中的全部内容。这些内容被保存在任务⾃⼰的 堆栈中,⼊栈⼯作完成后就把下⼀个将要运⾏的任务的当前状况从该任务的栈中重新装⼊CPU寄存器, 并开始下⼀个任务的运⾏,这⼀过程就是context switch。
1.死循环进程如何运行?
当一个进程获得CPU使用权时,它不会独占CPU直到完成所有任务,因为系统采用了时间片轮转机制。即使是死循环进程,也不会导致系统崩溃或长期独占CPU资源。
(时间片:当代计算机都是分时操作系统,没有进程都有它合适的时间片(其实就是⼀个计数 器)。时间⽚到达,进程就被操作系统从CPU中剥离下来。)
2.寄存器就是CPU内部的临时空间,寄存器!=寄存器里面的数据

1.1 如何切换
1.1.1 如何理解切换?
想象你正在上大学时选择参军入伍。离开校园相当于进程被暂时从CPU上移除,但退伍后你需要返校继续学业。为此,学校会在你离校期间保留学籍,待你返校时再恢复学籍记录。
进程切换遵循同样的原理:
- 当进程A被暂时切换时,它必须保存自己的运行状态(上下文数据)
- 这样在重新获得CPU资源时,就能准确恢复之前的执行状态,继续后续操作
进程切换的核心本质就是:保存和恢复当前进程的硬件上下文数据,具体来说就是CPU寄存器的内容。这是确保进程能够无缝衔接继续运行的关键所在。


1.1.2 数据保存位置
当进程离开CPU时,需要将其硬件上下文数据保存起来。这些数据会被存储在task_struct(内核数据结构)中,具体来说就是保存在TSS(任务状态段)里。

1.2 Linux2.6内核进程O(1)调度队列

1.2.1 运行队列(runqueue)
1.⼀个CPU拥有⼀个运行队列(runqueue),如果有多个CPU就要考虑进程个数的负载均衡问题。
2.优先级
- 普通优先级:100〜139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!)
- 实时优先级:0〜99(不关⼼)
3.活动队列
- 时间⽚还没有结束的所有进程都按照优先级放在该队列
- nr_active:总共有多少个运⾏状态的进程
- queue[140]:⼀个元素就是⼀个进程队列,相同优先级的进程按照FIFO规则进⾏排队调度,所以, 数组下标就是优先级!
从该结构中,选择⼀个最合适的进程,过程是怎么的呢?
- 从0下表开始遍历queue[140]
- 找到第⼀个⾮空队列,该队列必定为优先级最⾼的队列
- 拿到选中队列的第⼀个进程,开始运⾏,调度完成!
- 遍历queue[140]时间复杂度是常数!但还是太低效了!
- bitmap[5]:⼀共140个优先级,⼀共140个进程队列,为了提⾼查找⾮空队列的效率,就可以⽤ 5*32个⽐特位表⽰队列是否为空,这样,便可以提⾼查找效率!
bitmap:采用位图结构实现,具有以下特性:
- 由 5 个 unsigned int 类型元素组成,总计 160 位
- 支持 140 个优先级的管理(160 位足够覆盖)
- 每位对应一个优先级,1 表示该优先级存在进程,0 表示不存在
- 通过位图扫描可快速定位存在进程的优先级区间
- 发现有效区间后,再对该区间进行详细遍历

4.过期队列
- 过期队列和活动队列结构⼀模⼀样
- 过期队列上放置的进程,都是时间⽚耗尽的进程
- 当活动队列上的进程都被处理完毕之后,对过期队列的进程进⾏时间⽚重新计算
5.active指针和expired指针
- active指针永远指向活动队列
- expired指针永远指向过期队列
- 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间⽚到期时⼀直 都存在的。
- 没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了⼀批 新的活动进程!
- swap(&active,&expired)
总结:在系统当中查找⼀个最合适调度的进程的时间复杂度是⼀个常数,不随着进程增多⽽导致时间成 本增加,我们称之为进程调度O(1)算法!
1.2.2 O(1)进程调度算法核心逻辑
- 通过runqueue的active指针检查是否存在可运行进程
- 利用bitmap位图快速定位第一个有运行进程的优先级,并执行该进程
- 当进程时间片耗尽但未完成时,将其移至过期队列对应的优先级位置
- 当活跃进程数为零时,交换active和expired指针(swap(&active,&expired))
过期进程存在的意义:过期进程队列用于O (1) 调度器,存放时间片耗尽的进程。与活跃队列分开,调度只选活跃队列;空时直接交换指针,不遍历、不拷贝,保证调度效率恒定 O (1)。

1.2.3 题外话(lock)
调度器正在遍历 bitmap,已经扫过前面的优先级了,这时如果有高优先级进程抢占插入到「已经遍历过的位置」,会不会导致遍历漏掉这个进程?
想要抢占插入队列 + 修改 bitmap,必须先拿到 runqueue lock。
如果调度器已经在遍历 bitmap,它正持有 lock,插入操作会阻塞等待。
只有等遍历完成、锁释放后,插入才能成功。
下一次调度遍历从头开始,自然能看到新插入的进程。
一句话:lock 让「遍历」和「插入」完全串行,不会并发,因此永远不会漏进程。
2. 环境变量
2.1 基本概念
- 环境变量(environment variables)⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数
- 如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪 ⾥,但是照样可以链接成功,⽣成可执⾏程序,原因就是有相关环境变量帮助编译器进⾏查找。
- 环境变量通常具有某些特殊⽤途,还有在系统当中通常具有全局特性
2.2 常见环境变量
- PATH:指定命令的搜索路径
- HOME:指定⽤⼾的主⼯作⽬录(即⽤⼾登陆到Linux系统中时,默认的⽬录)
- SHELL:当前Shell,它的值通常是/bin/bash。

2.3 查看环境变量方法
- echo $环境变量名称:查看指定环境变量
- env指令:可以查看所有环境变量
2.4 和环境变量相关的命令
- echo: 显⽰某个环境变量值
- export: 设置⼀个新的环境变量
- env: 显⽰所有环境变量
- unset: 清除环境变量
- set: 显⽰本地定义的shell变量和环境变量
2.5 环境变量的组织方式
每个程序都会收到⼀张环境表,环境表是⼀个字符指针数组,每个指针指向⼀个以'\0'结尾的环境 字符串

2.6 命令行参数
2.6.1 main函数有参数吗?
有的,argc、argv和env
argc、argv
1.argc(argument count,int 类型):代表命令行参数的个数。
即使你运行程序时不传入任何参数,argc 的值也至少是 1(第一个参数默认是程序本身的路径 / 名称,如./code)。
2.argv(argument vector,char[] 或 char* 类型):是一个字符串数组,存储每个命令行参数的具体内容。
argv[0]:固定存储程序自身的名称 / 路径(系统自动填充);
argv[1]及后续元素:存储用户运行程序时手动传入的命令行参数(字符串形式);
程序通过读取这些元素,实现运行时的动态逻辑控制(如指定文件、配置参数等)。
(进程拥有一张表,argv表,用来支持实现选项功能!)

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

- main的命令行参数,是实现程序不同子功能的方法!!
- 这也是指令选项的实现原理!

env
main函数还有第三个参数------env
- char *env[]是 main 函数可选第三个参数,用于获取系统环境变量列表;
- 格式为"变量名=值"的字符串数组,非标准但编译器普遍支持;
- 标准推荐用getenv()读取指定环境变量,比直接用env参数更通用。
cpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
// 用getenv()读取指定环境变量
printf("%s\n", getenv("PATH"));
return 0;
}
cpp
// 命令⾏第三个参数
#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;
}
// 通过第三⽅变量environ获取
#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;
}

2.7 PATH环境变量
要执行一个程序,必须先找到它!!------------谁找?bash,通过环境变量PATH来找。
系统中存在环境变量,来帮助系统找到目标二进制文件!
环境变量:PATH------------>系统中搜索指令的默认搜索路径

要执行一个程序,必须知道他的路径,如果输入的不是路径,则会去默认的搜索路径里进行寻找

如果我们执行这个名为environ的程序的时候不想给定路径,我们可以通过修改环境变量PATH实现
(export指令:把变量变成环境变量,让子进程也能读到)

2.8 如何理解环境变量?
理解环境变量的存储机制:
- 从存储角度看,bash通过环境变量表来管理环境变量
- bash内部维护着两张关键表:
- 环境变量表
- 命令行参数表

2.9 获取环境变量的方法
环境变量通常用于特定用途,且在系统中具有全局性。
父进程的环境变量可被子进程继承。
获取方式:
- 通过
main(argc, argv, env)获取父进程(如bash)的环境变量 - 使用
getenv("环境变量名")获取指定环境变量 - 通过
extern char** environ访问环境变量表(environ指向该表)

2.10 本地变量和和环境变量
bash会记录两套变量:1.环境变量 2.本地变量
本地变量不会被子进程继承,只会在bash内部被使用
环境变量具有全局特性,子进程会继承父进程(bash)的环境变量

2.10.1 内建命令 buil-in command
进程是具有独立性的,子进程可以继承父进程(bash)的环境变量,子进程无法向父进程传递数据。但export命令却能将环境变量传递给父进程bash,这是如何实现的?
关键在于export是一个内建命令。它不需要创建子进程,而是由bash直接执行内部函数或系统调用来完成操作。
2.10.2 创建、删除和查看本地变量
创建变量: 变量名="值内容"
删除变量: unset 变量名
查看变量: set | grep 变量名