今天两条头条都不是新功能,而是把已经写错的地方纠正过来:net 核心的 netmem 引用计数一增一减对不上,四条路径正在悄悄漏内存;另一头高通把相机子系统的 6 路 CSI PHY 全链路送进主线,走完二十版才落地。
🎬 今日导读
/ section 头条 net:netmem 引用计数「加一个、减另一个」,IP-TFS / skb_split / skb_shift / 克隆 SKB 四条路径在漏内存
头条 media:高通 Kaanapali 相机子系统 v20——6 路 CSI PHY、3 路 CSID、3 个 VFE 全链路接进来
arch Apple M4 Pro/Max 第一批设备树,一份 2297 行的电源管理节点表是整台机器能不能跑起来的地基
mm memory.high 不再在收费现场回收;CXL 报的坏页被拦在伙伴系统之外
fs exfat 数据覆盖回归找到根因:转到 iomap 时丢了「清理陈旧别名」这一步
block kyber 从一个空链表里「取」出了请求——7.3-rc6 的通用保护性错误
DRM imagination 的 GPUVM 切成立即模式,为异步 VM_BIND 铺路
机制 cpuset housekeeping 检查、sched/fair 的 push task、Rust 分配器进 7.4-rc1、bpf arena 可 pin 成文件
📊 板块活跃度
/ column
数据截至 10-11 06:23 北京,近 24h 各板块真实计数:net 365 · DRM 167 · mm 109 · media 89 · fs 29 · PCI 25 · LSM 19 · block 17 · Rust 13 · arch 4 · virtio 1 · rt 0。 周末的量整体回落(比周五少了近一半),但重量反而集中在两处:net 侧是核心引用计数对称化, media 侧是高通那条走了二十版的相机子系统;fs 侧则给昨天的数据覆盖回归补上了根因。
💡 今日头条
/ section 引用计数「加一个、减另一个」:核心网络栈的四条路径正在漏内存
〔10-10 16:39 北京〕· [PATCH net-next v1 0/6] net: unify symmetric netmem and page_pool refcounting(Mina Almasry / Google)
现状 netmem(网络栈里的「一块内存」抽象,既可能是普通页,也可能是设备侧内存 net_iov) 身上挂着两套彼此独立的引用计数:一套管这块内存本身什么时候能释放 (普通页是 page->_refcount,net_iov 是 binding->ref, 统称非 pp 计数);另一套管它在 page_pool 里的回收(pp_ref_count)。 page_pool 自己持有那份非 pp 计数。带 pp_recycle=1 的 SKB 持有的是 pp 计数, 带 pp_recycle=0 的持有非 pp 计数。
痛点 问题出在核心 helper 把这两套用得不一致:skb_frag_ref() 永远加非 pp 计数(走 get_netmem()),而 skb_frag_unref() 在 skb->pp_recycle 置位时减的是 pp_ref_count。任何「在一个 pp_recycle=1 的 SKB 上 ref 分片」、 或者用写死的 recycle=false 去 unref 的路径,都是给一个计数器加、给另一个减—— 内存从此泄漏,同时 pp_ref_count 一路下溢。已经中招的路径有一串: IP-TFS、skb_split()、skb_shift(),以及克隆 SKB 的 uncloning。
方案 把分片引用计数拆成几组对称的配对,每一层明确自己动的是哪个计数: 非 pp 计数走 get/put_page()、get/put_net_iov()、 get/put_netmem();pp 计数走 napi_pp_get/put_page(); SKB 层的 netmem 走 skb_netmem_ref/unref(netmem, recycle); SKB 分片走 skb_frag_ref/unref(skb, f)。系列一共 6 帖, 最后一帖补上 747 行 kunit 测试。
为什么 原来的 API 太含糊了,含糊的代价是所有人都自己动手:驱动和上层协议 (ULP)干脆 open-code 操作 pp_ref_count, 或者各写一个一次性 helper——skb_pp_frag_ref() 就是这么来的。 与其继续补丁摞补丁,不如让「加」和「减」按计数种类成对出现, 调用点就没有办法写错。这就是这次重构真正的取舍。
效益 IP-TFS 上那个分片共享导致的 pp_ref_count 下溢被从根上修掉; 驱动与 ULP 不再需要自造 helper。新增的 kunit 测试专门盯 netmem / page_pool / SKB 分片这三者的引用计数——引用计数 bug 最怕的就是「没人能在 CI 里复现」。
下一步 v1 正在 netdev 评审。被替换掉的 skb_page_unref()、 __skb_frag_unref() 这些旧 API 会逐步消失, 之后接进来的新代码统一走新配对。
一句话点评 引用计数 bug 的典型形态就是「加一个、减另一个」——它不会当场崩,只会让内存慢慢漏、计数慢慢下溢。 这类修复的价值不在补丁大小,在于把对称性写进了 API 名字里, 让下一个人想说错都说不出。
🔗 原文
高通把相机子系统的 6 路 CSI PHY 接进主线:Kaanapali 走到第二十版
〔10-11 00:01 北京〕· [PATCH v20 00/13] media: qcom: camss: Add Kaanapali support(Hangxiang Ma / Qualcomm)
现状 在 Qualcomm 平台上,camera 采集链路由 CAMSS 驱动承担:CSIPHY 把 MIPI CSI-2 的差分信号收进来, CSID 解出像素,VFE(Video Front End)处理后再从 RDI 端口交给 /dev/videoX。 这条 RDI(Raw Dump Interface)路径做的是原始数据直接搬进内存,不做 ISP 后处理。
痛点 新一代 Kaanapali 的相机子系统换了几块硬件:CSIPHY 变成两相(two-phase)的 v2.4.0、 CSID 与 VFE 上了 1080 版本、TPG(测试图案发生器)也升到 v2.4.0。 更麻烦的是它需要 CAMNOC 的 qdss_debug_xo 时钟才能工作—— 老驱动既认不出这些新版本号,也没有这条时钟。
方案 13 帖按维护者一分为二。PHY 侧(Vinod Koul)3 帖:加 Kaanapali CSI2 PHY, 并把公共状态寄存器的偏移参数化——硬件变了,寄存器布局也跟着挪, 不如让偏移变成数据。CAMSS 侧(Bryan O'Donoghue)10 帖:加 Kaanapali compatible、 给 csiphy / csid / vfe / tpg 各自补上新版本支持,再补上 Kaanapali 的 CAMSS 与 CSIPHY 设备树块、CCI 控制器节点、相机 MCLK pinctrl,最后叠一块 S5KJN5 传感器的 DT overlay。
为什么 这一版把 RDI only 写在开头——只做 raw dump 那条最直接的路径, ISP 后处理不上。对一个刚接入主线的平台,这是最稳的切法: 先让数据能出来、能验证,再谈处理管线。反过来一次全上,评审会卡在细节里出不来。
效益 硬件规模是 6×CSIPHY、3×TPG、3×CSID + 2×CSID Lite、 3×VFE(每个 5 个 RDI)+ 2×VFE Lite(每个 4 个 RDI)。 作者用 S5KJN5 在 4096×3072 SGBRG10 上跑通了完整链路: media-ctl 配 msm_csiphy2 → msm_csid0 → msm_vfe0_rdi0, 再用 yavta 抓 20 帧。
下一步 v20 的改动全是评审意见的收尾:去掉 VFE 里冗余的空检查、给 CSIPHY 节点定义 port 属性、 把 CCI 的 I2C 引脚从 bias-pull-up 改成 bias-disable(板级已有外部上拉, 内部弱上拉反而添乱)。PHY 与 CAMSS 两部分各自走对应的维护者树。
一句话点评 二十版才收敛,说明这条路径的评审有多细。对做 camera 驱动的人, 这 13 帖是一份现成的新平台接入清单——PHY、解码、前端、时钟、DT,一样都不能少。
🔗 原文
📰 arch 平台
/ column ★ Apple M4 Pro/Max 的第一批设备树:先只点亮最少的那部分硬件
〔10-11 00:59 北京〕· [PATCH 00/10] Initial Apple Silicon M4 Pro/Max device trees and dt-bindings(Yureka Lilian)
定位 arm64 上的 Apple Silicon 平台支持(arch/arm64/boot/dts/apple/), 从「能被认出来」这一步做起。
做法 10 帖里 9 帖是 dt-bindings——watchdog、AIC2 中断控制器、CPU core、pmgr 电源管理、 PWM、pinctrl、i2c 各自加上 t6041 compatible;剩下一帖是设备树本身: 给 MacBook Pro(j614 / j616)与 Mac mini / Mac Studio(j575 / j773)建最小 dts。 其中 t6041-pmgr.dtsi 一份就 2297 行——那是从硬件里导出来的电源域节点表。
效益 作者说这些硬件在 M3 与 M4 之间基本没变,所以这批 dtsi 以后能直接复用到 M5 Pro/Max。 整份没有驱动改动,合入走 apple-soc 树。
和你相关 平台 bring-up 的第一步永远是「让它能被认出来」。这份 3364 行的系列里真正有信息量的是 pmgr 那张表——电源域是一台机器能不能跑起来的地基,也是后来所有驱动的前提。
🔗 原文
📰 mm 内存管理
/ column ★ memory.high 不再在收费现场回收:内核线程与「远程收费」改交给 worker
〔10-11 05:10 北京〕· [PATCH for-7.5 0/5] memcg: clean up memory.high enforcement(Shakeel Butt)
定位 cgroup v2 内存控制器(mm/memcontrol.c)里 memory.high 这条「软上限」的执法路径。 它不像 memory.max 那样硬拦,而是超了就触发回收、给用户态一点压力。
做法 把执法逻辑从 try_charge_memcg() 里抽出来,改由 high_work 这个 worker 承担两类特殊收费:内核线程——它们永远不会回到用户态去跑延迟回收, 而且在收费路径上回收、睡眠还会拖慢它们替别人干的活;远程收费—— 收费记到的 cgroup 和调用者的 mm 不是同一个(比如文件系统通知,或者替别的进程缺页), 原来的 high handler 用 current->mm 判断限额, 看的会是调用者的限额而不是被收费 cgroup 的限额。
效益 用 mm_match_cgroup() 识别出「不在当前 mm 的 cgroup 及其祖先里」的收费, 这类改走 worker;本地用户态收费保持原样。另外跳过 PF_MEMALLOC 任务排队—— 回收本身也会收费(比如 zswap 存页时),不加这道闸,high_work 会给自己续命。 作者实测一个不可压缩的 zswap 场景,不带这道闸时它给自己的回收排了 163 次队。
下一步 作者写明了这是 best effort:快手分配器仍能把用量顶在 memory.high 之上; worker 路径也不做 memory.swap.high 的限流与 high 事件上报。 memory.max 与 memory.swap.max 的检查不变。系列基于 next-20261009,五帖里首尾两帖不改行为。
和你相关 「在收费现场做回收」是 memcg 里反复被拿出来讨论的取舍——快,但会把延迟塞进别人的上下文。 这次是把两种最不该在现场做的情况挑出来外包,思路可以直接搬到任何「同步路径上做重活」的地方。
🔗 原文
★ CXL 报出来的坏页别进伙伴系统:一份把「毒」拦在分配器之外的 RFC
〔10-11 00:21 北京〕· [RFC PATCH 0/5] cxl, mm: Keep device-reported poison out of the page allocator
定位 CXL 内存设备会上报介质损坏的地址表(poison),而 mm 的页分配器是决定 「这块内存能不能给别人用」的关口。RFC 动的是这两者之间的接口。
做法 加一份上线前的毒页注册表:CXL 侧先把设备侧的 poison 列表取快照, 在区域把这段内存当作 RAM 暴露给系统之前就登记进去; mm/page_alloc 在把页交给伙伴系统时检查它是否在表里。
效益 目标是让已知损坏的地址根本不进入分配器, 而不是分配出去之后靠 memory-failure 事后兜底。RFC 阶段,接口与命名大概率还会变。
和你相关 「坏硬件的坏消息如何在子系统之间传递」是驱动与核心之间最容易扯皮的地方。 这份 RFC 换了个角度:不问谁负责修,先问谁先知道、谁负责拦。
🔗 原文
📰 net 网络
/ column ★ vsock 的 RX worker 不再每包抢一次锁:把一个包的开销摊到一批上
〔10-10 22:23 北京〕· [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead
定位 virtio-vsock 的接收路径——虚拟机与宿主之间那条 socket 通道的 RX worker。 它的开销大头不在搬数据,而在每个包都要重新查 socket、重新抢一次锁。
做法 五步逐层把锁开销摊开:把 socket 查找从加锁的包处理里拆出来;对同一个 native socket 上 一连串连续的 STREAM/RW 包,只持一次 socket 锁;后面同地址元组的包复用这把锁; 在锁还持着的时候把默认的写空间通知合并掉;默认的「可读」回调推迟到整批解锁之后再发。
效益 作者给了完整的基准方法:单流 vsock 连接、接收线程与 virtio 中断分别钉在不同核、 传输量随 payload 等比放大以避免长跑被宿主噪声污染,共 16 组 AB/BA 配对。 这类「每包一次锁」变「每批一次锁」对虚拟化里的高包速率场景是直接收益。
和你相关 这个套路——把逐包操作攒成批、把通知合并到批边界——在 NAPI、io_uring、 以及任何 RX 批处理路径上都通用,值得当模板记。
🔗 原文
★ devmem 的两处「设备注销时没收尾」:netlink socket 关掉时踩已释放的 dma_dev
〔10-10 10:55 北京〕· [PATCH net v1 0/4] net: devmem: fix RX and TX binding teardown on device unregistration(Mina Almasry / Google)
定位 devmem TCP——把一块 dma_buf 直接绑到网络收发路径上。绑定对象(binding)分 RX / TX 两种, 各自跟着队列或设备的生命周期走。
做法 RX 侧:在最后一个接收队列卸载时,同步在 binding->lock 下解绑并 unmap dma_buf——原来 netlink socket 会拖到 dma_dev 释放之后才关, 于是 dma_unmap_sg_attrs() 里撞上 slab-use-after-free。 TX 侧:补一个 NETDEV_UNREGISTER 钩子,设备注销时把 TX binding 完整解绑并 unmap。
效益 顺带给 netdevsim 加上 devmem RX/TX 绑定能力,于是这两个生命周期 bug 可以在软件里用 selftest 复现——两个新测试直接进了 tools/testing/selftests/net/nl_netdev.py。
和你相关 「谁先死」是驱动里最经典的顺序问题。这条值得抄的地方不是修法, 而是修 bug 的同时补了一条能在 CI 里跑的复现路径——否则同一个坑还会被人挖第二遍。
🔗 原文
📰 media 视频采集
/ column ★ 摄像头模块卸载一次就回不来了:ipu-bridge 把软件节点的生命周期理清楚
〔10-10 23:56 北京〕· [PATCH v4 0/2] media: ipu-bridge: survive module unload and reuse the software nodes on rebind
定位 Intel IPU6 平台上,ipu-bridge 负责替 MIPI 传感器建「软件节点」 (software nodes:用软件而非固件描述设备属性,给 ACPI 描述不全的传感器兜底)。
做法 两处:一是别再从软件节点里引用模块镜像本身(模块卸载后那个引用就悬空了), 二是重绑定时复用已有的软件节点,而不是重新建一套。
效益 修的是条真实序列:PCI remove → 模块卸载 → rescan → modprobe。 今天这条走到第二次 prob 就会以 -EEXIST 直接失败。 作者在 Surface Pro 7+(IPU6,OV5693 + OV8865 + OV7251)上验证三颗相机 在每次重绑定后都能出流。
和你相关 「软件节点」是 ACPI 描述不全时的补丁式方案,它的引用与生命周期归谁管一直是模糊地带。 这条把一个具体到不能再具体的运维动作(卸载再加载)修通了—— 这类问题在产线上比在任何理论分析里都更容易被撞到。
🔗 原文
📰 fs 文件系统
/ column ★ exfat 的数据覆盖回归有解了:转到 iomap 时丢了「清理陈旧别名」这一步
〔10-11 03:15 北京〕· [PATCH] exfat: clean stale bdev buffers of newly allocated clusters
定位 exfat 的目录项与文件数据都通过块设备的 buffer_head 读写。删除目录项,会把那个 buffer 弄脏。
做法 如果这个目录随后被删掉、它占的簇又分给了文件数据,而写回还没发生, 那个脏 buffer 就留在块设备映射里;下一次设备级 flush(fsync / syncfs / umount) 会把它写回去,覆盖掉新文件的数据——文件静静读回时, 有一个扇区已经被替换成已删目录项的内容。修法是在 exfat_alloc_cluster() 里对每一段连续分配的簇清理陈旧别名。
为什么 这个 bug 是 iomap 迁移带出来的:数据 I/O 还走 exfat_get_block() 的时候, __block_write_begin_int() 与 do_direct_IO() 会替新分配的块清理别名;换成 iomap 之后没人再做这件事。 修在选择分配时刻而不是映射时刻清理,是为了覆盖所有分配路径—— 缓冲写、直接写,以及 fallocate() 与扩展 truncate 这类「先分配、后写数据」的路径。
效益 复现器在 KVM 里跑:删目录项 → 删目录 → 用文件覆盖刚释放的簇, 覆盖了缓冲写(含与不含 fsync)、O_DIRECT、fallocate 之后、扩展 ftruncate 之后, 以及释放簇落在碎片化分配中间的情形。 不带补丁 55/55 全部损坏,带补丁 0/55。 补丁带 Fixes 标签,指向两个 iomap 提交。
和你相关 昨天报的回归,今天有了根因与修复。这是一个标准案例:框架迁移时, 老的隐式清理动作最容易漏,而且漏了之后的表现是数据损坏而不是崩溃。
🔗 原文
📰 block 块层
/ column ★ kyber 从一个空链表里「取」出了请求:7.3-rc6 的通用保护性错误
〔10-10 15:44 北京〕· [BUG] 7.3-rc6: GPF in __blk_mq_do_dispatch_sched - kyber returns entry from empty khd->rqs list
定位 blk-mq 的 kyber 调度器——按读/写/丢弃三个域分别排队, 用 sbitmap(kcq_map)记录哪个域还有活。
做法 报告者在 7.3-rc6 上重压写(装 Flatpak)+ NVMe(dm-crypt/LUKS + btrfs)时拿到 GPF。 解码寄存器后定位到:kyber_dispatch_cur_domain() 把链表头本身当成请求返回了。可能的路径是 sbitmap_any_bit_set(&khd->kcq_map[WRITE]) 为真, 但 kyber_flush_busy_kcqs() 实际什么都没摘出来, 而代码用的是 list_first_entry()(不是 _or_null)—— 对空链表取首元素。那段代码注释里的假设是「kcq_map 的位被置上就一定有排队的请求」。
效益 开 slub_debug=FZP 让它在越界读的第一时间就报出来 (RAX 读到的是紧邻 khd 前面的 slub 红区)。这类「位图说有货、链表说没有」的不一致, 如果只在别的分配器配置下发生,会被伪装成一个难以复现的随机崩溃。
和你相关 这是状态机里「两个数据结构不同步」的经典形态。对写块层、 或者任何「bit 图 + 链表」双结构的代码,这一课很值: 置位与入队如果不是同一个原子动作,取元素就必须用 _or_null 兜住。
🔗 原文
📰 DRM 显示
/ column ★ 为异步 VM_BIND 铺路:imagination 的 GPUVM 切成「立即模式」
〔10-11 01:22 北京〕· [PATCH 0/2] drm/imagination: Switch the GPUVM to immediate mode
定位 drm/imagination(Imagination PowerVR 驱动)的 GPU 虚拟内存管理, 以及压在它上面的 VM_BIND 接口。
做法 把 GPUVM 从延迟更新改成立即模式——不再把映射与解绑攒起来批量提交, 而是立即生效。顺带修掉 Sashiko 报出的一个问题:更新其它对象的 GPUVA 链表时, 没有持有它们各自的 reservation。
效益 这是异步 VM_BIND 的前置条件(作者说改版后的 VM_BIND 系列会单独发)。 在 BeagleY-AI 上开 lockdep / KASAN / failslab 跑, IGT 的 imagination 测试全过,包括 pvr_vm_map。
和你相关 「先把同步路径改成立即生效,再做异步」是 VM_BIND 这类接口的常规推进节奏。 两条补丁加起来只动了 pvr_vm.c 的 80 行, 但它是后面大改的地基——这种「看起来什么都没做」的补丁,往往最值得读。
🔗 原文
📌 机制雷达:5 条跨域改动
/ section cpuset housekeeping 把 isolated_cpus_can_update() 合并进 prstate_housekeeping_conflict(), 做 housekeeping 检查时考虑全部独占 CPU,并修掉分区状态切换失败时没能正确禁用分区的问题(v2,6 帖)· 原文 sched/fair push task 把「cache-aware 调度触发的 push」与其它 push 分开——只有前者会把任务推向首选 LLC。 讨论里提到唤醒路径上做 cache-aware 迁移会因多 CPU 无同步地推向同一 LLC 造成任务弹跳, 所以当前做法只在 push 路径生效,并要回头确认新内核里是否还成立 · 原文 Rust 分配器进 7.4-rc1 merge window 的拉取请求开始出现:KBox::pin_slice() 允许不同错误类型 (于是可以用不会失败的初始化器)、新增 NumaNode::id(), 并给 Rust [ALLOC] 加了 Alice Ryhl 作为共同维护者(3 帖)· 原文 bpf arena 可 pin 成文件 新增 arena 标志以保留已分配的页,把 pin 住的 arena 暴露成有大小限制的 bpffs 文件映射; 配套 selftest 测试两个客户机双向访问同一个 arena(bpf-next,5 帖)· 原文 hazptr 讨论继续 hazard pointer 这套新的无锁回收原语(RFC v3,15 帖)这一轮在改共享扫描等待者的 Bloom filter 与 try-acquire 快路径,争议集中在内存序与溢出链表的扫描成本上 · 原文
📖 本期概念速查
/ section netmem 网络栈里「一块内存」的抽象,既可能是普通 page,也可能是设备侧内存 net_iov。
pp_recycle SKB 上的标志位:置位表示这份分片属于 page_pool,回收走 pp_ref_count,而不是普通页引用计数。
CAMSS / RDI CAMSS 是高通的相机子系统驱动;RDI(Raw Dump Interface)是 VFE 里把原始数据直接搬进内存的出口。
软件节点(software nodes) 用软件而非固件描述设备属性的机制,专门给 ACPI 描述不全的传感器兜底。
bdev 别名 同一块磁盘扇区被页缓存以多个 buffer 引用时的俗称;清理陈旧别名 = 把已经失效的那些引用丢掉。
GPUVM / VM_BIND GPU 驱动的虚拟内存管理,以及「用户态直接提交映射」的接口;立即模式指映射不做批量延迟提交。
如果对你有用,点个赞,或转给需要的朋友。
数据来源:lore.kernel.org(全内核 13 列表)· 北京时间