最近在学进程调度和环境变量,记点心得

最近在学进程调度和环境变量,记点心得

我最近在学进程调度,越看越觉得内核里有些设计是真的巧妙。比如怎么把结构体和链表串起来,又比如那个 container_of,一开始我真的没看懂,后来踩了个坑、反复推了一遍,才彻底搞明白。今天就把这部分整理成一篇博客,顺便把命令行参数也一起记下来。

从一个问题说起:怎么从成员地址找到结构体地址?

先抛一个问题:假设你有一个结构体,你只知道其中某个成员的地址,怎么拿到整个结构体的起始地址?

先看代码:

c 复制代码
struct A{
    int A;
    int B;//当前已知地址
    double C;
}
//伪代码
#define container_of(ptr,type,member)({
	const typeof(((type*)0)->member) *__mptr=(ptr);
    (type*)((char*)__mptr-offsetof(type,member));
})

我第一次看 container_of 是有点懵的。关键思路其实是:算出 B 在结构体 A 里的偏移量 ,然后拿 B 的地址减去这个偏移量,就得到结构体首地址了。

为什么可以对 0 强转?因为如果你假设结构体从地址 0 开始,那么 &((struct A*)0->B) 取到的地址,本质上就等于 B 的偏移量。用这个偏移量去做减法,就能从成员地址反推出结构体地址,确实妙。

task_struct 和链表串起来

有了 container_of 这个"反查"能力,Linux 里的进程管理链表就好理解了。

我们可以在 task_struct 中定义一个链表节点结构体,里面放两个指针,分别指向其他进程的链表节点。这样多个进程结构体就能被串成一个链表,而且只要拿到了这个节点,就可以通过 container_of 访问到整个 task_struct 的其他成员。

这种设计的好处我觉得是拓展性特别强:链表管理只维护一份代码,进程如果需要同时存在于多个队列,只需要在结构体里定义多个链表节点就行。Linux 中确实会把所有进程放在一个大的双链表里统一管理,同时一个进程也可以出现在运行队列里。只要多几个节点,就能在多个队列中自由切换。

每个 CPU 都有自己的 runqueue

继续看进程调度。每个 CPU 都会有一个运行队列,对应结构体叫 struct runqueue

里面有一个 queue[140],这个数组下标本身代表优先级:

  • 0 ~ 99:实时进程(笔记里可以直接先不关心)
  • 100 ~ 139:普通进程用的队列

也就是说,优先级数字本质上是数组下标。选择进程的时候,其实是根据优先级做一个 hash 映射,找到对应下标的队列;一旦确定是哪个队列,后续的操作就有点像 FIFO 了。

比如你现在是分时操作系统,要相对公平地让每个进程都能拿到 CPU,不能老让一个进程霸占着。有了这样一个按优先级组织的队列结构,调度器可以按规则去挑选进程。

active 和 expired:两个队列交换

笔记里还提到了一个很有意思的机制:在 runqueue 里存在 prio_array_t 这样的结构,而且一般会有两个这样的结构体。一个叫 active,一个叫 expired,分别用指针指向哈希表下标为 0 和 1 的位置。

画出来大概是这样的:

活跃进程在 active 队列里运行,等它们的时间片用完了,就挪到 expired 队列。当 active 队列里没有可运行进程时,就把两个指针交换一下,expired 变成新的 active。这样来保证所有进程都有机会被调度到,也是分时系统里一种很直观的公平策略。

命令行参数:main 函数不简单

学完进程调度,再看命令行参数,感觉轻松很多。

以前写 C 语言都是直接 int main(),但其实 main 是能带参数的:

c 复制代码
       #include<stdio.h>
       
       int main(int argc,char *argv[]){
           int i=0;
           for(;i<argc;i++){
               printf("argv[%d]->%s\n",i,argv[i]);
           }
           return 0;                                                   
       }                

运行之后会打印出所有命令行参数。argc 是参数个数,argv 是一个字符串数组,每个元素都指向一个参数。所谓"命令行参数",本质上就是让你在启动程序的时候,通过不同选项去控制程序执行不同的功能。

这里有几个细节容易漏:

  • 细节1:命令行参数最少是 1,也就是 argc >= 1,因为 argv[0] 一定会有内容,它指向程序本身的名字。
  • 细节2:选项之间以空格分隔,就算只有一个字符,它也是一个完整的字符串。
  • 细节3:参数一共有 argc 个,argv[argc-1] 是最后一个参数,而 argv[argc] 一定是 NULL,可以作为遍历的结束标志。

最后

这次主要记录的是进程调度里"链式结构 + 队列交换"的思路,以及命令行参数的基本用法。标题里的"环境变量"其实还没整理完,等我把环境变量和命令行参数的关系啃透了,再继续写一篇补充。进程调度这块虽然第一次看有点绕,但拆开来看,也就是结构体、链表、数组和指针的组合应用,搞懂之后是真的爽。

相关推荐
分支预测失败1 小时前
RISC-V 核间中断 IPI 实战:从 MSIP 寄存器到 IMSIC 消息投递
linux·后端
用户850869276151 小时前
Rust系统编程在任务调度中的踩坑与优化之路
后端
LiaCode2 小时前
别让 AI 把仓库写成“代码平行宇宙”:从 Deslop 看生成前查重
前端·后端·github
啵啵啵鱼2 小时前
DS-顺序表
java·数据结构·笔记·后端·链表·obsidian
回家吃饭去吧2 小时前
AI Agent Function Calling / Tool Use 深度解析
后端
大黄评测2 小时前
Oracle 锁等待、会话阻塞故障复盘:生产事故排查步骤
后端
大勇前进3 小时前
Oracle 慢 SQL 优化避坑:索引建了为什么依然走全表扫描
后端
良在掘金542233 小时前
匹配服排行榜位置原子交换与锁粒度设计
后端
用户233376852183 小时前
Docker部署Kafka排障实录
后端