Linux设备驱动程序学习(20)-内存映射和DMA-基本概念

合集下载

Linux内核DMA机制-ShangShuWu

Linux内核DMA机制-ShangShuWu

Linux内核DMA机制-ShangShuWuDMA允许外围设备和主内存之间直接传输 I/O 数据, DMA 依赖于系统。

每一种体系结构DMA传输不同,编程接口也不同。

数据传输可以以两种方式触发:一种软件请求数据,另一种由硬件异步传输。

在第一种情况下,调用的步骤可以概括如下(以read为例):(1)在进程调用read 时,驱动程序的方法分配一个DMA 缓冲区,随后指示硬件传送它的数据。

进程进入睡眠。

(2)硬件将数据写入 DMA 缓冲区并在完成时产生一个中断。

(3)中断处理程序获得输入数据,应答中断,最后唤醒进程,该进程现在可以读取数据了。

第二种情形是在 DMA 被异步使用时发生的。

以数据采集设备为例:(1)硬件发出中断来通知新的数据已经到达。

(2)中断处理程序分配一个DMA缓冲区。

(3)外围设备将数据写入缓冲区,然后在完成时发出另一个中断。

(4)处理程序利用DMA分发新的数据,唤醒任何相关进程。

网卡传输也是如此,网卡有一个循环缓冲区(通常叫做 DMA 环形缓冲区)建立在与处理器共享的内存中。

每一个输入数据包被放置在环形缓冲区中下一个可用缓冲区,并且发出中断。

然后驱动程序将网络数据包传给内核的其它部分处理,并在环形缓冲区中放置一个新的 DMA 缓冲区。

驱动程序在初始化时分配DMA缓冲区,并使用它们直到停止运行。

DMA控制器依赖于平台硬件,这里只对i386的8237 DMA控制器做简单的说明,它有两个控制器,8个通道,具体说明如下:控制器1: 通道0-3,字节操作, 端口为 00-1F控制器2: 通道 4-7, 字操作, 端口咪 C0-DF- 所有寄存器是8 bit,与传输大小无关。

- 通道 4 被用来将控制器1与控制器2级联起来。

- 通道 0-3 是字节操作,地址/计数都是字节的。

- 通道 5-7 是字操作,地址/计数都是以字为单位的。

- 传输器对于(0-3通道)必须不超过64K的物理边界,对于5-7必须不超过128K边界。

linux中的MMAP和DMA

linux中的MMAP和DMA

PTRS_PER_PTE 每个页表的大小。两级处理器置 PTRS_PER_PMD 为 1,以避免处理中级。 unsigned long pgd_bal(pgd_t pgd) unsigned long pmd_val(pmd_t pmd) unsigned long pte_val(pte_t pte) 这三个宏被用来从有类型数据项中获取无符号长整数值。 这些宏通过在源码中使用严格 的数据类型有助于减小计算开销。 pgd_t *pgd_offset(struct mm_struct *mm,unsigned long address) pmd_t *pmd_offset(pgd_t *dir,unsigned long address) pte_t *pte_offset(pmd_t *dir,unsigned long address) 这些线入函数是用于获取与 address 相关联的 pgd,pmd 和 pte 项。 页表查询从一个指向 结构 mm_struct 的指针开始。与当前进程内存映射相关联的指针是 current->mm。指向 核心空间的指针由 init_mm 描述,它没有被引出到模块,因为它们不需要它。两级处理 器定义 pmd_offset(dir,add)为(pmd_t� )dir,这样就把 pmd 折合在 pgd 上。扫描页表的函 数总是被声明为 inline,而且编译器优化掉所有 pmd 查找。 unsigned long pte_page(pte_t pte) 这个函数从页表项中抽取物理页的地址。 使用 pte_val(pte)并不可行, 因为微处理器使用 pte 的低位存贮页的额外信息。这些位不是实际地址的一部分,而且需要使用 pte_page 从页表中、抽取实际地址。 pte_present(pte_t pte) 这个宏返回布尔值表明数据页当前是否在内存中。这是访问 pte 低位的几个函数中最常 用的一个——这些低位被 pte_page 丢弃。有趣的是注意到不论物理页是否在内存中, 页表始终在(在当前的 Linux 实现中) 。这简化了核心代码,因为 pgd_offset 及其它类似 函数从不失败;另一方面,即使一个有零“驻留存贮大小”的进程也在实际 RAM 中保 留它的页表。 仅仅看看这些列出的函数不足以使你对 Linux 的内存管理算法熟悉起来; 实际的内存管理要 复杂的多,而且还要处理其它一些繁杂的事,如高速缓存一致性。不过,上面列出的函数足 以给你一个关于页面管理实现的初步印象;你可以从核心源码的 include/asm 和 mm 子树中 得到更好的信息。

内存映射和DMA

内存映射和DMA

内存映射和DMA——Linux 的内存管理内存映射和DMA——Linux 的内存管理地址类型:用户虚拟地址:这是用户空间程序能看到的常规地址。

物理地址: 该地址在处理器和系统内存之间使用总线地址:该地址在外设总线和内存之间使用内核逻辑地址:组成了内核的常规地址空间。

该地至映射了部分内存,并经常被视为物理地址,在大多数体系结构中,逻辑地址与相关的物理地址的不同,仅仅在于它们之间相差一个固定的偏移量。

逻辑地址通常保存在UNSIGNED LONG ,和void*这样的内存变量中,用kmalloc返回的内存地址,就是内核逻辑地址内核虚拟地址:与逻辑地址相比,它与物理地址的映射不是线性和一一对应的。

所有的逻辑地址都是虚拟地址,而虚拟地址不一定是逻辑地址。

虚拟地址通常保存在指针变量中。

物理地址和页:物理地址被分为很多离散单元称为页,ARM中一个页的大小为4096字节/* PAGE_SHIFT determines the page size */#define PAGE_SHIFT 12 移动12位即得页帧数#define PAGE_SIZE (1UL 低端内存:存在于内核空间上的逻辑地址内存高端内存:指那些不存在逻辑地址的内存,它们处于内核虚拟地址之上内存映射和页结构:在高端内存中将无法使用逻辑地址,内存中处理内存的函数使用page结构的指针page结构中与驱动相关的成员:atomic—t count;对该页的访问计数void *virtual:如果该页被预设,则指向该页的虚拟地址,否则为NULL。

内核中维护一个或多个page数组,用来跟踪系统中的物理内存。

PAGE结构指针与虚拟地址之间的转换相关的宏:struct page *virt_to_page(void *kaddr);用来负责将逻辑地址转换为相应的page结构指针struct page *pfn_to_page(int pfn);通过给定的页祯号,返回page结构指针void *page_address(struct page*page)如果地址的话,返回该页的虚拟地址。

内存及DMA

内存及DMA

用I/O命令访问PCI总线设备配置空间pio_len = pci_resource_len (pdev, 0);mmio_start = pci_resource_start (pdev, 1);mmio_end = pci_resource_end (pdev, 1);mmio_flags = pci_resource_flags (pdev, 1);mmio_len = pci_resource_len (pdev, 1);这样看来0号bar就是提供的io映射,1号bar提供内存映射。

所以我想如果自己写一个以太网驱动的话,关于这块的代码就可以精简一下,只使用内存映射的方法就可以了。

下面是网上有人写的几种不同方式测试代码,他这些代码应该是在x86上试的,我没有试过,先分析,周末搞到arm上去跑一下。

[第一种]unsigned long mmio_start, addr1, addr2;void __iomem *ioaddr;mmio_start = pci_resource_start( pdev, 1);ioaddr = pci_iomap(pdev, 1, 0);addr1 = ioread32( ioaddr );addr2 = ioread32( ioaddr + 4 );printk(KERN_INFO "mmio start: %lX\n", mmio_start);printk(KERN_INFO "ioaddr: %p\n", ioaddr);printk(KERN_INFO "%02lX.%02lX.%02lX.%02lX.%02lX.%02lX\n",(addr1) & 0xFF,(addr1 >> 8) & 0xFF,(addr1 >> 16 ) & 0xFF,(addr1 >> 24 ) & 0xFF,(addr2) & 0xFF,(addr2 >> 8) & 0xFF );运行结果:Mar 10 22:34:56 localhost kernel: mmio start: E0000800Mar 10 22:34:56 localhost kernel: ioaddr: f8aa6800Mar 10 22:34:56 localhost kernel: 00.02.3F.AC.41.9D------------------------------------------------------------------------------------------------这种方法采用内存映射的方法,得到bar1的物理地址,mmio_start然后由pci_ioremap函数把bar1的这段地址映射到内核虚拟地址空间。

内存映射和DMA———vm_area_struct结构

内存映射和DMA———vm_area_struct结构

内存映射和DMA——vm_area_struct结构vm_area_struct结构在设备驱动中对mmp的实现中会用到。

为了优化查找方法,内核维护了VMA的链表和树型结构,vm_area-struct中很多成员函数都是用来维护这个结构的。

因此在驱动程序中不能随意创建VMA,或者打破这种组织结构。

