三年前,小方离开了某互联网大厂加入了一个外企,之后工作和生活相对balance,我开始在技术上做了一些收拢。我选择了底层技术原理方向深挖。
我之所以做这样的决定是:
-
意识到工作是永远做不完,工作只是谋生的手段之一,而不是生活的全部的意义
-
年龄变大,需要兼顾身体和家庭
-
技术更新换代带快,新式技术和框架层出不穷,一味地追求新技术,永远只能浮于表明,也没法培养自己的核心技术能力,而基础技术原理长期适用和实用
-
中美科技竞争越来越激烈,国家对掌握基础和核心技术的人才越来越重视,需求越来越多,尤其是国产信创大环境背景下
希望我的一些思考能对其他读者,尤其是担忧自己三十五岁危机的技术同学,有一些启发。
在 2024年 我深入地把linux 5.x的内核代码关于网络协议栈等部分深入地阅读和调试了一遍。在学习内核的过程中也发现了一些好书,其中有一本爱不释手,已经过去的春节一边阅读,一边翻译,现在它终于完工了,我把它做成专栏放在小方说服务器开发知识星球中供小伙伴阅读,专栏这两天将会上线,这个专栏的主要是linux内核调试和新模块开发,特点是非常细致,行文娓娓道来,如同与一位老友喝茶聊天,对新手学习和入门linux内核调试和开发非常友好,更让人惊喜的是,其使用的linux内核源码版本为5.x代码,非常新。毕竟讲操作系统原理和linux低版本内核的资料和书籍太多了,而新的内核变化如此之多,操作系统的理论知识我们也学的太多了,纸上得来终觉浅,与实际试一试差别很大。
以下是详细目录:
前言
第一部分:基础内容
第1章:内核工作区设置
技术要求
在虚拟机(VM)中运行Linux
-
安装64位Linux虚拟机
-
开启x86系统的虚拟化扩展支持
-
为磁盘分配足够空间
-
安装Oracle VirtualBox Guest Additions
-
试用树莓派(Raspberry Pi)
设置软件发行版和软件包
-
安装软件包 -
安装Oracle VirtualBox Guest Additions -
安装所需软件包 -
安装交叉工具链和QEMU -
安装交叉编译器 -
重要安装说明
其他有用的项目
-
使用Linux手册页 -
tldr变体 -
查找和使用Linux内核文档 -
从源代码生成内核文档 -
Linux内核静态分析工具 -
Linux跟踪工具包下一代(Linux Trace Toolkit next generation) -
procmap 实用程序 -
简单嵌入式ARM Linux系统开源项目 -
使用[e]BPF进行现代跟踪和性能分析 -
LDV(Linux驱动程序验证,Linux Driver Verification)项目
总结
问题
延伸阅读
第2章:从源代码构建5.x Linux内核 - 第1部分
技术要求
内核构建的前期准备
-
内核版本命名规则 -
内核开发工作流程 - 基础内容 -
内核源代码树的类型
从源代码构建内核的步骤
步骤1 - 获取Linux内核源代码树
-
下载特定的内核树 -
克隆Git树
步骤2 - 解压内核源代码树
-
内核源代码树简要介绍
步骤3 - 配置Linux内核
-
理解kbuild构建系统 -
生成默认配置 -
获取内核配置的良好起点 -
典型嵌入式Linux系统的内核配置 -
以发行版配置为起点的内核配置 -
通过 localmodconfig方法优化内核配置 -
开始使用 localmodconfig方法 -
通过 make menuconfig用户界面优化我们的内核配置 -
make menuconfig用户界面的示例用法 -
更多关于kbuild的内容 -
查找配置差异
自定义内核菜单 - 添加我们自己的菜单项
-
Kconfig*文件 -
在 Kconfig文件中创建新菜单项 -
关于Kconfig语言的一些细节
总结
问题
延伸阅读
第3章:从源代码构建5.x Linux内核 - 第2部分
技术要求
步骤4 - 构建内核映像和模块
步骤5 - 安装内核模块
-
在内核源代码中定位内核模块 -
安装内核模块
步骤6 - 生成初始内存盘(initramfs)映像和引导加载程序设置
-
在Fedora 30及更高版本上生成initramfs映像 -
深入了解initramfs映像的生成过程
理解initramfs框架
-
为什么使用initramfs框架? -
理解x86上引导过程的基础知识 -
更多关于initramfs框架的内容
步骤7 - 自定义GRUB引导加载程序
-
自定义GRUB - 基础内容 -
选择默认引导的内核 -
通过GNU GRUB引导加载程序引导我们的虚拟机 -
试用GRUB提示符
验证我们新内核的配置
树莓派的内核构建
-
步骤1 - 克隆内核源代码树 -
步骤2 - 安装交叉工具链 -
第一种方法 - 通过 apt安装软件包 -
第二种方法 - 通过源代码仓库安装 -
步骤3 - 配置和构建内核
内核构建的其他提示
-
最低版本要求 -
为其他站点构建内核 -
监控内核构建过程 -
构建过程的快捷Shell语法 -
处理编译器开关问题 -
处理缺少OpenSSL开发头文件的问题
总结
问题
延伸阅读
第4章:编写你的第一个内核模块 - LKMs第1部分
技术要求
理解内核架构 - 第1部分
-
用户空间和内核空间 -
库和系统调用API -
内核空间组件
探索可加载内核模块(LKMs,Loadable Kernel Modules)
-
LKMs框架 -
内核源代码树中的内核模块
编写我们的第一个内核模块
-
介绍我们的“Hello, world” LKM C代码 -
详细解析 -
0/-E返回约定 -
ERR_PTR和PTR_ERR宏 -
__init和__exit关键字 -
内核头文件 -
模块宏 -
入口和出口点 -
返回值
内核模块的常见操作
-
构建内核模块 -
运行内核模块 -
快速初识内核 printk()函数 -
列出活动的内核模块 -
从内核内存中卸载模块 -
我们的lkm便利脚本
理解内核日志记录和printk
-
使用内核内存环形缓冲区
-
内核日志记录和systemd的
journalctl -
使用
printk日志级别 -
pr_<foo>便利宏 -
连接到控制台 -
将输出写入树莓派控制台 -
启用 pr_debug()内核消息 -
限制
printk实例的速率 -
从用户空间生成内核消息
-
通过
pr_fmt宏标准化printk输出 -
可移植性和
printk格式说明符
理解内核模块Makefile的基础知识
总结
问题
延伸阅读
第5章:编写你的第一个内核模块 - LKMs第2部分
技术要求
一个更好的内核模块Makefile模板
-
配置“调试”内核
交叉编译内核模块
-
为交叉编译设置系统 -
尝试1 - 设置“特殊”环境变量 -
尝试2 - 将Makefile指向目标的正确内核源代码树 -
尝试3 - 交叉编译我们的内核模块 -
尝试4 - 交叉编译我们的内核模块
收集最小系统信息
-
提高安全意识
内核模块的许可
为内核模块模拟“类似库”的功能
-
通过多个源文件进行库模拟 -
理解内核模块中的函数和变量作用域 -
理解模块堆叠 -
试用模块堆叠
向内核模块传递参数
-
声明和使用模块参数 -
插入后获取/设置模块参数 -
模块参数的数据类型和验证 -
验证内核模块参数 -
覆盖模块参数的名称 -
与硬件相关的内核参数
内核中不允许使用浮点数
系统启动时自动加载模块
-
模块自动加载 - 更多细节
内核模块和安全性 - 概述
-
影响系统日志的 proc文件系统可调参数 -
内核模块的加密签名 -
完全禁用内核模块
内核开发者的编码风格指南
为内核主线做出贡献
-
开始为内核做出贡献
总结
问题
延伸阅读
第二部分:理解和使用内核
第6章:内核内部基础 - 进程和线程
技术要求
理解进程和中断上下文
理解进程虚拟地址空间(VAS,Virtual Address Space)的基础知识
组织进程、线程及其堆栈 - 用户空间和内核空间
-
用户空间组织 -
内核空间组织 -
总结当前情况 -
查看用户和内核堆栈 -
查看给定线程或进程的内核空间堆栈 -
查看给定线程或进程的用户空间堆栈 -
查看堆栈的传统方法 -
[e]BPF - 查看两种堆栈的现代方法 -
进程VAS的宏观视图
理解和访问内核 task 结构
-
查看任务结构
-
使用
current访问 task 结构 -
确定上下文
通过current使用 task 结构
-
内置内核辅助方法和优化 -
试用打印进程上下文信息的内核模块 -
认识到Linux操作系统是单体内核 -
使用 printk进行安全编码
遍历内核的任务列表
-
遍历任务列表I - 显示所有进程 -
遍历任务列表II - 显示所有线程 -
区分进程和线程 - 线程组ID(TGID,Thread Group ID)和进程ID(PID,Process ID) -
遍历任务列表III - 代码实现
总结
问题
延伸阅读
第7章:内存管理内部基础
技术要求
理解虚拟内存分割(VM split)
-
深入分析“Hello, world” C程序
-
超越 printf()API -
64位Linux系统上的虚拟内存分割
-
虚拟寻址和地址转换 -
进程VAS - 完整视图
探究进程虚拟地址空间
-
详细探究用户虚拟地址空间 -
procmap进程VAS可视化实用程序 -
解释 /proc/PID/maps输出 -
vsyscall页面 -
使用 procfs直接查看进程内存映射 -
查看进程内存映射的前端工具 -
理解虚拟内存区域(VMA,Virtual Memory Area)基础
检查内核段
-
32位系统上的高端内存 -
编写一个内核模块来显示内核段的信息 -
空陷阱页 -
通过 dmesg在树莓派上查看内核段 -
描述内核段布局的宏和变量 -
试用 - 查看内核段详细信息 -
通过 procmap查看内核VAS -
试用 - 查看用户段 -
查看关于内存布局的内核文档
随机化内存布局 - 内核地址空间布局随机化(KASLR,Kernel Address Space Layout Randomization)
-
用户模式地址空间布局随机化(User-mode ASLR) -
KASLR -
使用脚本查询/设置KASLR状态
物理内存
-
物理随机存取存储器(RAM,Random Access Memory)组织 -
节点 -
内存区域(Zones) -
直接映射的RAM和地址转换
总结
问题
延伸阅读
第8章:模块作者的内核内存分配 - 第1部分
技术要求
介绍内核内存分配器
理解和使用内核页分配器(或伙伴系统分配器,BSA,Buddy System Allocator)
-
页分配器的基本工作原理
-
最简单的情况 -
更复杂的情况 -
失败的情况 -
空闲列表组织
-
页分配器的工作过程
-
分析一些场景
-
页分配器内部 - 更多细节
-
学习如何使用页分配器API
-
确切的页分配器API -
处理GFP标志 -
使用页分配器释放页面 -
编写一个内核模块来演示使用页分配器API -
部署我们的 lowlevel_mem_lkm内核模块 -
页分配器和内部碎片 -
深入研究GFP标志
-
在中断或原子上下文中绝不能睡眠
理解和使用内核slab分配器
-
对象缓存的概念 -
学习如何使用slab分配器API -
分配slab内存 -
释放slab内存 -
数据结构 - 一些设计技巧 -
实际用于 kmalloc的slab缓存 -
编写一个使用基本slab API的内核模块
kmalloc API的大小限制
-
测试限制 - 单次调用内存分配 -
通过 /proc/buddyinfo伪文件检查
slab分配器 - 一些其他细节
-
使用内核的资源管理内存分配API -
额外的slab辅助API -
控制组和内存
使用slab分配器的注意事项
-
背景细节和结论 -
使用 ksize()测试slab分配 - 案例1 -
使用 ksize()测试slab分配 - 案例2 -
解释案例2的输出 -
绘制图表 -
内核中的slab层实现
总结
问题
延伸阅读
第9章:模块作者的内核内存分配 - 第2部分
技术要求
创建自定义slab缓存
-
在内核模块中创建和使用自定义slab缓存 -
创建自定义slab缓存 -
使用新的slab缓存内存 -
销毁自定义缓存 -
自定义slab - 一个演示内核模块 -
理解slab收缩器 -
slab分配器的优缺点总结
在slab层进行调试
-
通过slab中毒进行调试 -
试用 - 触发一个使用后释放(UAF,Use-After-Free)漏洞 -
在引导和运行时的SLUB调试选项
理解和使用内核vmalloc() API
-
学习使用 vmalloc系列API -
关于内存分配和请求调页的简要说明 -
vmalloc()的相关函数 -
指定内存保护 -
测试 - 一个快速的概念验证 -
为什么将内存设置为只读? -
kmalloc()和vmalloc()API的快速比较
内核中的内存分配 - 何时使用哪些API
-
可视化内核内存分配API集 -
选择合适的内核内存分配API -
关于直接内存访问(DMA,Direct Memory Access)和连续内存分配器(CMA,Contiguous Memory Allocator)的说明
内存不足(OOM,Out-Of-Memory)杀手
-
回收内存 - 内核的清理任务和OOM -
故意调用OOM杀手 -
通过Magic SysRq调用OOM杀手 -
使用疯狂的分配器程序调用OOM杀手 -
理解OOM杀手背后的原理 -
案例1 - vm.overcommit设置为2,关闭过度提交 -
案例2 - vm.overcommit设置为0,开启过度提交(默认设置) -
请求调页和OOM -
理解OOM分数
总结
问题
延伸阅读
第10章:CPU调度器 - 第1部分
技术要求
学习CPU调度内部原理 - 第1部分 - 基本背景知识
-
Linux上的KSE是什么? -
POSIX调度策略
可视化流程
-
使用 perf可视化流程 -
通过其他(命令行界面,CLI,Command-Line Interface)方法可视化流程
学习CPU调度内部原理 - 第2部分
-
理解模块化调度类
-
查询调度类
-
关于完全公平调度器(CFS,Completely Fair Scheduler)和虚拟运行时(vruntime)值的说明
线程 - 调度策略和优先级
学习CPU调度内部原理 - 第3部分
-
谁运行调度器代码? -
调度器何时运行? -
上下文切换 -
定时器中断部分 -
进程上下文部分 -
可抢占内核 -
CPU调度器入口点
总结
问题
延伸阅读
第11章:CPU调度器 - 第2部分
技术要求
使用LTTng和trace-cmd可视化流程
-
使用LTTng和Trace Compass进行可视化 -
使用LTTng记录内核跟踪会话 -
使用图形用户界面(GUI,Graphical User Interface)报告 - Trace Compass -
使用 trace-cmd进行可视化 -
使用 trace-cmd record记录示例会话 -
使用 trace-cmd report(命令行界面)进行报告和解释 -
使用图形用户界面前端进行报告和解释
理解、查询和设置CPU亲和性掩码
-
查询和设置线程的CPU亲和性掩码 -
使用 taskset(1)设置CPU亲和性 -
设置内核线程的CPU亲和性掩码
查询和设置线程的调度策略和优先级
-
在内核中 - 对内核线程进行设置
使用控制组(cgroups)进行CPU带宽控制
-
在Linux系统上查找cgroups v2 -
试用 - cgroups v2 CPU控制器
将主线Linux转换为实时操作系统(RTOS,Real-Time Operating System)
-
为x86_64上的主线5.x内核构建实时Linux(RTL,Real-Time Linux) -
获取RTL补丁 -
应用RTL补丁 -
配置和构建RTL内核 -
主线和RTL - 技术差异总结
延迟及其测量
-
使用 cyclictest测量调度延迟 -
获取并应用RTL补丁集 -
在设备上安装 cyclictest(和其他所需软件包) -
运行测试用例 -
查看结果 -
使用现代BPF工具测量调度器延迟
总结
问题
延伸阅读
第三部分:深入探究
第12章:内核同步 - 第1部分
临界区、互斥执行和原子性
-
什么是临界区? -
一个经典案例 - 全局 i++ -
概念 - 锁 -
要点总结
Linux内核中的并发问题
-
多核对称多处理(SMP,Symmetric Multi-Processing)系统和数据竞争 -
可抢占内核、阻塞I/O和数据竞争 -
硬件中断和数据竞争 -
锁定准则和死锁
互斥锁(Mutex)还是自旋锁(Spinlock)?何时使用哪个
-
理论上确定使用哪个锁 -
实际中确定使用哪个锁
使用互斥锁
-
初始化互斥锁 -
正确使用互斥锁 -
互斥锁的加锁和解锁API及其用法 -
互斥锁 - 通过[不可]中断睡眠? -
互斥锁示例驱动程序 -
互斥锁 - 剩余要点 -
互斥锁的 trylock变体 -
互斥锁的可中断和可终止变体 -
互斥锁的I/O变体 -
互斥锁API变体 -
信号量(Semaphore)和互斥锁 -
优先级反转和实时互斥锁(RT-mutex) -
内部设计
使用自旋锁
-
自旋锁的简单用法 -
自旋锁示例驱动程序 -
测试 - 在原子上下文中睡眠 -
在5.4调试内核上测试 -
在5.4非调试发行版内核上测试
锁与中断
-
使用自旋锁的快速总结
总结
问题
延伸阅读
第13章:内核同步 - 第2部分
使用atomic_t和refcount_t接口 649
-
较新的 refcount_t与较旧的atomic_t接口 650 -
更简单的 atomic_t和refcount_t接口 652 -
在内核代码库中使用 refcount_t的示例 654 -
64位原子整数操作符 656
使用读-修改-写(RMW)原子操作符 658
-
读-修改-写原子操作 - 对设备寄存器进行操作 658 -
使用读-修改-写按位操作符 661 -
使用按位原子操作符 - 示例 663 -
高效搜索位掩码 666
使用读写自旋锁 666
-
读写自旋锁接口 667
-
注意事项 669
-
读写信号量 670
缓存影响和伪共享 671
使用每个CPU变量的无锁编程 673
-
每个CPU变量 674 -
分配、初始化和释放每个CPU变量 675 -
对每个CPU变量执行I/O(读和写) 676 -
使用每个CPU变量 675 -
每个CPU变量 - 一个内核模块示例 678 -
内核中每个CPU变量的使用 682
内核中的锁调试 684
-
配置用于锁调试的调试内核 685 -
锁验证器 lockdep- 尽早捕获锁问题 687 -
示例 - 使用 lockdep捕获死锁错误 690 -
修复它 694 -
示例1 - 使用 lockdep捕获自死锁错误 690 -
示例2 - 使用 lockdep捕获AB - BA死锁 695 -
lockdep- 注释和问题 700 -
lockdep注释 700 -
lockdep问题 701 -
锁统计信息 702 -
查看锁统计信息 702
内存屏障 - 简介 704
-
在设备驱动程序中使用内存屏障的示例 705
总结 707
问题 708
进一步阅读 708
加入星球方式:
专栏对全体小方说服务器开发知识星球球友开发,加入星球后,看置顶帖子即可阅读。
星球提供五大服务:
-
优问优答
-
不定期的技术直播和录像
-
优质源码分享和指导
-
模拟面试、职业解惑和简历review
-
星球专属技术专栏。
小方说服务器开发知识星球详细介绍点击这里。
如果你还不是球友,原价325一年,现在可以通过下面的优惠券扫码加入,立省40元:

老球友续费扫码如下(可在半价的基础上再优惠20元):

推荐阅读
-
高级 C++ 开发综合岗面试题,能挑战否? -
大型开源 FTP 软件 FileZilla 源码分析 -
如何尽快适应大型 C++ 项目?
-
如果你想低成本的快速提高开发水平,推荐这个 -
《用C++17从零开发一个调试器》新专栏上线

