
1. 为什么把大模型拉进内核代码审查这件事值得认真做内核驱动代码审查一直是件苦差事尤其是drivers/staging这个目录。做过一段时间内核开发的人都知道staging 树本质上是个半成品收容所——里面堆着大量功能可用但代码质量参差不齐的驱动有的来自小厂商随手甩出来的 BSP 包有的来自个人开发者练手的实验性模块还有的是从其他操作系统移植过来、只求能跑通的粗糙实现。这些代码要往drivers/net、drivers/gpu、drivers/media这些正式子系统里挪中间隔着的就是一轮又一轮的审查和整改。我前后参与过几个 staging 驱动的清理工作最直观的感受是审查的瓶颈不在看不懂而在看得太慢。一个中等规模的驱动动辄几千行里面混杂着编码风格问题、资源泄漏隐患、并发保护缺失、错误路径处理不当、API 用法过时等一大堆问题。人工逐行过一遍眼睛会疲劳注意力会漂移很多问题不是发现不了而是根本没精力看到那里。更麻烦的是staging 代码的审查往往需要对照内核编码规范、子系统维护者的偏好、以及当前内核版本的 API 现状这些知识分散在文档、邮件列表历史和个人经验里新人上手门槛极高。大语言模型LLM在这个场景里的价值不是替代审查者做最终判断而是把机械性、模式化、可枚举的那部分问题先扫一遍把人的注意力解放出来集中到真正需要架构判断和上下文推理的地方。这个定位很重要——我见过不少人一上来就想让模型审查整个驱动结果拿到一堆似是而非的建议反而增加了甄别成本。正确的用法是把它当成一个不知疲倦的初级审查助手你给它明确的检查清单和上下文它给你结构化的候选问题列表你再做二次确认。这篇内容适合三类人看一是刚开始接触内核驱动、想搞清楚 staging 代码到底要审什么的新手二是已经在做驱动维护、想用工具提升审查效率的老手三是对 LLM 辅助代码分析这个方向感兴趣、想了解实际落地边界的技术人员。我会把整个策略拆成可复现的流程包括提示词怎么设计、检查项怎么分类、模型输出怎么验证、哪些坑必须提前避开。所有操作都基于公开的内核开发实践和通用的 LLM 使用方式不涉及任何特定厂商工具。需要先说明一个前提LLM 对内核代码的理解是有天花板的。它能识别明显的风格违规、常见的资源管理错误、可疑的锁使用模式但对涉及硬件时序、内存屏障语义、特定子系统内部约定的深层问题它的判断经常不可靠。所以整套方法的核心原则是——让模型做高召回、低精度的第一遍筛查人做高精度的最终裁决。理解了这个分工后面的所有设计就都顺了。2. 整体策略设计把审查拆成模型擅长和不擅长的两部分2.1 审查任务的分解逻辑直接让模型审查这个驱动文件是最差的用法。原因很简单任务太宽泛模型不知道你关心什么输出会变成一篇泛泛而谈的代码评论既没有优先级也没有可操作性。我的做法是把 staging 驱动的审查拆成若干个边界清晰、判定标准明确的子任务每个子任务单独喂给模型。拆分的依据是这个检查项能不能用相对确定的规则描述。能描述的交给模型批量扫不能描述的留给人。具体来说我通常拆成这么几类编码风格类缩进、行宽、括号位置、空格使用、注释格式、命名规范。这类问题判定标准最明确模型表现也最稳定。API 使用类是否使用了已废弃的接口、是否该用某个更现代的替代 API、参数顺序和类型是否匹配。这类需要模型对内核 API 演进有一定了解表现中等。资源管理类内存分配与释放是否配对、错误路径是否遗漏清理、引用计数是否平衡。这类是模型相对擅长的模式识别任务。并发与同步类锁的获取释放是否成对、临界区是否合理、是否存在明显的竞态模式。这类模型能抓明显问题深层语义靠不住。错误处理类返回值是否检查、错误码是否合理传播、失败路径是否完整。这类表现不错。架构与语义类这个驱动该不该放在 staging、它的整体设计是否符合子系统约定、硬件操作时序是否正确。这类基本别指望模型人来做。这个拆分不是为了好看而是为了控制每次输入的上下文规模和输出范围。一个文件几千行全塞进去模型注意力会稀释拆成子任务后每次只让它关注一类问题输出的信噪比明显提升。2.2 为什么选择分块清单而不是整文件自由审查我试过两种极端方式。第一种是把整个驱动文件丢给模型让它自由发挥。结果是输出又长又散前面几条还算靠谱越往后越像凑数而且经常把同一个问题换个说法重复好几遍。第二种是只给模型一个函数让它逐行审查。这样精度上去了但效率太低一个文件几十个函数来回调用成本受不了。最后稳定下来的方案是按文件分块、按检查清单驱动。具体做法是先把驱动按逻辑单元切成若干块通常按函数或按功能模块每块控制在几百行以内然后针对每一块用一份固定的检查清单去提问清单里的每一项都对应一个明确的判定标准。这样既保证了上下文完整又让模型的输出有结构、可对比、可汇总。这里有个细节值得说分块的边界要顺着代码的自然结构走不要机械按行数切。比如一个probe函数和它调用的初始化辅助函数应该放在同一块里因为资源分配和清理的配对关系跨函数存在切开就看不出问题了。我一般按一个对外入口函数 它直接调用的内部函数作为一个块这样资源生命周期基本在一个块内闭合。2.3 模型输出在流程中的定位必须反复强调模型的输出是候选问题不是结论。我在流程里给模型输出设了一道强制的人工复核关卡每一条模型提出的问题都要经过是否真实存在→是否值得修→怎么修三步确认。这道关卡不是走形式实测下来模型提出的问题里真正需要修改的大概占六到七成剩下的是误报或者过度挑剔。误报主要来自几个方面模型对某些内核惯用写法不熟悉把正常代码当成问题模型把可以更好当成必须改提出一些风格偏好级别的建议模型对上下文理解不全比如没看到某个资源在别处已经释放了。这些都需要人来过滤。所以整套流程的效率提升不是来自模型直接给出正确答案而是来自人只需要复核候选列表不用从零开始找问题。这个区别很关键想清楚这一点才不会对工具产生不切实际的期待。3. 核心检查清单的设计与提示词工程3.1 检查清单怎么写成模型能用的形式检查清单是整个方法的核心资产。写得好模型输出精准写得含糊模型就开始自由发挥。我的经验是每一条检查项都要包含三要素检查什么、判定标准是什么、输出格式是什么。举个具体的例子。关于内存分配的检查项不能只写检查内存泄漏而要写成类似这样的结构检查本代码块中所有的内存分配调用如kmalloc、kzalloc、devm_kzalloc、vmalloc等。对每一次分配追踪其对应的释放路径确认1正常执行路径上是否有释放2所有错误返回路径上是否有释放3是否存在释放后继续使用的情况。如果发现分配与释放不配对输出分配所在行号、分配函数名、缺失释放的路径描述。这样写的好处是模型有了明确的搜索目标和输出模板不会跑偏去评论别的东西。我整理了一份常用的检查项模板覆盖 staging 驱动审查里最高频的问题类型实际使用时按驱动类型做增删。检查类别典型检查项模型可靠性人工复核重点编码风格行宽超限、缩进用空格、括号换行不规范高确认是否属于工具已覆盖的范围API 使用调用已废弃接口、参数类型不匹配中核对当前内核版本的 API 现状内存管理分配释放不配对、错误路径遗漏释放中高确认跨函数的资源生命周期锁与同步加锁解锁不配对、临界区过大中判断锁的语义是否正确错误处理返回值未检查、错误码传播不当高确认错误码是否符合子系统约定类型与转换隐式类型转换、指针类型混用中高核对硬件寄存器宽度日志与调试使用printk而非dev_*系列高确认日志级别是否合理这张表是我自己用的版本你可以根据自己的驱动类型调整。比如网络驱动要额外关注 skb 的生命周期字符设备要关注 file_operations 的各个回调是否完整USB 驱动要关注 urb 的提交与回调配对。检查清单不是一成不变的它应该随着你踩过的坑不断增补——每次人工复核发现一个模型漏掉的问题类型就把它补进清单下次就能自动扫到。3.2 提示词的结构化写法提示词不是越长越好但结构一定要清楚。我用的模板大致分四段角色设定、上下文说明、检查任务、输出约束。角色设定部分简短即可告诉模型它扮演的是一个熟悉内核编码规范的有经验审查者。上下文说明部分要交代清楚这是什么类型的驱动、目标内核版本大概是什么范围、当前代码块在整个驱动里的位置。检查任务部分就是把上面说的检查清单贴进去。输出约束部分最关键要明确规定输出格式比如每条问题用一行格式为行号 | 问题类别 | 问题描述 | 建议修改方向并且要求如果没有发现问题明确输出无问题不要编造。这里有个实操心得在提示词里明确要求模型标注置信度。我一般让它对每条问题标高/中/低三档置信度人工复核时优先看高置信度的低置信度的快速扫一眼即可。这个简单的标注能显著降低复核成本因为大部分误报都集中在低置信度那部分。还有一个技巧是给模型提供正例和反例。比如在检查锁使用时贴一小段正确加锁解锁的代码和一小段遗漏解锁的代码让模型对照。实测下来带示例的提示词比纯文字描述的提示词输出准确率能高出一截。示例不用多每个检查类别给一组就够。3.3 上下文窗口的管理策略内核驱动文件动辄几千行加上检查清单和示例很容易超出模型的上下文窗口。我的处理原则是宁可多切几块也不要硬塞。一块代码控制在 300 到 500 行是比较舒服的范围既能保证函数间的调用关系可见又不会让模型注意力涣散。切块的时候要注意保留必要的上下文。比如一个函数用到了文件顶部定义的结构体那这个结构体的定义要跟着块一起给模型否则它看不懂字段含义。我的做法是在每个块的头部附上一段相关声明摘要把这块代码依赖的结构体定义、宏定义、全局变量声明摘出来。这段摘要可以手工整理也可以让模型先做一遍提取——后者更省事但提取结果要抽查。另外跨块的问题需要单独处理。比如一个资源在 A 函数分配、在 B 函数释放分块后就看不出来了。这类跨函数的资源配对我会在分块审查完成后单独做一轮全局资源追踪把所有分配点和释放点列出来对照。这一轮可以人工做也可以让模型基于汇总信息做但结论必须人工确认。4. 实操流程从拿到驱动到产出审查报告4.1 第一步摸清驱动的整体结构拿到一个 staging 驱动先别急着上模型。花十几分钟把目录结构、主要源文件、Kconfig 和 Makefile 过一遍搞清楚这个驱动大概做什么、有多少行代码、依赖哪些子系统。这一步的目的是建立全局认知后面分块和判断问题严重性都靠它。我通常会用几个命令快速摸底。统计各文件行数找出最大的几个文件重点关照看看有没有明显的代码异味比如单个函数超过几百行、嵌套层级特别深、大量重复代码块。这些结构性异味往往预示着审查工作量的大头在哪里。# 统计各源文件行数按从大到小排序 find drivers/staging/your_driver -name *.c -o -name *.h | xargs wc -l | sort -rn | head -20 # 粗略看看函数规模分布找出超长函数 grep -n ^static\|^int\|^void drivers/staging/your_driver/*.c | head -50摸底之后我会画一个简单的模块关系草图标出哪些文件是核心逻辑、哪些是硬件抽象层、哪些是辅助工具。这个草图决定了后面分块的优先级——核心逻辑文件优先审辅助文件可以粗审。4.2 第二步跑自动化工具做基础清扫在把代码交给 LLM 之前先让传统工具把能自动修的问题处理掉。这一步不能省因为风格类问题占了 staging 代码问题的一大半而这些恰恰是checkpatch.pl和clang-format最擅长的。让模型去干这些活是浪费而且模型对风格问题的判定不如工具稳定。标准动作是跑一遍checkpatch.pl把输出按严重程度分类。ERROR级别的必须修WARNING级别的逐条看CHECK级别的酌情处理。跑完之后能自动修的用工具修不能自动修的记下来作为后续模型审查的输入之一——把 checkpatch 的输出作为上下文喂给模型让它重点关注那些工具没覆盖到的语义问题而不是重复报告风格问题。# 对单个文件跑 checkpatch输出详细报告 ./scripts/checkpatch.pl --no-tree --strict -f drivers/staging/your_driver/your_file.c # 对整个驱动目录跑汇总统计 find drivers/staging/your_driver -name *.c | xargs ./scripts/checkpatch.pl --no-tree -f 21 | tee checkpatch_report.txt这一步跑完你手里应该有一份工具已覆盖的问题清单和一份待人工和模型处理的语义问题清单。两份清单分开管理避免重复劳动。4.3 第三步分块喂给模型做清单式审查这是整个流程的主体。按前面说的分块策略把代码切好每块配上对应的检查清单逐块调用模型。我一般会写一个简单的脚本把这件事批量化避免手工复制粘贴出错。脚本的逻辑很朴素读入代码块文件拼接提示词模板调用模型接口把输出按块保存。这里不展开具体代码因为不同环境的接口调用方式差异很大但核心结构就是模板 数据 请求。关键是模板要稳定每次调用用同一份这样输出格式一致方便后续汇总。调用的时候有几个参数值得注意。温度参数调低让输出更确定、更少发散最大输出长度给足避免报告被截断如果模型支持系统提示词把角色设定和输出约束放在系统提示词里把具体代码块放在用户消息里效果更稳。每块审查完成后我会快速扫一眼输出如果发现某块的问题数量异常多或者异常少就检查一下是不是分块切得不好、上下文给得不全。这种异常往往是分块策略需要调整的信号。4.4 第四步汇总去重与人工复核所有块跑完之后把输出汇总成一张总表。汇总的时候要做两件事一是去重同一个问题可能在不同块里被重复报告比如一个全局函数被多个块引用二是排序按问题类别和置信度排让人工复核有优先级。人工复核是整个流程里最花时间但也最不能省的环节。我的复核顺序是先看高置信度的资源管理和错误处理问题这类问题一旦属实影响最大再看中置信度的 API 和并发问题最后快速扫低置信度的风格类建议大部分直接忽略。复核时对每条问题做三选一的判定确认存在且需修、确认存在但可暂缓、误报。判定结果记在表里形成最终的审查报告。这份报告才是真正交付的东西模型输出只是它的原材料。4.5 第五步形成可提交的整改清单审查的终点不是一份问题列表而是一份可执行的整改清单。每条待修问题要写清楚问题位置、问题性质、修改方案、验证方式。修改方案要具体到代码层面不能只说建议修复内存泄漏而要写在第 X 行的错误分支中补充kfree(buf)。这份清单可以直接作为提交补丁的依据也可以作为和上游维护者沟通的材料。我习惯把清单按必须修和建议修分成两部分前者是阻塞性问题后者是锦上添花。这样在时间有限的时候能保证先把硬伤解决掉。5. 常见问题与排查技巧实录5.1 模型误报太多怎么办误报是这套方法最常见的抱怨。我踩过的坑里误报主要来自三个源头对应三种解法。第一种是检查项描述太模糊。比如检查代码质量这种检查项模型只能自由发挥输出自然发散。解法是把检查项写到可判定的粒度前面 3.1 节已经说过这里不重复。第二种是上下文给得不全。模型看不到某个资源在别处已经释放就会误报泄漏。解法是完善分块时的相关声明摘要把跨块的依赖信息补进去。如果某个误报反复出现说明这个块的上下文切得有问题考虑把相关代码合并到同一块。第三种是模型对内核惯用写法不熟悉。比如某些子系统有自己的一套资源管理宏模型没见过就当成问题。解法是在提示词里补充这类惯用写法的说明或者干脆在检查清单里排除这类模式。我一般会维护一个已知误报模式列表每次复核发现新的误报模式就记进去下次提示词里明确告诉模型以下写法是正常的不要报告。5.2 模型漏报严重问题怎么补漏报比误报更危险因为漏掉的问题不会进入人工复核环节。我遇到过几次模型完全没提、但人工一看就是硬伤的情况基本都是涉及深层语义的内存屏障用错、锁的粒度设计不合理、硬件寄存器访问顺序有问题。对付漏报单靠调提示词效果有限因为这类问题本来就超出模型能力边界。我的做法是用人工检查清单兜底。针对每类驱动整理一份模型靠不住、必须人工过的检查项审查时专门花时间逐项确认。比如网络驱动必查 skb 在各条路径上的释放、字符设备必查 file_operations 每个回调的返回值处理、DMA 相关代码必查映射与解映射的配对。这份人工清单和模型清单是互补关系不是替代关系。另外一个补救手段是换角度提问。同一个问题正面问模型可能漏换个问法可能就抓到了。比如不问有没有内存泄漏而问列出本块中所有分配点和释放点并说明每个分配点的释放路径。这种先枚举再判断的问法能逼模型把相关代码都过一遍漏报率会低一些。5.3 不同模型表现差异大怎么选我用过几种不同规模的模型做这件事体感差异很明显。大参数量的模型对内核 API 和编码规范的理解更到位误报少但对长上下文的分块更敏感成本也高小参数量模型响应快、便宜适合做初筛但误报明显多需要更严格的人工复核。我的实际搭配是用能力较强的模型做资源管理和并发这类需要推理的检查用轻量模型做风格和 API 这类模式匹配的检查。这样在成本和效果之间取了个平衡。如果你的环境只能用一种模型那就选能力强的那个然后通过分块和清单设计把它的优势发挥出来。还有一点值得注意同一个模型在不同提示词下的表现差异可能比不同模型之间的差异还大。所以与其纠结选哪个模型不如先把提示词打磨好。我见过有人抱怨模型不行结果一看提示词就一句话帮我审查这段代码那再强的模型也发挥不出来。5.4 审查报告怎么组织才有人看审查报告写成一坨问题列表维护者是不会看的。我组织报告的原则是按影响面排序按修复成本分组。影响面大的放前面会导致崩溃的、会泄漏资源的、会在特定条件下出错的这些优先。影响面小的放后面风格问题、日志不规范、注释缺失这些可以批量处理。每组内部再按修复成本排改一行就能解决的放前面需要重构的放后面。报告里每条问题都要有明确的定位和可操作的修改建议最好附上修改前后的代码对比。维护者看报告是为了决定改不改、怎么改不是来猜谜的。这一点上模型输出的原始格式往往不够需要人工整理成最终形态。5.5 常见问题速查表现象可能原因排查方向处理建议模型输出大量风格类建议检查清单未排除工具已覆盖项检查提示词是否包含风格检查项从清单移除风格项交给 checkpatch同一问题被重复报告分块边界导致跨块重复检查分块是否按函数边界切汇总时去重或调整分块策略模型报告未发现问题但人工发现硬伤问题超出模型能力边界对照人工检查清单补充人工兜底检查项输出格式混乱无法汇总输出约束不明确检查提示词输出格式部分固定输出模板要求严格遵循模型对某 API 判断错误模型知识过时核对当前内核版本 API 现状提示词中补充 API 现状说明长文件审查到后面质量下降上下文过长注意力稀释检查单块行数缩小分块粒度控制在 500 行内6. 边界认知哪些事该交给模型哪些必须人来6.1 模型能稳定做好的事经过一段时间的实践我对模型的能力边界有了比较清楚的认识。模式化、可枚举、判定标准明确的任务模型做得又快又稳。具体来说编码风格的机械检查、常见 API 的用法核对、资源分配释放的配对追踪、返回值的检查覆盖、日志调用的规范性这些交给模型基本不会出大错人工只需要抽查确认。这类任务的共同特点是判定不依赖深层语义只看代码表面就能得出结论。模型在这方面的优势是速度快、不知疲倦、不会因为看了几千行就注意力涣散。一个几千行的驱动人工过一遍风格问题要大半天模型几分钟就能给出候选列表人工复核半小时搞定。这个效率提升是实打实的。6.2 模型靠不住、必须人工把关的事反过来涉及硬件语义、并发正确性、子系统约定的深层问题模型基本靠不住。内存屏障该不该加、加在哪、用哪种锁的粒度设计是否合理临界区里能不能睡眠硬件寄存器的访问顺序有没有约束这些问题的判断需要对具体硬件和子系统有深入理解模型只能给出表面看起来合理的建议但经常是错的。我印象很深的一次模型对一个 DMA 缓冲区的同步操作提出了优化建议说某个屏障调用是多余的。这个建议表面上有道理但完全忽略了硬件对访问顺序的要求照做就是引入难以复现的偶发故障。这类问题一旦被模型自信地报出来如果审查者经验不足照单全收后果很严重。所以我在流程里反复强调模型对语义类问题的输出置信度一律按最低档处理必须人工从原理层面确认。6.3 人机分工的稳定配比总结下来我的稳定配比是模型负责广度人负责深度。模型把整个驱动扫一遍保证没有明显的、模式化的问题被漏掉人把精力集中在模型标记为高影响、以及模型明确表示不确定的地方做深入判断。这个配比下审查的覆盖率和准确率都比纯人工或纯模型要高。具体到时间分配一个中等规模驱动的审查模型调用和汇总大概占两三成时间人工复核和深入判断占七八成。看起来人工还是大头但和纯人工相比人的时间从逐行找问题变成了复核候选问题单位时间的产出高了不少而且不容易因为疲劳漏掉明显问题。7. 我个人的几点实操体会这套方法用下来最大的感受是工具的价值取决于你怎么用它而不是它本身有多强。同样一个模型有人用出来是人工智障有人用出来是效率倍增差别全在任务拆解和提示词设计上。我见过太多人一上来就想让模型干最难的活结果失望而归然后得出结论说模型不行。其实换个用法让它干它擅长的活效果立刻就不一样。第二个体会是检查清单要持续迭代。我最初的清单只有十几项现在扩充到几十项每增加一项背后都是一次人工复核发现的漏报。这份清单是这套方法真正的护城河模型会换、工具会变但你对这类驱动容易出什么问题的认知是带得走的。建议每个用这套方法的人都维护一份自己的清单边用边补。第三个体会是别追求全自动。我试过把整个流程串成一条流水线从分块到出报告全自动结果发现人工复核环节一旦省掉报告质量断崖式下降。后来老老实实把人工复核加回来接受它占大头时间的事实。审查这件事最终的责任在人工具只是放大器放大的是人的判断力不是替代它。最后一个实用建议从小处开始。别一上来就挑最大的驱动练手找个几百行的小模块把整套流程走一遍熟悉每个环节的手感再逐步扩大规模。流程跑顺了再考虑批量化、脚本化。我当初就是在一个几百行的字符设备驱动上把方法磨出来的那个驱动的问题类型比较全练手很合适。等流程稳定了再上几千行的网络驱动心里就有底了。