Linux内核scatterlist API介绍
1. 前言
我们在那些需要和用户空间交互大量数据的子系统(例如MMC[1]、Video、Audio等)中,经常看到scatterlist的影子。对我们这些“非英语母语”的人来说,初见这个词汇,脑袋瞬间就蒙圈了。scatter可翻译成“散开、分散”,list是“列表”的意思,因而scatterlist可翻译为“散列表”。“散列表”又是什么?太抽象了!
之所以抽象,是因为这个词省略了主语----物理内存(Physical memory),加上后,就好理解了多了,既:物理内存的散列表。再通俗一些,就是把一些分散的物理内存,以列表的形式组织起来。那么,也许你会问,有什么用处呢?
当然有用,具体可参考本文后续的介绍。
2. scatterlist产生的背景
我没有去考究scatterlist API是在哪个kernel版本中引入的(年代太久远了),凭猜测,我觉得应该和MMU有关。因为在引入MMU之后,linux系统中的软件将不得不面对一个困扰(下文将以图片1中所示的系统架构为例进行说明):
假设在一个系统中(参考下面图片1)有三个模块可以访问memory:CPU、DMA控制器和某个外设。CPU通过MMU以虚拟地址(VA)的形式访问memory;DMA直接以物理地址(PA)的形式访问memory;Device通过自己的IOMMU以设备地址(DA)的形式访问memory。
然后,某个“软件实体”分配并使用了一片存储空间(参考下面图片2)。该存储空间在CPU视角上(虚拟空间)是连续的,起始地址是va1(实际上,它映射到了3块不连续的物理内存上,我们以pa1,pa2,pa3表示)。
那么,如果该软件单纯的以CPU视角访问这块空间(操作va1),则完全没有问题,因为MMU实现了连续VA到非连续PA的映射。
不过,如果软件经过一系列操作后,要把该存储空间交给DMA控制器,最终由DMA控制器将其中的数据搬移给某个外设的时候,由于DMA控制器只能访问物理地址,只能以“不连续的物理内存块”为单位递交(而不是我们所熟悉的虚拟地址)。
此时,scatterlist就诞生了:为了方便,我们需要使用一个数据结构来描述这一个个“不连续的物理内存块”(起始地址、长度等信息),这个数据结构就是scatterlist(具体可参考下面第3章的说明)。而多个scatterlist组合在一起形成一个表(可以是一个struct scatterlist类型的数组,也可以是kernel帮忙抽象出来的struct sg_table),就可以完整的描述这个虚拟地址了。
最后,从本质上说:scatterlist(数组)是各种不同地址映射空间(PA、VA、DA、等等)的媒介(因为物理地址是真实的、实在的存在,因而可以作为通用语言),借助它,这些映射空间才能相互转换(例如从VA转到DA)。
图片1 cpu_dma_device_memory
图片2 cpu_view_memory
3. scatterlist API介绍
3.1 struct scatterlist
struct scatterlist用于描述一个在物理地址上连续的内存块(以page为单位),它的定义位于“include/linux/scatterlist.h”中,如下:
struct scatterlist { |
page_link,指示该内存块所在的页面。bit0和bit1有特殊用途(可参考后面的介绍),因此要求page最低4字节对齐。
offset,指示该内存块在页面中的偏移(起始位置)。
length,该内存块的长度。dma_address,该内存块实际的起始地址(PA,相比page更接近我们人类的语言)。
dma_length,相应的长度信息。
3.2 struct sg_table
在实际的应用场景中,单个的scatterlist是没有多少意义的,我们需要多个scatterlist组成一个数组,以表示在物理上不连续的虚拟地址空间。通常情况下,使用scatterlist功能的模块,会自行维护这个数组(指针和长度),例如[2]中所提到的struct
mmc_data:
struct mmc_data { … unsigned int sg_len; /* size of scatter list */ |
不过呢,为了使用者可以偷懒,kernel抽象出来了一个简单的数据结构:struct sg_table,帮忙保存scatterlist的数组指针和长度:
struct sg_table { |
其中sgl是内存块数组的首地址,orig_nents是内存块数组的size,nents是有效的内存块个数(可能会小于orig_nents)。
以上心思都比较直接,不过有一点,我们要仔细理解:
scatterlist数组中到底有多少有效内存块呢?这不是一个很直观的事情,主要有如下2个规则决定:
1)如果scatterlist数组中某个scatterlist的page_link的bit0为1,表示该scatterlist不是一个有效的内存块,而是一个chain(铰链),指向另一个scatterlist数组。通过这种机制,可以将不同的scatterlist数组链在一起,因为scatterlist也称作chain
scatterlist。2)如果scatterlist数组中某个scatterlist的page_link的bit1为1,表示该scatterlist是scatterlist数组中最后一个有效内存块(后面的就忽略不计了)。
3.3 API介绍
理解了scatterlist的含义之后,再去看“include/linux/scatterlist.h”中的API,就容易多了,例如(简单介绍一下,不再详细分析):
#define sg_dma_address(sg) ((sg)->dma_address) #ifdef CONFIG_NEED_SG_DMA_LENGTH |
sg_dma_address、sg_dma_len,获取某一个scatterlist的物理地址和长度。
#define sg_is_chain(sg) ((sg)->page_link & 0x01) |
sg_is_chain可用来判断某个scatterlist是否为一个chain,sg_is_last可用来判断某个scatterlist是否是sg_table中最后一个scatterlist。
sg_chain_ptr可获取chain scatterlist指向的那个scatterlist。
static inline void sg_assign_page(struct scatterlist *sg, struct page *page) static inline void sg_set_page(struct scatterlist *sg, struct page *page, unsigned int len, unsigned int offset) static inline struct page *sg_page(struct scatterlist *sg) static inline void sg_set_buf(struct scatterlist *sg, const void *buf, unsigned int buflen) #define for_each_sg(sglist, sg, nr, __i) \ static inline void sg_chain(struct scatterlist *prv, unsigned int prv_nents, static inline void sg_mark_end(struct scatterlist *sg) static inline dma_addr_t sg_phys(struct scatterlist *sg) |
sg_assign_page,将page赋给指定的scatterlist(设置page_link字段)。
sg_set_page,将page中指定offset、指定长度的内存赋给指定的scatterlist(设置page_link、offset、len字段)。
sg_page,获取scatterlist所对应的page指针。
sg_set_buf,将指定长度的buffer赋给scatterlist(从虚拟地址中获得page指针、在page中的offset之后,再调用sg_set_page)。for_each_sg,遍历一个scatterlist数组(sglist)中所有的有效scatterlist(考虑sg_is_chain和sg_is_last的情况)。
sg_chain,将两个scatterlist 数组捆绑在一起。
sg_mark_end、sg_unmark_end,将某个scatterlist 标记(或者不标记)为the last one。
sg_phys、sg_virt,获取某个scatterlist的物理或者虚拟地址。
等等(不再罗列了,感兴趣的同学直接去看代码就行了)。
Linux内核scatterlist API介绍的更多相关文章
- linux内核的简单介绍
linux的版本号分为两个部分:内核(Kernel)和发行套件(distribution)版本. 内核版本是linus领导下的开发小组开发出的系统内核的版本号,而发行套件是由其他组织或者厂家将linu ...
- linux内核--定时器API
/**<linux/timer.h> 定时器结构体 struct timer_list { ........ unsigned long expires; --内核希望定时器执行的jiff ...
- 例说Linux内核链表(三)
经常使用的linux内核双向链表API介绍 linux link list结构图例如以下: 内核双向链表的在linux内核中的位置:/include/linux/list.h 使用双向链表的过程,主要 ...
- 《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #2 如何编译Linux内核
HACK #2 如何编译Linux内核 本节介绍编译Linux内核的方法.当发现bug而修改源代码或者添加新功能时,就需要对内核进行重新编译,生成二进制映像文件.另外,如果想要使用发布版内核中无效的功 ...
- 《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #1 如何获取Linux内核
HACK #1 如何获取Linux内核 本节介绍获取Linux内核源代码的各种方法.“获取内核”这个说法看似简单,其实Linux内核有很多种衍生版本.要找出自己想要的源代码到底是哪一个,必须首先理解各 ...
- linux 内核视频-英本网
01.Linux内核学习入门 http://v.youku.com/v_show/id_XNjc1NzEzODAw.html02.Linux内核介绍 http:// ...
- Linux内核tracepoints
Linux内核tracepoints 简单介绍 内核中的每个tracepoint提供一个钩子来调用probe函数. 一个tracepoint可以打开或关闭.打开时,probe函数关联到tracepoi ...
- Linux 内核的文件 Cache 管理机制介绍
Linux 内核的文件 Cache 管理机制介绍 http://www.ibm.com/developerworks/cn/linux/l-cache/ 1 前言 自从诞生以来,Linux 就被不断完 ...
- Linux 内核开发—内核简单介绍
内核简单介绍 Linux 构成 Linux 为什么被划分为系统空间和内核空间 隔离核心程序和应用程序,实现对核心程序和数据的保护. 什么内核空间,用户空间 内核空间和用户空间是程序执行的两种不同的状态 ...
随机推荐
- Gradle for Android 翻译 -1
英文版电子书下载 参考:Gradle for Android 一.从 Gradle 和 AS 开始 [Getting Started with Gradle and Android Studio] ...
- Android开发之Drag&Drop框架实现拖放手势
Android3.0提供了drag/drop框架,利用此框架可以实现使用拖放手势将一个view拖放到当前布局中的另外一个view中.本文将介绍如何使用拖放框架. 一.实现拖放的步骤 首先,我们先了解一 ...
- Oracle Agile PLM Web Services 的实现
Oracle 的产品Agile PLM内置了许多Web Services,其他系统可以通过Web Servcies实现对Agile PLM系统资源的访问.快速学会使用的方法,是去Oracle的官网下载 ...
- isPostback 的原理及作用(很easy)
1.IsPostBack用来推断表单是否是回发. (不是第一次请求),是点击表单的提交button回发过来的.是否是回发与get请求还是Post请求无关.可是普通情况下回发都是Post请求. 一般Ge ...
- [Node.js]33. Level 7: Persisting Questions
Let's go back to our live-moderation app and add some persistence, first to the questions people ask ...
- 如何自定义CollectionView中每个元素的大小和间距
问题: 让每个元素大小变为104 x 104 Step 1: 在你的视图控制器头文件中实现UICollectionViewFlowLayout协议 eg: @interface XXViewContr ...
- 【转】TCP/IP详解学习笔记(一)
TCP/IP详解学习笔记 这位仁兄写得太好了. http://blog.csdn.net/goodboy1881/category/204448.aspx TCP/IP详解学习笔记(13)-T ...
- vlc的应用之三:动态调用vlc-0.9.4的libvlc.dll【转】
vlc的应用之三:动态调用vlc-0.9.4的libvlc.dll 2008-12-03 17:38:46 标签:WinForm C# libvlc.dll 休闲 vlc 原创作品,允许转载,转载时 ...
- Linux 下搭建流媒体服务器
http://blog.csdn.net/huangtaishuai/article/details/9836581 ----------------------------------------- ...
- red5源代码编译并打包公布
编译环境:ubuntu14.04/JDK7 步骤: 1.svn检出源代码(两种方式) svn co --depth empty https://github.com/Red5/red5-server ...