上一节中介绍了设备号的申请和释放,这一节开始了解字符设备的相关操作。
首先定位到<linux/cdev.h>文件,查看内核提供给字符设备的接口。
cdev结构
struct cdev { struct kobject kobj; //内嵌的kobject对象 struct module *owner; //此结构所属模块 const struct file_operations *ops; //文件操作结构 struct list_head list; //通用双向链表 dev_t dev; //设备号 unsigned int count; };
owner成员一般初始化为 THIS_MODULE,THIS_MODULE 是一个指向当前模块的 struct module结构指针,也就是指向当前模块。
字符设备的函数接口
void cdev_init(struct cdev *, const struct file_operations *); struct cdev *cdev_alloc(void); int cdev_add(struct cdev *, dev_t, unsigned); void cdev_del(struct cdev *);
以上是<linux/cdev.h>提供的部分函数接口。接下来一个一个地解决掉它们。
首先是 cdev_init 函数,先看源码。
void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
此函数的功能是初始化一个 cdev 结构。参数 cdev 即为要进行初始化的结构,参数 fops则是此设备的 file_operations(具体作用留到后面)。
可以看到 cdev_init 的主要作用就是初始化 cdev 结构,把 fops 指针连接到 cdev结构。做好此设备被添加到系统的准备。
接下来看 cdev_alloc 函数。
struct cdev *cdev_alloc(void) { struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p; }
此函数为 cdev 结构申请了一块内存,并返回它的地址,失败时返回NULL。
下一个函数是 cdev_add,它将一个字符设备添加到系统。
int cdev_add(struct cdev *p, dev_t dev, unsigned count) { int error; p->dev = dev; p->count = count; error = kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); if (error) return error; kobject_get(p->kobj.parent); return 0; }
参数 p 是设备的 struct cdev指针,dev 是首个设备号,count 是连续的次设备号的数量。函数通过把指针 p 添加到系统,来描述设备的添加,使设备立即生效。若添加失败,则返回负数的错误码。
最后一个函数是 cdev_del,它的功能是从系统移除一个 cdev 结构。
void cdev_del(struct cdev *p) { cdev_unmap(p->dev, p->count); kobject_put(&p->kobj); }
此函数从系统中移除指针 p,有可能会释放 p 指向的结构。
字符设备的注册和注销
还记得上一节中未实现的 mycdev_setup 函数和 mycdev_del 函数吗?现在我们已经可以实现它们啦。
#include <linux/cdev.h> #include <linux/device.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/module.h> dev_t devno; //设备号 static struct class *my_class; static struct cdev my_cdev; static struct file_operations my_fops; static int __init mycdev_init(void) { int ret; ret = alloc_chrdev_region(&devno, 0, 1, "mycdev"); if(ret != 0){ printk(KERN_NOTEICE "Alloc device number failed."); return -1; } //开始实现cdev_setup() cdev_init(&my_cdev, &my_fops); my_cdev.owner = THIS_MODULE; ret = cdev_add(&my_cdev, devno, 1); if(ret < 0){ printk(KERN_NOTEICE "Add cdev failed."); return -2; } //cdev_setup()结束 my_class = class_create(THIS_MODULE, "mycdev"); device_create(my_class, NULL, devno, NULL, "mycdev"); return 0; } static void mycdev_exit(void) { //mycdev_del()实现 cdev_del(&my_cdev); //mycdev_del()结束 device_destroy(my_class, devno); class_destroy(my_class); unregister_chrdev_region(devno); } module_init(mycdev_init); module_exit(mycdev_exit); MODULE_LICENSE(“Dual BSD/GPL”);
现在我们就完成了一个基本的字符设备模块,它实现了设备号的申请与注销,设备文件的创建与销毁以及字符设备的初始化、注册与注销。
但是,这还不够。我们的目的是使用字符设备,至少需要读或者写此设备。如何让字符设备模块提供读写功能呢?这就和 struct file_operations 结构有关了,留待下一节详细叙述。