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

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

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

最后

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

相关推荐
猿究院_夏燊5 小时前
Spring Bean 生命周期
java·后端·spring
CCblandy6 小时前
雪花 ID 四个边界实测:时钟回拨重号 500 个,前端 JSON.parse 直接丢精度
后端·python·算法·性能优化·架构
我是人✓6 小时前
SpringBoot项目的五种创建方式
java·spring boot·后端
念念清晰8 小时前
000-OpenJDK17 源码构建:MacOS环境(M芯片)
java·后端
来福猿8 小时前
SpringCloud Config 源码分析:配置拉取、刷新与多环境管理
后端·spring·spring cloud
IT_陈寒10 小时前
React组件莫名重渲染?你可能忘了这个小细节
前端·人工智能·后端
用户98883218717411 小时前
给 .NET 网络调用设定“预算”:用 CancellationTokenSource.CancelAfter 处理超时取消
后端
清风儿32911 小时前
一块不会说话的电表,把我们折腾了半个月
后端
预知同行11 小时前
深入解析 MoE 混合专家模型:从 Router 到 All-to-All 的架构全景与生产实践
后端·架构
upala11 小时前
【仓颉语言入门 · 第二篇 第 8 课】
后端