VMA的作用是用于管理进程地址空间中不同区域的数据结构下面是这个结构体,红色部分标识了与驱动程序相关的部分,现在只能先做一个感性理解了,作为驱动工程师,应该是个实用主义者。

/** This struct defines a memory VMM memory area. There is one of these* per VM-area/task. A VM area is any part of the process virtual memory* space that has a special rule for the page-fault handlers (ie a shared* library, the executable area etc).*/struct vm_area_struct {struct mm_struct * vm_mm; /* The address space we belong to. */unsigned long vm_start; /* Our start address within vm_mm. */unsigned long vm_end; /* The first byte after our end addresswithin vm_mm. *//* linked list of VM areas per task, sorted by address */struct vm_area_struct *vm_next;pgprot_t vm_page_prot; /* Access permissions of this VMA. */unsigned long vm_flags; /* Flags, listed below. */描述该区域的一段标志,驱动程序最感兴趣的是VM—IO和VM—RESERVED。

linuxDMA接口知识点详解

linuxDMA接口知识点详解

linuxDMA接⼝知识点详解1.两种DMA映射类型1.1. ⼀致性DMA映射(Consistent DMA mappings )主要⽤于映射长时间使⽤的区域。

CPU和DMA controller不需要考虑cache的影响。

这⾥的consistent实际上是coherent的概念,不能保证consistent,也就是说需要memory barrier来保证memory order。

1.2 流式DMA映射(streaming DMA mapping)主要⽤于⼀次性DMA传输,传输完成后就会释放。

2.指定DMA设备的寻址范围include/linux/dma-mapping.h// ⽤于⼀致性内存映射的映射范围static inline int dma_set_coherent_mask(struct device *dev, u64 mask)// ⽤于流式内存映射的映射范围static inline int dma_set_mask(struct device *dev, u64 mask);3.DMA映射接⼝3.1⼀致性DMA接⼝分配较⼤DMA buffer// dev DMA控制器设备// size 要分配的DMA buffer⼤⼩// dma_handle 返回DMA buf的物理地址// flag 分配标志// 返回值 DMA buffer的虚拟地址void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag)// dev DMA控制器设备// size 释放的DMA buffer⼤⼩// cpu_addr DMA buf的虚拟地址// dma_handle DMA buf的物理地址void dma_free_coherent(struct device *dev, size_t size,void *cpu_addr, dma_addr_t dma_handle)分配较⼩DMA buffer,从dma poll中申请。

linux内存映射相关知识点

linux内存映射相关知识点

linux内存映射相关知识点说明:此文档综合了网上很多文章,并结合自己的分析,综合《情景分析》。

里面的代码是网上的,尚未亲自验证,有时间了好好搞搞,若用版权问题,请及时通知,务必删除,谢谢。

1.外设内存资源通常,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。

2.CPU对外设内存资源的访问对外部设备的访问有两种不同的形式:I/O映射方式(I/O-mapped)典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",这个存储空间与内存分属两个不同的体系,CPU无法通过访问内存的指令而只能通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元;内存映射方式(Memory-mapped)RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分。

此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。

3.Linux下对外设内存资源的操作需要注意:CPU并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围;Linux下,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访问内存指令访问这些I/O内存资源;Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB-4GB)中,原型如下:[cpp] view plaincopyprintstatic inline void __iomem * ioremap (unsigned longoffset, unsigned long size){ return__ioremap(offset, size, 0); } 在将I/O 内存资源的物理地址映射成虚地址后,理论上讲我们就可以象读写RAM那样直接读写I/O内存资源了。

Linux中关于MMAP and DMA的知识总结

Linux中关于MMAP and DMA的知识总结

3项技术:1,mmap系统调用可以实现将设备内存映射到用户进程的地址空间。

2,使用get_user_pages,可以把用户空间内存映射到内核中。

3,DMA的I/O操作,使得外设具有直接访问系统内存的能力。

-------------内存管理内核用来管理内存的数据结构---------地址内型Linux是一个虚拟内存系统,即用户程序使用的地址与硬件使用的物理地址是不等同的。

虚拟内存引入了一个间接层,使得许多操作成为可能:*有了虚拟内存,系统中运行的程序可以分配比物理内存更多的内存。

*虚拟地址还能让程序在进程的地址空间内使用更多的技巧,包括将程序的内存映射到设备内存上。

地址内型列表*用户虚拟地址每个进程都有自己的虚拟地址空间。

*物理地址处理器访问系统内存时使用的地址。

*总线地址在外围总线和内存之间使用。

MMU可以实现总线和主内存之间的重新映射。

当设置DMA操作时,编写MMU相关的代码是一个必需的步骤。

*内核逻辑地址内核逻辑地址组成了内核的常规地址空间,该地址映射了部分(或全部)内存,并经常被视为物理地址。

在大多数体系架构中,逻辑地址与其相关联的物理地址的不同,仅仅在于它们之间存在一个固定的偏移量。

kmalloc返回的内存就是内核逻辑地址。

*内核虚拟地址内核虚拟地址与逻辑地址相同之处在于,都将内核空间的地址映射到物理地址上。

不同之处在于,内核虚拟地址与物理地址的映射不是线性的和一对一的。

vmalloc返回一个虚拟地址,kmap函数也返回一个虚拟地址。

------------------物理地址和页物理地址被分为离散的单元,称之为页。

系统内部许多对内存的操作都是基于单个页的。

大多数系统都使用每页4096个字节,PAGE_SIZE <asm/page.h>给出指定体系架构下的页大小。

观察内存地址,无论是虚拟的还是物理的,它们都被分为页号和一个页内的偏移量。

如果每页4096个字节,那么最后的12位就是偏移量,剩余的高位则指定页号。

第15章 内存映射和DMA

第15章 内存映射和DMA

