大学晚会会场布置图片:cdev 结构体与字符设备的注册

来源:百度文库 编辑:中财网 时间:2024/04/24 03:39:01


cdev结构

在Linux2.6内核中一个字符设备用cdev结构来描述,其定义如下:
struct cdev {
        struct kobject kobj;
        struct module *owner;   //所属模块
        const struct file_operations *ops;  
                //文件操作结构,在写驱动时,其结构体内的大部分函数要被实现
        struct list_head list;
        dev_t dev;          //设备号,int 类型,高12位为主设备号,低20位为次设备号
        unsigned int count;
};
可以使用如下宏调用来获得主、次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)
MKDEV(int major,int minor) //通过主次设备号来生成dev_t
以上宏调用在内核源码中如此定义:

#define MINORBITS       20
#define MINORMASK       ((1U << MINORBITS) - 1)
        //(1<<20 -1) 此操作后,MINORMASK宏的低20位为1,高12位为0
#define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))
//摘自:
http://lxr.linux.no/linux/include/linux/kdev_t.h#L1
下面一组函数用来对cdev结构体进行操作:
void cdev_init(struct cdev *, const struct file_operations *);
        //初始化,建立cdev和file_operation 之间的连接
struct cdev *cdev_alloc(void);  //动态申请一个cdev内存
void cdev_put(struct cdev *p);   //释放
int cdev_add(struct cdev *, dev_t, unsigned); 
        //注册设备,通常发生在驱动模块的加载函数中
void cdev_del(struct cdev *);//注销设备,通常发生在驱动模块的卸载函数中

在注册时应该先调用: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)函数代替,他们之间的区别在于:register_chrdev_region()用于已知设备号时,另一个用于动态申请,其优点在于不会造成设备号重复的冲突。
在注销之后,应调用:void unregister_chrdev_region(dev_t from,unsigned count)函数释放原先申请的设备号。
他们之间的顺序关系如下:
register_chrdev_region()-->cdev_add()     //此过程在加载模块中
cdev_del()-->unregister_chrdev_region()     //此过程在卸载模块中

cdev 结构体与字符设备的注册

在 linux 2.6内核中,使用 cdev结构体描述字符设备,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 结构体中的 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 ,失败则返回负值。