1. 为什么把LLM拉进staging目录的代码审查流程drivers/staging这个目录在Linux内核社区里一直是个特殊存在。它收留的是那些能跑但还不够格进主线的驱动代码质量参差不齐风格五花八门维护者流动性大历史包袱重。任何一个接手过staging子系统review工作的人都知道这里的patch往往不是逻辑有多复杂而是低级问题密度高得离谱——拼写错误、过时的API调用、缺失的错误处理路径、不规范的锁使用、copy_from_user返回值没检查、内存分配失败没处理诸如此类。这些问题单看都不难但量大、重复、枯燥人工逐行看下来极其消耗精力。我最初对LLM介入内核代码审查是持怀疑态度的。原因很直接内核代码的上下文依赖太强一个看似无害的改动可能触发锁序问题或者RCU宽限期的微妙bug而通用大模型对内核语义的理解深度有限很容易给出看起来对但实际错的建议。但在实际把LLM引入到staging驱动的review辅助流程里跑了一段时间之后我的看法有了明显调整——关键不在于让LLM做最终判断而在于把它放在第一道粗筛的位置上专门处理那些机械性强、规则明确、人工看纯属浪费时间的检查项。这个定位很重要。staging目录的代码审查有两个层次第一层是合规性筛查检查代码是否符合内核编码风格、是否使用了已废弃的接口、是否有明显的资源泄漏模式、错误处理是否完整第二层是语义正确性审查涉及并发模型、内存模型、硬件交互时序这些需要深度领域知识的部分。LLM在第一层能发挥相当大的作用在第二层只能作为提示性参考。把这两层混在一起让LLM一把梭结果一定是灾难分层使用它就是一个相当趁手的辅助工具。适合读这篇内容的人我大致分三类一是刚开始参与staging子系统review、还在被各种checkpatch警告淹没的开发者二是维护自己公司内部驱动代码、想引入自动化审查手段但不确定边界的工程师三是对LLM辅助代码分析这个方向感兴趣、想看看在真实内核场景下怎么落地的人。下面我会把整个策略、实操流程、踩过的坑和具体技巧拆开讲尽量给到可以直接复现的东西。2. staging驱动代码的典型问题分布与LLM的匹配度分析2.1 staging目录的问题特征决定了LLM的用武之地要搞清楚LLM在staging review里能干什么先得摸清这个目录的问题分布。我统计过自己经手的一批staging驱动patch按问题类型粗略归类大致是这样的比例编码风格类问题行长度、缩进、空格、注释格式、括号位置占四成以上API使用类问题废弃接口、参数顺序错误、返回值未检查占两成多资源管理类问题alloc/free不配对、错误路径漏释放、引用计数失衡占一成半剩下的才是逻辑语义类问题。这个分布意味着超过七成的问题是规则可描述、模式可匹配的而这恰恰是LLM擅长的区间。编码风格这块其实checkpatch.pl已经覆盖得不错但checkpatch有两个局限一是它只报这一行违反了哪条规则不告诉你这段代码整体应该怎么改二是它对跨行的模式识别能力弱比如一个多行宏定义的格式问题它可能只报其中一行。LLM在这两个点上能补位——你把一段代码贴给它它能给出完整的重写建议而且能解释为什么这样改更符合内核惯例。API使用类问题更是LLM的强项因为内核API的废弃和演进是有明确文档记录的LLM的训练数据里包含了大量的内核代码和提交历史对这个函数已经被什么替代这类知识掌握得相当好。但资源管理类问题就要小心了。LLM能识别出这里alloc了但错误路径没free这种直线型问题可一旦涉及复杂的错误处理嵌套、goto链的跳转逻辑、或者跨函数的资源所有权转移它的判断就容易出偏差。我见过它把一段正确的goto清理链误判为存在泄漏也见过它漏掉一个藏在宏展开里的释放路径。所以这一类问题LLM的输出只能当线索必须人工复核。2.2 哪些检查项适合交给LLM哪些必须人来把关我把staging review的检查项按LLM可信度做了个分级实际用下来这个分级挺稳的检查项类别LLM可信度使用方式典型例子编码风格与格式高直接采纳建议人工扫一眼行超80列、缩进用空格、注释格式废弃API识别高直接采纳查文档确认create_proc_entry应换proc_create返回值检查缺失中高采纳但需确认调用上下文copy_from_user返回值未判断简单资源泄漏中作为线索人工复核错误分支漏kfree锁使用规范中低仅作提示自旋锁里调可能睡眠的函数并发与内存序低不作为依据RCU读侧临界区边界硬件时序逻辑极低完全不依赖寄存器写入顺序这个表不是拍脑袋定的是拿一批已知结论的patch反复测试后收敛出来的。比如锁使用规范这一档LLM经常能指出这里在spinlock保护区内调用了可能睡眠的函数这种问题这个判断本身是对的但它有时会把spin_lock_irqsave和spin_lock的适用场景搞混给出的修改建议反而不对。所以这一档只能当提示不能当结论。提示不要试图用一个prompt覆盖所有检查项。按类别拆成多个专用prompt每个prompt只聚焦一类问题准确率会明显提升。混在一起问LLM容易顾此失彼而且输出冗长难读。2.3 一个反直觉的发现LLM对老代码的判断比新代码更准这点我一开始没想到。staging目录里大量代码是多年前写的用的还是老版本内核的API和惯用法。对于这类代码LLM的表现反而更稳定因为它的训练数据里这类老代码的样本量极大而且老API该换成什么是有标准答案的。反倒是刚写出来的新驱动如果用了比较新的内核特性LLM可能因为训练数据截止的问题给出过时建议。所以我在实际流程里会给代码年龄做一个粗略判断明显是老代码的patchLLM的API建议可信度高涉及较新内核版本的代码API相关的LLM输出必须对照当前内核源码树确认。3. 把LLM审查嵌入实际工作流的具体做法3.1 审查流程的整体编排我不建议把LLM做成一个一键审查的黑盒那样出了问题根本没法追溯。我的做法是把它拆成三个独立的检查阶段每个阶段有明确的输入输出和人工介入点第一阶段是预处理与分块。一个staging驱动的patch动辄几百上千行直接整段丢给LLM效果很差——上下文太长会导致注意力分散而且输出会变得笼统。我的做法是按函数边界切分每个函数单独送审同时附上该函数所在文件的头部信息include列表、宏定义、结构体定义作为上下文。这个预处理步骤用脚本自动化切分逻辑基于简单的括号匹配和函数签名识别不需要多复杂。第二阶段是分类审查。针对每个代码块依次跑风格检查prompt、API检查prompt、错误处理检查prompt。三个prompt的输出分别落到三个结果文件里互不干扰。这样做的好处是每类问题的召回率和准确率都能单独调优而且人工复核时可以按类别批量处理效率高。第三阶段是人工汇总与裁决。把三个结果文件合并按严重程度排序人工逐条过。风格类问题基本扫一眼就能确认API类问题对照内核文档确认错误处理类问题必须回到代码上下文仔细看。这个阶段是LLM输出真正转化为review意见的地方也是质量控制的关键。3.2 分块策略里的几个实操细节分块看着简单实际有几个坑。第一个坑是宏和inline函数。staging驱动里经常有大量宏定义和static inline函数这些代码块往往很短但语义密集单独送审时LLM容易因为上下文不足而误判。我的处理是把同一文件里连续的宏定义打包成一个块送审并且在prompt里明确说明以下是宏定义集合请检查是否存在参数未加括号、多语句宏未用do-while包裹等问题。第二个坑是跨函数的调用关系。一个函数调用了本文件里的另一个static函数单独看调用方可能觉得参数没问题但被调用方的实现里对参数有额外假设。LLM看不到被调用方就会漏判。我的做法是在送审调用方时把被调用函数的签名和简短注释一并附上让它至少有基本的类型和语义感知。完整实现不附太长但签名加注释这个粒度实测够用。第三个坑是条件编译。staging驱动里#ifdef CONFIG_XXX满天飞同一段代码在不同配置下走不同分支。LLM对条件编译的处理很不稳定有时会把某个分支里的代码当成无条件执行的。我的应对是在prompt里显式声明注意代码中的条件编译块分析时请分别考虑各分支并且在分块时尽量让一个条件编译块完整落在同一个块里不要从中间切断。3.3 prompt设计的核心原则prompt这块我试过很多版本最后稳定下来的原则有这么几条。第一角色和任务要窄。不要说你是一个内核专家请审查代码要说你负责检查以下C代码是否符合Linux内核编码风格重点关注行长度、缩进、空格使用、注释格式不要评论逻辑正确性。任务越窄输出越聚焦误报越少。第二给出明确的输出格式。我要求LLM按固定格式输出问题所在行号、问题类别、问题描述、修改建议。行号这个要求很关键没有行号的结果人工复核时得满代码找效率极低。虽然LLM给的行号偶尔会偏尤其是代码块较长时但大部分情况下是准的偏的时候也能根据描述定位。第三提供正反例。在prompt里塞一两个这是错误写法/这是正确写法的对照对提升准确率帮助很大。比如检查copy_from_user返回值时给一个错误直接使用未检查返回值和一个正确检查返回值并跳转错误处理的例子LLM的输出质量立竿见影地提升。第四明确禁止越界。每个prompt末尾都加一句只报告你确定的问题不确定的不要输出不要对代码的整体设计发表意见。不加这句LLM特别喜欢对驱动架构指指点点输出一堆没法落地的泛泛之谈。3.4 一个具体的prompt模板拿错误处理检查这个prompt举例我实际用的模板大致是这样你负责检查以下Linux内核驱动代码的错误处理完整性。 只关注以下问题类型 1. 内存分配kmalloc/kzalloc/devm_kzalloc等后未检查返回值 2. 错误分支中遗漏资源释放kfree/iounmap/clk_disable等 3. goto错误处理链的跳转目标与释放操作不匹配 4. copy_from_user/copy_to_user返回值未检查 输出格式每条问题一行 行号 | 问题类型 | 具体描述 | 修改建议 只报告你确定的问题。不确定的不要输出。 不要评论代码设计、性能或并发正确性。 代码如下 [代码块]这个模板跑下来错误处理类问题的召回率我粗估在七成左右准确率八成以上。漏掉的主要是藏在宏展开和复杂goto链里的情况误报主要是把某些故意不检查的场景比如某些确定不会失败的分配当成问题。这两个偏差都在可接受范围内因为最终有人工复核兜底。4. 实测中暴露的LLM审查盲区与应对手段4.1 盲区一goto清理链的跳转逻辑这是踩得最狠的一个坑。内核驱动里错误处理大量使用goto链一个函数里可能有七八个错误标签每个标签负责释放不同阶段申请的资源。LLM看这种代码时经常把某个标签是否会被跳到和跳到该标签时哪些资源已经申请这两件事搞混。我遇到过它信誓旦旦地说label_free_irq分支遗漏了释放dma_buffer实际上那个分支根本不会在dma_buffer申请之后被跳到因为申请dma_buffer的代码在跳转到该标签的条件判断之后。应对手段是强制人工复核所有涉及goto链的LLM输出并且在prompt里加一条分析goto链时请先列出每个标签的跳转来源再判断资源释放是否完整。加了这条之后LLM的输出会带上跳转分析虽然还是可能错但至少错误变得可追溯人工复核时能快速定位它哪一步推理出了问题。4.2 盲区二对内核版本演进的时序错乱LLM的训练数据是跨版本的它可能把3.x时代的惯用法和5.x时代的混在一起。典型表现是建议你把某个API换成更新的替代品但那个替代品在你实际用的内核版本里还没引入或者反过来它认为某个API已经废弃实际上在你用的版本里还是标准用法。这个盲区在staging目录尤其危险因为staging代码的目标内核版本跨度很大。应对手段很直接所有API相关的LLM建议必须对照当前内核源码树的include/和Documentation/确认。我一般会快速grep一下建议的新API是否存在于当前树的头文件里存在且签名匹配才采纳。这一步花不了多少时间但能挡掉大部分版本错乱的问题。4.3 盲区三把风格问题误升级为逻辑问题LLM有时候会把一个纯粹的格式问题描述成潜在的逻辑隐患。比如看到if (x) y 1; else y 2;这种单行写法它会说建议加大括号否则未来添加语句时可能引入逻辑错误。这话本身没错但它把一个风格建议包装成了逻辑风险容易让reviewer过度紧张。更麻烦的是反过来——把真正的逻辑问题轻描淡写成风格问题。应对手段是在prompt里严格限定问题类别并且在输出格式里要求标注严重程度。我让LLM对每条问题标注风格/潜在缺陷/确定缺陷三档人工复核时按档位分配注意力。实测下来标确定缺陷的条目准确率最高标潜在缺陷的需要仔细看标风格的基本可以直接按建议改。4.4 盲区四长代码块的注意力衰减一个函数超过两百行时LLM对后半部分的检查质量明显下降经常漏掉后面的问题。这不是staging特有的但在staging驱动里长函数特别多所以影响明显。应对手段是对超长函数做二次切分按逻辑段落比如初始化段、主循环段、清理段切开分别送审每段控制在百行以内。切分点选在空行或注释行处尽量不切断控制流。5. 从LLM输出到可提交review意见的转化技巧5.1 去重与合并三个分类prompt跑完结果里必然有重复。同一个问题可能被风格prompt和错误处理prompt都报出来比如某个错误分支的代码格式也有问题。直接合并会导致review意见冗余提交出去显得不专业。我的做法是先按行号聚类同一行号附近的条目归为一组组内按严重程度取最高档描述合并成一条。这个去重逻辑用脚本做规则简单但有效。5.2 把LLM的建议翻译成review意见LLM的输出是建议这样改但提交到邮件列表的review意见需要是这里存在什么问题、为什么需要改、建议怎么改的结构。直接转发LLM输出会显得很生硬而且有些LLM的表述不符合内核社区的沟通习惯。我的做法是人工过一遍把每条意见改写成符合社区风格的表述先指出问题引用相关编码规范或API文档作为依据再给修改方向。LLM的输出在这里只是素材最终措辞必须人来定。5.3 哪些LLM意见绝对不能直接提交有几类LLM输出我是绝对不直接提交的。一是涉及并发和锁的前面说过可信度不够二是涉及硬件寄存器操作顺序的LLM对硬件时序的理解基本靠猜三是对驱动整体架构的评论这类输出往往空泛且容易冒犯原作者四是任何带可能也许建议考虑这种模糊措辞的这种意见提交出去只会引发无谓的讨论。这四类要么人工彻底重写要么直接丢弃。注意staging子系统的维护者对patch质量的要求这些年一直在提高。LLM辅助审查能帮你把低级问题清干净但提交前自己必须完整过一遍确保每条意见你都真正理解并且能为之负责。把LLM的输出不加消化直接甩到邮件列表是很容易被社区反感的做法。6. 工具链搭建与自动化落地的经验6.1 最小可用的工具链组成不需要搞得很复杂。我的工具链就三样东西一个分块脚本Python百来行、一个调用LLM接口的脚本负责拼prompt、发请求、存结果、一个结果合并去重脚本。三个脚本串起来输入一个patch文件输出三个分类结果文件加一个合并文件。整个流程跑一个中等规模的staging驱动patch大概几分钟。分块脚本的核心逻辑是识别函数边界。我用的是简化版的正则加括号计数找到形如类型 函数名(参数)的行从该行开始计数花括号计数归零时即为函数结束。这个逻辑对绝大多数C代码够用遇到宏里的花括号会误判但staging代码里这种情况不多误判了人工调整一下分块边界就行。6.2 调用环节的几个工程细节调用LLM接口时有几个细节值得注意。一是超时和重试。长代码块的响应时间可能比较长超时设置太短会导致频繁失败。我设的是单次请求超时六十秒失败重试两次重试间隔递增。二是结果缓存。同一段代码重复送审没有意义我按代码块的哈希值缓存结果命中缓存直接返回省时间也省调用成本。三是并发控制。一个patch切出几十个块串行跑太慢但并发太高又容易触发接口限流。我实测下来并发度控制在四到六之间比较稳。6.3 结果的可追溯性设计这点容易被忽略但很重要。每个结果文件里我都记录了原始patch的标识、代码块在文件中的起止行号、使用的prompt版本、LLM返回的原始文本。这样任何一条review意见都能追溯到它是哪个代码块、哪个prompt、哪次调用产生的。出了问题能复盘prompt迭代时也能对比新旧版本的效果差异。没有这层追溯整个流程就是个黑盒用久了必然失控。6.4 和现有工具的分工LLM审查不是要替代checkpatch.pl和sparse这些工具而是补位。checkpatch负责机械规则检查速度快、零成本、结果确定该跑还得跑。sparse负责类型和地址空间检查LLM完全做不了这个。LLM的位置是在这两者之后、人工review之前处理那些规则能描述但checkpatch覆盖不到和需要一点语义理解但不到人工深度的中间地带。把分工理清楚各工具各司其职整体效率才是最高的。7. 这套做法跑下来的一些个人体会用LLM辅助staging驱动审查这段时间最大的感受是它改变的不是审查的结论而是审查的节奏。以前看一个几百行的staging patch光是清理风格问题和明显的API误用就要花掉大半时间真正用来思考逻辑正确性的精力被严重挤压。现在这些机械部分被LLM先过一遍人工直接进入需要判断力的环节同样时间能覆盖的patch量明显上去了而且对每个patch的思考深度反而更深。但边界感必须始终清楚。LLM在staging review里是个高效的初级筛查员它能把你从重复劳动里解放出来但它对内核语义的理解深度远没到可以托付关键判断的程度。我见过有人把LLM的审查结果直接当成review结论发出去结果被维护者指出一堆误判场面很难看。工具再好用最终签字负责的还得是人。prompt的迭代是个持续过程。我现在的几个prompt都是改了十几版才稳定的每改一版都拿一批已知结论的patch回归测试看召回率和准确率的变化。这个过程没法偷懒因为不同项目的代码风格差异很大通用prompt直接拿来用效果往往一般。如果你要在这套流程上落地建议先拿自己项目里几十个历史patch做基准测试把prompt调到在自己项目的代码分布上表现稳定了再正式用。最后一个实际的小建议把LLM审查结果和人工review意见分开存放不要混在一个文件里。混在一起时间长了根本分不清哪条是谁提的复盘的时候会很痛苦。分开存定期对比两者的差异既能评估LLM的实际贡献也能发现人工容易漏掉的盲区一举两得。