大数跨境
0
0

《Linux 5.x内核开发与调试 完全指南》 专栏已上线

《Linux 5.x内核开发与调试 完全指南》 专栏已上线 CppGuide
2025-03-17
1

三年前,小方离开了某互联网大厂加入了一个外企,之后工作和生活相对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_PTRPTR_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_trefcount_t接口 649

  • 较新的refcount_t与较旧的atomic_t接口 650
  • 更简单的atomic_trefcount_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

加入星球方式:

专栏对全体小方说服务器开发知识星球球友开发,加入星球后,看置顶帖子即可阅读。

星球提供五大服务:

  1. 优问优答

  2. 不定期的技术直播和录像

  3. 优质源码分享和指导

  4. 模拟面试、职业解惑和简历review

  5. 星球专属技术专栏。

小方说服务器开发知识星球详细介绍点击这里

如果你还不是球友,原价325一年,现在可以通过下面的优惠券扫码加入,立省40元:


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



推荐阅读



【声明】内容源于网络
0
0
CppGuide
专注于高质量高性能C++开发,站点:cppguide.cn
内容 1260
粉丝 0
CppGuide 专注于高质量高性能C++开发,站点:cppguide.cn
总阅读582
粉丝0
内容1.3k