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

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

我最近在学进程调度,越看越觉得内核里有些设计是真的巧妙。比如怎么把结构体和链表串起来,又比如那个 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,可以作为遍历的结束标志。

最后

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

相关推荐
Wx-bishekaifayuan3 分钟前
springboot甘肃特产服务平台50301-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
Thneonl5 分钟前
服务不通先别抓包:DNS 解析器的坑比内核网络栈多
后端·程序员
Thneonl5 分钟前
一启动就 Exited(137):内存限制背后藏了四个参数
后端·架构
斯维赤12 分钟前
从 @Tool 到 Agent 流水线:LangChain4j 进阶教学,一个库打全套
java·后端
对象存储与RustFS14 分钟前
RustFS 生产部署实战:Docker / 二进制 / K8s Operator 三条路径怎么选
后端
知守观21 分钟前
Spring Boot 2.1.0 停维护5年,10个依赖4个有CVE——2022年老项目的安全体检报告
spring boot·后端
SimonKing24 分钟前
SSE项目`nexus-sse`持续优化,不一样的视觉效果
java·后端·程序员
斑鸠喳喳24 分钟前
读写锁模式 Read-Write Lock
java·后端
Thneonl29 分钟前
etcd 磁盘写满的那 6 分钟:控制面是怎么一步步瘫的
后端·架构
Tim00729 分钟前
deepseek harness 导出公司报表实战
后端