第15章内存映射和DMA本章研究Linux 内存管理的部分, 重点在对于设备驱动作者有用的技术. 许多类型的驱动编程需要一些对于虚拟内存子系统如何工作的理解; 我们在本章涉及到的材料来自手头, 而不是象我们曾进入更加复杂和性能关键的子系统一样. 虚拟内存子系统也是Linux 内核核心的非常有趣的部分, 并且因而, 值得一见.本章的材料分为 3 个部分:•第一部分涉及mmap 系统调用的实现, 它允许设备内存直接映射到一个用户进程地址空间. 不是所有的设备需要mmap 支持, 但是, 对一些, 映射设备内存可产生可观的性能提高.•我们接着看从其他的方向跨过边界, 用对直接存取用户空间的讨论. 相对少驱动需要这个能力; 在大部分情况下, 内核做这种映射而驱动甚至不知道它. 但是了解如何映射用户空间内存到内核(使用get_user_pages)会有用.•最后一节涵盖直接内存存取( DMA ) I/O 操作, 它提供给外设对系统内存的直接存取.当然, 所有这些技术需要一个对Linux 内存管理如何工作的理解, 因此我们从对这个子系统的总览开始.15.1. Linux 中的内存管理不是描述操作系统的内存管理理论, 本节试图指出Linux 实现的主要特点. 尽管你不必是一位Linux 虚拟内存专家来实现mmap, 一个对事情如何工作的基本了解是有用的. 下面是一个相当长的对内核使用来管理内存的数据结构的描述. 一旦必要的背景已被覆盖, 我们就进入使用这个结构.15.1.1. 地址类型Linux 是, 当然, 一个虚拟内存系统, 意味着用户程序见到的地址不直接对应于硬件使用的物理地址. 虚拟内存引入了一个间接层, 它允许了许多好事情. 有了虚拟内存, 系统重运行的程序可以分配远多于物理上可用的内存; 确实, 即便一个单个进程可拥有一个虚拟地址空间大于系统的物理内存. 虚拟内存也允许程序对进程的地址空间运用多种技巧, 包括映射成员的内存到设备内存.至此, 我们已经讨论了虚拟和物理地址, 但是许多细节被掩盖过去了. Linux 系统处理几种类型的地址, 每个有它自己的含义. 不幸的是, 内核代码不是一直非常清楚确切地在每个情况下在使用什么类型地地址, 因此程序员必须小心.下面是一个Linux 中使用的地址类型列表. 图Linux 中使用的地址类型显示了这个地址类型如何关联到物理内存.图 15.1. Linux 中使用的地址类型User virtual addresses这是被用户程序见到的常规地址. 用户地址在长度上是32 位或者64位, 依赖底层的硬件结构, 并且每个进程有它自己的虚拟地址空间. Physical addresses在处理器和系统内存之间使用的地址. 物理地址是32- 或者64-位的量;甚至32-位系统在某些情况下可使用更大的物理地址.Bus addresses在外设和内存之间使用的地址. 经常, 它们和被处理器使用的物理地址相同, 但是这不是必要的情况. 一些体系可提供一个I/O 内存管理单元(IOMMU), 它在总线和主内存之间重映射地址. 一个IOMMU 可用多种方法使事情简单(例如, 使散布在内存中的缓冲对设备看来是连续的, 例如), 但是当设定DMA 操作时对IOMMU 编程是一个必须做的额外的步骤. 总线地址是高度特性依赖的, 当然.Kernel logical addresses这些组成了正常的内核地址空间. 这些地址映射了部分(也许全部)主存并且常常被当作它们是物理内存来对待. 在大部分的体系上, 逻辑地址和它们的相关物理地址只差一个常量偏移. 逻辑地址使用硬件的本地指针大小并且, 因此, 可能不能在重装备的32-位系统上寻址所有的物理内存.逻辑地址常常存储于unsigned long 或者void * 类型的变量中. 从kmalloc 返回的内存有内核逻辑地址.Kernel virtual addresses内核虚拟地址类似于逻辑地址, 它们都是从内核空间地址到物理地址的映射. 内核虚拟地址不必有逻辑地址空间具备的线性的, 一对一到物理地址的映射, 但是. 所有的逻辑地址是内核虚拟地址, 但是许多内核虚拟地址不是逻辑地址. 例如, vmalloc 分配的内存有虚拟地址(但没有直接物理映射). kmap 函数(本章稍后描述)也返回虚拟地址. 虚拟地址常常存储于指针变量.如果你有逻辑地址, 宏__pa() ( 在<asm/page.h> 中定义)返回它的关联的物理地址. 物理地址可被映射回逻辑地址使用__va(), 但是只给低内存页.不同的内核函数需要不同类型地址. 如果有不同的 C 类型被定义可能不错, 这样请求的地址类型是明确的, 但是我们没有这样的好运. 在本章, 我们尽力对在哪里使用哪种类型地址保持清晰.15.1.2. 物理地址和页物理内存被划分为离散的单元称为页. 系统的许多内部内存处理在按页的基础上完成. 页大小一个体系不同于另一个, 尽管大部分系统当前使用4096-字节的页. 常量PAGE_SIZE (定义在<asm/page.h>) 给出了页大小在任何给定的体系上.如果你查看一个内存地址- 虚拟或物理- 它可分为一个页号和一个页内的偏移. 如果使用4096-字节页, 例如, 12 位低有效位是偏移, 并且剩下的, 高位指示页号. 如果你丢弃偏移并且向右移动剩下的部分offset 位, 结果被称为一个页帧号(PFN). 移位来在页帧号和地址之间转换是一个相当普通的操作. 宏PAGE_SHIFT 告诉必须移动多少位来进行这个转换.15.1.3. 高和低内存逻辑和内核虚拟地址之间的不同在配备大量内存的32-位系统中被突出. 用32 位, 可能寻址 4 G 内存. 但是, 直到最近, 在32-位系统的Linux 被限制比那个少很多的内存, 因为它建立虚拟地址的方式.内核( 在x86 体系上, 在缺省配置里) 在用户空间和内核之间划分4-G 虚拟地址; 在 2 个上下文中使用同一套映射. 一个典型的划分分出 3 GB 给用户空间, 和 1 GB 给内核空间. [47]内核的代码和数据结构必须要适合这个空间, 但是内核地址空间最大的消费者是物理内存的虚拟映射. 内核不能直接操作没有映射到内核的地址空间. 内核, 换句话说, 需要它自己的虚拟地址给任何它必须直接接触的内存. 因此, 多年来, 能够被内核处理的的最大量的物理内存是能够映射到虚拟地址的内核部分的数量, 减去内核代码自身需要的空间. 结果, 基于x86的Linux 系统可以工作在最多稍小于 1 GB 物理内存.为应对更多内存的商业压力而不破坏32-位应用和系统的兼容性, 处理器制造商已经增加了"地址扩展"特性到他们的产品中. 结果, 在许多情况下, 即便32-位处理器也能够寻址多于4GB 物理内存. 但是, 多少内存可被直接用逻辑地址映射的限制还存在. 这样内存的最低部分(上到 1 或 2 GB, 根据硬件和内核配置)有逻辑地址; 剩下的(高内存)没有. 在存取一个特定高地址页之前, 内核必须建立一个明确的虚拟映射来使这个也在内核地址空间可用. 因此, 许多内核数据结构必须放在低内存; 高内存用作被保留为用户进程页.术语"高内存"对有些人可能是疑惑的, 特别因为它在PC 世界里有其他的含义. 因此, 为清晰起见, 我们将定义这些术语:Low memory逻辑地址在内核空间中存在的内存. 在大部分每个系统你可能会遇到, 所有的内存都是低内存.High memory逻辑地址不存在的内存, 因为它在为内核虚拟地址设置的地址范围之外.在i386 系统上, 低和高内存之间的分界常常设置在刚刚在 1 GB 之下, 尽管那个边界在内核配置时可被改变. 这个边界和在原始PC 中有的老的640 KB 限制没有任何关联, 并且它的位置不是硬件规定的. 相反, 它是, 内核自身设置的一个限制当它在内核和用户空间之间划分32-位地址空间时.我们将指出使用高内存的限制, 随着我们在本章遇到它们时.15.1.4. 内存映射和struct page历史上, 内核已使用逻辑地址来引用物理内存页. 高内存支持的增加, 但是, 已暴露这个方法的一个明显的问题-- 逻辑地址对高内存不可用. 因此, 处理内存的内核函数更多在使用指向struct page 的指针来代替(在<linux/mm.h> 中定义). 这个数据结构只是用来跟踪内核需要知道的关于物理内存的所有事情.2.6 内核(带一个增加的补丁)可以支持一个"4G/4G" 模式在x86 硬件上, 它以微弱的性能代价换来更大的内核和用户虚拟地址空间.系统中每一个物理页有一个struct page. 这个结构的一些成员包括下列:atomic_t count;这个页的引用数. 当这个count 掉到0, 这页被返回给空闲列表.void *virtual;这页的内核虚拟地址, 如果它被映射; 否则, NULL. 低内存页一直被映射;高内存页常常不是. 这个成员不是在所有体系上出现; 它通常只在页的内核虚拟地址无法轻易计算时被编译. 如果你想查看这个成员, 正确的方法是使用page_address 宏, 下面描述.unsigned long flags;一套描述页状态的一套位标志. 这些包括PG_locked, 它指示该页在内存中已被加锁, 以及PG_reserved, 它防止内存管理系统使用该页.有很多的信息在struct page 中, 但是它是内存管理的更深的黑魔法的一部分并且和驱动编写者无关.内核维护一个或多个struct page 项的数组来跟踪系统中所有物理内存. 在某些系统, 有一个单个数组称为mem_map. 但是, 在某些系统, 情况更加复杂. 非一致内存存取( NUMA )系统和那些有很大不连续的物理内存的可能有多于一个内存映射数组, 因此打算是可移植的代码在任何可能时候应当避免直接对数组存取. 幸运的是, 只是使用struct page 指针常常是非常容易, 而不用担心它们来自哪里.有些函数和宏被定义来在struct page 指针和虚拟地址之间转换:struct page *virt_to_page(void *kaddr);这个宏, 定义在<asm/page.h>, 采用一个内核逻辑地址并返回它的被关联的struct page 指针. 因为它需要一个逻辑地址, 它不使用来自vmalloc 的内存或者高内存.struct page *pfn_to_page(int pfn);为给定的页帧号返回struct page 指针. 如果需要, 它在传递给pfn_to_page 之前使用pfn_valid 来检查一个页帧号的有效性.void *page_address(struct page *page);返回这个页的内核虚拟地址, 如果这样一个地址存在. 对于高内存, 那个地址仅当这个页已被映射才存在. 这个函数在<linux/mm.h> 中定义. 大部分情况下, 你想使用kmap 的一个版本而不是page_address.#include <linux/highmem.h>void *kmap(struct page *page);void kunmap(struct page *page);kmap 为系统中的任何页返回一个内核虚拟地址. 对于低内存页, 它只返回页的逻辑地址; 对于高内存, kmap 在内核地址空间的一个专用部分中创建一个特殊的映射. 使用kmap 创建的映射应当一直使用kunmap 来释放;一个有限数目的这样的映射可用, 因此最好不要在它们上停留太长时间. kmap 调用维护一个计数器, 因此如果 2 个或多个函数都在同一个页上调用kmap, 正确的事情发生了. 还要注意kmap 可能睡眠当没有映射可用时.#include <linux/highmem.h>#include <asm/kmap_types.h>void *kmap_atomic(struct page *page, enum km_type type);void kunmap_atomic(void *addr, enum km_type type);kmap_atomic 是kmap 的一种高性能形式. 每个体系都给原子的kmaps维护一小列插口( 专用的页表项); 一个kmap_atomic 的调用者必须在type 参数中告知系统使用这些插口中的哪个. 对驱动有意义的唯一插口是KM_USER0 和KM_USER1 (对于直接从来自用户空间的调用运行的代码), 以及KM_IRQ0 和KM_IRQ1(对于中断处理). 注意原子的kmaps 必须被原子地处理; 你的代码不能在持有一个时睡眠. 还要注意内核中没有什么可以阻止 2 个函数试图使用同一个插口并且相互干扰( 尽管每个CPU 有独特的一套插口). 实际上, 对原子的kmap 插口的竞争看来不是个问题.在本章后面和后续章节中当我们进入例子代码时, 我们看到这些函数的一些使用,15.1.5. 页表在任何现代系统上, 处理器必须有一个机制来转换虚拟地址到它的对应物理地址. 这个机制被称为一个页表; 它本质上是一个多级树型结构数组, 包含了虚拟-到-物理的映射和几个关联的标志. Linux 内核维护一套页表即便在没有直接使用这样页表的体系上.设备驱动通常可以做的许多操作能涉及操作页表. 幸运的是对于驱动作者, 2.6内核已经去掉了任何直接使用页表的需要. 结果是, 我们不描述它们的任何细节; 好奇的读者可能想读一下Understanding The Linux Kernel 来了解完整的内容,作者是Daniel P. Bovet 和Marco Cesati (O' Reilly).15.1.6. 虚拟内存区虚拟内存区( VMA )用来管理一个进程的地址空间的独特区域的内核数据结构. 一个VMA 代表一个进程的虚拟内存的一个同质区域: 一个有相同许可标志和被相同对象(如, 一个文件或者交换空间)支持的连续虚拟地址范围. 它松散地对应于一个"段"的概念, 尽管可以更好地描述为"一个有它自己特性的内存对象".一个进程的内存映射有下列区组成:•给程序的可执行代码(常常称为text)的一个区.•给数据的多个区, 包括初始化的数据(它有一个明确的被分配的值, 在执行开始), 未初始化数据(BBS), [48]以及程序堆栈.•给每个激活的内存映射的一个区域.一个进程的内存区可看到通过/proc/<pid/maps>(这里pid, 当然, 用一个进程的ID 来替换). /proc/self 是一个/proc/id 的特殊情况, 因为它常常指当前进程. 作为一个例子, 这里是几个内存映射(我们添加了简短注释)# cat /proc/1/maps look at init08048000-0804e000 r-xp 00000000 03:01 646520804e000-0804f000 rw-p 00006000 03:01 646520804f000-08053000 rwxp 00000000 00:00 040000000-40015000 r-xp 00000000 03:01 9627840015000-40016000 rw-p 00014000 03:01 9627840016000-40017000 rw-p 00000000 00:00 042000000-4212e000 r-xp 00000000 03:01 802904212e000-42131000 rw-p 0012e000 03:01 8029042131000-42133000 rw-p 00000000 00:00 0bffff000-c0000000 rwxp 00000000 00:00 0ffffe000-fffff000 ---p 00000000 00:00 0/sbin/init text /sbin/init data zero-mapped BSS /lib/ld-2.3.2.so text /lib/ld-2.3.2.so data BSS for ld.so /lib/tls/libc-2.3.2.so text /lib/tls/libc-2.3.2.so data BSS for libc Stack segment vsyscall page# rsh wolf cat /proc/self/maps #### x86-64 (trimmed)00400000-00405000 r-xp 00000000 03:01 1596291 /bin/cat text00504000-00505000 rw-p 00004000 03:01 1596291 /bin/cat data00505000-00526000 rwxp 00505000 00:00 0 bss3252200000-3252214000 r-xp 00000000 03:01 1237890 /lib64/ld-2.3.3.so 3252300000-3252301000 r--p 00100000 03:01 1237890 /lib64/ld-2.3.3.so 3252301000-3252302000 rw-p 00101000 03:01 1237890 /lib64/ld-2.3.3.so7fbfffe000-7fc0000000 rw-p 7fbfffe000 00:00 0 stackffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0 vsyscall每行的字段是:start-end perm offset major:minor inode image每个在/proc/*/maps (出来映象的名子) 对应struct vm_area_struct 中的一个成员:start end这个内存区的开始和结束虚拟地址.perm带有内存区的读,写和执行许可的位掩码. 这个成员描述进程可以对属于这个区的页做什么. 成员的最后一个字符要么是给"私有"的p 要么是给"共享"的s.offset内存区在它被映射到的文件中的起始位置. 0 偏移意味着内存区开始对应文件的开始.major minor持有已被映射文件的设备的主次编号. 易混淆地, 对于设备映射, 主次编号指的是持有被用户打开的设备特殊文件的磁盘分区, 不是设备自身.inode被映射文件的inode 号.image已被映射的文件名((常常在一个可执行映象中).15.1.6.1. vm_area_struct 结构当一个用户空间进程调用mmap 来映射设备内存到它的地址空间, 系统通过一个新VMA 代表那个映射来响应. 一个支持mmap 的驱动(并且, 因此, 实现mmap 方法)需要来帮助那个进程来完成那个VMA 的初始化. 驱动编写者应当, 因此, 为支持mmap 应至少有对VMA 的最少的理解.让我们看再struct vm_area_struct 中最重要的成员( 在<linux/mm.h> 中定义). 这些成员应当被设备驱动在它们的mmap 实现中使用. 注意内核维护VMA 的链表和树来优化区查找, 并且vm_area_struct 的几个成员被用来维护这个组织. 因此, VMA 不是有一个驱动任意创建的, 否则这个结构破坏了. VMA 的主要成员是下面(注意在这些成员和我们刚看到的/proc 输出之间的相似)unsigned long vm_start;unsigned long vm_end;被这个VMA 覆盖的虚拟地址范围. 这些成员是在/proc/*/maps中出现的头 2 个字段.struct file *vm_file;一个指向和这个区(如果有一个)关联的struct file 结构的指针.unsigned long vm_pgoff;文件中区的偏移, 以页计. 当一个文件和设备被映射, 这是映射在这个区的第一页的文件位置.unsigned long vm_flags;描述这个区的一套标志. 对设备驱动编写者最感兴趣的标志是VM_IO和VM_RESERVUED. VM_IO 标志一个VMA 作为内存映射的I/O 区.在其他方面, VM_IO 标志阻止这个区被包含在进程核转储中.VM_RESERVED 告知内存管理系统不要试图交换出这个VMA; 它应当在大部分设备映射中设置.struct vm_operations_struct *vm_ops;一套函数, 内核可能会调用来在这个内存区上操作. 它的存在指示内存区是一个内核"对象", 象我们已经在全书中使用的struct file.void *vm_private_data;驱动可以用来存储它的自身信息的成员.象struct vm_area_struct, vm_operations_struct 定义于<linux/mm.h>; 它包括下面列出的操作. 这些操作是唯一需要来处理进程的内存需要的, 它们以被声明的顺序列出. 本章后面, 一些这些函数被实现.void (*open)(struct vm_area_struct *vma);open 方法被内核调用来允许实现VMA 的子系统来初始化这个区. 这个方法被调用在任何时候有一个新的引用这个VMA( 当生成一个新进程, 例如). 一个例外是当这个VMA 第一次被mmap 创建时; 在这个情况下, 驱动的mmap 方法被调用来替代.void (*close)(struct vm_area_struct *vma);当一个区被销毁, 内核调用它的关闭操作. 注意没有使用计数关联到VMA; 这个区只被使用它的每个进程打开和关闭一次.struct page *(*nopage)(struct vm_area_struct *vma, unsigned long address, int *type);当一个进程试图存取使用一个有效VMA 的页, 但是它当前不在内存中, nopage 方法被调用(如果它被定义)给相关的区. 这个方法返回structpage 指针给物理页, 也许在从第2 级存储中读取它之后. 如果nopage方法没有为这个区定义, 一个空页由内核分配.int (*populate)(struct vm_area_struct *vm, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock);这个方法允许内核"预错"页到内存, 在它们被用户空间存取之前. 对于驱动通常没有必要来实现这个填充方法.15.1.7. 进程内存映射内存管理难题的最后部分是进程内存映射结构, 它保持所有其他数据结构在一起. 每个系统中的进程(除了几个内核空间帮助线程)有一个struct mm_struct ( 定义在<linux/sched.h>), 它含有进程的虚拟内存区列表, 页表, 和各种其他的内存管理管理信息, 包括一个旗标( mmap_sem )和一个自旋锁( page_table_lock ). 这个结构的指针在任务结构中; 在很少的驱动需要存取它的情况下, 通常的方法是使用current->mm. 注意内存关联结构可在进程之间共享; Linux 线程的实现以这种方式工作, 例如.这总结了我们对Linux 内存管理数据结构的总体. 有了这些, 我们现在可以继续mmap 系统调用的实现.[47] 许多非-x86体系可以有效工作在没有这里描述的内核/用户空间的划分, 因此它们可以在32-位系统使用直到4-GB 内核地址空间. 但是, 本节描述的限制仍然适用这样的系统当安装有多于4GB 内存时.[48] BSS 的名子是来自一个老的汇编操作符的历史遗物, 意思是"由符号开始的块". 可执行文件的BSS 段不存储在磁盘上, 并且内核映射零页到BSS 地址范围.15.2. mmap 设备操作内存映射是现代Unix 系统最有趣的特性之一. 至于驱动, 内存映射可被实现来提供用户程序对设备内存的直接存取.一个mmap 用法的明确的例子可由查看给X Windows 系统服务器的虚拟内存区的一个子集来见到:cat /proc/731/maps000a0000-000c0000 rwxs 000a0000 03:01 282652 /dev/mem000f0000-00100000 r-xs 000f0000 03:01 282652 /dev/mem00400000-005c0000 r-xp 00000000 03:01 1366927 /usr/X11R6/bin/Xorg006bf000-006f7000 rw-p 001bf000 03:01 1366927 /usr/X11R6/bin/Xorg2a95828000-2a958a8000 rw-s fcc00000 03:01 282652 /dev/mem2a958a8000-2a9d8a8000 rw-s e8000000 03:01 282652 /dev/mem...X 服务器的VMA 的完整列表很长, 但是大部分此处不感兴趣. 我们确实见到, 但是, /dev/mm 的 4 个不同映射, 它给出一些关于X 服务器如何使用视频卡的内幕. 第一个映射在a0000, 它是视频内存的在640-KB ISA 孔里的标准位置. 再往下, 我们见到了大映射在e8000000, 这个地址在系统中最高的RAM 地址之上. 这是一个在适配器上的视频内存的直接映射.这些区也可在/proc/iomem 中见到:000a0000-000bffff : Video RAM area000c0000-000ccfff : Video ROM000d1000-000d1fff : Adapter ROM000f0000-000fffff : System ROMd7f00000-f7efffff : PCI Bus #01e8000000-efffffff : 0000:01:00.0fc700000-fccfffff : PCI Bus #01fcc00000-fcc0ffff : 0000:01:00.0映射一个设备意味着关联一些用户空间地址到设备内存. 无论何时程序在给定范围内读或写, 它实际上是在存取设备. 在X 服务器例子里, 使用mmap 允许快速和容易地存取视频卡内存. 对于一个象这样的性能关键的应用, 直接存取有很大不同.如你可能期望的, 不是每个设备都出借自己给mmap 抽象; 这样没有意义, 例如, 对串口或其他面向流的设备. mmap 的另一个限制是映射粒度是PAGE_SIZE. 内核可以管理虚拟地址只在页表一级; 因此, 被映射区必须是PAGE_SIZE 的整数倍并且必须位于是PAGE_SIZE 整数倍开始的物理地址. 内核强制size 的粒度通过做一个稍微大些的区域, 如果它的大小不是页大小的整数倍.这些限制对驱动不是大的限制, 因为存取设备的程序是设备依赖的. 因为程序必须知道设备如何工作的, 程序员不会太烦于需要知道如页对齐这样的细节. 一个更大的限制存在当ISA 设备被用在非x86 平台时, 因为它们的ISA 硬件视图可能不连续. 例如, 一些Alpha 计算机将ISA 内存看作一个分散的8 位, 16 位, 32 位项的集合, 没有直接映射. 这种情况下, 你根本无法使用mmap. 对不能进行直接映射ISA 地址到Alph 地址可能只发生在32-位和64-位内存存取, ISA 可只做8-位和16-位发送, 并且没有办法来透明映射一个协议到另一个.使用mmap 有相当地优势当这样做可行的时候. 例如, 我们已经看到X 服务器, 它传送大量数据到和从视频内存; 动态映射图形显示到用户空间提高了吞吐量, 如同一个lseek/write 实现相反. 另一个典型例子是一个控制一个PCI 设备的程序. 大部分PCI 外设映射它们的控制寄存器到一个内存地址, 并且一个高性能应用程序可能首选对寄存器的直接存取来代替反复地调用ioctl 来完成它的工作.mmap 方法是file_operation 结构的一部分, 当发出mmap 系统调用时被引用. 用了mmap, 内核进行大量工作在调用实际的方法之前, 并且, 因此, 方法的原型非常不同于系统调用的原型. 这不象ioctl 和poll 等调用, 内核不会在调用这些方法之前做太多.系统调用如下一样被声明(如在mmap(2) 手册页中描述的);mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset)另一方面, 文件操作声明如下:int (*mmap) (struct file *filp, struct vm_area_struct *vma);方法中的filp 参数象在第 3 章介绍的那样, 而vma 包含关于用来存取设备的虚拟地址范围的信息. 因此, 大量工作被内核完成; 为实现mmap, 驱动只要建立合适的页表给这个地址范围, 并且, 如果需要, 用新的操作集合替换vma->vm_ops.有 2 个建立页表的方法:调用remap_pfn_range 一次完成全部, 或者一次一页通过nopage VMA 方法. 每个方法有它的优点和限制. 我们从"一次全部"方法开始, 它更简单. 从这里, 我们增加一个真实世界中的实现需要的复杂性.15.2.1. 使用remap_pfn_range建立新页来映射物理地址的工作由remap_pfn_range 和io_remap_page_range 来处理, 它们有下面的原型:int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t prot);由这个函数返回的值常常是0 或者一个负的错误值. 让我们看看这些函数参数的确切含义:vma页范围被映射到的虚拟内存区virt_addr重新映射应当开始的用户虚拟地址. 这个函数建立页表为这个虚拟地址范围从virt_addr 到virt_addr_size.pfn页帧号, 对应虚拟地址应当被映射的物理地址. 这个页帧号简单地是物理地址右移PAGE_SHIFT 位. 对大部分使用, VMA 结构的vm_paoff 成员正好包含你需要的值. 这个函数影响物理地址从(pfn<<PAGE_SHIFT) 到(pfn<<PAGE_SHIFT)+size.size正在被重新映射的区的大小, 以字节.prot给新VMA 要求的"protection". 驱动可(并且应当)使用在vma->vm_page_prot 中找到的值.给remap_fpn_range 的参数是相当直接的, 并且它们大部分是已经在VMA 中提供给你, 当你的mmap 方法被调用时. 你可能好奇为什么有 2 个函数, 但是. 第一个(remap_pfn_range)意图用在pfn 指向实际的系统RAM 的情况下, 而io_remap_page_range 应当用在phys_addr 指向I/O 内存时. 实际上, 这2 个函数在每个体系上是一致的, 除了SPARC, 并且你在大部分情况下被使用看到remap_pfn_range . 为编写可移植的驱动, 但是, 你应当使用remap_pfn_range 的适合你的特殊情况的变体.另一种复杂性不得不处理缓存: 常常地, 引用设备内存不应当被处理器缓存. 常常系统BIOS 做了正确设置, 但是它也可能通过保护字段关闭特定VMA 的缓存. 不幸的是, 在这个级别上关闭缓存是高度处理器依赖的. 好奇的读者想看看来自drivers/char/mem.c 的pgprot_noncached 函数来找到包含什么. 我们这里不进一步讨论这个主题.15.2.2. 一个简单的实现如果你的驱动需要做一个简单的线性的设备内存映射, 到一个用户地址空间, remap_pfn_range 几乎是所有你做这个工作真正需要做的. 下列的代码从drivers/char/mem.c 中得来, 并且显示了这个任务如何在一个称为simple( Simple Implementation Mapping Pages with Little Enthusiasm)的典型模块中进行的.。

linux dma 的使用流程

linux dma 的使用流程

linux dma 的使用流程下载温馨提示:该文档是我店铺精心编制而成,希望大家下载以后,能够帮助大家解决实际的问题。

文档下载后可定制随意修改,请根据实际需要进行相应的调整和使用,谢谢!并且,本店铺为大家提供各种各样类型的实用资料,如教育随笔、日记赏析、句子摘抄、古诗大全、经典美文、话题作文、工作总结、词语解析、文案摘录、其他资料等等,如想了解不同资料格式和写法,敬请关注!Download tips: This document is carefully compiled by theeditor.I hope that after you download them,they can help yousolve practical problems. The document can be customized andmodified after downloading,please adjust and use it according toactual needs, thank you!In addition, our shop provides you with various types ofpractical materials,such as educational essays, diaryappreciation,sentence excerpts,ancient poems,classic articles,topic composition,work summary,word parsing,copy excerpts,other materials and so on,want to know different data formats andwriting methods,please pay attention!Linux DMA 使用流程详解在嵌入式系统和高性能计算领域,Linux 内核中的 Direct Memory Access (DMA) 技术扮演着至关重要的角色,它允许设备直接访问内存,而无需CPU介入数据传输过程。

一文详解DMA(直接存储器访问)

一文详解DMA(直接存储器访问)

