对所管理的设备,驱动程序能完成哪些不同的操作。打开的设备在内核内部由file结构标识,内核使用file_operations结构访问驱动程序的函数。file_operations结构是一个定义在中的函数指针数组。每个文件都与它自己的函数集相关联。这些操作主要负责系统调用的实现,并因此被命名为open、read等。
file_operations结构或指向这类结构的指针被称为fops;这个结构中的每一个字段都必须指向驱动程序中实现特定操作的函数。对于不支持的操作,对应的字段可被置为NULL。对各个函数而言,如果对应字段被赋为NULL指针,那么内核的具体处理行为是不尽相同的。
scull设备驱动程序所实现的只是最重要的设备方法,并且采用标记化格式声明它的file_operations结构:
struct file_operations scull_fops={
llseek:scull_llseek,
read:scull_read,
write:scull_write,
ioctl:scull_ioctl,
open:scull_open,
release:scull_release,
};
在《linux/fs.h》中定义的struct file是设备驱动程序所使用的第二个最重要的数据结构。
file结构代表一个打开的文件。由内核在open时创建,并传递给在该文件上进行操作的所有函数,直到最后的close函数。
在内核源代码中,指向struct file的指针通常被称为file或filp。
1、open方法
open 方法提供给驱动程序以初始化的能力,从而为以后的操作做准备。此外,open一般还会递增设备的使用记数,防止在文件关闭前模块被卸载出内核。这个记数值在release方法中被递减。
在大部分驱动程序中,open应完成如下工作:
1)递增使用记数
2)检查设备特定的错误(诸如设备未就绪或类似的硬件问题)
3)如果设备是首次打开,则对其初始化
4)识别次设备号,并且如果有必要,更新f_op
5)分配并填写被置于filp->private_data里的数据结构。
在scull中,前面这些操作大部分都取决于被打开设备的次设备号。因此,首先要做的就是识别要操作的是哪个设备。可以通过查看inode->i_rdev做到这一点。不同的次设备号用来访问不同的设备,或以不同的方式打开同一个设备。
sxull驱动程序是这样使用次设备号的:字节的高4位标识设备的类型,低4位可以在某个类型的设备支持多个设备实例时,用于区分各个设备。源代码中定义了两个宏,用来从设备号中分解出这些位,如下所示:
#define TYPE(dev) (MINOR(dev)>>4) /*字节高四位*/
#define NUM(dev) (MINOR(dev)&0xf) /*字节低四位*/
对于每个设备类型,scull定义了一个特定的file_operations结构,它在open操作时赋予filp->f_op。下面的代码显示了多个fops是如何实现的:
struct file_operations *scull_fop_array[]={
&scull_fops, /*类型0*/
&scull_priv_fops, /*类型1*/
&scull_pipe_fops, /*类型2*/
&scull_sngl_fops, /*类型3*/
&scull_user_fops, /*类型4*/
&scull_wusr_fops, /*类型5*/
};
#define SCULL_MAX_TYPE 5
/*在xcull_open的实现中,scull_fop_array数组根据TYPE(dev)的值来决定对其数组成员的引用*/
int type=TYPE(inode->i_rdev);
filp->f_op=scull_fop_array[type];
内核根据主设备号调用open ,scull则使用上述宏分解出的次设备号。TYPE用来索引scull_fop_array数组,以丛中取出正被打开的设备类型所对应的方法集。
scull_open的实际代码如下:
int scull_open(struct inode *inode,struct file *filp)
{
Scull_Dev *dev; /*设备信息*/
int num=NUM(inode->i_rdev);
int type=TYPE(inode->i_rdev);
/*如果私有数据(private_data )无效,则表明当前采用的不是devfs,所以使用type(取自次设备号)来选择一个新的f_op
*/
if (!filp->private_data && type) {
if (type > SCULL_MAX_TYPE) return -ENODEV;
filp->f_op = scull_fop_array[type];
return filp->f_op->open(inode,filp); /*分发致对应的open实现*/
}
/*类型0,检查设备号(除非private_data是有效的)*/
dev = (Scull_Dev *)filp->private_data;
if (!dev) {
if (num >= scull_nr_devs) return -ENODEV;
dev = &scull_devices[num];
filp->private_data = dev; /*提供给其他方法*/
}
MOD_INC_USE_COUNT;/*在可能进入睡眠之前*/
/*如果open为只写模式,则把设备长度消减至0*/
if ((filp->f_flags & O_ACCMODE) == O_WRONLY) {
if (down_interruptible(&dev->sem)) {
MOD_DEC_USE_COUNT;
return -ERESTARTSYS;
}
scull_trim(dev);/*忽略错误*/
up(&dev->sem);
}
return 0; /*成功*/
}
2、release方法
release方法的作用正好与open相反。有时这个方法的实现被称为device-close,而不是device-release。无论是哪种形式,这个设备方法都应该完成下面的任务:
1)释放由open分配的、保存在filp->private_data中的所有内容。
2)在最后一次关闭操作时关闭设备
3)使用记数减1
scull的基本模型没有需要关闭的设备,因此所需的代码最少:
int scull_release(struct inode *inode,struct file *filp)
{
MOD_DEC_USE_COUNT;
return 0;
}
如果在open期间递增使用记数的话,则不应忘记对其递减,因为如果使用记数不归0,内核就无法卸载模块。
3、read和write
在介绍read和write操作以前,先看看scull如何并且为什么进行内存分配。为了彻底理解代码,需要知道“如何分配”,而“为什么分配”则表明了驱动程序编写者所需做出的选择。
用来保存设备信息的数据结构如下:
typedef struct Scull_Dev{
void **data;
struct Scull_Dev *next;/*下一个链表项*/
int quantum;/*当前的量子大小*/
int qset;/*当前的量子集大小*/
unsigned long size;
devfs_handle_t handle;/*仅在devfs方式下使用*/
unsigned int access_key;/*由sculluid和scullpriv使用*/
struct semaphore sem;/*互斥信号量*/
} Scull_Dev;
下面的代码片段说明了如何利用Scull_Dev来保存数据。scull_trim函数负责释放整个数据区,并且在文件以写方式打开时由scull_open调用。它简单地遍历链表,释放所有找到的量子和量子集。
int scull_trim(Scull_Dev *dev)
{
Scull_Dev *next,*dptr;
int qset = dev->qset; /*"dev"不能为null*/
int i;
for (dptr = dev; dptr; dptr = next) { /*遍历所有的链表项*/
if (dptr->data) {
for (i = 0;i
if (dptr->data[i])
kfree(dptr->data[i]);
kfree(dptr->data);
dptr->data=NULL;
}
next=dptr->next;
if (dptr!=dev) kfree(dptr); /*释放链表中除第一个节点以外的所有节点*/
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->next = NULL;
return 0;
}
调用程序对read的返回值解释如下:
1)如返回值等于传递给read系统调用的count参数,那么说明所请求的字节数传输成功完成了。
2)如果返回值是正的,但是比count小,则说明只有部分数据传送成功。
3)如果返回值为0,则表示已经到达了文件尾
4)负值意味着发生了错误
下面是read的代码:
ssize_t scull_read(struct file *filp,char *buf,size_t count,loff_t *f_pos)
{
Scull_Dev *dev = filp->private_data; /*链表的首节点*/
Scull_Dev *dptr;
int quantum = dev->quantum;
int qset = dev->qset;
int itemsize = quantum *qset;/*链表节点中的字节数目*/
int item,s_pos,q_pos,rest;
ssize_t ret = 0;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
if (*f_pos>=dev->size)
goto out;
if (*f_pos+count>dev->size)
count = dev->size - *f_pos;
/*找到待读取数据所在的链表接点、量子集索引号和在量子中的偏移量*/
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/*遍历链表,直至到达正确的节点位置(该)*/
dptr = scull_follow(dev,item);
if (!dptr->data)
goto out; /*不填充空洞*/
if (!dptr->data[s_pos])
goto out;
/*仅读至当前量子的末尾*/
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_to_user(buf,dptr->data[s_pos]+q_pos,count)) {
ret = -EFAULT;
goto out;
}
*f_pos += count;
ret = count;
out:
up(&dev->sem);
return ret;
}
write方法
与read类似,根据如下返回值规则,write也能除数少于请求的数据量:
1)如果返回值等于count,则完成了请求数目的字节传送
2)如果返回值是正的,但小于count,则只传输了部分数据。程序很可能再次试图写入余下的数据
3)如果值为0,意味着什么也没写入。
4)负值意味着发生了错误
与read方法一样,xcull的write代码每次只处理一个量子:
ssize_t scull_write(struct file *filp,const char *buf,size_t count,loff_t *f_pos)
{
Scull_Dev *dev = filp->private_data;
Scull_Dev *dptr;
int quantum = dev->quantum;
int qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t ret = -ENOMEM;/*用于“gotoout”语句中的返回值*/
if (dowm_interruptible(&dev->sem))
return -ERESTARTSYS;
/*找到待读取数据所在的链表接点、量子集索引号和在量子中的偏移量*/
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/*遍历链表,直至到达正确的节点位置(该)*/
dptr = scull_follow(dev,item);
if (!dptr->data) {
dptr->data = kamlloc(qset *sizeof(char *),GFP_KERNEL);
if (!dptr->data)
goto out;
memset(dptr->data, 0, qset *sizeof(char *));
}
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if (!dptr->data[s_pos])
goto out;
}
/*仅写至哦当前量子的末尾*/
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
ret = -EFAULT;
goto out;
}
*f_pos += count;
ret = count;
/*更新设备的长度*/
if (dev->size
dev->size = *f_pos;
out:
up(&dev->sem);
return ret;
}