前言
字符设备是linux驱动中最基本的一种设备类型。这里介绍linux系统中,字符设备的框架。 主要的内容是cdev结构体和file_operation函数操作集
正文
cdev结构体
在linux中,使用cdev来描述一个字符设备。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
在上述的 cdev 结构体中,有两个很重要的成员,分别是 file_operations 和 dev_t 。
首先来说一下这个 dev,这里的dev是用来保存申请的设备号的。
dev_t 是一个 32 位类型的变量,它的高 12 位为主设备号,低设备号为次设备号。可以使用 kdev_t.h中的 MAJOR(dev)和MINOR(dev) 两个宏从设备号中获得主设备号和次设备号;也可以通过 MKDEV(ma,mi) 由主设备号和次设备号合成设备号。
另一个结构体成员是一个函数操作集,他是字符设备的主体。
内核中提供了一组函数用来操作cdev结构体
void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
cdev_alloc()函数用于动态的申请一个cdev内存。
/**
* cdev_alloc() - allocate a cdev structure
*
* Allocates and returns a cdev structure, or NULL on failure.
*/
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结构体,比如需要进行初始化的操作。
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
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_init() 函数,主要的是将给cdev 的成员 ops 进行 赋值操作。
这样,以后我们的操作,可以通过找到cdev,调用cdev的 ops中的各种方法了。
初始操作完善后,需要将cdev添加到一个全局的 cdev_map 中。在这之前,还需要为字符设备申请一个设备号。 通过 alloc_chrdev_region()得到设备号
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
主设备号由系统进行分配,需要提供申请的此设备号是多少。连续申请几个设备,还有申请设备的名字。
如果已知设备号,可以使用 int register_chrdev_region(dev_t from, unsigned count, const char *name) 进行注册设备
在申请完设备号之后,需要将 cdev 添加到系统中。
/**
* 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);
}
字符设备的注册过程基本就是这样,需要 设备号 对cdev的操作,还有 ops函数操作集。
有申请,有注册的过程,必然有释放、撤销的过程
对设备号的释放
/**
* unregister_chrdev_region() - return a range of device numbers
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from. The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count)
{
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
}
对cdev的操作
/**
* cdev_del() - remove a cdev from the system
* @p: the cdev structure to be removed
*
* cdev_del() removes @p from the system, possibly freeing the structure
* itself.
*/
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
这里介绍了 cdev 相关的一些操作函数。 下一节在介绍file_operations 函数操作集的相关内容。
总结
字符设备框架就是围绕着 cdev 来进行的一些操作。
注册的过程,要申请cdev结构体 初始化cdev 申请设备号 注册cdev
注销的过程和注册的过程顺序想法,先释放申请的设备号,后释放cdev