1.DMA,全称Direct Memory Access,即直接存储器访问。

DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。

我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU.CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。

比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理,DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。

有了DMA使CPU更专注于更加实用的操作–计算、控制等。

2.DMA定义:DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。

无须CPU的干预,通过DMA数据可以快速地移动。

这就节省了CPU的资源来做其他操作。

DMA传输方式DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。

四种情况的数据传输如下:外设到内存内存到外设内存到内存外设到外设3.DMA传输参数我们知道,数据传输,首先需要的是1 数据的源地址 2 数据传输位置的目标地址,3 传递数据多少的数据传输量,4 进行多少次传输的传输模式 DMA所需要的核心参数,便是这四个当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA 控制器就会启动数据传输,当剩余传输数据量为0时达到传输终点,结束DMA传输,当然,DMA 还有循环传输模式当到达传输终点时会重新启动DMA传输。

Linux内核驱动之DDR3(二)内存映射

Linux内核驱动之DDR3(二)内存映射

一 内存映射的概念上文中的内存寻址主要讲的是内存控制器如何去访问DDR3芯片基本存储单元本文中的内存映射主要讲的是如何将内存控制器管理的DDR3芯片地址空间映射到SOC芯片为DDR3预留的地址范围。

比如基于ARM的SOC芯片,DDR3的预留地址一般都是0x80000000,如果没有使用内存映射,SOC去访问0x80000000地址时会造成整个系统崩溃,因为访问的地址并不存在实际的内存DDR3控制器有两种映射模式:非交织映射和交织映射(interlave).交织映射,即双通道内存技术,当访问在控制器A上进行时,控制器B为下一次访问做准备,数据访问在两个控制器上交替进行,从而提高DDR 吞吐率。

支持128byte,256byte,512byte的交织模式。

如果要使用交织模式,要保证有两个内存控制器以及两个内存控制器有对称的物理内存(即两块内存大小一致;在各自的控制器上的地址映射一致)非交织映射,即两个内存控制器的内存映射在各自的映射范围内线性递增。

对于只存在1个内存控制器或者只使用1个内存控制器时则只能使用非交织的线性映射模式。

二 内存映射具体介绍下面以DM385和DM8168来介绍内存映射DM385有1个DDR控制器EMIF0支持JEDEC标准的DDR2,DDR3芯片.当然DM385只能使用非交织映射模式数据总线支持16bit和32bit.DM385有4个内存映射寄存器,所以最多可以映射4段地址空间DMM_LISA_MAP__0, DMM_LISA_MAP__1,DMM_LISA_MAP__2, DMM_LISA_MAP__3下图是该寄存器的具体介绍SYS_ADDR: 映射到SOC系统上的物理地址,比如需要映射到0x8000000则SYS_ADDR = 80SYS_SIZE: 映射的内存大小,讲的是主要给系统映射了多大的内存SDRC_INTL: 是否使用交织模式,以及使用何种交织模式映射SDRC_MAP: 交织映射则为3,否则为1或者2SDRC_ADDR:内存控制器的高位地址即没有映射前他的内存地址一般都是从0x00000000开始下面是我们DM385板卡的内存映射#define DDR3_DMM_LISA_MAP__0 0x00#define DDR3_DMM_LISA_MAP__1 0x00#define DDR3_DMM_LISA_MAP__3 0x80400100#define DDR3_DMM_LISA_MAP__4 0xB0400110使用了两个映射寄存器,所以主要映射了两段地址空间EMIF0 SYSTEM ADDR0x00000000 0x80000000Section 0256MB0x10000000256MB0xB0000000Section 10x1FFFFFFF从上图可以看出来第一段映射是将EMIF0的0x00000000映射到SOC系统地址0x8000000上,映射长度为256MB第二段映射是将EMIF0的0x10000000映射到SOC系统地址0xB000000上,映射长度为256MB下图是访问模式,线性访问物理地址由于DM385只有一个内存控制器EMIF0所以只能非交织映射当然对于上述映射方式可以变为如下映射#define DDR3_DMM_LISA_MAP__0 0x00#define DDR3_DMM_LISA_MAP__1 0x00#define DDR3_DMM_LISA_MAP__3 0x00#define DDR3_DMM_LISA_MAP__4 0x80500100将EMIF0的0x00000000映射到SOC系统地址0x80000000,映射长度是512MBDM8168内存映射2个DDR控制器EMIF0和EMIF1,支持JEDEC标准的DDR2,DDR3芯片DM8168支持非交织模式映射和交织模式映射数据总线支持16bit和32bit.DM8168有4个内存映射寄存器,所以最多可以映射4段地址空间下面是我们DM8168板卡的内存映射#define DDR3_DMM_LISA_MAP__0 0x00#define DDR3_DMM_LISA_MAP__1 0x00#define DDR3_DMM_LISA_MAP__3 0x80640300#define DDR3_DMM_LISA_MAP__4 0xC0640320使用了两个映射寄存器,所以主要映射了两段地址空间EMIF0 SYSTEM ADDR0x00000000 0x800000001GB512MB0x20000000512MBEMIF10x00000000 0xC00000001GB512MB0x20000000512MB从上图可知第一段映射是将EMIF0和EMIF1的0x0000000交织映射到SOC系统的0x80000000,对于系统来说了总共映射了1GB的大小,从EMIF0映射了512MB,EMIF1映射了512MB第二段映射是将EMIF0和EMIF1的0x2000000交织映射到SOC系统的0xC0000000,对于系统来说了总共映射了1GB的大小,从EMIF0映射了512MB,EMIF1映射了512MB下图是访问模式,交织访问物理地址,128字节交替映射,在交织访问模式下,系统送出的物理地址在两个内存控制器上交替访问当然也可以使用如下非交织映射#define DDR3_DMM_LISA_MAP__0 0x00#define DDR3_DMM_LISA_MAP__1 0x00#define DDR3_DMM_LISA_MAP__3 0x80600100#define DDR3_DMM_LISA_MAP__4 0xC0600200将EMIF0的0x00000000映射到SOC系统地址0x80000000,映射长度是1GB 将EMIF1的0x00000000映射到SOC系统地址0xC0000000,映射长度是1GB 使用下图的线性访问模式。

Linux系统下的内存映射原理

Linux系统下的内存映射原理

内存映射对于提供了MMU(存储管理器,辅助操作系统进行内存管理,提供虚实地址转换等硬件支持)的处理器而言,Linux提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB。

进程的4GB内存空间被人为的分为两个部分--用户空间和内核空间。

用户空间地址分布从0到3GB(PAGE_OFFSET,在0x86中他等于0xC0000000),3GB到4GB为内核空间,如下图:内核空间中,从3G到vmalloc_start这段地址是物理内存映射区域(该区域中包含了内核映像、物理页框表mem_map等等),比如我们使用的VMware虚拟系统内存是160M,那么3G~3G+160M这片内存就应该映射物理内存。

在物理内存映射区之后,就是vmalloc 区域。

对于160M的系统而言,vmalloc_start位置应在3G+160M附近(在物理内存映射区和vmalloc_start期间还存在一个8M的gap 来防止跃界),vmalloc_end的位置接近4G(最后位置系统会保留一片128k大小的区域用于专用页面映射),如下图:kmalloc和get_free_page申请的内存位于物理内存映射区域,而且在物理上也是连续的,他们和真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系,virt_to_phys()能够实现内核虚拟地址转化为物理地址:#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)extern inline unsigned long virt_to_phys(volatile void * address){return __pa(address);}上面转换过程是将虚拟地址减去3G(PAGE_OFFSET=0XC000000)。

和之对应的函数为phys_to_virt(),将内核物理地址转化为虚拟地址:#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))extern inline void * phys_to_virt(unsigned long address){return __va(address);}virt_to_phys()和phys_to_virt()都定义在include\asm-i386\io.h中。

Linux内核DMA机制

Linux内核DMA机制

DMA控制器硬件结构1DMA 通道使用的地址 (3)DMA操作函数 (4)DMA映射 (6)(1 )建立一致DMA映射 (7)(2 )建立流式DMA映射 (8)(3)分散/集中映射 (10)DMA 池 (11)一个简单的使用DMA例子 (15)DMA空制器硬件结构DMA允许外围设备和主内存之间直接传输I/O数据,DMA依赖于系统。

每一种体系结构DMA专输不同,编程接口也不同。

数据传输可以以两种方式触发:一种软件请求数据,另一种由硬件异步传输。

在第一种情况下,调用的步骤可以概括如下(以read为例):(1)在进程调用read时,驱动程序的方法分配一个DMA缓冲区,随后指示硬件传送它的数据。

进程进入睡眠。

(2)硬件将数据写入DMA缓冲区并在完成时产生一个中断。

(3)中断处理程序获得输入数据,应答中断,最后唤醒进程,该进程现在可以读取数据了。

第二种情形是在DMA被异步使用时发生的。

以数据采集设备为例:(1)硬件发出中断来通知新的数据已经到达。

(2)中断处理程序分配一个DMA g冲区。

(3)外围设备将数据写入缓冲区,然后在完成时发出另一个中断。

(4)处理程序利用DMA^发新的数据,唤醒任何相关进程。

网卡传输也是如此,网卡有一个循环缓冲区(通常叫做DMA环形缓冲区)建立在与处理器共享的内存中。

每一个输入数据包被放置在环形缓冲区中下一个可用缓冲区,并且发出中断。

然后驱动程序将网络数据包传给内核的其它部分处理, 并在环形缓冲区中放置一个新的 DMA 缓冲区。

驱动程序在初始化时分配 DMA S 冲区,并使用它们直到停止运行。

DMA 空制器依赖于平台硬件,这里只对i386的8237 DMA 控控制器做简单的说明, 它有两个控制器,8个通道,具体说明如下:控制器1:通道0-3,字节操作,端口为00-仆控制器2:通道4-7,字操作,端口咪C0-DF-所有寄存器是8 bit ,与传输大小无关。

-通道4被用来将控制器1与控制器2级联起来。

linux dma用法

linux dma用法

在Linux中,DMA(Direct Memory Access)是一种允许设备直接访问内存的技术。

