知行笔记

2026-10-25 · 每周学习总结(视频转ai总结)

2026-10-25 · 复盘总结

# 整体评价 这是一份**以自己写的简易内核代码(trap/中断、PCB任务块)为载体,复盘C语言基础**的分享稿,思路非常好:不是孤立背语法,而是拿着工程代码去理解C,非常贴合你做内核、操作系统开发的路线。 优点: 1. 知道区分全局/局部、自动/静态局部变量、函数返回值、头文件声明、结构体、指针、链表,能联系到内核里的 `trap_handler`、PCB; 2. 自己主动画图理解指针(箭头模型),抓准了指针“存地址、间接访问”的核心; 3. 意识到很多地方自己还模糊、留了后续学习空间,不是强行装懂。 下面分两块:**精简版整理稿(可以直接用来做周末分享PPT/讲稿) + 逐条纠错 + 知识点思辨补充**。 > 说明:原文里大量口误拼写:`onside the long` = `unsigned long`;`trop` = `trap`;`Stick` = `static`;`Alt` 是你自己的命名习惯标记;`chop` 大概率也是 `trap` 口误。下面统一修正术语。 --- ## 一、整理后的通顺总结(可直接拿去讲) 这个周末我复盘这周学的C语言。之前发现C基础薄弱,这周抽时间补,拿我写的内核trap相关代码做例子,从 `trap.c / trap.h` 的中断处理函数切入,梳理C基础。 1. **函数结构:返回类型、函数名、形参列表** 以 `unsigned long trap_handler(unsigned long frame_sp)` 举例: - `unsigned long`:返回值类型;括号里 `unsigned long frame_sp` 是形参; - 类型要匹配:返回值类型和最后return出来的值类型要一致;无返回就写 `void`; - `unsigned long` 是无符号长整型,它占用字节由平台ABI决定(RISC-V下一般是8字节),用来存地址、寄存器值,内核里大量用。 2. **变量分类:全局变量、局部变量(自动auto / static静态局部)** - 全局变量:定义在函数外面,默认初始值为0,作用域整个文件,习惯命名加g_区分; - 自动局部变量(默认,auto关键字省略):定义在函数内部,**没有默认初值,是随机垃圾值**,必须手动初始化;函数调用时在栈上分配,函数退出自动销毁; - static静态局部变量:同样定义在函数内部,但不在栈上、放在全局数据区;**只在第一次进入函数初始化一次,之后多次调用函数,值会保留,不会随函数退出销毁;默认初始值0**。 > 我自己的习惯:自动局部变量加标记区分,方便读代码。 3. **函数声明、头文件、跨文件调用** 函数要在`.h`头文件里声明,`.c`里实现;别的文件include头文件之后,才能调用这个函数,编译器提前知道函数签名(返回类型、参数类型),保证调用类型匹配。 比如 `trap_init` 是void无返回函数,在内核里做中断初始化,里面会操作指针、把入口地址绑定到全局的trap入口变量。 4. **数组、结构体(内核PCB核心)** - 数组:同类型变量连续存放,先写类型,数组名,方括号长度;本次暂时没有深度用到; - 结构体 `struct`:把一堆不同类型的成员打包在一起,非常适合描述内核控制块,典型就是PCB任务控制块 `struct task`:里面存task id、状态、程序计数器pc、栈指针sp、上下文寄存器等。 - 我们可以定义多个结构体实例task_a、task_b,分别代表不同任务,统一管理。 5. **指针(本次难点)** 指针本身也是变量,专门存**内存地址**,指针必须带类型:`int *p` 代表p是一个指向int变量的指针。 - `&x`:取地址符,拿到变量x的地址;`p = &x`:让指针p指向x; - `*p`:解引用,顺着p里面存的地址,去读写那个地址上的值; > 形象理解:p里面放的是盒子x的门牌号;`p`是门牌号;`*p`是打开门牌号对应的盒子,拿里面的值。 6. **结构体访问符号 . 和 ->** - 结构体**实体(不是指针)**:用 `.` 访问成员,如 `task.id` - 结构体**指针**:用 `->` 访问成员,如 `ptask->id` 7. **链表** 链表就是结构体 + 结构体指针:结构体里放数据 + 一个同结构体类型的next指针,用指针把一个个节点串起来,内核调度队列大量用链表。 --- ## 二、原文里错误/不准确的点(重点,按顺序) > 标注:❌错误 / ⚠️理解有偏差 1. > `unsigned long` 其实是 unsigned long int ✅这句本身语法上对,C里`unsigned long`等价`unsigned long int`; ⚠️但是:**不是所有平台字节都不一样**。RISC-V 64位平台 `unsigned long` 固定8字节;32位RISC-V是4字节;它是由**架构+ABI**规定,不是随便变化。内核开发必须清楚当前目标平台,不能模糊理解。 2. > 静态static局部变量:不管怎么调用,它的值都跟初始化的值一样,不会变化 ❌ 这里是核心错误! static局部变量**只初始化一次**,不是值固定不变! ```c void test(){ static int cnt = 0; cnt++; printf("%d", cnt); } ``` 第一次调用输出1,第二次2,第三次3。 👉 关键点:初始化语句 `static int cnt=0` 只跑1次;代码里依然可以修改它的值;只是函数退出,变量不会销毁、值保留。 你原话理解成“值不能改”,是典型误区。 3. > 自动局部变量我们也喜欢在前面加Alt ⚠️ 这是你自己自定义标记,C没有alt关键字,只是个人命名习惯,没问题,但要分清:C关键字是`auto`,auto局部变量就是默认局部变量,平时都省略不写auto。 4. > `p = x` (你想写指针赋值) ❌ 正确写法:`p = &x`; `p = x` 是把x里面的数值直接赋值给p(指针变量),类型不匹配,编译报错。 `&` 是取地址运算符,这个符号不能丢,这是指针入门最容易写错的地方。 5. > `S = *p`,S就拿到x的值;printf输出p会输出10 ❌ - p存的是x的**内存地址**,printf直接打印p,出来的是一串地址数字,不是x的值; - `*p` 才代表x里面存放的数据(也就是10); ```c int x = 10; int *p = &x; int s; s = *p; // s = 10 ✅ printf("%p", p); // 打印x的地址 printf("%d", *p); // 打印10 ``` 6. > 指针就是一个箭头,用来把不同的值连接起来 ⚠️ 比喻可以这么记,但严谨定义:指针变量存**内存地址**;箭头只是你理解的模型,指针本身是内存里一个存地址的数值,不是什么抽象连线。 7. > 链表:struct里面放next形的指针 ⚠️ 严谨写法: ```c struct node { int data; struct node *next; // 指向同类型节点的指针 }; ``` 必须是 `struct node *next`,不能随便写next类型。 8. > 结构体指针用大于符号、普通变量用点 ❌ 口误:结构体指针访问成员运算符是 `->`,不是大于号 `>`。 - 结构体实例:`.` - 结构体指针:`->` 9. > 全局变量默认0,static局部变量默认0,auto局部无初值 ✅ 这一条你说对了,这个点很多人搞混,你抓对了! --- ## 三、知识点思辨板块(给你自己加深理解,分享的时候也可以加上) ### 1. 为什么内核里疯狂用 unsigned long? 内核到处操作寄存器、虚拟地址、物理地址,地址一定是非负数;signed long有符号位,最高位会被解释成负数,地址运算很容易溢出出错。所以地址、帧指针sp、寄存器值统一用无符号类型。 ### 2. 为什么区分auto / static局部变量?什么时候在内核里用static? - auto局部变量放栈:栈空间很小,函数退出自动回收,适合临时变量;中断、trap handler里大部分临时变量都是auto; - static局部放数据段:生命周期和整个程序一样。内核里尽量少用static局部,容易并发竞态;一般用来放一次性初始化、只读的东西。 ### 3. 头文件 .h 和实现 .c 的意义(你找trap_handler找不到的时候就是这个问题) 函数声明放.h,给别的文件看(告诉编译器长什么样);实现放.c; 你在main.c里include `trap.h`,编译器知道 `trap_handler` 签名;链接阶段才去trap.c找函数实体。 > 找不到函数两种情况:①头文件没写声明;②链接脚本没把对应.o编进去;③名字拼写错误(trap / chop) ### 4. 指针和数组的关系(后面你写链表、缓冲区会碰到) 数组名在很多场景下会退化成首元素指针;但数组不是指针:数组自带长度信息,分配方式不一样。 ### 5. PCB结构体为什么是操作系统的核心? 内核要同时跑多个任务,每个任务的寄存器、sp、id、状态打包进struct task,就是PCB;调度器就是在操作一堆PCB节点的链表。 ## 四、工具运用(Git/仓库角度,贴合你内核项目) 1. 写这类学习复盘,推荐在仓库里新建 `notes/c_catchup.md`,每次学完commit,把口播稿、错误点、代码示例一起存; 2. 遇到函数找不到的时候:用 `grep -r "trap_handler" .` 在仓库全局搜索符号,快速定位声明和定义; 3. 区分报错:编译报错=类型/声明不对;链接报错=找到声明、找不到函数实现。 --- ## 五、留给你后续要补的清单(你自己也意识到模糊的地方) 1. 不同架构下 `unsigned long` / uint64_t 的区别,stdint.h固定宽度类型(内核推荐用uint64_t,比unsigned long更稳定) 2. 指针运算(p++不是简单+1,是+sizeof(type)) 3. 结构体内存对齐 4. 链表的增删改查、内核list_head(Linux风格链表,后面你大概率会接触) 5. 函数指针(trap入口、中断向量表本质就是函数指针,刚好贴合你trap代码) 疑问 1:unsigned long,它的字节长度,是不是随计算机系统不一样?我这块不清楚 原话:unsigned long 根据你的计算机系统,好像有不同的字节常数,这个我不是很清楚。 ✅ 结论:是的,它是平台 ABI 相关,不是固定字节 RISC-V64 / x86_64:unsigned long = 8字节(64bit) RISC-V32 /x86 32 位:unsigned long = 4字节(32bit) 但不是 “随便变”,是架构 + ABI 规定死的,同一个平台下永远不变。 内核开发推荐:如果想要固定不变的宽度,用 <stdint.h> 里的 uint64_t、uint32_t;unsigned long 适合存放地址(地址宽度和它天然对齐)。 疑问 2:局部变量没初始化,如果直接使用,会发生什么?(你说:这个很蠢点,我得往下思考) 原话:局部变量我好像没进行初始化,我不知道这个点,我想一下。 ✅ 结论: auto 局部变量放在栈上,不会清零,里面是栈上残留的垃圾值(随机值)。 行为:C 标准里这属于未定义行为,程序时而正常、时而崩溃、算出奇怪数字; 内核里非常危险:中断 handler、trap 里面乱读未初始化局部变量,会随机炸内核。 对比记忆:全局 /static 变量在数据段,加载时会默认清零。 疑问 3:trap_init 里面 trap.entry、addr,哪个是全局、哪个是局部?这里没搞懂 原话:trap.entry 肯定是全局变量,这个 addr 肯定也是一个全局变量?对,不是,这我没搞懂。 简单判断规则: 看定义位置,不是看它被谁赋值: 在函数外面定义 → 全局; 在函数{}内部定义 → 局部。 举例子: c 运行 unsigned long trap_entry; // 在函数外面:全局变量 void trap_init(unsigned long addr) { // addr是形参,本质是局部变量 trap_entry = addr; } 👉 trap_entry:全局;addr:函数形参(属于局部 auto 变量) 你这里是把局部 addr 的值拷贝给全局 trap_entry,不是说赋值给谁,谁就和它同生命周期。 疑问 4:指针这块:printf直接打印 p,会不会输出 10?这里明显感觉自己错了 原话:printf 输出 p 等于?它 p 本来就一个地址,怎么能输出 10 呢,这个地方需要再理解。 c 运行 int x = 10; int *p = &x; p:指针变量,存的是 x 的内存地址,打印 p(% p)出来是一串地址数字,不是 10 *p:解引用,去 p 存的地址上拿数据,*p == 10,printf("%d", *p) 才会打印 10 一句话区分: 不带*:操作地址本身 带*:顺着地址,操作地址里面存的数据 疑问 5:结构体指针访问成员,到底是 -> 还是大于号?(口误混淆) 原话:如果里面是指针的话就是通过大于符号,如果是简单的变量的话就是点 ✅ 纠正:不是大于 >!是箭头运算符 -> 结构体实体(普通变量):. c 运行 struct task t; t.id = 1; 结构体指针:-> c 运行 struct task *pt = &t; pt->id = 2; 等价写法:(*pt).id = 2,-> 就是这个的简写。 疑问 6:函数调用、头文件、找不到 trap_handler 的疑惑:为什么.h 里声明了,在很多文件里翻找不到实现? 原话:在.h 里面看到接口声明,但是到处找找不到函数实现。 两个核心原因: 声明和实现分离:.h 只告诉编译器 “这个函数长这样”,代码实现在另一个.c; 搜索的时候名字写错(你一直口误 chop /trap); 工程查找命令(内核项目非常好用) bash 运行 grep -r "trap_handler" . 找到 unsigned long trap_handler(...); → 这是声明(h 文件) 找到 unsigned long trap_handler(...) { ... } → 这是实现(c 文件) 疑问 7:链表结构体里面那个 next 指针为什么必须是 struct node *next?有点扯不清楚 原话:结构体里面的 next 指针,这个感觉有点扯不清楚。 c 运行 struct node { int data; struct node *next; }; 链表节点要串下一个同类型节点,所以指针类型必须是自己这个结构体; 难点:写的时候struct node还没定义完,但 C 允许不完整类型指针,所以可以写。 作用:每个节点存下一个节点的地址,把零散节点串成一条链,内核任务队列就是这么干的。

← 内核中对两种中断机制的区别分析总结  ·  动力来源分解 →