最近在学进程调度和环境变量,记点心得
我最近在学进程调度,越看越觉得内核里有些设计是真的巧妙。比如怎么把结构体和链表串起来,又比如那个 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,可以作为遍历的结束标志。
最后
这次主要记录的是进程调度里"链式结构 + 队列交换"的思路,以及命令行参数的基本用法。标题里的"环境变量"其实还没整理完,等我把环境变量和命令行参数的关系啃透了,再继续写一篇补充。进程调度这块虽然第一次看有点绕,但拆开来看,也就是结构体、链表、数组和指针的组合应用,搞懂之后是真的爽。