当前位置:文档之家› 字符设备驱动框架

字符设备驱动框架

Linux中设备分类:按照对设备的访问方式可分为以下三类:1.字符设备(char device)(1)例如:键盘、鼠标、串口、帧缓存等;(2)通过/dev/下的设备节点访问;以字节为单位访问;(3)一般只支持顺序访问;(特例:帧缓存framebuffer)(4)无缓冲。

2.块设备(block device)(1)例如:磁盘、光驱、flash等;(2)以固定大小为单位访问:磁盘以扇区(512B)为单位;flash以页为单位。

(3)支持随机访问;(4)有缓冲(减少磁盘IO,提高效率)。

3.网络设备(network device)(1)无设备文件(节点);(2)应用层通过socket接口访问网络设备(报文发送和接收的媒介)。

设备驱动在内核中的结构:1.VFS虚拟文件系统作用:向应用层提供一致的文件访问接口,正是由于VFS的存在,才可以将设备以文件的方式访问。

2.虚拟文件系统,存在于内存中,不在磁盘上,掉电丢失。

例如:/proc、/sys、/tmp。

设备号:1.作用:唯一地标识一个设备;2.类型:dev_t devno;即32位无符号整型;3.组成:(1)主设备号:用于区分不同类型(按功能划分)的设备;(2)此设备号:用于区分相同类型的不同设备。

注意:相同类型的设备(主设备号相同)可以使用同一个驱动。

4.构建设备号:int major = 250; int minor = 0;(1)dev_t devno = (major << 20) | minor;不建议使用;(2)利用宏来构建:dev_t devno = MKDEV (major, minor);注意:我们可以通过文件$(srctree)/documentation/device.txt来查看内核对设备号的分配情况。

(1)该文本中的有对应设备文件的设备号是已经被申请过的,我们不可以重复使用(申请);(2)从中可以看出,我们在编写驱动程序时可以使用的主设备号范围为240~254,为了方便记忆,通常使用250作为主设备号。

字符设备驱动框架:驱动:作用,为应用层提供访问设备的接口(对设备发的各种操作)。

一、申请设备号1.构建设备号:dev_t devno = MKDEV (major, minor);2.申请设备号:(1)动态申请:alloc_chrdev_region;(2)静态申请: register_chrdev_region。

(3)静态申请设备号的优缺点:优点:可以提前创建设备文件;缺点:有可能会发成冲突,导致申请失败。

(4)register_chrdev_region函数详解:注意:①最后一个参数是设备名称。

且在/proc/devices下会有关于当前系统已经注册成功的设备信息;②申请设备号应在加载函数中实现;同时,在卸载函数中我们也要调用unregister_chrdev_region来释放设备号。

二、实现操作集合struct file_operations {……}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 *);unsigned int (*poll) (struct fiel *, poll_table *);int (*fasync) (int, struct file *, int);long (*unlock_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*lock) (struct file *, int, struct file_lock *);……};除owner成员外,其他成员均是对文件进行相关操作的函数指针。

1.对设备的大多数操作函数都有参数struct file *filp;2.在起始阶段我们可以先定义一个空的操作集合:struct file_operations { .owner = THIS_MODULE };注意:(1)可以看出宏THIS_MODULE代表结构体struct module的起始地址;(2)我们可以通过“.<struct_member_name> = <……>,”语法来给结构体指定成员赋值。

三、注册字符设备:1.通过结构体struct cdev将设备号devno和操作集合file_operations关联起来;2.并将cdev结构体加入到内核维护的cdev链表中。

(计算机系统支持很多字符设备,内核会维护一个cdev链表,这样我们就可以通过设备号找到对应的操作集合了)3.cdev结构体的定义如下:4.我们通过调用内核函数cdev_init和cdev_add来完成字符设备的注册。

注意:注册字符设备应在加载函数中实现;同时,在卸载函数中,我们要调用cdev_del函数来注销字符设备。

且注销字符设备应在释放设备号之前进行。

应用层如何访问设备:1.在Linux中一切皆文件;2.因为VFS(向应用层提供一致的文件访问接口),应用层也可以将设备当做文件来访问;3.应用层要想访问设备,首先要创建设备节点:命令:mknod <device_name> c/b <major> <minor>注意:(1)mknod /dev/hello c 250 0(2)mknod /dev/hello c 250 1 错误;(3)mknod /dev/hello1 c 250 0 正确,即可以有多个设备节点指向同一个设备。

操作集合打开open:1.统计计数,检查错误;(一个设备可以被多个进程打开)2.申请资源:(1)在xxx_open()函数中也可以申请资源;(2)若在open中申请资源,则对应要在release中释放资源;(3)若在加载函数中申请资源,则对应要在卸载函数中释放资源。

3.识别次设备号。

(在一个驱动识别多个设备中有应用)4.在应用层调用open函数打开设备的实现过程:(1)int fd = open (“/dev/hello”, O_RDWR);(2)sys_open();系统调用(3)vfs_open();虚拟文件系统提供的操作接口注意:在vfs_open()函数执行时,会在内存中创建两个结构体:①struct inode { … dev_t i_rdev; … struct cdev *i_cdev;…}记录所打开文件的静态信息;是将磁盘上的inode节点信息读过来的。

②struct file {……struct file *f_op;……}记录所打开文件的动态信息:包括打开方式、当前读写位置、用户信息等。

(4)xxx_open(struct inode *, struct file *);注意:①应用层的文件描述符fd与内核中的struct file结构体是一一对应的,即每打开一个设备文件,就会在内存中创建一个struct file类型的结构体变量。

每个进程都维护有一个文件描述符表:fd与structfile的对应关系。

②与xxx_open()函数相对应的是xxx_release()函数。

(1)file结构体:(文件的静态属性)struct file{……const struct file_operations *f_op;//操作集合结构体指针unsigned int f_flags;//文件打开方式:如O_NONBLOCKloff_t f_ops;//当前读写位置void *private_data;//文件私有数据,通常用来存放设备结构体的地址……};应用:①在多个同类型的设备共用一个驱动程序时:为了在驱动中识别不同的设备,可以将设备结构体的地址保存到file结构体的私有成员中;②利用file结构体的f_flags成员,判断进程是以阻塞还是非阻塞方式访问文件。

(2)inode结构体:(文件的动态信息)struct inode{umode_t i_mode;//inode的权限uid_t i_uid;//inode拥有者的idgid_t i_gid;//inode所属的组iddev_t i_rdev;//设备号struct timespec i_atime;//inode最近一次的存取时间struct timespec i_mtime;//inode最近一次的修改时间struct timespec i_ctime;//inode的产生时间union {//若是块设备,为其对应的block_device结构体指针struct block_device *i_bdev;struct cdev *i_cdev;//若是字符设备,为其对应的cdev结构体指针};……};读read:相应地,read()--->sys_read()--->vfs_read()-->xxx_read()-->copy_to_user。

APP:ssize_t read (int fd, void *buf, size_t count);driver:ssize_t xxx_read (struct file *,char __user *,size_t,loff_t *);读(写)操作在内核空间和用户空间会发生数据交互。

在xxx_read(xxx_write)函数中我们通过调用内核函数:copy_to_user(cpy_from_user)将数据返回给用户buffer(将用户数据读到本地buffer)。

1.read()函数:(1)成功:返回实际读到的字节数;(2)失败:返回-1并设置errno(非负)。

注意:errno就是通过将xxx_read()函数的失败返回值取绝对值得到的。

2.xxx_read()函数:(1)成功:返回实际读到的字节数;(2)失败:返回负的错误码-EFAULT。

相关主题