📄 ch04s03.html
字号:
<pre class="programlisting">void *next(struct seq_file *sfile, void *v, loff_t *pos); </pre><p>这里, v 是从前一个对 start 或者 next 的调用返回的 iterator, pos 是文件的当前位置. next 应当递增有 pos 指向的值; 根据你的 iterator 是如何工作的, 你可能(尽管可能不会)需要递增 pos 不止是 1. 这是 scull 所做的:</p><pre class="programlisting">static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos){ (*pos)++; if (*pos >= scull_nr_devs) return NULL; return scull_devices + *pos;}</pre><p>当内核处理完 iterator, 它调用 stop 来清理:</p><pre class="programlisting">void stop(struct seq_file *sfile, void *v); </pre><p>scull 实现没有清理工作要做, 所以它的 stop 方法是空的.</p><p>设计上, 值得注意 seq_file 代码在调用 start 和 stop 之间不睡眠或者进行其他非原子性任务. 你也肯定会看到在调用 start 后马上有一个 stop 调用. 因此, 对你的 start 方法来说请求信号量或自旋锁是安全的. 只要你的其他 seq_file 方法是原子的, 调用的整个序列是原子的. (如果这一段对你没有意义, 在你读了下一章后再回到这.)</p><p>在这些调用中, 内核调用 show 方法来真正输出有用的东西给用户空间. 这个方法的原型是:</p><pre class="programlisting">int show(struct seq_file *sfile, void *v); </pre><p>这个方法应当创建序列中由 iterator v 指示的项的输出. 不应当使用 printk, 但是; 有一套特殊的用作 seq_file 输出的函数:</p><div class="variablelist"><dl><dt><span class="term"><span>int seq_printf(struct seq_file *sfile, const char *fmt, ...);</span></span></dt><dd><p>这是给 seq_file 实现的 printf 对等体; 它采用常用的格式串和附加值参数. 你必须也将给 show 函数的 set_file 结构传递给它, 然而. 如果seq_printf 返回非零值, 意思是缓存区已填充, 输出被丢弃. 大部分实现忽略了返回值, 但是.</p></dd><dt><span class="term"><span>int seq_putc(struct seq_file *sfile, char c);</span></span></dt><dd></dd><dt><span class="term"><span>int seq_puts(struct seq_file *sfile, const char *s);</span></span></dt><dd><p>它们是用户空间 putc 和 puts 函数的对等体.</p></dd><dt><span class="term"><span>int seq_escape(struct seq_file *m, const char *s, const char *esc);</span></span></dt><dd><p>这个函数是 seq_puts 的对等体, 除了 s 中的任何也在 esc 中出现的字符以八进制格式打印. esc 的一个通用值是"\t\n\\", 它使内嵌的空格不会搞乱输出和可能搞乱 shell 脚本.</p></dd><dt><span class="term"><span>int seq_path(struct seq_file *sfile, struct vfsmount *m, struct dentry *dentry, char *esc);</span></span></dt><dd><p>这个函数能够用来输出和给定命令项关联的文件名子. 它在设备驱动中不可能有用; 我们是为了完整在此包含它.</p></dd></dl></div><p>回到我们的例子; 在 scull 使用的 show 方法是:</p><pre class="programlisting">static int scull_seq_show(struct seq_file *s, void *v){ struct scull_dev *dev = (struct scull_dev *) v; struct scull_qset *d; int i; if (down_interruptible (&dev->sem)) return -ERESTARTSYS; seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n", (int) (dev - scull_devices), dev->qset, dev->quantum, dev->size); for (d = dev->data; d; d = d->next) { /* scan the list */ seq_printf(s, " item at %p, qset at %p\n", d, d->data); if (d->data && !d->next) /* dump only the last item */ for (i = 0; i < dev->qset; i++) { if (d->data[i]) seq_printf(s, " % 4i: %8p\n", i, d->data[i]); } } up(&dev->sem); return 0;}</pre><p>这里, 我们最终解释我们的" iterator" 值, 简单地是一个 scull_dev 结构指针.</p><p>现在已有了一个完整的 iterator 操作的集合, scull 必须包装起它们, 并且连接它们到 /proc 中的一个文件. 第一步是填充一个 seq_operations 结构:</p><pre class="programlisting">static struct seq_operations scull_seq_ops = { .start = scull_seq_start, .next = scull_seq_next, .stop = scull_seq_stop, .show = scull_seq_show}; </pre><p>有那个结构在, 我们必须创建一个内核理解的文件实现. 我们不使用前面描述过的 read_proc 方法; 在使用 seq_file 时, 最好在一个稍低的级别上连接到 /proc. 那意味着创建一个 file_operations 结构(是的, 和字符驱动使用的同样结构) 来实现所有内核需要的操作, 来处理文件上的读和移动. 幸运的是, 这个任务是简单的. 第一步是创建一个 open 方法连接文件到 seq_file 操作:</p><pre class="programlisting">static int scull_proc_open(struct inode *inode, struct file *file){ return seq_open(file, &scull_seq_ops);}</pre><p>调用 seq_open 连接文件结构和我们上面定义的序列操作. 事实证明, open 是我们必须自己实现的唯一文件操作, 因此我们现在可以建立我们的 file_operations 结构:</p><pre class="programlisting">static struct file_operations scull_proc_ops = { .owner = THIS_MODULE, .open = scull_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release }; </pre><p>这里我们指定我们自己的 open 方法, 但是使用预装好的方法 seq_read, seq_lseek, 和 seq_release 给其他.</p><p>最后的步骤是创建 /proc 中的实际文件:</p><pre class="programlisting">entry = create_proc_entry("scullseq", 0, NULL);if (entry) entry->proc_fops = &scull_proc_ops;</pre><p>不是使用 create_proc_read_entry, 我们调用低层的 create_proc_entry, 我们有这个原型:</p><pre class="programlisting">struct proc_dir_entry *create_proc_entry(const char *name,mode_t mode,struct proc_dir_entry *parent); </pre><p>参数和它们的在 create_proc_read_entry 中的对等体相同: 文件名子, 它的位置, 以及父目录.</p><p>有了上面代码, scull 有一个新的 /proc 入口, 看来很象前面的一个. 但是, 它是高级的, 因为它不管它的输出有多么大, 它正确处理移动, 并且通常它是易读和易维护的. 我们建议使用 seq_file , 来实现包含多个非常小数目的输出行数的文件.</p></div></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="TheioctlMethod.sect"></a>4.3.2. ioctl 方法</h3></div></div></div><p>ioctl, 我们在第 1 章展示给你如何使用, 是一个系统调用, 作用于一个文件描述符; 它接收一个确定要进行的命令的数字和(可选地)另一个参数, 常常是一个指针. 作为一个使用 /proc 文件系统的替代, 你可以实现几个用来调试用的 ioctl 命令. 这些命令可以从驱动拷贝相关的数据结构到用户空间, 这里你可以检查它们.</p><p>这种方式使用 ioctl 来获取信息有些比使用 /proc 困难, 因为你需要另一个程序来发出 ioctl 并且显示结果. 必须编写这个程序, 编译, 并且与你在测试的模块保持同步. 另一方面, 驱动侧代码可能容易过需要实现一个 /proc 文件的代码.</p><p>有时候 ioctl 是获取信息最好的方法, 因为它运行比读取 /proc 快. 如果在数据写到屏幕之前必须做一些事情, 获取二进制形式的数据比读取一个文本文件要更有效. 另外, ioctl 不要求划分数据为小于一页的片段.</p><p>ioctl 方法的另一个有趣的优点是信息获取命令可留在驱动中, 当调试被禁止时. 不象对任何查看目录的人(并且太多人可能奇怪"这个怪文件是什么")都可见的 /proc 文件, 不记入文档的 ioctl 命令可能保持不为人知. 另外, 如果驱动发生了怪异的事情, 它们仍将在那里. 唯一的缺点是模块可能会稍微大些.</p></div><div class="footnotes"><br><hr width="100" align="left"><div class="footnote"><p><sup>[<a name="ftn.id421348" href="#id421348">14</a>] </sup>连字号, 或者减号, 是一个"魔术"标识以阻止 syslogd 刷新文件到磁盘在每个新消息, 有关文档在 syslog.conf(5), 一个值得一读的 manpage.</p></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch04s02.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch04.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch04s04.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">4.2. 用打印调试 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 4.4. 使用观察来调试</td></tr></table></div></body></html><div style="display:none"><script language="JavaScript" src="script.js"></script> </div>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -