6.5 定位设备
最后, 我们讨论一下 llseek
方法, 对于某些设备来说, 该方法非常重要, 其实现非常简单。
6.5.1 llseek实现
llseek
方法实现了 lseek
和 llseek
系统调用。 llseek
方法在设备的操作中缺失, 内核中的默认实现是通过修改 filp->f_pos
来定位当前的读写位置。 如果想要 lseek
系统调用能够正常工作, read
和 write
方法必须更新它们的参数变量 offset
。
如果 seek
操作对应于设备的物理操作, 那么需要提供你自己的 llseek
方法。 下面是 scull
中的实现:
loff_t scull_llseek(struct file *filp, loff_t off, int whence)
{
struct scull_dev *dev = filp->private_data;
loff_t newpos;
switch(whence) {
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = dev->size + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0) return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
这里唯一特定于设备的操作是从设备中检索文件长度。 在 scull
中, read
和 write
方法根据需要进行协作,如第3章所示。
上面的实现, 对于处理明确定义的数据区域的 scull
有意义, 但大多数设备提供数据流而不是数据区域(比如, 串行端口或键盘), 定位这些设备没有意义。 因为默认情况下是支持定位的, 所以不声明 llseek
方法不能实现不定位设备。 必须在你的 open
实现中明确调用 nonseekable_open
告知内核不支持 llseek
方法。
int nonseekable_open(struct inode *inode; struct file *filp);
此调用标记给定的 filp
是不可定位的; 内核永远不允许对这样的文件进行 lseek
调用。 通过以这种方式标记文件,您还可以确保不会尝试通过 pread
和 pwrite
系统调用来定位文件。
为了完整性, 在你自己的 file_operations
结构体中使用特殊的辅助函数 no_llseek
来设置 llseek
方法, 该辅助函数定义在 <linux/fs.h>
。