4. 字符设备的注册
在内核调用设备的操作之前,必须分配并注册一个或多个struct cdev结构(在<linux/cdev.h>中定义)。
分配和初始化该结构有两种方式。如果在模块运行时需要获取一个独立的cdev结构,则应该编写如下代码:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
如果cdev结构需要嵌入到自己的设备特定结构中,就使用:
void cdev_init(struct cdev *dev, struct file_operations *fops);
在cdev结构设置好后,就可以调用如下函数告诉内核该结构的信息:
int cdev_add((struct cdev *dev, dev_t num, unsigned int count);
在使用cdev_add时,需要注意:一是这个调用可能会失败;二是只要cdev_add返回成功,就表明内核可以用该设备了。
从系统中移除一个字符设备:
void cdev_del(struct cdev *dev);
4.1. scddp中的设备注册
在scddp内部,是由下面这个结构来表示每个设备:
typedef struct scddp_dev { struct scddp_qset *data; /* 指向第一个量子集的指针*/ struct scddp_dev *next; /* 下一个scddp设备 */ int quantum; /* 当前量子的大小 */ int qset; /* 当前数组的大小 */ unsigned long size; /* 保存在其中的数据总量 */ struct semaphore sem; /* 互斥信号量 */ struct cdev cdev; /* 字符设备结构 */ } scddp_dev_s;
该结构需要被初始化并添加到系统中,代码如下:
static scddp_setup_cdev(struct scddp_dev *dev, int index) { int err; cdev_init(&dev, &scddp_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scddp_fops; err = cdev(&dev->cdev, devno, 1); if(err) printk(KERN_NOTICE "Error %d adding scddp%d/n ", err, index); }
4.2. 早期的设备注册
注册一个字符设备驱动程序的经典方式:
int register_chrdev(unsigned int major, const char*name,struct file_operations *fops);
从系统移除设备的对应函数是:
int unregister_chrdev(unsigned int major, const char*name);
5. open和release方法
5.1. open方法
open方法提供给驱动程序初始化的能力,完成的工作如下:
1) 检查设备特定的错误
2) 如果是首次打开,则对其进行初始化
3) 如有必要,初始化f_op指针
4) 分配并填写filep->private_data的数据结构
open方法原型:
int (*open)( struct inode *inode, struct file *filep);
我们通常不需要cdev结构,而是希望得到包含cdev结构的scddp结构。在这种情况下,内核帮助我们实现了此类技术,在<linux/kernel.h>中的container_of宏有实现。
container_of(pointer, container_type,container_field);
这个宏需要一个container_field字段的指针,该字段包含在container_type类型的结构中,然后返回包含该字段的结构指针。
另外一个确定要打开的设备的方法是:检查保存在inode结构中的次设备号。如果利用register_chrdev注册地设备,则必须使用该技术,而且一定要用iminor宏从inode结构中获取次设备号,并确保它对应于驱动程序真正准备打开的设备。
int scddp_open(struct inode *inode, struct file *filp) { struct scddp_dev *dev; dev = container_of(inode->i_cdev, struct scddp_dev, cdev); filp->private_data = dev; if((filp->f_flags & O_ACCMODE) == O_WEONLY) { scddp_trim(dev); } return 0; }
这段代码没有做什么工作,是因为scddp设备被设计为全局而持久。由于我们并不维护scddp的打开计数,而只维护模块的使用计数,因此没有类似"首次打开时要初始化设备"的工作。对设备唯一的实际操作是当设备以写方式打开时,它的长度将截为0。
5.2. release方法
relsease方法完成下面任务:
1) 释放由open分配、保存在filp->private_data中的所有内存
2) 在最后一次关闭操作时关闭设备
由于scddp没有需要关闭的硬件,因此代码简单:
int scddp_release(struct inode *inode, struct file *filp)
{
return 0;
}
注意:并不是每个close系统调用都会引起对release方法的调用。内核对每个file结构维护其被使用多少次的计数器。无论是fork还是dup,都不会创建新的数据结构(进由open创建),只是增加已有结构的计数。只有在file结构的计数归为0时,close系统调用才会真正的调用release方法。同时,flush方法在应用程序每次调用close时都会被调用。
6. scddp内存使用
scddp使用的内存区域称为设备,其长度是可变的,写得越多,它就变得越长。Scddp驱动程序引入了Linux内核中用于内存管理的两个核心函数:
void *kmalloc(size_t size, inf flags);
void kfree(void *ptr);
在scddp中,每个设备都是一个指针链表,其中每个指针都指向一个scddp_qset结构:
struct scddp_qset{ void **data; struct scddp_qset *next; };
下面是scddp_trim函数,负责释放整个数据区,并且在文件以写方式打开时由scddp_open调用。
int scddp_trim(struct scddp_dev *dev) { scddp_dev_s *next, *dptr; int qset = dev->qset; /* "dev" 不是NULL */ int i; for (dptr = dev; dptr; dptr = next) { /* 释放所有的数据区域 */ if (dptr->data) { for (i = 0; i < qset; 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 = scddp_quantum; dev->qset = scddp_qset; dev->next = NULL; return 0; }
模块的清除函数也调用scddp_trim函数,以把由scddp所使用的内存返回给系统。