cdev 结构体与字符设备的注册
在 linux 2.6内核中,使用 cdev结构体描述字符设备,cdev 的定义在 <linux/cdev.h> 中可找到,其定义如下:
引用
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
cdev 结构体中的 dev_t 成员定义了设备号,为 32 位,其中高 12 位为主设备号,低 20 位为次设备号。
其中,struct kobject 是内嵌的 kobject 对象;
struct module 是所属模块;
struct file_operations 为文件操作结构体。
使用以下宏可以从 dev_t 获得主设备号和次设备号:
引用
MAJOR (dev_t dev);
MINOR (dev_t dev);
而使用下面宏可以通过主设备号和次设备号生成 dev_t :
引用
MKDEV (int major, int minor);
有两个方法可以分配并初始化 cedv 结构。如果希望在运行时动态的获得一个独立的 cdev 结构,可以如下这么做:
引用
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
cdev_alloc(void) 函数的代码为(对 cdev 结构体操作的系列函数可在 fs/char_dev.c 中找到):
引用
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_alloc() 的源代码可能由于内核版本号的不同而有差别(上面的代码为 2.6.30)
有时可能希望就把 cdev 结构内嵌在自己的特定设备结构里,那么在分配好 cdev 结构后,就用 cdev_init() 函数对其初始化:
引用
void cdev_init (struct cdev *cdev, struct file_operations *fops)
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 中的 owner 要设置为 THIS_MOULE 。
一旦 cdev 结构体设置完毕,最后一步就是要把这事告诉给内核,使用下面的函数:
引用
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
cdev_add() 对应的代码为:
引用
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
参数 p 是 cdev 结构体的指针;
参数 dev 是设备响应的第一个设备号;
参数 count 和设备相关联的设备号的数目。
一般的,count 的值为 1,但是有些情形也可能是大于 1 的数。比如 SCSI 磁带机,它通过给每个物理设备安排多个此设备号来允许用户在应用程序里选择操作模式(比如密度)。
cdev_add 如果失败了,那么返回一个负值,表明驱动无法加载到系统中。然而它一般情况下都会成功,一旦 cdev_add 返回,设备也就 “活” 了起来,于是所对应的操作方法(file_operations 结构里所定义的各种函数)也就能为内核所调用。
从系统中移除一个字符设备,可以调用:
引用
void cdev_del(struct cdev *p)
老版本的字符设备注册与注销
在许多驱动程序代码里,会看到许多字符设备驱动并没有用 cdev 这个接口。这是一种老式的方法,但新写的代码应该使用 cdev 接口。
用于注册字符设备驱动程序的老式函数 register_chrdev() 函数定义如下:
引用
int register_chardev (unsigned int major, const char *name, struct file_operations *fops)
利用该函数注册时,应先定义好主设备号、设备驱动程序的名称、file_operations 结构体的变量。
应用程序中利用设备文件搜索设备驱动程序的时候使用主设备号 (major) 。
在内核中表示 proc 文件系统或错误代码时,使用设备驱动程序名称。
另外,利用 unregister_chrdev() 函数注销字符设备驱动程序时,可以作为区分标志。注册函数中关键的地方是定义 file_operations 结构体变量的地址。
所谓注册字符设备驱动程序,应理解为在内核中注册与主设备号相关的 file_operations 结构体。
register_chrdev() 函数注册完设备驱动程序,把定义主设备号的 major 设置为 0,返回注册的主设备号(动态分配),把已知的主设备号设为 major 值时,返回 0 (人工指定)。注册失败时,返回负值
从内核中注销字符设备驱动程序的 unregister_chrdev() 函数形式如下:
引用
int unregister_chrdev (unsigned int major, const char *name)
该函数中使用主设备号(major) 和设备驱动程序名称 (name) 与 register_chrdev 函数中使用的值相同,因为内核会把这些参数作为注销字符设备驱动程序的基准对比两个设定内容。从内核成功注销了字符设备驱动程序时,返回 0 ,失败则返回负值。