DMA控制器用于管理这种直接访问,但具体的DMA控制器使用方法因硬件和驱动程序而异。

一般来说,为了使用DMA,需要遵循以下步骤:
1. 确定设备是否支持DMA。

这可以通过检查设备的规格表或相关文档来确认。

2. 配置DMA控制器。

这通常包括设置DMA通道的基地址、大小和其他相关参数。

这些设置通常需要在设备驱动程序中完成。

3. 启动DMA传输。

这通常需要向DMA控制器提交一个描述传输参数的描述符,包括源地址、目标地址、传输大小等。

4. 检查DMA传输的状态。

这可以通过读取DMA控制器的状态寄存器或使用驱动程序提供的接口来完成。

在Linux中,可以使用内核API来访问DMA控制器。

例如,在Linux 2.6内核中,可以使用dma_map_single()函数来映射一块内存用于DMA传输,使用dma_unmap_single()函数来取消内存映射,使用dma_map_sg()函数来映射一个scatter-gather列表用于DMA传输,使用dma_unmap_sg()函数来取消scatter-gather列表的映射。

需要注意的是,由于不同的硬件平台和驱动程序可能有不同的实
现方式,因此在使用DMA时需要参考特定硬件和驱动程序的文档。

关于linuxkernel中dma内存的使用

关于linuxkernel中dma内存的使用

对DMA内存的使用有两种方式:1,一致DMA映射通过dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag)来直接得到一块用于dma的内存,同时得到这一段内存的虚拟地址和总线地址,分别用于CPU和device 的访问。

通过这种方式得到的dma内存,开发者不用担心cache的问题,但是要注意在执行DMA操作之前flush write buffer。

2,流式DMA映射先通过kmalloc, get_free_pages等得到一段物理连续的内存空间(注意,除非目标平台有IOMMU,否则必须要求物理地址连续,即vmalloc分配得到的内存空间不能用于DMA操作。

)然后使用dma_map_single, dma_map_pages, dma_map_sg将之前分配的内存空间映射,得到总线地址,使之能被device访问。

这种方式不保证cache的一致性,需要开发者手动处理(调用dma_sync_single_for_cpu/device函数?);另外,必须保证内存的虚拟地址空间边界与cache line length对齐,因cache line length不确定,所以一般选择page 对齐。

一致DMA映射具有更长的生命周期,它在driver的整个生命周期内都有效,且不用关心cache 效应。

流式DMA映射则只在driver填充完要传输的内容到device完成传输这段时间内有效(理论上是从map到unmap,但有效时间如前所述),凡使用流式DMA映射的内存区域在map之后,就只对device有效,driver在unmap之前不能在读写这一段内存区域。

或者使用dma_sync_single_for_cpu由cpu获得读写权利,然后driver可对其进行读写。

dma_mmap_coherent函数

dma_mmap_coherent函数

dma_mmap_coherent函数标题:深入理解dma_mmap_coherent函数在Linux内核开发中,dma_mmap_coherent函数是一个非常重要的接口,主要用于处理直接内存访问(DMA)的内存映射问题。

本文将详细解析dma_mmap_coherent函数的工作原理和实现步骤。

一、DMA与内存映射的基本概念直接内存访问(DMA)是一种硬件技术,允许外部设备(如网卡、硬盘驱动器等)直接读写系统内存,而无需CPU的干预。

这种方式可以显著提高数据传输效率,减轻CPU的负担。

内存映射则是操作系统的一种机制,它将物理内存地址空间映射到进程的虚拟地址空间,使得进程可以直接访问物理内存。

这种机制简化了内存管理,提高了数据访问速度。

二、dma_mmap_coherent函数的作用dma_mmap_coherent函数是Linux内核提供的一种用于将DMA缓冲区映射到用户空间的接口。

它的主要作用是为DMA操作分配一段连续的物理内存,并将其映射到用户空间,使得用户程序可以直接访问这段内存。

三、dma_mmap_coherent函数的实现步骤1. 参数解析:dma_mmap_coherent函数接受四个参数,分别是struct device *dev(设备结构体指针)、struct vm_area_struct *vma(虚拟内存区域结构体指针)、void cpu_addr(指向CPU可访问的内存地址的指针)、dma_addr_t *dma_handle(指向DMA地址的指针)。

2. 内存分配:函数首先调用dma_alloc_coherent函数为DMA操作分配一段连续的物理内存。

这个函数会返回一个指向CPU可访问的内存地址的指针和对应的DMA地址。

3. 映射设置:然后,函数通过调用remap_pfn_range函数将这段物理内存映射到用户空间的虚拟地址空间。

这个函数需要传入虚拟内存区域结构体vma,以及物理页帧号、映射大小和映射权限等信息。

linux内存映射讲解

linux内存映射讲解

Linux的内存映射在讲解内存映射之前,不得不去探讨Linux内存管理方面的知识。

需要说明的是,我们并不需要深入的理解Linux虚拟内存才能去实现Linux的内存映射,所以对于Linux内存管理方面的知识也仅限于最基础的概念。

一、Linux的内存管理Linux的内存管理子系统是采用请求调页式的虚拟存储器技术实现的,有关虚拟存储器方面的知识可以参考《深入理解计算机系统》第二版的第9章内容,在这里就不做说明。

1、Linux进程的虚拟空间及其划分在32位硬件平台上,Linux的逻辑地址为32位,因此,每个进程的虚拟地址空间为4GB,在4GB的空间中,操作系统占用了高端的1GB,而低端的3GB则留给用户程序使用。

如下图所示:1) Linux内核虚拟存储器Linux中1GB的内核虚拟存储器空间又被划分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和系统保留映射区这几个区域。

一般情况下,物理内存映射区最大长度为896MB,系统的物理内存被顺序映射到物理内存映射区中。

当系统物理内存大于896MB时,超过系统物理内存的那部分内存称为高端内存(小于896MB的系统物理内存称为常规内存),内核在存取高端内存时必须将它们映射到高端内存映射区中。

下图可以反映出Linux 内核虚拟存储器与物理内存之间的映射关系。

注意:物理内存中0~896MB区域通常由内核使用,当然内核不用时用户程序可以使用;896MB以上的区域通常由用户程序来使用。

2) Linux用户虚拟存储器Linux用户虚拟存储器总是通过页表访问内存,决不会直接访问。

如下图所示:2、进程空间的描述内核为系统中的每个进程维护一个单独的任务结构task_struct。

任务结构中的元素包含或者指向内核运行该进程所需要的所有信息(例如,PID、指向用户栈的指针、可执行目标文件的名字以及程序计数器)。

task_struct中的一个条目指向mm_struct,它描述进程使用的地址空间,我们感兴趣的两个字段是pgd和mmap,其中pgd指向第一级页表(页全局目录)的基址,而mmap指向一个vm_area_struct(区域结构)的链表,每个vm_area_struct结构描述的是进程的一个用户区。

一致性DMA理解

一致性DMA理解

一致性DMA的理解这里提到的DMA设备是非PCI设备在framebuffer的prob函数中,用到了这样一个函数,下面分析下它的作用/** s3c_fb_map_video_memory():*分配DRAM的缓存区给frame buffer。

*这个缓存区是一个non-cached,non-buffered的。

*这片内存区域允许调色板和像素在写入时不刷新cache缓存。

*一旦这片区域重新映射,那么所有用来访问video memory的虚拟内存将会*对应另外一片新的区域,即另外一片物理地址*/int __init s3c_fb_map_video_memory(struct s3c_fb_info *fbi){......fbi->map_size_f1 = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE);fbi->map_cpu_f1 = dma_alloc_writecombine(fbi->dev, fbi->map_size_f1,&fbi->map_dma_f1, GFP_KERNEL);......}首先说说6410的DMA虚拟地址和物理地址的映射。

2.6.29中,比2.6.24有一些出入2.6.29内核中,在arch/arm/mm/dma-mapping.c 中实现了DMA映射的函数。

其中#define CONSISTENT_END (0xffe00000)#define CONSISTENT_BASE (CONSISTENT_END - CONSISTENT_DMA_SIZE)CONSISTENT_ENT 是DMA虚拟地址的结束地址CONSISTENT_ENT 是DMA虚拟地址的起始地址而CONSISTENT_DMA_SIZE定义在/arch/arm/include/asm/memory.h/** Size of DMA-consistent memory region. Must be multiple of 2M,* between 2MB and 14MB inclusive.*/#ifndef CONSISTENT_DMA_SIZE#define CONSISTENT_DMA_SIZE (SZ_8M + SZ_4M) //+ chachi - SZ_2M#endifDMA的大小必须是2M的整数倍。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Linux设备驱动程序学习(20)-内存映射和DMA-基本概念 (2011-09-25 15:47) 标签: 虚拟内存设备驱动程序Linux技术分类:Linux设备驱动程序
这部分主要研究 Linux 内存管理的基础知识, 重点在于对设备驱动有用的技术. 因为许多驱动编程需要一些对于虚拟内存(VM)子系统原理的理解。

