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

📄 ch03s03.html

📁 完整的Linux 设备驱动第3版
💻 HTML
📖 第 1 页 / 共 2 页
字号:
<html xmlns:cf="http://docbook.sourceforge.net/xmlns/chunkfast/1.0"><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>3.3.&#160;一些重要数据结构</title><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="ch03.html" title="第&#160;3&#160;章&#160;字符驱动"><link rel="prev" href="ch03s02.html" title="3.2.&#160;主次编号"><link rel="next" href="ch03s04.html" title="3.4.&#160;字符设备注册"></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">3.3.&#160;一些重要数据结构</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s02.html">上一页</a>&#160;</td><th width="60%" align="center">第&#160;3&#160;章&#160;字符驱动</th><td width="20%" align="right">&#160;<a accesskey="n" href="ch03s04.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="SomeImportantDataStructures.sect"></a>3.3.&#160;一些重要数据结构</h2></div></div></div><p>如同你想象的, 注册设备编号仅仅是驱动代码必须进行的诸多任务中的第一个. 我们将很快看到其他重要的驱动组件, 但首先需要涉及一个别的. 大部分的基础性的驱动操作包括 3 个重要的内核数据结构, 称为 file_operations, file, 和 inode. 需要对这些结构的基本了解才能够做大量感兴趣的事情, 因此我们现在在进入如何实现基础性驱动操作的细节之前, 会快速查看每一个.</p><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="FileOperation.sect"></a>3.3.1.&#160;文件操作</h3></div></div></div><p>到现在, 我们已经保留了一些设备编号给我们使用, 但是我们还没有连接任何我们设备操作到这些编号上. file_operation 结构是一个字符驱动如何建立这个连接. 这个结构, 定义在 &lt;linux/fs.h&gt;, 是一个函数指针的集合. 每个打开文件(内部用一个 file 结构来代表, 稍后我们会查看)与它自身的函数集合相关连( 通过包含一个称为 f_op 的成员, 它指向一个 file_operations 结构). 这些操作大部分负责实现系统调用, 因此, 命名为 open, read, 等等. 我们可以认为文件是一个"对象"并且其上的函数操作称为它的"方法", 使用面向对象编程的术语来表示一个对象声明的用来操作对象的动作. 这是我们在 Linux 内核中看到的第一个面向对象编程的现象, 后续章中我们会看到更多.</p><p>传统上, 一个 file_operation 结构或者其一个指针称为 fops( 或者它的一些变体). 结构中的每个成员必须指向驱动中的函数, 这些函数实现一个特别的操作, 或者对于不支持的操作留置为 NULL. 当指定为 NULL 指针时内核的确切的行为是每个函数不同的, 如同本节后面的列表所示.</p><p>下面的列表介绍了一个应用程序能够在设备上调用的所有操作. 我们已经试图保持列表简短, 这样它可作为一个参考, 只是总结每个操作和在 NULL 指针使用时的缺省内核行为.</p><p>在你通读 file_operations 方法的列表时, 你会注意到不少参数包含字串 __user. 这种注解是一种文档形式, 注意, 一个指针是一个不能被直接解引用的用户空间地址. 对于正常的编译, __user 没有效果, 但是它可被外部检查软件使用来找出对用户空间地址的错误使用.</p><p>本章剩下的部分, 在描述一些其他重要数据结构后, 解释了最重要操作的角色并且给了提示, 告诫和真实代码例子. 我们推迟讨论更复杂的操作到后面章节, 因为我们还不准备深入如内存管理, 阻塞操作, 和异步通知.</p><div class="variablelist"><dl><dt><span class="term"><span>struct module *owner</span></span></dt><dd><p>第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 &lt;linux/module.h&gt; 中定义的宏.</p></dd><dt><span class="term"><span>loff_t (*llseek) (struct file *, loff_t, int);</span></span></dt><dd><p>llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).</p></dd><dt><span class="term"><span>ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);</span></span></dt><dd><p>用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).</p></dd><dt><span class="term"><span>ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);</span></span></dt><dd><p>初始化一个异步读 -- 可能在函数返回前不结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).</p></dd><dt><span class="term"><span>ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);</span></span></dt><dd><p>发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.</p></dd><dt><span class="term"><span>ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);</span></span></dt><dd><p>初始化设备上的一个异步写.</p></dd><dt><span class="term"><span>int (*readdir) (struct file *, void *, filldir_t);</span></span></dt><dd><p>对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.</p></dd><dt><span class="term"><span>unsigned int (*poll) (struct file *, struct poll_table_struct *);</span></span></dt><dd><p>poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.</p></dd><dt><span class="term"><span>int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);</span></span></dt><dd><p>ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误. </p></dd><dt><span class="term"><span>int (*mmap) (struct file *, struct vm_area_struct *);</span></span></dt><dd><p>mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.</p></dd><dt><span class="term"><span>int (*open) (struct inode *, struct file *);</span></span></dt><dd><p>尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.</p></dd><dt><span class="term"><span>int (*flush) (struct file *);</span></span></dt><dd><p>flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.</p></dd><dt><span class="term"><span>int (*release) (struct inode *, struct file *);</span></span></dt><dd><p>在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.</p></dd><dt><span class="term"><span>int (*fsync) (struct file *, struct dentry *, int);</span></span></dt><dd><p>这个方法是 fsync 系统调用的后端, 用户调用来刷新任何挂着的数据. 如果这个指针是 NULL, 系统调用返回 -EINVAL.</p></dd><dt><span class="term"><span>int (*aio_fsync)(struct kiocb *, int);</span></span></dt><dd><p>这是 fsync 方法的异步版本.</p></dd><dt><span class="term"><span>int (*fasync) (int, struct file *, int);</span></span></dt><dd><p>这个操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述. 这个成员可以是NULL 如果驱动不支持异步通知.</p></dd><dt><span class="term"><span>int (*lock) (struct file *, int, struct file_lock *);</span></span></dt><dd><p>lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.</p></dd><dt><span class="term"><span>ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);</span></span></dt><dd></dd><dt><span class="term"><span>ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);</span></span></dt><dd><p>这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).</p></dd><dt><span class="term"><span>ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);</span></span></dt><dd><p>这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个. 例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.</p></dd><dt><span class="term"><span>ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);</span></span></dt><dd><p>sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.</p></dd><dt><span class="term"><span>unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);</span></span></dt><dd><p>这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中. 这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.<sup>[<a name="id415071" href="#ftn.id415071">10</a>]</sup></p></dd><dt><span class="term"><span>int (*check_flags)(int)</span></span></dt><dd><p>这个方法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志.</p></dd>

⌨️ 快捷键说明

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