上一节中介绍了设备号的申请和释放,这一节开始了解字符设备的相关操作。
首先定位到<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 结构有关了,留待下一节详细叙述。