ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux内核学习地图:五大子系统与核心机制实战指南

Linux内核学习地图:五大子系统与核心机制实战指南 先放个结论Linux 内核确实庞大但绝大多数人根本不需要一上来就钻源码。你要做的第一件事是先把“五大子系统”这条主线抓出来。我见过太多人一上来就啃kernel/sched/fair.c结果读了两周还在原地打转核心原因就是大脑里没有一张地图。五大子系统其实是内核设计与学习的经典切分方式进程调度、内存管理、文件系统、网络、进程间通信。再算上驱动模型、安全模块、虚拟化这些横向机制整个内核的骨架就完整了。这篇文章我打算用从业者的视角把每个子系统到底在解决什么问题、核心数据结构是什么、实际应用里怎么和它们打交道讲透顺带把大家高频问到的内核拦截、透明加密、内核同步、编译裁剪这些点全部串起来。如果你正想转内核开发、做性能调优或者要基于内核搞安全产品、嵌入式裁剪这篇内容可以当你的第一张地图。1. 起步看懂五大子系统等于拿到内核的地图1.1 五大子系统到底指什么最早把内核拆成五大子系统是很多内核教材和从业者约定俗成的分法。进程调度子系统负责决定“下一个该谁用CPU”内存管理子系统负责虚拟地址与物理页帧的映射、分配和回收文件系统子系统通过VFS抽象层把磁盘、闪存、网络存储统一成“文件”的形态网络子系统处理从socket到网卡驱动的整个数据包路径进程间通信子系统则给进程之间提供管道、信号、共享内存、消息队列等协作手段。这五个子系统并不是并列存在的它们之间有非常紧密的依赖关系。调度器要运行进程进程要有地址空间和页表文件系统要读写数据page cache要占用内存页网络协议栈收发数据包要分配sk_buff这又依赖内存管理。所以你把内存管理和进程调度这两块先啃下来后面看任何子系统都会顺畅得多。1.2 子系统之间的依赖关系决定了学习顺序我自己带过不少新人给出的建议顺序是进程调度 → 内存管理 → 文件系统 → 网络 → IPC。原因很简单调度和内存是地基。比如你搞文件系统透明加密最终要拦截的是read/write路径但你在分析VFS层之前必须先搞清楚一个进程打开文件之后这个文件描述符对应的file结构体、它指向的inode、以及读写时这些数据如何落到物理内存否则很难真正理解“页缓存回写”这一步。反过来如果后面遇到网络性能问题你可能要看网卡中断、软中断和NAPI机制这些又涉及进程调度中的下半部机制和内存管理中的DMA内存分配。所以五大子系统不是孤立的知识点而是一张互相引用的网。学习时可以按顺序推进但一定要接受“交叉引用”这件事随时往回翻。1.3 别被代码量吓倒先看数据结构再读逻辑内核代码最劝退新人的地方是函数调用链太长。我自己有个习惯不管读哪个子系统先不追函数调用而是把这个子系统的核心数据结构画出来。进程调度里的task_struct、内存管理里的mm_struct和vm_area_struct、文件系统的super_block、inode、dentry、file、网络的sk_buff这些结构体就是各子系统的“身份证”。数据结构的字段定义了系统的边界把边界搞清楚函数逻辑只是在这个边界内的流动。这个方法在面试和学习里都极其好用。你不需要背代码但你能说出每个子系统的核心结构体有哪些、它们之间的关系是什么、一次操作在结构体之间是怎么流转的这已经超过大部分候选人了。2. 进程调度子系统CPU时间的分配规则2.1 调度器要解决的三个问题以及一个终极目标进程调度子系统的核心问题是在多个可运行进程之间分配CPU时间。现代Linux系统上运行中的任务数量通常远大于CPU核数所以调度器必须在“每个任务都有进展”的前提下同时满足三类需求交互式任务要低延迟后台计算任务要高吞吐实时任务要严格满足截止时间。终极目标其实是一个trade-off让系统整体看起来流畅、各任务公平获取CPU、同时把切换开销降到最低。三个目标在数学上不可能同时最优所以调度器本质是在做策略权衡。Linux调度的演进路线——O(1)调度器、CFS调度器、目前对CFS的各种改进——都是在找更好的权衡点。2.2 CFS完全公平调度器是怎么“一碗水端平”的我最早学内核时对调度器的理解停留在“按优先级排队”的阶段。后来读了CFSCompletely Fair Scheduler完全公平调度器之后才发现它压根不是排队而是用虚拟运行时间排序。每个可运行任务都有一个vruntime表示它累计运行了多少“虚拟时间”。CFS用红黑树维护所有可运行任务键值就是vruntime每次调度时从树的最左节点选出vruntime最小的任务执行。因为每次选的都是跑得最少的任务从长期看所有任务能获得近似相等的CPU时间这就是“完全公平”的核心思想。这里有个容易忽略的细节vruntime的递增速率会受到任务优先级的影响。优先级高的任务它的vruntime增长得慢所以同样运行一个调度周期高优先级任务实际占用的CPU时间更多优先级低的任务vruntime增长快很快就会被“公平地”换下来。优先级通过nice值调节nice值的本质不是直接给一个权重值而是影响vruntime的增长斜率。2.3 进程状态、上下文切换与内核抢占调度器的调度对象是任务内核里统一叫进程或线程。每个任务对应一个task_struct它记录任务的寄存器现场、内核栈指针、调度策略、优先级、信号掩码等一大堆信息。进程状态是整个调度机制的基础常见的有TASK_RUNNING可运行或正在运行、TASK_INTERRUPTIBLE可中断睡眠、TASK_UNINTERRUPTIBLE不可中断睡眠典型场景是等待IO、__TASK_STOPPED、__TASK_TRACED以及退出后的EXIT_ZOMBIE。上下文切换是调度器真正干活的环节。CPU要切换到另一个进程时内核会保存当前进程的寄存器现场到它的内核栈然后加载新进程的内核栈和寄存器现场最后更新页表。这中间还有个步骤是处理TLB冲刷这也是为什么频繁上下文切换会拖垮性能。特别提醒新手一个经典误区在驱动和内核代码里持锁状态下绝对不能睡眠否则可能出现进程在持有自旋锁时被调度走别的进程又拿不到锁系统的行为变得不可预测。如果你是在中断上下文或者自旋锁临界区里干活只能用不会睡眠的机制这个规则直接影响后面学内核同步时的锁选择。2.4 实际观察调度器的几种手段读调度器源码之前先学会和它“聊天”。我经常用三条命令观察运行队列和负载top看进程CPU占用和%wavmstat看运行队列长度cat /proc/stat看各CPU时间片分布。运行队列长度长期大于CPU核数说明系统已经超负荷%wa过高则要怀疑IO瓶颈而不是CPU问题。做性能测试时我习惯用chrt调整实时优先级用nice调整普通进程优先级再用perf sched看调度延迟和切换次数。实测下来绝大部分性能优化问题最后都会落在“调度延迟是不是太高”“是不是有进程在忙等”这两类原因上。把运行队列和上下文切换次数趋势图拉出来很多问题一眼就定位了。3. 内存管理子系统从虚拟内存到物理页帧的“账房先生”3.1 虚拟内存给每个进程一台“独立假主机”内存管理子系统是整个内核里最像“账房先生”的部分它管理两本账一本是虚拟地址空间一本是物理页帧还要维护两者之间的映射关系。现代CPU通过MMU内存管理单元把进程访问的虚拟地址翻译成物理地址每个进程以为自己拥有一整块连续内存很安全实际上物理页可能是离散的甚至可以换出到磁盘。这套机制带来的直接好处有三个内存隔离一个进程无法直接访问另一个进程的地址空间内存共享多个进程可以通过映射关系共享同一物理页比如动态库代码内存扩展物理内存不够时可以把不常用的页换出。虚拟地址空间的布局在不同架构上有差异常见的进程地址空间从上到下是栈区、mmap区、堆区、BSS段、数据段、代码段内核空间则通常占据高地址部分。3.2 内存管理中的重要数据结构这一节面试大概率碰上很多人在面试里被问过“Linux内存管理子系统中有哪些重要的数据结构”这也是搜索热词。我在这里一次讲全每个都配上它负责的“角色”数据结构角色定位关键作用mm_struct进程地址空间的“总账本”记录整个进程地址空间的页表、代码段/数据段/堆/栈的边界、映射数量和锁vm_area_struct一段连续虚拟地址的“区块描述符”描述一段虚拟地址区间的起始、结束、权限、映射文件或匿名映射struct page物理页帧的“身份证”每个物理页对应一个struct page记录页的引用计数、映射关系、标志位pglist_dataNUMA节点的“分区账本”在NUMA架构下按内存节点管理物理内存内存节点再分成多个管理区zone内存管理区的“分类账本”把物理页按DMA区、普通区、高端内存区分类便于不同类型分配页表项PTE虚拟地址到物理地址的“翻译记录”MMU通过页表完成地址翻译PTE记录物理页框号和权限位这里最容易混淆的是mm_struct和vm_area_struct的分工。mm_struct是整个进程的地址空间vm_area_struct是地址空间里的一段区间。一个进程的堆是一个vm_area_struct栈是另一个vm_area_structmmap映射的文件又是若干个vm_area_struct。内核把同一个mm_struct下的VMA按地址排成红黑树缺页异常时要根据出错地址快速找到对应的VMA再决定怎么处理这个缺页。3.3 分配器伙伴系统与slab分配器各自解决什么问题物理内存分配有两套主力机制。伙伴系统是页面分配器负责分配连续的物理页框。它把空闲页按2的幂次分组成块要分配4页就拿一个大小是4页的块如果当前没有合适的块就从小块向上合并成大块再从大块里拆出需要的部分。这种分配方式的优点是外部碎片少、分配效率高、回收时可以合并相邻空闲块。slab分配器则是为内核里的大量小对象服务的。内核里到处要创建task_struct、inode、dentry每次都去页分配器要几个字节的页显然不合理。slab机制会把同类型对象放进高速缓存创建对象时从缓存中取一个已初始化或未使用的槽位释放时归还缓存从而避免反复初始化。你打开/proc/slabinfo就能看到内核里所有slab缓存的使用情况这是分析内存泄漏时很有用的入口。3.4 malloc背后发生了什么从用户态到页表再到物理页用一个最简单的例子串一遍链路用户态调用malloc(1024)。第一步malloc通常从堆上分配内存堆空间不够时会调用brk或mmap系统调用。brk调整堆的结束地址mmap则创建一块新的VMA。第二步内核在VMA里记录这段地址区间为进程做地址空间扩展但此时还没有分配物理页。第三步进程真正访问这块地址时MMU发现页表里没有对应映射触发缺页异常。第四步内核的缺页异常处理程序执行do_page_fault为这个虚拟地址分配物理页帧、建立页表映射然后返回用户态重新执行触发缺页的指令。这个过程能解释很多性能现象malloc之后再memset一大块内存会比不memset慢不少因为页表映射和物理页分配都发生在第一次访问。这也是Linux“随写分配”机制的体现。如果你在做高性能服务的内存优化合理的做法是预分配后主动madvise或提前touch避免运行时频繁缺页。3.5 内存观测与内存泄漏排查要观测内存子系统最直接的是/proc/meminfo。里面有MemTotal、MemFree、Buffers、Cached、Slab、SwapTotal等字段。注意Cached和Buffers都算可回收内存程序看到内存占用高不用慌要看真正的可用内存要算MemAvailable。排查内核态内存泄漏我常用的路径是先看/proc/meminfo里Slab是否持续增长再用slabtop定位是哪一个缓存增长。如果是用户态程序的内存疯涨用valgrind或jemalloc的profiling 功能定位。实际上很多所谓“内存泄漏”是内核页表和VMA没有及时释放的假象这时候/proc/pid/smaps能帮你逐段分析虚拟内存映射这个文件是定位用户态内存问题的利器。4. 文件系统子系统一切皆文件的枢纽与透明加密的落地点4.1 VFS把磁盘、闪存、网络存储统一成“文件”的抽象层文件系统子系统最核心的设计不是某个具体文件系统而是VFSVirtual File System虚拟文件系统这个抽象层。VFS 定义了一套统一接口让用户态只用open/read/write/close就能操作磁盘文件、设备文件、procfs、sysfs、网络文件系统等所有对象。这正是Linux“一切皆文件”设计哲学的根基。VFS向下屏蔽具体文件系统差异向上提供系统调用。比如用户态执行read(fd, buf, len)实际会按sys_read → vfs_read → file-f_op-read_iter → ext4_file_read_iter的路径走下去。VFS把“怎么读一个文件”分解成VFS通用逻辑和具体文件系统的实现方法前者处理权限校验、锁、页缓存后者处理磁盘布局和IO。理解这条路径是理解所有文件系统hook和拦截方案的起点。4.2 super_block、inode、dentry、file四个结构体的分工与协作很多人被VFS里这几个结构体搞晕根源是没分清它们分别描述什么。super_block描述整个文件系统实例包含文件系统类型、挂载选项、根目录dentry等inode描述磁盘上某个文件或目录的元数据比如大小、权限、时间戳、数据块位置inode和具体文件一一对应不管这个文件被打开多少次inode只有一个dentry描述路径中的一个分量“/home/test.txt”这条路径会生成多个dentryfile描述一个进程打开文件后的实例包含当前文件偏移、打开模式、指向dentry和inode的指针。一次打开操作的过程是进程调用open后VFS根据路径逐级解析dentry找到对应的inode分配一个file结构体并把file-f_op初始化为该文件系统提供的操作函数表。这个f_op就是后面拦截的基础。file是“一次打开”的视图同一个文件被打开两次会有两个file但inode始终是同一个。4.3 file_operations 拦截 read/write透明加密的关键路径解析搜索热词里反复出现“linux 内核 动态加载 file_operations 拦截 read write”和“透明加密”。这确实是文件系统领域的高频需求。透明加密的核心诉求是用户态进程读写某个文件时数据在写入磁盘前被加密从磁盘读回后被解密整个过程应用层完全无感。在VFS这个模型下实现透明加密有几条路。最简单直接的是替换file-f_op在open时拦截文件打开操作把原来的f_op保存起来换成我们自己的read/write方法。用户态调用read时先进我们的read实现从真实文件系统读取密文后做解密再返回调用write时先进我们的write实现把明文加密后再调用真实的写方法。这种方式适合做单机透明加密产品但要注意两点一是需要处理mmap路径二是要处理open之后又被其他模块获取f_op的场景。更系统化的方案是使用堆叠文件系统。内核已经帮你准备好了一个现成的例子ecryptfs。它不修改底层文件系统而是在VFS层把加密逻辑“夹”在用户态和真实文件系统之间。底层文件系统里存的是密文上层看到的是解密后的明文。在内核模块里注册自己的文件系统用mount挂载时可以指定底层目录实际IO走底层目录数据经过加密层处理。这个思路避免了直接篡改f_op带来的兼容性问题工程上更稳妥。4.4 register_filesystem 与动态加载文件系统模块“linux 内核 register_filesystem”也是热词这涉及如何把自定义文件系统“注册”进内核。Linux提供了一套注册机制文件系统模块通过register_filesystem()接口把file_system_type结构体注册到VFS层。这个结构体包含文件系统名称、挂载回调mount、卸载回调kill_sb等字段。注册完成之后内核就知道了这个文件系统存在后续mount -t myfs ...才能找到它。早期学习时我写过最简单的一个内存文件系统模块关键流程大致是实现file_system_type声明.name myfs实现.mount回调在回调里用mount_nodev创建一个super_block再在超级块里初始化根inode和根dentry。然后用insmod加载模块内核会在加载时调用模块初始化函数里的register_filesystem()。rmmod卸载时则要调用unregister_filesystem()。这里有个坑如果文件系统还在挂载状态unregister_filesystem()会失败必须先卸载挂载点再卸载模块。如果你只想要一个能在用户态验证的文件系统逻辑也可以借助FUSEFilesystem in Userspace把文件系统实现放到用户态。但如果你做的是透明加密、文件审计这类安全产品还是要把FUSE替换为内核态方案因为FUSE每次IO都有上下文切换性能差距明显。5. 网络子系统数据包在内核中的旅程5.1 socket层到协议栈一次send/recv的完整路径网络子系统负责把用户态socket调用变成真正的网络收发。发送一条数据的大致路径是用户态调用send进入socket层经过协议族分发后到达具体的协议处理函数比如TCP的tcp_sendmsg把用户数据拷贝到内核缓冲区组成sk_buff通过IP层的ip_queue_xmit选择路由、填充IP头再交给邻居子系统和网卡驱动最终把数据写入网卡的DMA环形缓冲区触发网卡发送。接收方向是相反的但更复杂。网卡收到数据后通过DMA把数据写入内存并触发中断中断处理程序把数据帧上送到协议栈经过链路层校验、IP层重组、传输层解包最后把数据放入对应socket的接收队列唤醒正在recv的进程。这里要特别提一个机制NAPI。在高速网络下每次都触发中断会形成中断风暴CPU被完全吃掉。NAPI的做法是第一次中断到来时先关闭中断切换到轮询模式批量收包处理完一批再开中断大幅降低中断次数提升收包吞吐。5.2 sk_buff贯穿内核协议栈的“数据快递箱”网络子系统最重要的数据结构是sk_buff。它承载整个网络栈的处理状态一个数据包在内核里从网卡驱动到协议栈再到socket全靠sk_buff在各层间传递。sk_buff里最核心的元素是head/data/tail/end指针各层协议通过调整data指针来添加或去掉头部。比如IP层要发送时会把data指针前移写入IP头TCP层再往前移写TCP头最后网卡驱动再往前移写以太网头数据就从用户态一路完成封装了。sk_buff里还有指向sock结构的指针用来关联协议控制块有len、truesize等长度字段有cb字段供各协议私有使用还有一系列链表节点。分析网络方面的性能问题时sk_buff的槽位分配和释放往往是热点它使用的就是内存管理子系统的cache机制。一次send从用户态拷贝数据到内核态本身的代价不低所以高性能方案里普遍会用sendfile、splice、io_uring来减少拷贝次数甚至用零拷贝绕过用户态缓冲。5.3 内核态网络拦截的常见入口搜索热词里也出现了不少“拦截”相关的内容网络方向的拦截同样经典。Linux网络协议栈在IPv4层的接收和发送路径上提供了Netfilter框架允许内核模块注册hook函数在PRE_ROUTING、LOCAL_IN、FORWARD、LOCAL_OUT、POST_ROUTING五个点上对数据包进行判断和处理。防火墙、NAT、流量控制、透明代理很多都是基于这套机制实现的。如果你只是想快速实现流量统计或拦截结合eBPF会是更现代的选择。eBPF允许在内核协议栈的不同挂载点运行用户定义的程序安全性比直接改内核好很多性能也优于传统iptables规则。我用eBPF做过完整的出口流量审计只改动内核数据路径的一小部分效果相当稳定。可以这么说在内核开发领域早些年你只能靠改内核代码或者加载内核模块来加功能现在大部分场景都可以考虑用eBPF实现。如果想要深入理解网络子系统我仍然建议先弄懂Netfilter链路因为它完整揭示了协议栈的骨架。6. 进程间通信与内核同步让子系统协作起来的“黏合剂”6.1 IPC的几种姿势管道、信号、共享内存、消息队列、socket进程之间要协作必须有通信机制。Linux提供的IPC手段非常多各自适用场景也不同。管道是最经典的流式通信常用于父子进程之间pipeline思路用到现在信号适合传递小量异步通知比如SIGTERM让进程退出但不适合传数据共享内存是效率最高的数据传输方式多个进程通过映射同一个物理页通信开销只有用户态访问内存缺点是要自己处理同步。消息队列则提供有边界的消息传递内核帮忙维护消息队列属性适合“一条条消息”的生产消费模型socket不仅能跨主机通信本机也可以走UNIX domain socket。我个人的经验是选IPC方案不要只看吞吐要看你的同步模型。如果你的业务天然是“按消息处理”的消息队列比共享内存更好写如果你追求极致延迟共享内存加原子变量几乎是唯一解但这要求你对内存序和缓存一致性有深刻理解。6.2 内核同步的方法自旋锁、互斥锁、读写锁、RCU到底怎么选搜索热词里“linux内核同步的方法”几乎是必背面试题。内核态并发场景比用户态复杂得多除多核CPU真正的并行执行外中断处理、软中断、抢占都可能在代码执行到一半时插入执行。因此内核提供了一整套同步工具选错锁在实际项目中会直接引发睡不睡得着觉、性能崩不崩的问题。自旋锁适用于临界区极短且不能睡眠的场景拿不到锁的CPU会原地等待。互斥锁mutex允许睡眠适合临界区较长或可能阻塞的场景但绝对不能在中断上下文或自旋锁临界区里使用。读写锁区分读者和写者读多写少时能提升并发度但如果写者反复到来可能导致读者长时间拿不到锁——这正是RCU出现的原因。RCURead-Copy Update让读者几乎不加锁写者通过复制、修改、发布指针的方式更新数据旧版本数据等待所有读者离开临界区后回收。读多写少且读者路径要求极低延迟的场景RCU基本是首选。还有一个容易被忽视的同步机制是per-CPU变量。每个CPU只操作自己的副本彻底避免锁竞争在统计场景比如每CPU计数器里非常好用。配套的还包括原子操作和内存屏障内存屏障解决的是CPU和编译器乱序执行带来的可见性问题。逻辑上临界区很短但频繁优先原子变量或自旋锁临界区允许阻塞用mutex读多写少用RCU或读写锁。6.3 死锁排查lockdep 是最值得依赖的调试工具内核死锁排查是真正体现经验的地方。写内核模块时最容易犯的是持有一个锁时又去获取另一个锁而另一个代码路径却以相反顺序获取这两个锁形成经典的AB-BA死锁。这种问题不一定每次都能复现跑几小时才崩一次非常折磨人。我强烈建议你在编译内核或加载模块时开启CONFIG_PROVE_LOCKING也就是lockdep锁依赖校验器。lockdep会在系统运行过程中自动记录锁的获取顺序一旦它检测到可能的循环等待就会在日志里打印出完整的锁依赖链直接告诉你哪个文件哪一行出问题。开启lockdep跑一遍相关测试比自己靠眼睛扫代码高效太多。平时开发我还会配合hung_task检测和softlockup检测前者用来发现长时间不可中断的任务后者用来检测CPU长时间处于硬中断或软中断状态它们能把大部分锁问题暴露出来。7. 经常被单独拎出来问的两个方向input子系统和内核编译裁剪7.1 input子系统从按键到/dev/input/eventX很多人入门驱动开发的第一站不是某个块设备而是input子系统因为按键、触摸屏、鼠标这些设备最容易上手效果也直观。input子系统在Linux内核里是一个典型的“分层”设计底层是各种物理设备驱动它们注册成input_dev中间是input核心维护设备与处理器之间的匹配关系上层是input_handler常见的如evdev负责把输入事件写入/dev/input/eventX字符设备用户态通过read读取标准化的input_event结构。实际点亮的流程大概是驱动初始化时分配并初始化input_dev调用input_register_device注册用户态程序打开/dev/input/eventX读取事件内核从设备驱动收到中断后将坐标、按键码封装成input_event上报再由evdev层把事件送给用户态。如果你想快速验证input子系统直接在设备上运行evtest选择对应event设备按下按键就能看到事件流这套链路对理解整个内核的设备模型都有帮助。7.2 内核编译与裁剪内核不是“装的越多越好”很多人在自己的工作机上喜欢把内核功能全部编成模块但这在嵌入式设备上是不可接受的。内核裁剪是嵌入式Linux开发的基本功。裁剪的核心渠道是Kconfig体系执行make menuconfig打开图形化配置界面你可以把不需要的文件系统、驱动、网络协议全部取消最终得到一个极小的内核。裁剪之后一般还要配合设备树精简和外设驱动的裁剪才能把内核镜像压到目标大小。裁剪时最容易遇到的问题是两个模块之间的依赖关系。某个配置项可能在菜单里显示为独立选项实际上它依赖另一个配置项才生效或者你关掉了一个驱动但另一个功能在启动时还要访问它。所以裁剪之后一定要实测启动流程验证每个硬件都能正常初始化。我个人经验是先保持一个能稳定运行的配置作为基线再分批次裁剪每裁一批就验证一次不要一次性删太多。否则出了问题你根本不知道是哪个配置导致的。7.3 WSL2在Windows上碰真实Linux内核的现代入口搜索热词里出现了大量的“适用于 Linux 的 Windows 子系统”“wsl.exe --install”这是另一条高频入口。WSL2和第一代WSL完全不同它不是翻译系统调用而是把微软维护的Linux内核跑在一个轻量级虚拟机上所以你可以在WSL2里获得接近真实Linux内核的体验。WSL2自己的内核源码也是公开的你可以下载后自行编译、替换很多内核模块实验完全可以放在WSL2里做省掉双系统切换和实体机重启的麻烦。WSL2里面跑着完整的/proc、/sys也可以加载你编译的内核模块只是需要注意不同内核版本之间KABI兼容的问题。安装时注意两个组件要完整Windows的“适用于Linux的Windows子系统”可选功能加上WSL2系统。如果你打算用WSL2做内核开发建议直接用终端执行wsl.exe --update把WSL更新到最新版再装一个自己习惯的发行版就能开始编译第一个内核模块了。8. 一些个人建议和踩坑后的真心话我在接触Linux内核前几年也走过弯路。回头看最值得坚持的学习方式是“结构先行场景驱动”。不要把内核当成一本书从头背到尾而是先搭好五大子系统的骨架然后带着具体问题去读源码想知道一个文件读写为什么会慢去读文件系统和页缓存想知道为什么一个服务延迟突然抖动去看调度和CPU运行队列想做一个文件加密产品去研究file_operations和VFS层。学习路径上我建议你第一遍只做三件事读懂进程调度里task_struct和CFS的调度流程读懂内存管理里VMA和struct page的映射关系读懂VFS里super_block、inode、dentry、file的协作方式。这三条主线梳通之后再往网络和IPC方向延伸会轻松很多。最后分享一个小技巧读内核不要死磕宏也不要一上来就想看懂所有分支。先找一条最主线的路径读完比如read系统调用从入口一直到磁盘驱动再回来研究边界条件和异常路径。这条主线读完之后你会发现自己突然能看懂以前完全看不懂的代码上下文了。内核开发是一门实践性很强的学问读十篇博客不如自己改一行代码跑一遍动手永远是最快的路径。
返回列表