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

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

我最近在学进程调度,越看越觉得内核里有些设计是真的巧妙。比如怎么把结构体和链表串起来,又比如那个 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 小时前
【Python量化波动率实战 01】波动率是怎么算的:年化、滚动与区间估计一次讲清
后端
特立独行的猫A1 小时前
AI 时代,我为什么推荐用 cj-tauri 开发桌面应用
java·后端·python
金銀銅鐵1 小时前
从一个类名到一张类图:用反射自动生成 PlantUML
java·后端·uml
追逐时光者2 小时前
一款基于 .NET 开源、免费的数据库自治诊断平台
后端·.net
追逐时光者2 小时前
让 GitHub Copilot 更懂你的 .NET 代码风格:一份精简实用的 Copilot Instructions
后端·.net
小满zs2 小时前
Go语言第十八章(架构设计)
后端·go
山甫aa3 小时前
锁一篇搞定:乐观锁、悲观锁、CAS、数据库锁与分布式锁【从零开始的 Web 后端学习】
java·数据库·分布式·后端·学习·web
IT_陈寒3 小时前
Java开发实战:从入门到精通
前端·人工智能·后端
jktantan3 小时前
注解式脱敏:用 Jackson 的 ContextualSerializer 做字段级策略(一)
spring boot·后端·算法·spring cloud·系统架构
独泪了无痕12 小时前
探索Pinyin4j-Java中的拼音转换利器
java·后端