11.字符设备的注册
更多内容请参考Linux设备驱动程序学习----目录
字符设备的注册
内核内部使用struct cdev结构来表示字符设备,在内核调用设备的操作之前,必须分配注册一个或多个数据结构,参考上节重要的数据结构,因该包含头文件<linux/cdev.h>。
分配和初始化struct cdev的方式有两种,
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
初始化一分配到的cdev结构:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
cdev结构的所有者字段,应该设置为THIS_MODULE:
.owner = THIS_MODULE
cdev结构设置好之后,需要告诉内核该结构的信息:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
dev: 是cdev结构
num: 是该设备对应的第一个设备编号
count: 是应该和该设备关联的设备编号的数量,经常取count = 1
在某些情况下,会有多个设备编号对应于一个特定的设备;
在使用cdev_add()函数时应注意,这个调用可能会失败,如果返回一个负的错误码,该设备不会被添加到系统中。因此,在驱动程序还没有完全准备好处理设备上的操作时,就不能调用cdev_add()函数。
从系统中移除一个字符设备:
void cdev_del(struct cdev *dev);
调用cdev_del()函数之后,就不应该再访问cdev结构了。
scull中的设备注册
在scull驱动中,通过struct scull_dev结构来表示每个设备:
struct scull_dev {
struct scull_qset *data; // 指向第一个量子集的指针
int quantum; // 当前量子的大小
int qset; // 当前数组的大小
unsigned long size; // 保存在其中的数据总量
unsigned int access_key; // 由sculluid和sculloriv使用
struct semaphore sem; // 互斥信号量
struct cdev cdev; // 字符设备结构
}
在scull驱动中,将struct cdev结构初始化并添加到系统中的操作如下:
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err;
dev_t devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding scull: %d\n", err, index);
}
早期的办法
在内核Linux-2.6之前的版本中,字符设备驱动程序没有使用cdev接口,而是使用老的接口,在新的代码中不应该使用老的接口,因为这种机制会在将来的内核中消失。
注册一个字符设备驱动程序的经典方式是:
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
major: 是设备的主设备号
name: 是驱动程序的名称(出现在/proc/devices中)
fops: 是默认的file_operations结构
register_chrdev()函数,可实现静态、动态注册:
动态注册,即major = 0时,类似alloc_chrdev_region()函数,系统会动态为设备分配一个未被使用的主设备号,并将分配的主设备号作为返回值;
静态注册,即major不为0,而是由驱动开发者指定,类似register_chrdev_region()函数,系统会检测指定的主设备号是否已经被使用,如果没有被使用,则分配给该设备使用,如果已经被占用,就返回一个错误值。
对register_chrdev()函数的调用将为给定的主设备号注册0~255作为次设备号,并为每一个设备建立一个对应的默认cdev结构,不能使用大于255的主设备号和次设备号。
注销一个已经注册的字符设备驱动程序,即将已经注册的设备从系统中移除的方式:
int unregister_chrdev(unsigned int major, const char *name);
// major和name必须和传递给register_chrdev()函数的参数值保持一致,否则该调用会失败。
更多内容请参考Linux设备驱动程序学习----目录