1、1、设备驱动的分类Linux 的一个重要特点就是将所有的设备都当做文件进行处理,这一类特殊文件就是设备文件,它们可以使用文件、I/O 相关函数进行操作,这样就大大方便了对设备的处理。它通常在/dev 下面存在一个对应的逻辑设备节点,这个节点以文件的形式存在1-1、字符设备驱动字符设备可以通过设备文件节点访问,它与普通文件之间的区别在于普通文件可以被随机访问(可以前后移动访问指针) ,而大多数字符设备只能提供顺序访问(但也有例外)1-2、块设备驱动块设备通常指一些需要以块为单位随机读写的设备,块设备也是通过文件节点来访问,它不仅可以提供随机访问,而且可以容纳文件系统(例如硬盘、闪存等) 。Lin
2、ux 可以使用户态程序像访问字符设备一样每次进行任意字节的操作,只是在内核态内部中的管理方式和内核提供的驱动接口上不同注解:在 dev 目录下执行 ls -l 命令,可以查看到已加载的设备驱动,能够通过首字母区分设备驱动类型,c 表示字符设备,b 表示块设备1-3、网络设备驱动内核和网络设备驱动程序之间的通信调用是一套数据包处理函数,完全不同于和字符设备驱动程序之间的通信(read、write 等函数)2、设备号设备号是一个数字,它是设备的标志,一个设备文件(也就是设备节点)可以通过mknod 命令来创建,其中指定了主设备号和次设备号2-1、主设备号主设备号表示设备的类型,与一个确定的驱动程序
3、对应,字符驱动程序编写时会指明自身的主设备号2-2、次设备号次设备号用于标明不同的属性,例如不同的使用方法、不同的位置、不同的操作等注解:可以通过 ls -l 命令查看 dev 目录下已加载的设备驱动的主设备号和次设备号,在日期之前,主次设备号不能大于 2553、设备驱动结构Linux 下的设备驱动程序是内核的一部分,运行在内核模式下,也就是说设备驱动程序为内核提供了一个 I/O 接口,用户使用这个接口实现对设备的操作3-1、设备控制器获得系统服务有两种方式:查询和中断3-2、设备服务子程序包含了所有与设备操作相关的处理代码注解:Linux 设备驱动程序包含中断处理程序(或查询方式)和设备服务
4、子程序两部分4、设备驱动接口每种类型的驱动程序,不管是字符设备还是块设备都为内核提供相同的调用接口,因此内核能以相同的方式处理不同的设备,如下图4-1、驱动程序与操作系统内核的接口这是通过数据结构 file_operations 来完成的4-2、驱动程序与系统引导的接口这部分利用驱动程序对设备进行初始化4-3、驱动程序与设备的接口这部分描述了驱动程序如何与设备进行交互,这与具体设备密切相关5、设备驱动特点5-1、内核代码设备驱动程序是内核的一部分,如果驱动程序出错,则可能导致系统崩溃5-2、内核接口提供标准的内核接口,不得随意改动5-3、内核机制和服务设备驱动程序使用一些标准的内核服务,如内存
5、分配等5-4、可装载大多数的 Linux 操作系统设备驱动程序都可以在需要时装载进内核,在不需要时从内核中卸载5-5、可设置Linux 操作系统设备驱动程序可以集成为内核的一部分,在制作时候可以选择编译成模块化还是集成到内核6、字符设备驱动编写模块在调用 insmod 命令时被加载,此时的入口点是 init_module()函数,通常在该函数中完成设备的注册。同样,模块在调用 rmmod 命令时被卸载,此时的入口点是cleanup_module()函数注解:终端执行 insmod 和 rmmod 命令加载和卸载设备驱动时候,设备驱动通过执行注册解注册来实现与内核的交互7、重要的数据结构7-1、
6、struct file_operationsstruct file_operations 是定义在 中的数据结构,它定义了常见文件 I/O 函数的入口,数据结构成员是 I/O 函数注解:这是一个内核结构,不会出现在用户空间的程序中,系统调用函数通过内核,最终调用对应的 struct file_operations 结构的接口函数来实现的,例如,open()文件操作是通过调用对应文件的 file_operations 结构的 open 函数接口而被实现7-2、struct inodestruct inode 结构提供了 dev 目录下设备节点的信息8、设备驱动注册在 Linux 内核中使用 st
7、ruct cdev 结构来描述字符设备,在驱动程序中必须将已分配到的设备号以及设备操作接口(即为 struct file_operations 结构)赋予 struct cdev 结构变量Step1、使用 cdev_alloc()函数向系统申请分配 struct cdev 结构,Step2、用 cdev_init()函数初始化已分配到的结构并与 file_operations 结构关联起来Step3、调用 cdev_add()函数将设备号与 struct cdev 结构进行关联并向内核正式报告新设备的注册,这样新设备可以被用起来了所需头文件 #include 函数原型sturct cdev *
8、cdev_alloc(void) void cdev_init(struct cdev *cdev, struct file_operations *fops) int cdev_add (struct cdev *cdev, dev_t num, unsigned int count) void cdev_del(struct cdev *dev) 函数传入值cdev:需要初始化/注册/删除的 struct cdev 结构 fops:该字符设备的 file_operations 结构 num:系统给该设备分配的第一个设备号 count:该设备对应的设备号数量 函数返回值成功: cdev_al
9、loc:返回分配到的 struct cdev 结构指针 cdev_add:返回 0 出错: cdev_alloc:返回 NULL cdev_add:返回 -19、用户空间和内核空间的数据交换不能使用诸如 memcpy()之类的函数,要使用 copy_to_user()或 copy_from_user()等函数所需头文件 #include 函数原型 unsigned long copy_to_user(void *to, const void *from, unsigned long count) unsigned long copy_from_user(*to, *from, count)函数
10、传入值to:数据目的缓冲区from:数据源缓冲区count:数据长度函数返回值成功:写入的数据长度 失败:-EFAULT注解:这两个函数不仅实现了用户空间和内核空间的数据转换,而且还会检查用户空间指针的有效性。如果指针无效,那么就不进行复制10、用户接口函数10-1、read、write 函数接口所需头文件 #include 函数原型ssize_t (*read) (struct file *filp, char *buff, size_t count, loff_t *offp) ssize_t (*write) (*filp, *buff, count,*offp)函数传入值filp:文件
11、指针buff:指向用户缓冲区count:传入的数据长度offp:用户在文件中的位置函数返回值 成功:写入的数据长度10-2、ioctl 函数接口所需头文件 #include 函数原型 int(*ioctl)(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg)函数传入值 inode:文件的内核内部结构指针filp:被打开的文件描述符cmd:命令类型arg:命令相关参数 11、驱动函数中常用的函数11-1、获取内存kmalloc():获得 2 的整次方长度的物理地址,且不会对获取的内存空间清零get_zeroed_page() :获得一个已清零页面 get_free_page() :获得一个或几个连续页面get_dma_pages() :获得用于 DMA 传输的页面注解:与之相对应的释放内存用也有 kfree()或 free_page 函数族11-2、驱动中打印信息