在我们编写CC1100驱动程序和倒车雷达驱动程序的时候,我们都用到了当前读取数据标志位,即我们在驱动程序中会定义一个容器用来存放数据,每一次我们读完一个数据时,我们都会将我们的读数据变量加1,这样下一次再来读取数据的时候就不会读到以前的数据了,都会是全新的数据。当然这个方法是我们自己在编写驱动程序时候自己定义的。是否在Linux内核中本来就有这样的机制呢?答案是肯定的。这个机制就是seek机制,那么seek机制又是如何实现记录读取数据位置的机智的呢?同poll机制一样,在file->fop里面也定义有seek的函数指针,那就是llseek,这个函数指针当然是指向我们自己编写的驱动程序的函数了。其实seek实现的机制比较简单,主要就是围绕当前文件指针file->f_ops这个指针来的,这个指针存放的是当前驱动程序文件的读写数据位置,我们在哪里用到了f_ops这个指针呢?首先我们来看一下我们比较熟悉的read函数和open函数,我们知道在这两个函数中我们所要用到的参数都是一样的CC1100_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)和static ssize_t CC1100_write(struct file *filp, const char *buff, size_t count, loff_t *offp)其里面的参数都是一样的,第一个参数是file(该设备文件)、第二个参数是用户层传过来的buff指针,第三个是用户层传过来的写入或者读取长度大小(size_t),那么第四个参数是什么呢,好像我们之前都没有用过,这第四个参数保存的数据就是我们上面讲的file->f_ops,用来记录我们驱动程序文件的读取数据的位置。通常如果我们不用seek机制的话,这个参数也一般都用不到,其值永远都是0。所以从上面我们也可以推出,要想使用read或者是write的第四个参数的话,我们必须使用 seek函数来改变f_ops指针的指向seek的机制就是用来改变文件指针的值的。通常我们使用seek机制都会遵循一定的模板,首先我们要在驱动程序中编写我们自己的seek函数,大致模板为:
在驱动程序中首先要定义自己的seek函数,该函数的目的是定义f_ops的起始偏移的位置,当然如果你不定义这个函数,直接使用read和write的f_ops也是没有问题的,只不过入股使用到seek机制都会定义这个函数,因为这样驱动程序就增加了一项功能,即可以选择不同的起始偏移位置。通常该函数的模式都是一样的:
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; //这句代码就是关键,改变驱动文件的f_pos的指向。
return newpos; //返回当前指针
}
static struct file_operations _fops =
{
.............
.llseek = scull_llseek
..............
}
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p) //当count+p> GLOBALMEM_SIZE则表示如果按照当前的读取数据大小 //和文件指针值读取数据的话,将会超过数据的最大值
count = GLOBALMEM_SIZE - p;
if (down_interruptible(&dev->sem))//获得信号量
{
return - ERESTARTSYS;
}
/*用户空间->内核空间*/
//数据复制的位置将从dev->mem + p开始
if (copy_from_user(dev->mem + p, buf, count))
ret = - EFAULT;
else
{
*ppos +=count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
up(&dev->sem); //释放信号量
return ret;
}
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
if (down_interruptible(&dev->sem))
{
return - ERESTARTSYS;
}
/*内核空间->用户空间*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
up(&dev->sem); //释放信号量
return ret;
}