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

📄 ch15s02.html

📁 介绍Linux内核驱动编程的一本书 最主要的是有源代码,都是可用的 学习操作系统很好
💻 HTML
📖 第 1 页 / 共 3 页
字号:
<html xmlns:cf="http://docbook.sourceforge.net/xmlns/chunkfast/1.0"><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>15.2.&#160;mmap 设备操作-Linux设备驱动第三版(中文版)</title><meta name="description" content="驱动开发" /><meta name="keywords" content="Linux设备驱动,中文版,第三版,ldd,linux device driver,驱动开发,电子版,程序设计,软件开发,开发频道" /><meta name="verify-v1" content="5asbXwkS/Vv5OdJbK3Ix0X8osxBUX9hutPyUxoubhes=" /><link rel="stylesheet" href="docbook.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.69.0"><link rel="start" href="index.html" title="Linux 设备驱动 Edition 3"><link rel="up" href="ch15.html" title="第&#160;15&#160;章&#160;内存映射和 DMA "><link rel="prev" href="ch15.html" title="第&#160;15&#160;章&#160;内存映射和 DMA "><link rel="next" href="ch15s03.html" title="15.3.&#160;进行直接 I/O"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">15.2.&#160;mmap 设备操作</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch15.html">上一页</a>&#160;</td><th width="60%" align="center">第&#160;15&#160;章&#160;内存映射和 DMA </th><td width="20%" align="right">&#160;<a accesskey="n" href="ch15s03.html">下一页</a></td></tr></table><hr></div><div class="sect1" lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="ThemmapDeviceOperation.sect1"></a>15.2.&#160;mmap 设备操作</h2></div></div></div><p>内存映射是现代 Unix 系统最有趣的特性之一. 至于驱动, 内存映射可被实现来提供用户程序对设备内存的直接存取.</p><p>一个 mmap 用法的明确的例子可由查看给 X Windows 系统服务器的虚拟内存区的一个子集来见到:</p><pre class="screen">cat /proc/731/maps 000a0000-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...</pre><p>X 服务器的 VMA 的完整列表很长, 但是大部分此处不感兴趣. 我们确实见到, 但是, /dev/mm 的 4 个不同映射, 它给出一些关于 X 服务器如何使用视频卡的内幕. 第一个映射在 a0000, 它是视频内存的在 640-KB ISA 孔里的标准位置. 再往下, 我们见到了大映射在 e8000000, 这个地址在系统中最高的 RAM 地址之上. 这是一个在适配器上的视频内存的直接映射.</p><p>这些区也可在 /proc/iomem 中见到:</p><pre class="screen">000a0000-000bffff : Video RAM area000c0000-000ccfff : Video ROM000d1000-000d1fff : Adapter ROM000f0000-000fffff : System ROMd7f00000-f7efffff : PCI Bus #01 e8000000-efffffff : 0000:01:00.0fc700000-fccfffff : PCI Bus #01 fcc00000-fcc0ffff : 0000:01:00.0 </pre><p>映射一个设备意味着关联一些用户空间地址到设备内存. 无论何时程序在给定范围内读或写, 它实际上是在存取设备. 在 X 服务器例子里, 使用 mmap 允许快速和容易地存取视频卡内存. 对于一个象这样的性能关键的应用, 直接存取有很大不同.</p><p>如你可能期望的, 不是每个设备都出借自己给 mmap 抽象; 这样没有意义, 例如, 对串口或其他面向流的设备. mmap 的另一个限制是映射粒度是 PAGE_SIZE. 内核可以管理虚拟地址只在页表一级; 因此, 被映射区必须是 PAGE_SIZE 的整数倍并且必须位于是 PAGE_SIZE 整数倍开始的物理地址. 内核强制 size 的粒度通过做一个稍微大些的区域, 如果它的大小不是页大小的整数倍.</p><p>这些限制对驱动不是大的限制, 因为存取设备的程序是设备依赖的. 因为程序必须知道设备如何工作的, 程序员不会太烦于需要知道如页对齐这样的细节. 一个更大的限制存在当 ISA 设备被用在非 x86 平台时, 因为它们的 ISA 硬件视图可能不连续. 例如, 一些 Alpha 计算机将 ISA 内存看作一个分散的 8 位, 16 位, 32 位项的集合, 没有直接映射. 这种情况下, 你根本无法使用 mmap. 对不能进行直接映射 ISA 地址到 Alph 地址可能只发生在 32-位 和 64-位内存存取, ISA 可只做 8-位 和 16-位 发送, 并且没有办法来透明映射一个协议到另一个.</p><p>使用 mmap 有相当地优势当这样做可行的时候. 例如, 我们已经看到 X 服务器, 它传送大量数据到和从视频内存; 动态映射图形显示到用户空间提高了吞吐量, 如同一个 lseek/write 实现相反. 另一个典型例子是一个控制一个 PCI 设备的程序. 大部分 PCI 外设映射它们的控制寄存器到一个内存地址, 并且一个高性能应用程序可能首选对寄存器的直接存取来代替反复地调用 ioctl 来完成它的工作.</p><p>mmap 方法是 file_operation 结构的一部分, 当发出 mmap 系统调用时被引用. 用了 mmap, 内核进行大量工作在调用实际的方法之前, 并且, 因此, 方法的原型非常不同于系统调用的原型. 这不象 ioctl 和 poll 等调用, 内核不会在调用这些方法之前做太多.</p><p>系统调用如下一样被声明(如在 mmap(2) 手册页中描述的 );</p><pre class="programlisting">mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset) </pre><p>另一方面, 文件操作声明如下:</p><pre class="programlisting">int (*mmap) (struct file *filp, struct vm_area_struct *vma);</pre><p>方法中的 filp 参数象在第 3 章介绍的那样, 而 vma 包含关于用来存取设备的虚拟地址范围的信息. 因此, 大量工作被内核完成; 为实现 mmap, 驱动只要建立合适的页表给这个地址范围, 并且, 如果需要, 用新的操作集合替换 vma-&gt;vm_ops.</p><p>有 2 个建立页表的方法:调用 remap_pfn_range 一次完成全部, 或者一次一页通过 nopage VMA 方法. 每个方法有它的优点和限制. 我们从"一次全部"方法开始, 它更简单. 从这里, 我们增加一个真实世界中的实现需要的复杂性.</p><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="Usingremap_pfn_range.sect2"></a>15.2.1.&#160;使用 remap_pfn_range</h3></div></div></div><p>建立新页来映射物理地址的工作由 remap_pfn_range 和 io_remap_page_range 来处理, 它们有下面的原型:</p><pre class="programlisting">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); </pre><p>由这个函数返回的值常常是 0 或者一个负的错误值. 让我们看看这些函数参数的确切含义:</p><div class="variablelist"><dl><dt><span class="term"><span>vma </span></span></dt><dd><p>页范围被映射到的虚拟内存区</p></dd><dt><span class="term"><span>virt_addr </span></span></dt><dd><p>重新映射应当开始的用户虚拟地址. 这个函数建立页表为这个虚拟地址范围从 virt_addr 到 virt_addr_size.</p></dd><dt><span class="term"><span>pfn</span></span></dt><dd><p>页帧号, 对应虚拟地址应当被映射的物理地址. 这个页帧号简单地是物理地址右移 PAGE_SHIFT 位. 对大部分使用, VMA 结构的 vm_paoff 成员正好包含你需要的值. 这个函数影响物理地址从 (pfn&lt;&lt;PAGE_SHIFT) 到 (pfn&lt;&lt;PAGE_SHIFT)+size.</p></dd><dt><span class="term"><span>size </span></span></dt><dd><p>正在被重新映射的区的大小, 以字节.</p></dd><dt><span class="term"><span>prot </span></span></dt><dd><p>给新 VMA 要求的"protection". 驱动可(并且应当)使用在 vma-&gt;vm_page_prot 中找到的值.</p></dd></dl></div><p>给 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 的适合你的特殊情况的变体.</p><p>另一种复杂性不得不处理缓存: 常常地, 引用设备内存不应当被处理器缓存. 常常系统 BIOS 做了正确设置, 但是它也可能通过保护字段关闭特定 VMA 的缓存. 不幸的是, 在这个级别上关闭缓存是高度处理器依赖的. 好奇的读者想看看来自 drivers/char/mem.c 的 pgprot_noncached 函数来找到包含什么. 我们这里不进一步讨论这个主题.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="ASimpleImplementation.sect2"></a>15.2.2.&#160;一个简单的实现</h3></div></div></div><p>如果你的驱动需要做一个简单的线性的设备内存映射, 到一个用户地址空间, remap_pfn_range 几乎是所有你做这个工作真正需要做的. 下列的代码从 drivers/char/mem.c 中得来, 并且显示了这个任务如何在一个称为 simple ( Simple Implementation Mapping Pages with Little Enthusiasm)的典型模块中进行的.</p><pre class="programlisting">static int simple_remap_mmap(struct file *filp, struct vm_area_struct *vma) { if (remap_pfn_range(vma, vma-&gt;vm_start, vm-&gt;vm_pgoff, vma-&gt;vm_end - vma-&gt;vm_start, vma-&gt;vm_page_prot)) return -EAGAIN; vma-&gt;vm_ops = &amp;simple_remap_vm_ops; simple_vma_open(vma); return 0; } </pre><p>如你所见, 重新映射内存只不过是调用 remap_pfn_rage 来创建必要的页表.</p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="AddingVMAOperations.sect2"></a>15.2.3.&#160;添加 VMA 的操作</h3></div></div></div><p>如我们所见, vm_area_struct 结构包含一套操作可以用到 VMA. 现在我们看看以一个简单的方式提供这些操作. 特别地, 我们为 VMA 提供 open 和 close 操作. 这些操作被调用无论何时一个进程打开或关闭 VMA; 特别地, open 方法被调用任何时候一个进程产生和创建一个对 VMA 的新引用. open 和 close VMA 方法被调用加上内核进行的处理, 因此它们不需要重新实现任何那里完成的工作. 它们对于驱动存在作为一个方法来做任何它们可能要求的附加处理.</p><p>如同它所证明的, 一个简单的驱动例如 simple 不需要做任何额外的特殊处理. 我们已创建了 open 和 close 方法, 它打印一个信息到系统日志来通知大家它们已被调用. 不是特别有用, 但是它确实允许我们来显示这些方法如何被提供, 并且见到当它们被调用时.</p><p>到此, 我们忽略了缺省的 vma-&gt;vm_ops 使用调用 printk 的操作:</p><pre class="programlisting">void simple_vma_open(struct vm_area_struct *vma){	printk(KERN_NOTICE "Simple VMA open, virt %lx, phys %lx\n", vma-&gt;vm_start, vma-&gt;vm_pgoff &lt;&lt; PAGE_SHIFT);} void simple_vma_close(struct vm_area_struct *vma){

⌨️ 快捷键说明

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