第8章Linux设备驱动程序开发【学习目标】了解Linux设备熟悉Linux设备驱动程序掌握Linux设备驱动程序开发第8章网络操作系统概述Linux设备驱动程序概述8.1Linux设备驱动程序与内核的关系8.2Linux设备驱动程序框架8.3设备访问方式及实现8.4第8章网络操作系统概述字符设备驱动8.5块设备驱动8.6网络设备驱动8.7思考与练习8.8驱动程序是一种可以使计算机和硬件设备进行通信的特殊程序,它包含有关硬件设备的信息,提供了访问各种硬件设备的统一接口,同时完全屏蔽硬件设备的工作细节,操作系统只有通过这个接口,才能控制硬件设备的工作。控制硬件是嵌入式系统的核心内容,Linux内核的绝大多数代码为设备驱动,Linux设备驱动程序属于Linux内核的一部分,在内核中起着重要的作用。新设备、新芯片、新驱动的需求也不断出现,因此Linux中的驱动设计是Linux开发中的重要部分。Linux操作系统的驱动程序分成3种类型,字符设备(CharacterDevice)、块设备(BlockDevice)和网络设备(NetworkDevice)。驱动程序的设计需要考虑以下方面:能够提供尽量多的选项给用户、能够提高驱动程序的速度和效率、简单、易于维护。8.1Linux设备驱动程序概述驱动程序工作在内核空间,而应用程序一般工作于用户状态下。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。设备驱动程序在整个计算机系统中的结构分布如图8-1所示。用户进程文件系统设备文件设备驱动程序硬件设备图8-1计算机体系结构Linux设备驱动开发调试有两种方法:一种是直接编译到内核,随同Linux启动时加载,启动内核时就会驱动此硬件设备。这种方法称为静态链接。另一种是编译为可加载模块(Loadablekernelmodules)的形式,编译生成一个.o文件,当应用程序需要时再动态加载进内核空间运行,这种方法称为动态链接。Linux提供了一批管理内核模块的命令,主要有1smod、insmod和rmmod等。1smod命令用于查看当前内核加载的模块信息;insmod命令将编译的模块直接插入内核,如果出现故障,可以使用rmmod命令从内核卸载模块,而不需要重新启动内核。1smod命令执行的结果如图8-2所示。图8-21smod命令执行的结果Linux设备驱动程序一般由3个部分组成:(1)自动配置和初始化子程序。本部分负责检测所要驱动的硬件设备是否存在和能否正常工作。如果设备正常,则对这个设备及其相关的设备驱动程序需要的软件状态进行初始化。这部分驱动程序仅在初始化时被调用一次。(2)服务于I/O请求的子程序。这部分又称为驱动程序的上半部,调用这部分程序是系统调用的结果。这部分程序在执行时与进行调试的进程仍然属于同一个进程,只是将用户态切换成内核态。它具有进行此系统调用的用户程序的运行环境,在这部分可以调用sleep()等与进程运行环境有关的函数。(3)中断服务程序。本部分又称作驱动程序的下半部。在Linux系统中并不是直接从终端向量表调用设备驱动程序的中断服务子程序,而是由Linux系统来接收硬件中断,再由系统调用中断服务子程序。中断可以在任何一个程序运行中产生,在终端服务程序被调用时,不能依赖于任何进程的状态,不能调用任何与进程运行环境有关的函数。Linux系统为每个设备分配了一个主设备号与次设备号,主设备号唯一标识了设备类型,次设备号用于标识使用同一个设备驱动程序的不同硬件设备。由同一个设备驱动控制的所有设备具有相同的主设备号。系统中每种设备都用一种特殊的设备相关文件来表示。块设备和字符设备的设备相关文件可以通过mknod命令来创建,并使用主从设备号来描述此设备。在驱动程序中,可以使用下列宏获得驱动的设备号:MAJOR(dev_tdev);MINOR(dev_tdev);如果想把设备号转换成dev_t类型,可以使用下面的函数:MKDEV(intmajor,intminor);操作系统中分为单体内核(Monolithickernel)和微内核(Microkernel)两种,单体内核是一个相对较大的程序,而微内核是一个较小的程序,操作系统的大部分功能运行在用户空间。Linux是一个单体内核,分成5个子系统,整个内核在一个地址空间。这样增加一个设备就比较麻烦,由于设备需要在内核空间运行,因此需要重新编译内核。Linux通过使用内核可以根据需要将各部分放入内核。模块可以不编译到内核中,在系统中增加一个模块的时候,不需要重新编译整个内核,只需要编译模块,再将其插入到内核中。8.2Linux设备驱动程序与内核的关系Linux设备驱动属于内核的一部分,内核可以通过几种不同的方式来调用设备驱动程序(1)配置内核在引导时调用驱动程序,检查并初始化设备。(2)I/O子系统调用驱动程序读或写数据。(3)用户可以发出控制请求,象打开或关闭设备。(4)设备在I/O结束,或其他状态改变时产生中断。Linux的设备驱动程序与外界的接口可以分为3个部分,驱动程序与操作系统内核的接口;驱动程序与系统引导的接口,这部分利用驱动程序对设备进行初始化;驱动程序与设备的接口,这部分描述了驱动程序如何与设备进行交互,与具体的设备密切相关。8.3Linux设备驱动程序框架Linux的设备驱动程序与外界的接口可以分为三个部分:1、驱动程序与操作系统内核的接口。通过file_operations(include/linux/fs.h)数据结构来完成的。2、驱动程序与系统引导的接口。这部分利用驱动程序对设备进行初始化。3、驱动程序与设备的接口。这部分描述了驱动程序如何与设备进行交互,与具体的设备密切相关。1.驱动程序的注册和注销设备驱动程序可以在系统启动的时候初始化,也可以在需要的时候动态加载。字符设备的初始化由chr_dev_init()完成,包括对内存(devfs_register_chrdev(MEM_MAJOR,“mem”,&memory_fops)),终端(tty_init()),打印机(lp_init()),鼠标(misc_init())等字符设备的初始化。2.设备的打开和释放打开设备是由open()来完成的。例如,打印机是用lp_open()打开的,而硬盘是用hd_open()打开的。在大部分设备驱动程序中,open()完成如下工作:1、增加设备的是用计数。2、检查设备的相关错误,如设备尚未准备好或是类似硬件的问题。3、检查是首次打开,则初始化设备。4、识别次设备号,如有必要则更新f_op指针。5、如果需要,分配且设置要放在filp-private_data里的数据结构。2.设备的打开和释放释放设备由release()来完成,例如释放打印机是用lp_release(),而释放终端设备是用tty_release()。释放设备的一般步骤包括:1、释放在filp-private_data中的open分配的内存。2、如果是最后一次释放,则关闭设备。3、递减设别的使用计数。3.设备的读写操作字符设备使用各自的read()和write()来进行数据读写。例如,对虚拟终端的读写是通过vcs_read()和vcs_write()来进行数据读写的。块设备使用通用的generic_file_read()和generic_file_write()来进行数据读写。这两个通用函数向请求表添加读写请求,内核可以通过ll_rw_block()优化请求顺序。由于是对内存缓冲区而不是设备进行操作的,因而可以加快读写请求。如果内存缓冲区内没有要读入的数据或是要将写请求写入设备,那么就要真正的执行数据传输。这是通过数据结构request_queue和request_fn()来完成(include/linux/blkdev.h)4.设备的控制操作除了读写操作,有时还要控制设备。这可以通过设备驱动程序中的ioctl()来完成。例如IDE硬盘的控制可以通过hd_ioctl(),对光驱的控制可以通过cdrom_ioctl()。与读写操作不同,ioctl()的用法与具体设备密切相关。5.设备的轮询和中断处理对于不支持中断的设备,读写时需要轮询设备状态,以及是否需要继续进行数据传输。例如,打印机。如果设备支持中断,则可按照中断方式进行。Linux内核在结构体file_operations中统一定义了设备文件的各个访问接口。该结构体如下所示:structfile_operations{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);ssize_t(*aio_read)(structkiocb*,conststructiovec*,unsignedlong,loff_t);ssize_t(*aio_write)(structkiocb*,conststructiovec*,unsignedlong,loff_t);int(*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);long(*unlocked_ioctl)(structfile*,unsignedint,unsignedlong);long(*compat_ioctl)(structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*,fl_owner_tid);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,structdentry*,intdatasync);int(*aio_fsync)(structkiocb*,intdatasync);int(*fasync)(int,structfile*,int);...};int(*lock)(structfile*,int,structfile_lock*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong);int(*check_flags)(int);int(*flock)(structfile*,int,structfile_lock*);ssize_t(*splice_write)(structpipe_inode_info*,structfile*,loff_t*,size_t,unsignedint);ssize_t(*splice_read)(structfile*,loff_t*,structpipe_inode_info*,size_t,unsignedint);int(*setlease)(structfile*,long,structfile_lock**);设备驱动程序开发的主要内容之一就是实现结构体file_operations中各个接口对应的函数。以下是file_operations中一些最