玛驰怎么样:[快速上手Linux设备驱动]之我看字符设备驱动

来源:百度文库 编辑:偶看新闻 时间:2024/05/04 19:32:19

[快速上手Linux设备驱动]之我看字符设备驱动

这段时间算是把linux下的字符设备给基本吃透了,这边walfred会根据自己的观点,分解拆卸linux下字符设备并将其整理出来。

预备知识

这边提到的linux字符设备驱动是基于linux动态加载模块的思想,所以请务必知道linux模块的应用,可参考内核模块编程入门程序及标准Makefile文件。

1Linux字符设备驱动描述图

说明:

1.1这里我向大多数介绍linux字符设备驱动的书籍一样,将字符设备linux字符设备驱动用户程序分别独立开来。

1.2通过上图可以简单的看出linux字符设备驱动这一块主要是有cdev这个玩意主导了一切呀,所以下文会讲到cdev以及cdev关联的file_operations结构体。

2拆分字符设备驱动

2.1两个结构体cdev和file_operations

2.1.1关于cdev结构体

在字符设备驱动中,cdev结构体顾名思义(char device)可以知道其就是来描述一个字符设备的,看下cdev结构体的定义:

linux-2.6.32/include/linux/cdev.h

struct cdev {

        struct kobject kobj;// kobj是一个嵌入在该结构中的内核对象。

        struct module *owner;// owner指向提供驱动程序的模块

        const struct file_operations *ops;// ops是一组文件操作,实现了与硬件通信的操作。

        struct list_head list;// list用来实现一个链表,其中包含所有表示该设备的设备特殊文件的inode.

        dev_t dev;// 指定了设备号

        unsigned int count;// count表示与该设备关联的从设备的数目

};

看到了这个cdev结构体还真不简单,里面包含这么一些东西,关于ops在cdev结构体定义中已经有解释,所以下面将是重点描述下file_operations结构体。

2.1.2关于file_operations结构体

file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。

        在系统内部,I/O设备的存取操作通过特定的入口点来进行,而这组特定的入口点恰恰是由设备驱动程序提供的。通常这组设备驱动程序接口是由结构file_operations结构体向系统说明的,它定义在include/linux/fs.h中。

linux-2.6.32/include/linux/fs.h

struct file_operations {

         struct module *owner;

         loff_t (*llseek) (struct file *, loff_t, int);

         ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

         ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

         ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

         ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

         int (*readdir) (struct file *, void *, filldir_t);

         unsigned int (*poll) (struct file *, struct poll_table_struct *);

         int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

         long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

         long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

         int (*mmap) (struct file *, struct vm_area_struct *);

         int (*open) (struct inode *, struct file *);

         int (*flush) (struct file *, fl_owner_t id);

         int (*release) (struct inode *, struct file *);

         int (*fsync) (struct file *, struct dentry *, int datasync);

         int (*aio_fsync) (struct kiocb *, int datasync);

         int (*fasync) (int, struct file *, int);

         int (*lock) (struct file *, int, struct file_lock *);

         ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

         unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

         int (*check_flags)(int);

         int (*flock) (struct file *, int, struct file_lock *);

         ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

         ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

         int (*setlease)(struct file *, long, struct file_lock **);

};

传统上, 一个 file_operation 结构或者其一个指针称为 fops( 或者它的一些变体).。结构中的每个成员必须指向驱动中的函数, 这些函数实现一个特别的操作, 或者对于不支持的操作留置为 NULL. 当指定为 NULL 指针时内核的确切的行为是每个函数不同的。

在你通读 file_operations 方法的列表时, 你会注意到不少参数包含字串 __user. 这种注解是一种文档形式, 注意, 一个指针是一个不能被直接解引用的用户空间地址. 对于正常的编译, __user 没有效果, 但是它可被外部检查软件使用来找出对用户空间地址的错误使用。

        struct file_operations是一个字符设备把驱动的操作和设备号联系在一起的纽带,是一系列指针的集合,每个被打开的文件都对应于一系列的操作,这就是file_operations,用来执行一系列的系统调用。

       在常见的字符设备驱动程序中,我们一般这样做, 它的 file_operations 结构是如下初始化的:

struct file_operations scull_fops = {

        .owner = THIS_MODULE,

        .llseek = scull_llseek,

        .read = scull_read,

        .write = scull_write,

        .ioctl = scull_ioctl,,

        .open = scull_open,

        .release = scull_release,

};

这种做法可以将我们自己定义的接口函数和file_operations结构体中定义的函数指针关联起来,说白了我们就是这样将file_operations结构体初始化的。

2.2 dev_t dev指定设备号

在cdev结构体中,我们对const struct file_operations *ops和dev_t dev关爱有加,其中我们在上面已经详细的说了file_operations,现在就重点说下dev_t dev,因为这玩意一旦指定分配了,在用户空间创建的字符设备必须也要使用相同的设备,然后进行对应起来,用户程序才会打开字符设备。

2.2.1dev_t类型

  在内核中,dev_t类型(定义在中)用来保存设备编号——包括主设备号和次设备号。在内核2.6.0之后,dev_t是一个32位的数,其中12位表示主设备号,20位表示次设备号。

  获得dev_t的主、次设备号:

  MAJOR(dev_t dev);

  MINOR(dev_t dev);

  生成(转换成)dev_t类型:

        MKDEV(int major, int minor);

       分配和释放设备号

2.2.2建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。其这主要函数在中声明:

//指定设备编号

int register_chrdev_region(dev_t first, unsigned int count,char*name);

//动态生成设备编号

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,

unsigned int count, char *name);

//释放设备编号

void unregister_chrdev_region(dev_t first, unsigned int count);

3 Linux字符设备模板

3.1 驱动加载和卸载函数

       加载函数中应该实现设备号的申请和cdev的注册,而在卸载函数中实现设备号的释放和cdev的注销。

       工程师习惯定义一个与设备相关的结构体,通常会涉及私有数据、cdev、信号量等信息(这个比较重要可以将与一个设备相关信息通过”面向对象”的方式包装起来)。

设备结构体

 Struct xxx_dev_t {

        Struct cdev cdev;

        …..

私有数据;

}

加载函数

static int __init xxx_init(void)

{

    1. 申请设备号

        2.cdev_init(&xxx_dev.dev,&xxx_fops);

        3.cdev_add;

}

卸载函数

static int __exit xxx_exit(void)

{

        1.  释放设备号

        2.  注销设备;

}

3.2 定义字符设备file_operations结构体成员函数

         read();

         write();

         ioctl();

3.3 file_operations结构体关联

Struct file_operations xxx_ops={

        .owner=THIS_MODULE,

        .read=xxx_read,

        .write=xxx_write,

        …….

};

结语:关于字符设备驱动,walfred就暂时分析到这边了,总体来说字符设备还是蛮简单的,其实只要掌握我画的那图,就基本ok了。可能我在上面分析时系统对cdev的操作没有详细的讲到,但这也无妨,希望读者通过通过我的分析能够理解Linux字符设备驱动程序的基本框架。