CDEV是linux中最庞大的一类设备。
struct cdev{
struct list_head list;
dev_t dev;
unsigned int count;
const struct file_operations* ops;
struct kobject kobj;
struct module* owner;
};
struct file_operations{
struct module* owner;
...
(*open)(...),
(*release)(...),
(*read)(...),
(*write)(...),
(*ioctl)(...),
(*llseek)(...),
(*poll)(...),
(*mmap)(...),
...
};
CDEV内嵌一个链节,所以CDEV可以构成链表。
CDEV内嵌一个kobject,所以它扩展了kobject,支持SYSFS文件系统访问机制。
它的几个重要成员是,dev, ops。
其中dev是设备号,ops是关联的文件操作集。可以看到,ops里,是一系列函数指针。
来看看内核中FILE是怎么定义的。
struct file{
...
struct path f_path;
struct inode * f_inode;
const struct file_operations * f_ops;
loff_t f_pos;
void* private_data;
...
};
struct i_node{
...
unsigned long i_type;
struct cdev* i_cdev;
struct block_device* i_bdev;
...
};
从中可以看出,INODE记录了类型,并关联到一个CDEV。
而FILE内嵌一个path,用来记录FILE在文件系统中的路径信息。
FILE关联到一个INODE,用来索引打开的文件在内核中的INODE。
FILE关联到一个FOPS,当内核为FILE提供文件操作服务时,会调用FOPS中的函数完成实际操作。
FILE的private_data,是一个通配句柄。通常用来做实体标签(InstanceTag)。
InstanceTag是SOD中常用的设计技巧。
在一个对象中,如果需要另外一个对象作为成员,有两种方式,一种是内嵌,另一种是关联引用。如果另一个对象占用空间很大,或者另一个对象也可能被其他的对象关联引用时,我们就不能使用内嵌方式,而应该使用关联引用的方式。
这就是InstanceTag。
通过在对象中放置一个指针,既可以缩小体积,又能够索引到所关联的另外一个对象。
很多时候,如果对象是动态生成的,那么就更应该使用InstanceTag。
我们来看一看File Operate Mechanism With CDEV。
kernel维护一个ACTIVE_CDEV_LIST,所有注册到内核中的CDEV,被添加到ACTIVE_CDEV_LIST中。
kernel维护一个INODE_TREE,如果一个INODE对应的是CDEV,那么当用户进程通过path索引到一个INODE_CDEV时,打开的FILE就关联到这个CDEV。
整个索引过程为,filep->f_inode->i_cdev->ops.
此时,内核会把CDEV的FOPS传递给FILE。这样,FILE就有了有效的FOPS。
FILE在open时,首先索引到CDEV,然后获取有效 的FOPS填充FILE的成员,最后,会调用CDEV的FOPS中的open函数。这里是进行实体标记的最佳位置,通常会用CDEV的衍生对象的句柄来填充filp->private_data。
我们可以看到,FOPS中的函数,首参都是FILE。
这是符合SOD设计思想的。object has method。
在linux环境下,没有OOD的语法表达形式。但是首参,却可以间接代表object。
当用户进程发起文件操作相关的调用时,首参是FILE,表示这个调用栈,是面向FILE的调用栈。后续的层层调用,都按约定,传入的首参为FILE,也就是表明这一系列的调用,都隶属于同一个调用栈。
虽然没有语法上的规定,表明非要这样去设计函数,但是,遵循SOD设计思想去设计函数,将使得程序的逻辑更易懂。
另外,FILE是一个完备的控制块,利用FILE,可以获取所需要的任何系统资源。
在open中配置好FILE中的InstanceTag之后,其他的事务性函数被内核调用时,都能够通过filp->private_data索引到Instance,找到Derived_CDEV。
整个驱动分为几个部分:
1)Derived_CDEV_CB定义,并实例化。
2)FOPS实例化,并填充。
3)驱动操作函数编写,分为机制性函数的编写和事务性函数的编写。
4)模块加载函数编写,在其中注册CDEV。
5)模块卸载函数编写,在其中反操作。
内核提供了CDEV注册相关的API。
alloc_chrdev_region,
cdev_init,
MKDEV,MAJOR, MINOR,
cdev_add,
cdev_del,
由于驱动运行在内核空间,而用户buf位于用户空间,所以内核提供了跨空间拷贝API。
copy_from_user,
copy_to_user,
put_user,
get_user
BASH命令mknod用来创建一个path对应的INODE
#mknod /dev/vser0 c 256 0
#mknod /dev/vser1 c 256 1
但是通常是由mdev管理设备节点,所以不需要手工写入mknod命令。
另一个方法是,在rcS启动脚本中,写入mknod命令。