嵌入式工程师
分类: LINUX
2013-08-09 08:51:19
原文地址:字符设备驱动学习笔记(2.6.23) 作者:niutao.linux
13struct cdev { 14 struct ; 15 struct *; 16 const struct *; 17 struct ; 18 dev_t dev; 19 unsigned int ; 20}; |
二、作用:
描述字符设备的基本结构体,用于进一步封装在更大的结构体内,以描述具体的字符设备,例如:
struct xxx_cdev {
const char *name;
struct cdev cdev;
...
}
这样就可以使用xxx_cdev结构描述具体的字符设备了。
三、各字段详解:
1、struct kobject kobj;
具体结构参见http://blog.chinaunix.net/u1/55599/showart.php?id=1086478,此处它是一个内嵌的结构,作用是提供引用计数。在调用cdev_init()函数初始化cdev结构体时,会调用kobject_init()函数初始化该数据域。具体见下文cdev_init()函数。
2、struct module *owner;
指出该字符设备所属的模块,一般初始化为THIS_MODULES。
3、struct file_operations *ops;
文件操作指针,也是该结构中最重要的数据域。字符设备驱动的编写主要就是实现file_operations里的一些常用函数,比如read,write,open,release等。关于file_operations结构的详解参见:http://blog.chinaunix.net/u2/73521/showart_1086491.html
4、struct list_head list;
与字符设备文件对应的索引节点链表头,用于收集相同字符设备驱动程序所对应的字符设备文件的索引节点。可能很多设备文件具有相同的设备号,并对应于相同的字符设备。该字段在cdev_alloc()和cdev_init()中进行了初始化。
5、dev_t dev;
代表该字符设备的设备号。dev_t实际上为unsigned int类型,32位。其中高12位为主设备号,低20位为次设备号。如果已知一个设备号,可以使用一下宏分别取得主设备号和次设备号:
MAJOR(dev_t dev) /*取得主设备号*/
MINOR(dev_t dev) /*取得次设备号*/
这两个宏的原型是:/linux/include/linux/kdev_t.h
4#define 20 5#define ((1U << ) - 1) 6 7#define (dev) ((unsigned int) ((dev) >> )) 8#define (dev) ((unsigned int) ((dev) & )) |
可见两个宏都是通过位运算实现的。还有如果已知主设备号和次设备号,可以使用MKDEV()宏求得设备号:
#define (,) ((() << ) | ()) |
6、unsigned int count;
表示设备号范围大小。一个设备驱动程序对应的设备号可以是一个范围,而不仅仅是一个号,设备号位于同一范围内的所有设备文件均由同一个字符设备驱动程序处理。
四、操作:
1、struct cdev * cdev_alloc(void);linux/fs/char_dev.c
509struct cdev *cdev_alloc(void) 510{ 511 struct cdev * = (sizeof(struct cdev), ); 512 if () { 513 ->. = &ktype_cdev_dynamic; 514 (&->); 515 (&->); 516 } 517 return ; 518} |
很显然从函数名和返回值可知该函数的作用是分配并返回一个cdev结构。调用kzalloc()函数申请sizeof(struct cdev)大小的空间并初始化为0,然后检查如果分配成功,则继续进行初始化工作。首先是初始化p->kobj.ktype域为ktype_cdev_dynamic。我们来看看ktype_cdev_dynamic究竟为何物:
489static void cdev_dynamic_release(struct *) 490{ 491 struct cdev * = (, struct cdev, ); 492 cdev_purge(); 493 (); 494} 500static struct ktype_cdev_dynamic = { 501 . = cdev_dynamic_release, 502}; |
可见是用来释放kzalloc()申请的空间的。然后初始化双向循环链表list和内嵌的kobj。最后返回一个cdev结构。如果加上对file_operations域的初始化,则该函数就相当于cdev_init()。
2、void cdev_init(struct cdev *,struct file_operations *);/linux/fs/char_dev.c
528void cdev_init(struct cdev *cdev, const struct *) 529{ 530 (cdev, 0, sizeof *cdev); 531 (&cdev->); 532 cdev->. = &ktype_cdev_default; 533 (&cdev->); 534 cdev-> = ; 535} |
函数的作用是初始化cdev,并建立cdev和file_operations之间的连接。在调用cdev_init()函数之前,cdev结构已经存在,此处先将其初始化为0,接下来进行和cdev_alloc()的初始化相同的动作,最后将传入的文件操作结构体指针fops赋值给file_operations域。ktype_cdev_default和前面讲的ktype_cdev_dynameic处理基本相同,具体参见:
/linux/fs/char_dev.c
3、int cdev_add(struct cdev *, dev_t , unsigned );/linux/fs/char_dev.c
457int cdev_add(struct cdev *, dev_t dev, unsigned ) 458{ 459 ->dev = dev; 460 -> = ; 461 return (cdev_map, dev, , , , , ); 462} |
向系统添加一个字符设备,并且立刻置于活动状态,如果返回一个负值则表示添加失败。由代码可知,调用cdev_add()函数之前,必须已经分配好设备号,并且确定了设备范围。kobj_map()函数的第一个参数cdev_map是被定义在/linux/fs/char_dev.c中的一个kobj_map类型的指针,exact_math和exact_lock是两个函数,具体参见/linux/fs/char_dev.c。
4、void cdev_del(struct cdev *);/linux/fs/char_dev.c
476void cdev_del(struct cdev *) 477{ 478 cdev_unmap(->dev, ->); 479 (&->); 480} |
从系统删除一个字符设备。我们先来看一下cdev_unmap():
464static void cdev_unmap(dev_t dev, unsigned ) 465{ 466 (cdev_map, dev, ); 467} |
cdev_unmap()函数只不过是对kobj_unmap()进行了封装而已。kobj_unmap()对应注册字符设备函数cdev_add()中的kobj_map(),kobject_put()减少引用计数对应cdev_init()中的调用kobject_init()初始化引用计数为1。可见cdev_del()做和cdev_add()完全相反的动作。
5、
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name);
void unregister_chrdev_region(dev_t from, unsigned count);
前两个为向系统申请设备号。register_chrdev_region()用在已知其实设备号的情况,如果设备号未知,则应使用alloc_chrdec_region()函数申请,系统会从254开始寻找未被使用的主设备号,如果找到了并成功分配了,则生成一个设备号放在第一个参数dev中,返回0,否则返回-EBUSY(资源繁忙)。unregister_chrdev_region()函数用于释放原先申请的设备号。
此三个函数具体参见:http://lxr.linux.no/linux/fs/char_dev.c
6、
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops);
void unregister_chrdev(unsigned int major, const char *name);
这两个函数可以说是大大的方便了字符驱动程序的注册和注销过程。
在register_chrdev()函数中,如果参数major为0,则系统动态为其分配一个设备号。如果major大于0则按照major给出的主设备号给其分配一个设备号。设备号分配成功后,register_chrdev()调用cdev_alloc()分配一个cdev结构,并进行初始化,建立cdev和file_operations之间的连接。之后调用cdev_add()向系统注册字符设备。对于它的返回值,在注册成功时,如果参数major为0则返回动态申请的设备号,否则返回0。在注册失败时返回错误码。
在unregister_chrdev()函数中,参数major就是你先前给出的主设备号,name为字符设备名(实际上没有使用),系统就会注销注册的字符设备。
在调用这两个函数时,没有牵扯到使用cdev结构,程序员只需要给出想要该字符设备做什么和怎样做(实现file_operations),给出该字符设备的主设备号或者不给就可以写出字符驱动程序了。