⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 (ldd) ch13-mmap和dma(转载).txt

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 4 页
字号:
      ,核心会将相关RAM页的使用计数减小。因此,驱动程序必须增加它所映射的每个页的使
      用计数(注意,这个计数在nopage增加它时不能为零,因为该页已经被fops->write分配
      了)。
       
      l         只要设备是被映射的,scullp就不能释放设备内存
      这与其说是个要求,不如说是项政策,这与scull及类似设备的行为不同,因为它们在被

      这与其说是个要求,不如说是项政策,这与scull及类似设备的行为不同,因为它们在被
      因写而打开时长度被截为0。拒绝释放被映射的scullp设备允许一个设备一个进程重写正
      被另一个进程映射的区段,这样你就可以测试并看到进程与设备内存之间是如何交互的
      。为避免释放一个被映射的设备,驱动程序必须保存一个活动映射的计数;设备结构中
      的vma域被用于这个目的。
       
      l         只有在scullp的序号order参数为0时才进行内存影射
      这个参数控制get_free_pages是如何调用的(见第七章中“get_free_pages和朋友们”
      一节)。这个选择是由get_free_pages的内部机制决定的——scullp利用的分配机制。
      为了最大化分配性能,Linux核心为每个分配的序号(order)维护一个空闲页的列表,
      在一个簇中只有第一页的页计数由get-free_pages增加和free_pages减少。如果分配序
      号大于0,那么对一个scullp设备来说mmap方法是关闭的,因为nopage只处理单项,而不
      是一簇页。
       
       
       
      最后一个选择主要是为了保证代码的简单。通过处理页的使用计数,也有可能为多页分
      配正确地实现mmap,但那样只能增加例子的复杂性,而不能带来任何有趣的信息。
       
      如果代码想按照上面提到的规则来映射RAM,它需要实现open,close和nopage,还要访
      问mem_map。
       
      scullp_mmap的实现非常短,因为它依赖于nopage来完成所有有趣的工作:

      scullp_mmap的实现非常短,因为它依赖于nopage来完成所有有趣的工作:
       
      (代码285  #1)
       
      开头的条件语句是为了避免映射未对齐的偏移和分配序号不为0的设备。最后,vm_ops->
      open被调用以更新模块的使用计数和设备的活动映射计数。
       
      open和close就是为了跟踪这些计数,被定义如下:
       
      (代码285  #2)
       
      由于模块生成了4个scullp设备并且也没有内存区域可用的private_data指针,所以open
      和close取得与vma相关联的scullp设备是通过从inode结构中抽取次设备号。次设备号被
      用来从设备结构的scullp_devices数组取偏移后得到指向正确结构的指针。
       
      大部分工作是由nopage完成的。当进程发生页面错时,这个函数必须取得被引用页的物
      理地址并返回给调用者。如果需要,这个方法可以计算address参数的页对齐。在scullp
      的实现中,address被用来计算设备里的偏移;偏移又被用来在scullp的内存树上查找正
      确的页。
       
      (代码286  #1)
       
      最后一行增加页计数;这个计数在atomic_t中生命,因此可以由一个原子操作更新。事

      最后一行增加页计数;这个计数在atomic_t中生命,因此可以由一个原子操作更新。事
      实上,在这种特定的情况下,原子更新并不是严格要求的,因为该页已经在使用中,并
      且没有与中断处理程序或别的异步代码的竞争条件。
       
      现在scullp可以按预期的那样工作了,正如你在工具mapper的示例输出中所看到的:
       
      (代码286  #2)
       
      (代码287  #1)
       
       
       
      重映射虚地址
       
      尽管很少需要重映射虚地址,但看看驱动程序如何用mmap将虚地址映射到用户空间是很
      有趣的。这里虚地址指的是由vmalloc返回的地址,也就是被映射到核心页表的虚地址。
      本节的代码取自scullv,这个模块与scullp类似,只是它通过vmalloc分配存储。
       
      scullv的大部分实现与我们刚刚看到的scullp完全类似,除了不需要检查分配序号。原
      因是vmalloc一次只分配一页,因为单页分配比多页分配容易成功的多。因此,使用计数
      问题在通过vmalloc分配的空间中不适用。
       
      scullv的主要工作是构造一个页表,从而可以象连续地址空间一样访问分配的页。而另

      scullv的主要工作是构造一个页表,从而可以象连续地址空间一样访问分配的页。而另
      一方面,nopage必须向调用者返回一个物理地址。因此,scullv的nopage实现必须扫描
      页表以取得与页相关联的物理地址。
       
      这个函数与我们在scullp中看到的一样,除了结尾。这个代码的节选只包括了nopage中
      与scullp不同的部分。
       
      (代码287  2#)
       
             atomic_inc(&mem_map[MAP_NR(page)]).count;
       
             return page;
       
         }
       
      页表由本章开始时介绍的那些函数来查询。用于这个目的的页目录存在核心空间的内存
      结构init_m中。
       
      宏VMALLOC_VMADDR(pageptr)返回正确的unsigned long值用于vmalloc地址的页表查询。
      注意,由于一个内存管理的问题,这个值的强制类型转换在早于2.1的X86核心上不能工
      作。在X86的2.1.1版中内存管理做了改动,VMALLOC_VMADDR被定义为一个实体函数,与
      在其它平台上一样。
       

       
      最后要提到的一点是init_mm是如何被访问的,因为我前面提到过,它并未引出到模块中
      。实际上,scullv要作一些额外的工作来取得init_mm的指针,解释如下。
       
      实际上,常规模块并不需要init_mm,因为它们并不期望与内存管理交互;它们只是调用
      分配和释放函数。为scullv的mmap实现很少见。本小节中介绍的代码实际上并不用来驱
      动硬件;我介绍它只是用实际代码来支持关于页表的讨论。
       
      不过,既然谈到这儿,我还是想给你看看scullv是如何获得init_mm的地址的。这段代码
      依赖于这样的事实:0号进程(所谓的空闲任务)处于内核中,它的页目录描述了核心地
      址空间。为了触到空闲任务的数据结构,scullv扫描进程链表直到找到0号进程。
       
      (代码288)
       
      这个函数由fops->mmap调用,因为nopage只在mmap调用后运行。
       
      基于上面的讨论,你也许还想将由vremap(如果你用Linux2.1,就是ioremap)返回的地
      址映射到用户空间。这很容易实现,因为你可以直接使用remap_page_range,而不用实
      现虚拟内存区域的方法。换句话说,remap_page_range已经可用以构造将I/O内存映射到
      用户空间的页表;并不需要象我们在scullv中那样查看由vremap构造的核心页表。
       
      直接内存访问
       

       
      直接内存访问,或DMA,是我们内存访问方面讨论的高级主题。DMA是一种硬件机制,它
      允许外围组件将I/O数据直接从(或向)主存中传送。
       
      为了利用硬件的DMA能力,设备驱动程序需要能正确地设置DMA传送并能与硬件同步。不
      幸的是,由于DMA的硬件实质,它非常以来于系统。每种体系结构都有它自己管理DMA传
      送的技术,编程接口也互不相同。核心也不能提供一个一致的接口,因为驱动程序很难
      将底层硬件机制适当地抽象。本章中,我将描述DMA在ISA设备及PCI外围上是如何工作的
      ,因为它们是目前最常用的外围接口体系结构。
       
      不过,我不想讨论ISA太多的细节。DMA的ISA实现过于复杂,在现代外围中并不常用。目
      前ISA总线主要用在哑外围接口上,而需要DMA能力的硬件生产商倾向于使用PCI总线。
       
      DMA数据传送的概况
       
      在介绍编程细节以前,我们先大致看看DMA传送是如何工作的。为简化讨论,只介绍输入
      传送。
       
      数据传送有两种方式触发:或者由软件请求数据(通过一个函数如read),或者由硬件
      将数据异步地推向系统。
       
      在第一中情况下,各步骤可如下概括:
       

       
      l         当一个进程调用一个read,这个驱动程序方法分配一个DMA缓冲区,并告诉硬
      件去传诵数据。进程进入睡眠。
       
      l         硬件向DMA缓冲区写数据,完成时发出一个中断。
       
      l         中断处理程序获得输入数据,应答中断,唤醒进程,它现在可以读取数据。
       
      有时DMA被异步地使用。例如,一些数据采集设备持续地推入数据,即使没有人读它。这
      种情况下,驱动程序要维护一个缓冲区,使得接下来的一个read调用可以将所有累积的
      数据返回到拥护空间。这种传送的步骤稍有不同:
       
      l         硬件发出一个中断,表明新的数据到达了。
       
      l         中断处理程序分配一个缓冲区,告诉硬件将数据传往何处。
       
      l         外围设备将数据写入缓冲区;当写完时,再次发出中断。
       
      l         处理程序派发新数据,唤醒所有相关进程,处理一些杂务。
       
       
       
      上面这两种情况下的处理步骤都强调:高效的DMA处理以来于中断报告。尽管可以用一个

      上面这两种情况下的处理步骤都强调:高效的DMA处理以来于中断报告。尽管可以用一个
      轮询驱动程序来实现DMA,这样做并无意义,因为轮询驱动程序会将DMA相对于简单的处
      理器驱动I/O获得的性能优势都抵消了。
       
      这里介绍的另一个相关问题是DMA缓冲区。为利用直接内存访问,设备驱动程序必须能分
      配一个特殊缓冲区以适合DMA。注意大多数驱动程序在初始化时分配它们的缓冲区,一直
      使用到关机——因此,上面步骤中“分配”一词指的是“获得以前已分配的缓冲区”。
       
      分配DMA缓冲区
       
      DMA缓冲区的主要问题是当它大于一页时,它必须占据物理内存的连续页,因为设备使用
      ISA或PCI总线传送数据,它都只携带物理地址。有趣的是注意到,这个限制对Sbus并不
      适用(见第15章“外围总线概览”中“Sbus”一节),它在外围总线上适用虚地址。
       
      尽管DMA缓冲区可以在系统引导或运行时分配,模块只能在运行时分配其缓冲区。第七章
      介绍了这些技术:“Playing Dirty”讲述在系统引导时分配;“kmalloc的真实故事”
      和“get_free_page和朋友们”讲述运行时分配
      --
      

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -