news 2026/4/27 13:30:10

Linux内核里这个‘物理内存拼图’:手把手带你玩转scatterlist API

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux内核里这个‘物理内存拼图’:手把手带你玩转scatterlist API

Linux内核中的物理内存拼图:scatterlist API实战指南

引言

在驱动开发的世界里,我们常常需要处理一个看似简单却异常棘手的问题:如何让DMA控制器高效地访问那些分散在物理内存各处的数据块?想象一下,你正试图将一幅被打散成数百块的拼图重新组合——这就是scatterlist要解决的核心问题。不同于用户空间连续的内存视图,内核开发者经常需要面对物理内存的碎片化现实,而scatterlist正是将这些碎片重新"拼接"成DMA可识别视图的神奇工具。

本文将带你深入scatterlist API的实战应用,从内存申请到最终释放,完整呈现一个驱动开发者需要掌握的每个关键步骤。我们将避开枯燥的理论说教,直接聚焦于那些你在实际编码时会用到的核心API和常见陷阱。无论你是在开发存储驱动、网络设备还是任何需要高效DMA传输的模块,这些技巧都将成为你的得力助手。

1. 环境准备与基础概念

1.1 scatterlist的适用场景

scatterlist主要解决三类典型问题:

  • 物理内存碎片化:系统长时间运行后,大块连续物理内存成为稀缺资源
  • 高效DMA传输:减少设备与内存间的传输次数,提升I/O性能
  • 零拷贝优化:避免数据在用户空间和内核空间之间的冗余拷贝

在以下场景中你会频繁接触scatterlist:

  • 块设备驱动(如NVMe、SATA)
  • 网络设备驱动(特别是支持SGIO的网卡)
  • 视频采集/输出设备
  • 加密加速设备

1.2 关键数据结构速览

理解几个核心结构体是使用API的前提:

struct scatterlist { unsigned long page_link; // 内存页指针+链标记 unsigned int offset; // 页内偏移 unsigned int length; // 数据长度 dma_addr_t dma_address; // DMA总线地址 }; struct sg_table { struct scatterlist *sgl; // 散列表头 unsigned int nents; // 有效条目数 unsigned int orig_nents; // 原始条目数 };

注意:page_link不仅存储页面指针,其低位还用作链标记(bit0为SG_CHAIN,bit1为SG_END),这种设计充分利用了指针对齐的特性。

2. scatterlist的申请与初始化

2.1 内存分配策略

内核为scatterlist提供了两种分配方式:

分配方式适用条件内存来源最大数量
整页分配nents ≥ SG_MAX_SINGLE_ALLOC(128)Buddy分配器128个/页
对象分配nents < SG_MAX_SINGLE_ALLOCkmalloc缓存由slab决定

典型分配流程:

struct sg_table table; int ret = sg_alloc_table(&table, nents, GFP_KERNEL); if (ret) { // 错误处理 }

2.2 分配时的常见陷阱

  • GFP标志选择:在原子上下文使用GFP_ATOMIC,否则可能导致死锁
  • 数量预估:orig_nents可能大于实际nents,预留足够空间
  • 内存泄漏:务必检查返回值,失败时可能已分配部分内存

一个健壮的分配示例:

struct sg_table *alloc_sg_table(unsigned int nents) { struct sg_table *table; int ret; table = kzalloc(sizeof(*table), GFP_KERNEL); if (!table) return ERR_PTR(-ENOMEM); ret = sg_alloc_table(table, nents, GFP_KERNEL); if (ret) { kfree(table); return ERR_PTR(ret); } return table; }

3. 组装内存拼图

3.1 关联物理页面的正确姿势

sg_set_page是关联物理页的核心API:

void sg_set_page(struct scatterlist *sg, struct page *page, unsigned int len, unsigned int offset)

典型使用场景:

struct page *page = alloc_pages(GFP_KERNEL, order); if (!page) { // 错误处理 } sg_set_page(&table.sgl[i], page, PAGE_SIZE << order, 0);

3.2 链式管理的艺术

当数据分散在多个不连续的缓冲区时,需要链式管理:

