字符设备驱动
来源:互联网 发布:穿衣搭配知多少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);
- 字符设备驱动--- 设备操作
- 字符设备驱动更新
- 字符设备驱动模板
- 字符设备驱动模板
- 字符设备驱动1
- 字符设备驱动编写
- LINUX--字符设备驱动
- 字符设备驱动01
- 字符设备驱动02
- LED字符设备驱动
- led字符设备驱动
- Linux字符设备驱动
- 字符设备驱动
- Linux字符设备驱动
- Linux字符设备驱动
- 字符设备驱动分析
- 字符设备驱动详解
- 字符设备驱动实验
- eclipse项目update project时会修改项目的编码为gbk编码,导致中文乱码问题
- http协议与web本质
- njRAT远程访问木马协议分析
- tr td分合并单元格
- url编码 ascii编码 unicode编码
- 字符设备驱动
- Android studio快捷键
- NNU_20161124_5
- R学习
- ajaxSubmit上传图片不回调success函数
- Android的时间选择器
- JSP的生命周期
- MySQL备份和还原(四)--使用LVM快照备份
- Android vector标签 PathData 画图超详解