而这些知识主要分为三个部分:
1、 mmap系统调用的实现原理:它允许设备内存直接映射到一个用户进程地址
空间. 这样做对一些设备来说可显著地提高性能.
2、与mmap的功能相反的应用原理:内核态代码如何跨过边界直接存取用户空间的内存页. 虽然较少驱动需要这个能力. 但是了解如何映射用户空间内存到内
核(使用 get_user_pages)会有用.
3、直接内存存取( DMA ) I/O 操作, 它提供给外设对系统内存的直接存取.
但所有这些技术需要理解 Linux 内存管理的基本原理, 因此我将先学习VM子
系统的基本原理.
一、Linux的内存管理
这里重点是 Linux 内存管理实现的主要特点,而不是描述操作系统的内存管理理论。

Linux虚拟内存管理非常的复杂,要写可以写一本书:《深入理解Linux 虚拟内存管理》。

学习驱动无须如此深入, 但是对它的工作原理的基本了解是必要的.
解了必要的背景知识后,才可以学习内核管理内存的数据结构.
Linux是一个虚拟内存系统(但是在没有MMU的CPU中跑的ucLinux除外), 意味着在内核启动了MMU 之后所有使用的地址不直接对应于硬件使用的物理地址,这些地址(称之为虚拟地址)都经过了MMU转换为物理地址之后再从CPU的内存总线中发出,读取/写入数据.
这样 VM 就引入了一个间接层, 它是许多操作成为可能: 1、系统中运行的程序可以分配远多于物理内存的内存空间,即便单个进程都可拥有一个大于系统的物理内存的虚拟地址空间. 2、虚拟内存也允许程序对进程的地址空间运用多种技巧, 包括映射程序的内存到设备内存.等等~~~
1、地址类型
Linux 系统处理几种类型的地址, 每个有它自己的含义:
用户虚拟地址:User virtual addresses,用户程序见到的常规地址. 用户地址在长度上是 32 位或者 64 位, 依赖底层的硬件结构, 并且每个进程有它自己
的虚拟地址空间.
总线地址:Bus addresses,在外设和内存之间使用的地址,但是这不是必要. 一些体系可提供一个 I/O 内存管理单元(IOMMU), 它在总线和主内存之间重映射地址.
物理地址:Physical addresses,在处理器和系统内存之间使用的地址. 有32bit 或64bit等。

以下的概念很重要,一定要理解了再往下看:
内核逻辑地址:Kernel logical addresses,他们是虚拟地址(需要经过MMU转换的地址),这些组成了常规的内核地址空间.这些地址映射了部分(也许全部)主存并且常常被当作物理内存. 在大部分的体系上, 逻辑地址和它们的相关物理地址只差一个常量偏移. 逻辑地址常常存储于 unsigned long 或者 void * 类型的变量中. 从 kmalloc 返回的内存就是内核逻辑地址.
内核虚拟地址:Kernel virtual addresses,它们都是从内核地址空间到物理地址的映射,但是内核虚拟地址并不必像逻辑地址空间一样具备线性的、一对一到物理地址的映射。

所有的逻辑地址是内核虚拟地址, 但是许多内核虚拟地址不是逻辑地址. (也就是说在启动MMU后所有的地址都是内核虚拟地址,但是有一部分可以称为内核逻辑地址,因为他们具有上面介绍的特性:线性且连续、与对应的物理地址只差一个常量偏移)
对于内核虚拟地址,vmalloc 分配的内存是虚拟地址. kmap 函数也返回虚拟地址. 虚拟地址常常存储于指针变量.
如果有内核逻辑地址, 可通过宏 __pa() ( 在 <asm/page.h> 中定义,但是也可能在它包含的头文件中)返回它关联的物理地址. 同时物理地址也可被映射回逻辑地址使用 __va(), 但是只适用于低端内存(后面会讲到).不同的内核函数需要不同类型地址.
2、物理地址和页
物理内存被划分为离散的单元称为页. 系统内部的许多内存处理都基于单个页. 页大小依赖体系结构, 大部分系统使用 4K字节为一页(ARM就是4KB/page). 在不同的体系上,常量 PAGE_SIZE (定义在 <asm/page.h>) 给出了页大小的具体定义.
1.物理内存会划分为固定大小的页来处理的原因,个人认为主要是由于MMU
的分页机制决定的(X86正常情况下MMU的页大小为4KB,ARM的MMU的
小页的页大小也是4KB)。

有的CPU的MMU分页的页大小是可调的,这可能使得内核配置的PAGE_SIZE也随之改变,比如MIPS构架的页大小可能
是4KB、8KB、16KB、32KB和64KB。

一个内存地址(虚拟或物理),它可分为一个页帧号和一个页内的偏移. 以使用4KB页为例, 低12 位有效位是偏移, 而剩下的高位为页帧号. 若忽略偏移并向右移动地址 offset 位, 结果即为页帧号 (PFN). 移位来在页帧号和地址之间转换是一个相当普通的操作. 宏 PAGE_SHIFT 告诉必须移动多少位来进行这个转换.
3、高端与低端内存
在拥有大量内存的 32bit系统中,内核逻辑地址和虚拟地址之间的差异就会突显出来。

32 bit 系统可寻址4G内存. 但是因为建立虚拟地址空间的限制(不能把所有虚拟地址空间都用完,必须留下一些作为临时的映射和IO空间映射之用),早
期的在 32bit Linux系统被限制使用少于4G的内存,比如我的DELL vostro 120笔记本,我升级到4G内存,原来的ubuntu 8.10 32bit只认到3G,且64bit根本装不上。

后来似乎是升级到了ubuntu 10.10 32bit才认到了4G内存。

内核(在x86体系的缺省配置里)划分4GB虚拟地址空间为用户空间和内核空间;在2个上下文中使用同一套映射.一个典型的划分:3GB用户空间-1GB内核空间。

内核的代码和数据结构必须要匹配这个空间,
占用内核地址空间最大部分的是物理内存的虚拟映射(包括逻辑地址映射和虚拟地址映射(如果有高端内存)). 内核不能直接操作没有映射到内核地址空间的内存. 换句话说, 内核对任何内存的访问需使用它自己的虚拟地址。

因此, 多年来, 能够被内核处理的最大物理内存量是能够映射到虚拟地址的内核部分的大小再减去内核代码自身的空间.因此,基于x86的Linux系统可以使用的最大内存量会比1GB稍小。

为了使用更多内存, 在不破坏 32bit应用和系统的兼容性的情况下,处理器制造商在产品中增加了"地址扩展"特性. 这样,即便32bit处理器也能够寻址多于4GB物理内存. 但可被直接用逻辑地址映射的内存大小限制还存在.这样内存的最低部分(根据硬件和内核配置一般是 1到2 GB, )有逻辑地址; 剩下的(高端内存)没有. 在访问一个特定高地址页前, 内核必须建立一个明确的虚拟映射来使此页可在内核地址空间中被访问. 因此, 许多内核数据结构必须放在低端内存; 高端内存主要为用户进程页所保留.
所以定义如下:
Low memory:低端内存
在内核空间中拥有逻辑地址的内存. 在大部分系统中(ARM构架几乎都是),几乎所有的内存都是低端内存.
High memory:高端内存
没有逻辑地址映射的内存,它位于内核逻辑地址范围之外,使用前必须使用vmalloc等内核函数做好映射.
在 i386 系统上, 低和高内存之间的分界在内核配置时可被改变,但常常设置在1GB以下。

这个边界与硬件无关.它是由内核自身设置的。

其实我认为这个分界线和两个内核配置因素有关:
(1)内核对于内核空间和用户空间比例的配置。

1GB :3GB、2GB :2GB、3GB :1GB等(例如比例是2GB :2GB,内核又有3G内存,则低端内存是可以超过1G的~~)(2)分配给内核虚拟地址空间的大小vmalloc,这个是可以在启动参数中设置的。

1.vmalloc=nn[KMG]
2.强制指定vmalloc区域的大小。

可用于增加vmalloc区域的最小尺寸(x86默认
128MB),也可以用于减少vmalloc的大小,增加更多的空间用于直接映射内核RAM。

当然硬件上的限制就是RAM的大小,低端内存和高端内存的和总不能超过RAM 总大小吧~~~
关于X86这方面的更详细的资料,我觉得可以参考以下链接:
linux的物理内存与线性地址空间布局--1
还有就是陈莉君老师的《解惑-linux内核空间(二)》
那么对于ARM构架来说,情况是一样的。

虽然说ARM构架中你很少能找到存在高端内存的设备,但是如果内存足够大(例如1GB、2GB的板子也不是没有,比如TI芯片DM8168的16路D1解决方案就用了1GB或2GB内存,当然其中一部分要分给视频协处理器和DSP),然后将vmalloc设置大些,也可以在系统中出现高端内存。

对于ARM构架,我稍微对内核的启动时的高低内存设置做了下跟踪,有兴趣的朋友可以看看:《Linux内核高-低端内存设置代码跟踪(ARM构架)》
最后对于LDD3的图,我感觉意思表达得不太好,自己画了一个。

我的理解是这样的,如果您觉得我错了,请指出来,先谢谢了!。

相关文档
最新文档