  1. 分配足够容纳所有片段的sg_table
  2. 为每个片段调用sg_set_page
  3. 使用sg_chain连接不同的sg数组
// 假设我们需要连接两个独立的sg数组 sg_init_table(sg1, SG_MAX_SINGLE_ALLOC); sg_init_table(sg2, SG_MAX_SINGLE_ALLOC); // ...填充sg1和sg2... // 将sg2链接到sg1的末尾 sg_chain(sg1, SG_MAX_SINGLE_ALLOC, sg2);

警告:链式操作会修改原始sg的page_link字段,确保在DMA取消映射后再操作

4. DMA映射实战

4.1 建立DMA视图

完成sg_table组装后,需要为DMA控制器创建映射:

int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir)

关键参数说明:

  • dev:执行DMA操作的设备
  • dir:数据传输方向(DMA_TO_DEVICE/DMA_FROM_DEVICE/DMA_BIDIRECTIONAL)

返回值是实际映射的sg数量,可能小于nents。

4.2 同步与一致性

根据设备能力选择适当的映射方式:

映射类型适用设备性能影响代码提示
一致性映射支持硬件缓存一致性高开销DMA_ATTR_FORCE_CONTIGUOUS
流式映射普通设备较低开销dma_map_sg_attrs

流式映射的同步操作:

void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir)

5. 资源释放与错误处理

5.1 安全释放模式

释放操作必须与分配配对:

// 取消DMA映射 dma_unmap_sg(dev, table.sgl, table.nents, dir); // 释放sg_table sg_free_table(table);

5.2 常见内存问题排查

  • 双释放:确保每个sg_table只释放一次
  • 映射泄漏:每个dma_map_sg必须对应dma_unmap_sg
  • 使用后释放:确保DMA操作完成后再释放资源

调试技巧:

// 检查sg是否有效 #define sg_dbg(sg) \ pr_debug("sg@%p: page=%p, offset=%u, length=%u\n", \ (sg), sg_page(sg), (sg)->offset, (sg)->length) // 遍历打印整个sg_table void dump_sg_table(struct sg_table *table) { struct scatterlist *sg; int i; for_each_sg(table->sgl, sg, table->nents, i) { sg_dbg(sg); } }

6. 性能优化技巧

6.1 预分配策略

高频操作场景建议使用预分配池:

struct sg_pool { struct sg_table table; struct list_head list; }; // 初始化预分配池 int init_sg_pool(int pool_size) { // ...创建多个预分配的sg_table... } // 从池中获取 struct sg_table *get_sg_from_pool(void) { // ...实现获取逻辑... } // 归还到池中 void put_sg_to_pool(struct sg_table *table) { // ...实现归还逻辑... }

6.2 合并相邻片段

利用sg_nextsg_is_last检测相邻片段:

struct scatterlist *sg = table->sgl; while (!sg_is_last(sg)) { struct scatterlist *next = sg_next(sg); if (sg_page(sg) + (sg->offset + sg->length) / PAGE_SIZE == sg_page(next) && (sg->offset + sg->length) % PAGE_SIZE == next->offset) { // 可以合并sg和next sg->length += next->length; sg->dma_length += next->dma_length; // 从链表中移除next... } sg = next; }

7. 真实案例:块设备驱动中的scatterlist

在NVMe驱动中,一个完整的I/O请求处理流程:

  1. 从请求队列获取bio结构
  2. 将bio转换为sg_table
  3. 建立DMA映射
  4. 提交给硬件队列
  5. 完成中断中取消映射

关键代码片段:

struct nvme_queue *nvmeq = dev->queues[qid]; struct nvme_command cmnd; struct scatterlist *sg; int nseg; // 将bio转换为scatterlist nseg = blk_rq_map_sg(q, req, nvmeq->sg); if (nseg < 0) return BLK_STS_RESOURCE; // 建立DMA映射 dma_map_sg(dev->dev, nvmeq->sg, nseg, rq_data_dir(req) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); // 准备命令 cmnd.rw.opcode = nvme_cmd_write; cmnd.rw.nsid = cpu_to_le32(ns->ns_id); cmnd.rw.slba = cpu_to_le64(sector >> shift); cmnd.rw.length = cpu_to_le16((sectors >> shift) - 1); // 设置PRP/SGL if (use_sgl) { cmnd.rw.flags |= NVME_RW_SGL_METABUF; nvme_setup_sgl(&cmnd.rw, nvmeq->sg, nseg); } else { nvme_setup_prps(&cmnd.rw, nvmeq->sg, nseg); } // 提交命令 nvme_submit_cmd(nvmeq, &cmnd);

在最近的一个项目中,我们通过优化scatterlist的预分配策略,将NVMe驱动的高负载下的IOPS提升了约15%。关键在于找到适合你工作负载的sg_table大小——太小会导致频繁分配,太大则会浪费内存。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/27 13:29:09

AutoTask终极指南:如何用5分钟实现Android自动化任务管理

AutoTask终极指南&#xff1a;如何用5分钟实现Android自动化任务管理 【免费下载链接】AutoTask An automation assistant app supporting both Shizuku and AccessibilityService. 项目地址: https://gitcode.com/gh_mirrors/au/AutoTask AutoTask是一款专业的Android自…

作者头像 李华
网站建设 2026/4/27 13:26:27

2022年最新专精特新企业信息库

01、数据介绍数据名称&#xff1a;专精特新信息库数据数据范围&#xff1a;全国专精特新企业数据年份&#xff1a;1949年-2022年02、数据指标样本数量41306条&#xff0c;27个变量指标企业名称、电话、企业类型、登记状态、更多电话、所属行业、法定代表人、邮箱、曾用名、注册…

作者头像 李华
网站建设 2026/4/27 13:22:26

惠普OMEN游戏本性能解锁终极指南:WMI BIOS控制与智能风扇调校

惠普OMEN游戏本性能解锁终极指南&#xff1a;WMI BIOS控制与智能风扇调校 【免费下载链接】OmenSuperHub 使用 WMI BIOS控制性能和风扇速度&#xff0c;自动解除DB功耗限制。 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 还在为惠普OMEN游戏本的性能限制…

作者头像 李华
网站建设 2026/4/27 13:21:20

避开那些坑:在FreeRTOS+LWIP环境下为STM32配置WolfSSL的完整避坑指南

STM32FreeRTOSLWIPWolfSSL实战&#xff1a;HTTPS通信的深度排错指南 当你在凌晨三点的实验室里盯着调试器闪烁的红灯&#xff0c;屏幕上不断刷新的TLS握手失败日志仿佛在嘲笑你的努力——这不是个例。根据2023年嵌入式安全调查报告&#xff0c;超过62%的开发者在使用轻量级TLS库…

作者头像 李华
网站建设 2026/4/27 13:17:26

如何在Windows上获得MacBook级别的触控体验:完整配置指南

如何在Windows上获得MacBook级别的触控体验&#xff1a;完整配置指南 【免费下载链接】mac-precision-touchpad Windows Precision Touchpad Driver Implementation for Apple MacBook / Magic Trackpad 项目地址: https://gitcode.com/gh_mirrors/ma/mac-precision-touchpad…

作者头像 李华
网站建设 2026/4/27 13:17:25

WPS-Zotero插件终极指南:告别跨平台文献管理的烦恼

WPS-Zotero插件终极指南&#xff1a;告别跨平台文献管理的烦恼 【免费下载链接】WPS-Zotero An add-on for WPS Writer to integrate with Zotero. 项目地址: https://gitcode.com/gh_mirrors/wp/WPS-Zotero 还在为学术写作中繁琐的文献引用而头疼吗&#xff1f;想象一下…

作者头像 李华