Linux设备驱动——字符设备驱动
2015-09-29 10:46
676 查看
介绍
字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。
字符设备驱动模型
1、cdev结构体
struct cdev { struct kobject kobj; /*内嵌的kobject结构,用于内核设备驱动模型的管理*/ struct module *owner; /*指向包含该结构的模块的指针,用于引用计数*/ const struct file_operations *ops; /*指向字符设备操作函数集的指针*/ struct list_head list; /*该结构将使用该驱动的字符设备连接成一个链表*/ /*与cdev对应的字符设备文件的inode->i_devices的链表头*/ dev_t dev; /*该字符设备的起始设备号,一个设备可能有多个设备号*/ unsigned int count; /*使用该字符设备驱动的设备数量*/ };
cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号
我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)
相反地,可以通过主次设备号来生成dev_t:
MKDEV(int major,int minor)
2、分配cdev
struct cdev btn_cdev; /*申请设备号,如果申请失败采用动态申请方式*/ if(major) { //静态 dev_id = MKDEV(major, 0); register_chrdev_region(dev_id, 1, "button"); } else { //动态 alloc_chardev_region(&dev_id, 0, 1, "button"); major = MAJOR(dev_id); }
静态申请:
int register_chrdev_region(dev_t from, unsigned count, const char *name); /*功能:申请使用从from开始的count个设备号(主设备号不变,次设备号增加)*/
from :要分配的设备编号范围的初始值(次设备号常设为0);
Count:连续编号范围.
name:编号相关联的设备名称. (/proc/devices);
静态申请相对较简单,但是一旦驱动被广泛使用,这个随机选定的主设备号可能会导致设备号冲突,而使驱动程序无法注册。
动态申请:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name); /*功能:请求内核动态分配count个设备号,且次设备号从baseminor开始。*/ //返回值小于0表示分配失败,系统自动返回没有占用的设备号
baseminor:是请求的最小的次编号;
count:是请求的连续设备编号的总数;
name:为设备名
然后通过major=MMOR(dev)获取主设备号
动态申请简单,易于驱动推广,但是无法在安装驱动前创建设备文件(因为安装前还没有分配到主设备号)。
销毁设备号
void unregister_chrdev_region(dev_t first, unsigned int count);
first为第一个设备号,count为申请的设备数量
3、初始化c_dev
内核在内部使用类型 struct cdev 的结构来代表字符设备.有 2 种方法来分配和初始化一个这些结构.①如果你想在运行时获得一个独立的 cdev 结构, 你可以为此使用这样的代码:
struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &my_fops;
②另一种是把cdv结构嵌入到你自己封装的设备结构中,这时需要使用下面的方法来分配和初始化:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
cdev_ init()函数用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接。
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; }
4、注册cdev
int cdev_add(struct cdev *dev, dev_t num, unsigned int count) //向系统添加一个cdev,完成字符设备的注册。
dev 是 cdev 结构, num 是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目. 常常 count 是 1。
5、硬件初始化
6、设备操作
file_operations结构struct file_operations { struct module *owner; //THIS_MODULE, <linux/module.h> 中定义的宏. /*用于修改文件当前的读写位置*/ loff_t(*llseek) (struct file *, loff_t, int); ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, 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); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); 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(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *); 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); };
参考:file_operations中各项解析
file结构,文件结构代表一个打开的文件
struct file{ mode_t fmode; //文件模式,如FMODE_READ,FMODE_WRITE*/ ...... loff_t f_pos; //当前读写位置.off_t是一个64位的数,驱动可以读这个值,如果它需要知道文件中的当前位置,但是正常地不应该改变它。 unsigned int f_flags; //文件标志,例如O_RDONLY,O_NONBLOCK,和O_SYNC.驱动应当检查 O_NONBLOCK标志来看是否是请求非阻塞操作 struct file_operations *f_op; void *private_data; //非常重要,用于存放转换后的设备描述结构指针*/ ....... };
inode 结构
内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:
struct inode{ dev_t i_rdev; //设备编号 struct cdev *i_cdev; }; //从inode中获取主次设备号 unsigned int imajor(struct inode *inode); unsigned int iminor(struct inode *inode);
7、字符设备驱动模块加载与卸载函数
在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 结构的注册
在卸载函数中应该实现设备号的释放与cdev结构的注销。
我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:
/*设备结构体*/ struct xxx_dev{ struct cdev cdev; char *data; struct semaphore sem; ...... }; /*模块加载函数*/ static int __init xxx_init(void) { ....... 初始化cdev结构; 申请设备号; 注册设备号; 申请分配设备结构体的内存; /*非必须*/ } /*模块卸载函数*/ static void __exit xxx_exit(void) { ....... 释放原先申请的设备号; 释放原先申请的内存; 注销cdev设备; }
参考:深入浅出:Linux设备驱动之字符设备驱动
实例1
#ifndef MEMDEV_MAJOR #define MEMDEV_MAJOR 251 /*预设的mem的主设备号*/ #endif #ifndef MEMDEV_NR_DEVS #define MEMDEV_NR_DEVS 2 /*设备数*/ #endif #ifndef MEMDEV_SIZE #define MEMDEV_SIZE 4096 #endif /*mem设备描述结构体*/ struct mem_dev { char *data; unsigned long size; }; static mem_major = MEMDEV_MAJOR; module_param(mem_major, int, S_IRUGO); struct mem_dev *gp_mem_dev; /*设备结构体指针*/ struct cdev g_cdev; /*文件打开函数*/ int mem_open(struct inode *inode, struct file *filp) { struct mem_dev *dev; /*获取次设备号*/ int num = MINOR(inode->i_rdev); if (num >= MEMDEV_NR_DEVS) return -ENODEV; dev = &gp_mem_dev[num]; /*将设备描述结构指针赋值给文件私有数据指针*/ filp->private_data = dev; 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; /*返回值*/ struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/ /*判断读位置是否有效*/ if (p >= MEMDEV_SIZE) /*要读取的偏移大于设备的内存空间*/ return 0; if (count > MEMDEV_SIZE - p) /*要读取的字节大于设备的内存空间*/ count = MEMDEV_SIZE - p; /*读数据到用户空间:内核空间->用户空间交换数据*/ if (copy_to_user(buf, (void*)(dev->data + p), count)) { ret = - EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "read %d bytes(s) from %d\n", count, p); } 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; struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/ /*分析和获取有效的写长度*/ if (p >= MEMDEV_SIZE) return 0; if (count > MEMDEV_SIZE - p) /*要写入的字节大于设备的内存空间*/ count = MEMDEV_SIZE - p; /*从用户空间写入数据*/ if (copy_from_user(dev->data + p, buf, count)) ret = - EFAULT; else { *ppos += count; /*增加偏移位置*/ ret = count; /*返回实际的写入字节数*/ printk(KERN_INFO "written %d bytes(s) from %d\n", count, p); } return ret; } /* seek文件定位函数 */ static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos; switch(whence) { case 0: /* SEEK_SET */ /*相对文件开始位置偏移*/ newpos = offset; /*更新文件指针位置*/ break; case 1: /* SEEK_CUR */ newpos = filp->f_pos + offset; break; case 2: /* SEEK_END */ newpos = MEMDEV_SIZE -1 + offset; break; default: /* can't happen */ return -EINVAL; } if ((newpos<0) || (newpos>MEMDEV_SIZE)) return -EINVAL; filp->f_pos = newpos; return newpos; } /*文件操作结构体*/ static const struct file_operations mem_fops = { .owner = THIS_MODULE, .llseek = mem_llseek, .read = mem_read, .write = mem_write, .open = mem_open, .release = mem_release, }; /*设备驱动模块加载函数*/ static int memdev_init(void) { int result; int i; dev_t devno = MKDEV(mem_major, 0); /* 申请设备号,当xxx_major不为0时,表示静态指定;当为0时,表示动态申请*/ /* 静态申请设备号*/ if(mem_major) result = register_chrdev_region(devno, 2, "memdev"); else /* 动态分配设备号 */ { result = alloc_chrdev_region(&devno, 0, 2, "memdev"); mem_major = MAJOR(devno); /*获得申请的主设备号*/ } if (result < 0) return result; /*初始化cdev结构,并传递file_operations结构指针*/ cdev_init(&g_cdev, &mem_fops); g_cdev.owner = THIS_MODULE; /*指定所属模块*/ g_cdev.ops = &mem_fops; /*注册字符设备*/ cdev_add(&g_cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS); /*为设备描述结构分配内存*/ gp_mem_dev = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL); if (!gp_mem_dev) /*申请失败*/ { result = - ENOMEM; goto fail_malloc; } memset(gp_mem_dev, 0, sizeof(struct mem_dev)); /*为设备分配内存*/ for (i=0; i < MEMDEV_NR_DEVS; i++) { gp_mem_dev[i].size = MEMDEV_SIZE; gp_mem_dev[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL); memset(gp_mem_dev[i].data, 0, MEMDEV_SIZE); } return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; } /*模块卸载函数*/ static void memdev_exit(void) { cdev_del(&g_cdev); /*注销设备*/ kfree(gp_mem_dev); /*释放设备结构体内存*/ unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/ } MODULE_AUTHOR("David Xie"); MODULE_LICENSE("GPL"); module_init(memdev_init); module_exit(memdev_exit);
参考:Linux 内核开发 - 第一个字符驱动程序
Makefile文件如下:
ifneq ($(KERNELRELEASE),) obj-m := cdevdemo.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
实例2
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/uaccess.h> #include <linux/timer.h> #include <asm/atomic.h> #include <linux/slab.h> #include <linux/device.h> #define CDEVDEMO_MAJOR 255 /*预设cdevdemo的主设备号*/ static int cdevdemo_major = CDEVDEMO_MAJOR; /*设备结构体,此结构体可以封装设备相关的一些信息等 信号量等也可以封装在此结构中,后续的设备模块一般都 应该封装一个这样的结构体,但此结构体中必须包含某些 成员,对于字符设备来说,我们必须包含struct cdev cdev*/ struct cdevdemo_dev { struct cdev cdev; }; struct cdevdemo_dev *cdevdemo_devp; /*设备结构体指针*/ /*文件打开函数,上层对此设备调用open时会执行*/ int cdevdemo_open(struct inode *inode, struct file *filp) { printk(KERN_NOTICE "======== cdevdemo_open "); return 0; } /*文件释放,上层对此设备调用close时会执行*/ int cdevdemo_release(struct inode *inode, struct file *filp) { printk(KERN_NOTICE "======== cdevdemo_release "); return 0; } /*文件的读操作,上层对此设备调用read时会执行*/ static ssize_t cdevdemo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { printk(KERN_NOTICE "======== cdevdemo_read "); } /* 文件操作结构体,文中已经讲过这个结构*/ static const struct file_operations cdevdemo_fops = { .owner = THIS_MODULE, .open = cdevdemo_open, .release = cdevdemo_release, .read = cdevdemo_read, }; /*初始化并注册cdev*/ static void cdevdemo_setup_cdev(struct cdevdemo_dev *dev, int index) { printk(KERN_NOTICE "======== cdevdemo_setup_cdev 1"); int err, devno = MKDEV(cdevdemo_major, index); printk(KERN_NOTICE "======== cdevdemo_setup_cdev 2"); /*初始化一个字符设备,设备所支持的操作在cdevdemo_fops中*/ cdev_init(&dev->cdev, &cdevdemo_fops); printk(KERN_NOTICE "======== cdevdemo_setup_cdev 3"); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &cdevdemo_fops; printk(KERN_NOTICE "======== cdevdemo_setup_cdev 4"); err = cdev_add(&dev->cdev, devno, 1); printk(KERN_NOTICE "======== cdevdemo_setup_cdev 5"); if(err) { printk(KERN_NOTICE "Error %d add cdevdemo %d", err, index); } } int cdevdemo_init(void) { printk(KERN_NOTICE "======== cdevdemo_init "); int ret; dev_t devno = MKDEV(cdevdemo_major, 0); struct class *cdevdemo_class; /*申请设备号,如果申请失败采用动态申请方式*/ if(cdevdemo_major) { printk(KERN_NOTICE "======== cdevdemo_init 1"); ret = register_chrdev_region(devno, 1, "cdevdemo"); }else { printk(KERN_NOTICE "======== cdevdemo_init 2"); ret = alloc_chrdev_region(&devno,0,1,"cdevdemo"); cdevdemo_major = MAJOR(devno); } if(ret < 0) { printk(KERN_NOTICE "======== cdevdemo_init 3"); return ret; } /*动态申请设备结构体内存*/ cdevdemo_devp = kmalloc(sizeof(struct cdevdemo_dev), GFP_KERNEL); if(!cdevdemo_devp) /*申请失败*/ { ret = -ENOMEM; printk(KERN_NOTICE "Error add cdevdemo"); goto fail_malloc; } memset(cdevdemo_devp,0,sizeof(struct cdevdemo_dev)); printk(KERN_NOTICE "======== cdevdemo_init 3"); cdevdemo_setup_cdev(cdevdemo_devp, 0); /*下面两行是创建了一个总线类型,会在/sys/class下生成cdevdemo目录 这里的还有一个主要作用是执行device_create后会在/dev/下自动生成 cdevdemo设备节点。而如果不调用此函数,如果想通过设备节点访问设备 需要手动mknod来创建设备节点后再访问。*/ cdevdemo_class = class_create(THIS_MODULE, "cdevdemo"); device_create(cdevdemo_class, NULL, MKDEV(cdevdemo_major, 0), NULL, "cdevdemo"); printk(KERN_NOTICE "======== cdevdemo_init 4"); return 0; fail_malloc: unregister_chrdev_region(devno,1); } void cdevdemo_exit(void) /*模块卸载*/ { printk(KERN_NOTICE "End cdevdemo"); cdev_del(&cdevdemo_devp->cdev); /*注销cdev*/ kfree(cdevdemo_devp); /*释放设备结构体内存*/ unregister_chrdev_region(MKDEV(cdevdemo_major,0),1); //释放设备号 } MODULE_LICENSE("Dual BSD/GPL"); module_param(cdevdemo_major, int, S_IRUGO); module_init(cdevdemo_init); module_exit(cdevdemo_exit);
参考:linux设备驱动–字符设备模型
相关文章推荐
- LINUX CETOS7
- linux磁盘管理
- Linux mysql 工具常用命令
- Linux文件I/O的lseek,fcntl和ioctl函数
- ubuntu中sudo man的tab自动补齐
- linux 添加用户、权限
- linux 防火墙arp问题
- linux上SVN解决冲突命令
- arm+linux交叉编译libxml2
- 双系统卸载linux和装双系统的方法
- Linux 网桥配置命令:brctl
- Linux中find常见用法示例
- Linux swap分区
- 用xmanager登陆Linux图形界面
- CentOS7升级内核到3.12.48
- 修改Linux最大连接数
- Linux 检测网卡与网线连接状态
- Linux strace命令
- error while loading shared libraries: libssl.so.4问题解决方法
- 每天一个linux命令(9):touch 命令