每周两龙:第 44 期
每周一都为大家报道 LoongArch 社区最前线的第一手新鲜资讯! 上周的进展主要有 Linux 和工具链。 详情点进来看!
因为笔者近期现实生活繁忙,本期改为周三更新前两周的内容,预计下期恢复周一更新。 本期周报的实际发稿时间是周四凌晨,因为这个点才写完——白天都在忙别的。 网页上显示的发稿时间仍然是 4 月 17 日: 这是 Docusaurus 早期为了修复一个时区 bug 而导致的又一个时区 bug。
如无特别说明,文中提及的日期、时间都为北京时间(UTC+8)。
先「马」再看
本栏目的内容具有一定延续性,将持续追踪报道 LoongArch 领域的重要或长期项目(坑)。
Linux
上期周报提到的 KFENCE
导致系统崩溃的问题,根因已被找到;Huacai Chen
提交了修复,
并已成功进入 v6.9-rc4
tag,请同学们自取。
所以原因是啥?
KFENCE 能够跟踪内存块分配、使用、释放,并对错误使用予以告警。这要求相应的虚拟地址必须由页表管理、经由 TLB 翻译——这样 KFENCE 才能将其内存池中的某些页标记为「不存在」,进而在其他地方有问题的逻辑试图访问这些页时, 使其触发缺页异常(对应 LoongArch 手册中列举的三种「页无效例外」),从而由 KFENCE 取得「事故现场」的控制权。
但是,Linux 在 LoongArch 上所采用的内核态虚拟地址,并非全部经由 TLB 翻译。实际上,为了性能——「不与用户态争 TLB」,目前 LoongArch Linux 所用的虚拟地址都是落在几个 LoongArch 直接映射窗口(DMW)之一的 1:1 映射地址。 DMW 的大小涵盖了整个物理内存范围,这使得 KFENCE 内存池中的每个页都有两种合法的地址表示:DMW 地址和 TLB 映射地址。 可以想见,如果对同一页混用两种表示方式,大概会产生一些不好的后果;而这就是近几个月来所发生的。
在 Linux 的有些驱动框架中,由于业务上关心的内容主要与页的属性相关,因此对涉及到的页,这些框架的数据结构中会记录
struct page
的指针,而非页本身的虚拟地址:这样可以省得每次访问页属性之前,都要拿着页地址去查询
struct page
。
Linux 块设备层的基本类型之一 struct bio_vec
「块 I/O 向量」就是这样一种数据结构:名字看似唬人,实际就表示「一段连续的物理内存」。
在「块设备多队列调度器」blk-mq 的「请求」struct request
中存放了一个 struct bio_vec
的指针 special_vec
;而「NVMe 核心」nvme_core
又有许多围绕 struct request
展开的逻辑,其中又恰好有一处用到了 special_vec
——discard 操作。相信爱护 SSD 的朋友们对
discard,或者它的另一个名字 TRIM 并不陌生:它对 SSD 寿命至关重要。
以上这些因素合起来,便意味着:
nvme_setup_discard
只要能成功分配内存,便用分配到的页存放 discard 参数,并将此页的struct page
指针存入special_vec
,并设置请求标志RQF_SPECIAL_PAYLOAD
「特殊载荷」;- 在请求结束时,NVMe 中断处理函数
nvme_irq
辗转调用到nvme_complete_batch_req
,从而来到nvme_cleanup_cmd
; nvme_cleanup_cmd
看到请求带有标志RQF_SPECIAL_PAYLOAD
且special_vec
是动态分配的页,便会kfree
之;kfree
需要虚拟地址而非struct page
指针,因而只能用辅助函数bvec_virt
做翻译;bvec_virt
用内存管理子系统(mm
)的辅助函数page_address
计算结果;- 在引入 KFENCE 之前,LoongArch Linux 只使用 DMW 形式的内核虚拟地址。因此截至问题被修复之前,在
LoongArch 上
page_address
计算结果都等于传入struct page
对应的 DMW 地址; kfree
收到的地址与当初kmalloc
给出的地址不一致,各种问题随即出现。