字符设备驱动

来源:互联网 发布:穿衣搭配知多少sina 编辑:程序博客网 时间:2024/06/06 04:51

相关概念

设备号

设备号概念

Linux系统将所有的字符设备抽象成文件呈献给用户,用户可以在/dev 目录下找到绝大部分的字符设备文件,比如/dev/tty等。应用程序访问设备的途径正是这些设备文件。这产生了一个问题:如何将这些设备文件跟具体的设备联系起来呢?事实上,Linux系统内部为了管理字符设备,给每个字符设备设置了唯一的“编号”,也叫设备号(其实就是一个32位的整数),通过这个设备号,内核可以知道哪个文件是对应哪个设备的。
每个设备号由两部分组成:主设备号和次设备号。前面说了设备号为设备文件和设备联系起来了,但是每个设备驱动可能不只处理一个设备,比如一个串口设备驱动可能要处理两个串口的收发。这时就可以将这主设备号和从设备号的功能细分了。即主设备号负责将设备文件与设备驱动联系起来,而次设备号就将设备驱动与具体的设备联系起来。

分配设备号:

静态分配设备号,用于设备号已知的情况,不常用:

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);

释放设备号

下面的函数用于将设备号释放,通常在驱动模块卸载的时候释放设备号:

int unregister_chrdev_region(dev_t dev, unsigned int count);

从设备号分出主、次设备号

int MAJOR(dev_t dev);//分离出主设备号int MINOR(dev_t dev);//分离出次设备号

合成设备号

dev_t MKDEV(unsigned int major,unsigned int minor);


主要数据结构介绍

字符设备描述结构struct cdev

在内核中每个设备都由一个结构来描述,并且采用了面向对象的思想,即属性和行为集合在一个结构中。但内核并没有采用C++等面向对象语言,因此采用结构体描述,并且在结构体中包含一个函数集。
字符设备由结构体 struct cdev 来描述,其定义如下:

struct cdev{    struct kobject kobj;      //模块kobject     struct module *owner;     //模块所有者    const struct file_operations *ops;   //驱动操作函数集    struct list_head list;    //字符设备结构链表    dev_t dev;                //设备号    unsigned int count;      //子设备数};

编写驱动主要关注 ops、dev、count 这三个字段,其余字段由内核维护,其中ops 指向这个设备所支持的操作集,dev 是这个设备的设备号,count 是这个设备的子设备数比如串口设备,这个变量指出串口的个数。

打开文件结构struct file

内核中每个打开的文件都由一个struct file结构体描述,这个结构体包含了进程打开的这个文件的所有属性,因为这个结构体比较庞大,因此这里只列出与驱动编写相关的字段:

struct file {    ...    struct file_operations *f_op; //这个文件支持的操作    loff_t   f_pos;               //当前读写的位置    void     *private_data;       //可用的额外指针    ...}

f_op 指向这个函数可以执行的操作集, f_pos是当前文件的读写位置,private_data 可以指向驱动其他空间,驱动工程师可以将这个指针指向自己开辟的内核空间。

文件操作集合struct file_operations

每个打开的文件都会对应自己的操作集合,用以响应用户空间调用的文件系统调用。如open、read、write、close、ioctl等系统调用,最终响应这些系统调用的就是驱动中定义在file_operations 的函数。其定义如下:

truct 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 *);/* remove by cym 20130408 support for MT660.ko */#if 0//#ifdef CONFIG_SMM6260_MODEM#if 1// liang, Pixtree also need to use ioctl interface...    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);#endif#endif/* end remove */    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 *, 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 **);    long (*fallocate)(struct file *file, int mode, loff_t offset,              loff_t len);/* add by cym 20130408 support for MT6260 and Pixtree */#if defined(CONFIG_SMM6260_MODEM) || defined(CONFIG_USE_GPIO_AS_I2C)    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);#endif/* end add */};

文件索引节点struct inode

内核用inode 结构在内部表示文件,但是不同于file结构,inode结构在系统中是唯一的,因为它描述了一个文件的存在。而file结构描述了一个进程打开文件后的状态,不同的进程可能打开同一个文件。不同的进程打开同一个文件后,每个进程都有各自的file结构,但是都指向了同一个inode 结构。inode结构中包含了大量有关文件的信息,但是这里我们只关注对驱动编写有关的两个字段:

struct inode{    ...    dev_t  i_rdev;        //对表示设备文件的inode结构,该字段包含了真正的设备号。    struct cdev *i_cdev;  //当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针。    ...};

设备注册与注销

设备注册

前面提到内部用struct cdev结构来表示字符设备,在内核调用设备的操作之前,我们必须将这个结构初始化并注册到内核中。
初始化cdev:

void cdev_init(struct cdev *dev,struct file_operations *fops);

这个函数将cdev 结构与具体的file_operations 联系,如果追踪这个函数的实现可以发现,其实这个函数就是将fops赋给cdev->ops。

注册cdev:

int cdev_add(struct cdev *dev,dev_t num, unsigned int count);

所谓注册设备就是告诉内核该结构的信息。在驱动中一旦调用这个函数成功,则我们的设备就“活”了,内核就能调用它的操作,因此我们的驱动在没准备好操作集(file_operations)之前,不要调用这个函数。

设备注销

通过下面的方法将设备注销掉:

void cdev_del(struct cdev *dev);

思维导图

根据上面提到的知识,画出编写一个字符设备驱动的思维导图如下:

设备驱动模型

范例代码

最后给出一个字符设备驱动的范例代码,该驱动中开辟一块内核内存模拟设备中的内存,应用程序可以对这个虚拟设备进行读写。

#include <linux/module.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/cdev.h>#include <asm/uaccess.h>#include <linux/slab.h>int dev1_registers[5];int dev2_registers[5];struct cdev cdev; dev_t devno;/*文件打开函数*/int mem_open(struct inode *inode, struct file *filp){    /*获取次设备号*/    int num = MINOR(inode->i_rdev);    if (num==0)        filp->private_data = dev1_registers;    else if(num == 1)        filp->private_data = dev2_registers;    else        return -ENODEV;  //无效的次设备号    return 0; }/*文件释放函数*/int mem_release(struct inode *inode, struct file *filp){    return 0;}/*读函数*/static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){    unsigned long p =  *ppos;    unsigned int count = size;    int ret = 0;    int *register_addr = filp->private_data; /*获取设备的寄存器基地址*/    /*判断读位置是否有效*/    if (p >= 5*sizeof(int))    return 0;    if (count > 5*sizeof(int) - p)    count = 5*sizeof(int) - p;    /*读数据到用户空间*/    if (copy_to_user(buf, register_addr+p, count))    {    ret = -EFAULT;    }    else    {    *ppos += count;    ret = count;    }    return ret;}/*写函数*/static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos){    unsigned long p =  *ppos;    unsigned int count = size;    int ret = 0;    int *register_addr = filp->private_data; /*获取设备的寄存器地址*/    /*分析和获取有效的写长度*/    if (p >= 5*sizeof(int))    return 0;    if (count > 5*sizeof(int) - p)    count = 5*sizeof(int) - p;    /*从用户空间写入数据*/    if (copy_from_user(register_addr + p, buf, count))    ret = -EFAULT;    else    {    *ppos += count;    ret = count;    }    return ret;    }    /* seek文件定位函数 */    static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)    {     loff_t newpos;    switch(whence) {    case SEEK_SET:     newpos = offset;    break;    case SEEK_CUR:     newpos = filp->f_pos + offset;    break;    case SEEK_END:     newpos = 5*sizeof(int)-1 + offset;    break;    default:     return -EINVAL;    }    if ((newpos<0) || (newpos>5*sizeof(int)))    return -EINVAL;    filp->f_pos = newpos;    return newpos;}/*文件操作结构体*/static const struct file_operations mem_fops ={    .llseek = mem_llseek,    .read = mem_read,    .write = mem_write,    .open = mem_open,    .release = mem_release,};/*设备驱动模块加载函数*/static int memdev_init(void){    /*初始化cdev结构*/    cdev_init(&cdev, &mem_fops);    /* 注册字符设备 */    alloc_chrdev_region(&devno, 0, 2, "memdev");    cdev_add(&cdev, devno, 2);}/*模块卸载函数*/static void memdev_exit(void){    cdev_del(&cdev);   /*注销设备*/    unregister_chrdev_region(devno, 2); /*释放设备号*/}MODULE_LICENSE("GPL");module_init(memdev_init);module_exit(memdev_exit);
0 0
原创粉丝点击