第十章嵌入式Linux驱动程序开发10.1嵌入式Linux的设备管理Linux将设备分成三大类:一类是块设备,类似磁盘以记录块或扇区为单位,成块进行输入/输出的设备;另一类是字符设备,类似键盘以字符为单位,逐个进行输入、输出的设备。另一类网络设备是介于块设备和字符设备之间的一种特殊设备。块设备接口仅支持面向块的I/O操作,所有I/O操作都通过在内核地址空间中的I/O缓冲区进行。文件系统通常都建立在块设备上。字符设备接口支持面向字符的I/O操作,不经过系统的快速缓存,它们负责管理自己的缓冲区结构。字符设备接口只支持顺序存取的功能,一般不能进行任意长度的I/O请求,而是限制I/O请求的长度必须是设备要求的基本块长的倍数。处理器与设备间数据交换方式通常有3种:查询方式、中断方式和直接内存存取(DMA)方式。1.查询方式设备驱动程序通过设备的I/O端口空间或存储器空间完成数据的交换。例如,网卡一般将自己的内部寄存器映射为设备的I/O端口,而显示卡则利用大量的存储器空间作为视频信息的存储空间。利用这些地址空间,驱动程序可以向外设发送指定的操作指令。由于外设的操作耗时较长,因此,当处理器实际执行了操作指令之后,驱动程序可采用查询方式等待外设完成操作。驱动程序在提交命令之后,开始查询设备的状态寄存器,当状态寄存器表明操作完成时,驱动程序可继续后续处理。查询方式的优点是硬件开销小,使用起来比较简单。但在此方式下,CPU要不断地查询外设的状态,当外设未准备好时,就只能循环等待,不能执行其他程序,这样就浪费了CPU的大量时间,降低了处理器的利用率。2.中断方式中断方式是多任务操作系统中利用处理器最有效的方式。当CPU进行主程序操作时,外设的数据已存入端口的数据输入寄存器,或端口的数据输出寄存器已空,此时由外设通过接口电路向CPU发出中断请求信号。CPU在满足一定条件下,暂停执行当前正在执行的主程序,转入执行相应能够进行输入/输出操作的子程序,待执行完毕之后,CPU再返回并继续执行原来被中断的主程序。这样,CPU就避免了把大量时间耗费在等待、查询外设状态的操作上,使其工作效率得以大大提高。3.直接访问内存(DMA)方式利用中断,系统和设备之间可以通过设备驱动程序传送数据,但是,当传送的数据量很大时,因为中断处理上的延迟,利用中断方式的效率会大大降低。而直接内存访问(DMA)可以解决这一问题。DMA允许设备和系统内存间在没有处理器参与的情况下传输大量数据。设备驱动程序在利用DMA之前,需要选择DMA通道并定义相关寄存器,以及数据的传输方向,即读取或写入,然后将设备设定为利用该DMA通道传输数据。设备完成设置之后,可以立即利用该DMA通道在设备和系统的内存之间传输数据,传输完毕后产生中断以便通知驱动程序进行后续处理。在利用DMA进行数据传输的同时,处理器仍然可以继续执行指令。设备驱动程序的概念设备驱动程序是处理和操作硬件控制器的软件,从本质上讲,是内核中具有最高特权级的、驻留内存的、可共享的底层硬件处理例程。驱动程序是内核的一部分,是操作系统内核与硬件设备的直接接口,屏蔽了硬件的细节,完成以下功能:对设备初始化和释放;对设备进行管理,包括实时参数设置、提供对设备的操作接口;读取应用程序传送给设备文件的数据,或者回送应用程序请求的数据;检测和处理设备出现的错误。Linux操作系统将所有的设备全部看成文件,对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口。一般来说,是把设备映射为一个特殊的设备文件,用户程序可以像对其他文件一样对此设备文件进行操作。由于每一个设备至少由文件系统的一个文件代表,因而都有一个“文件名”。应用程序通常可以通过系统调用open()打开设备文件,建立起与目标设备的连接。可以通过read()、write()、ioctl()等常规的文件操作对目标设备进行操作。设备文件的属性由三部分信息组成:第一部分是文件的类型,第二部分是一个主设备号,第三部分是一个次设备号。其中类型和主设备号结合在一起惟一地确定了设备文件驱动程序及其界面,而次设备号则说明目标设备是同类设备中的第几个。应用程序发出系统调用命令后,会从用户态转到核心态,通过内核将open()这样的系统调用转换成对物理设备的操作。驱动程序的结构与功能1.自动配置和初始化子程序;用来检测所需驱动的硬件设备是否工作正常、对正常工作的设备及其相关驱动程序所需要的软件状态进行初始化。2.服务于I/O请求的子程序;该子程序称为驱动程序的上半部。这部分程序在执行时,系统仍认为与进行调用的进程属于同一个进程,只是由用户态变成了核心态,可以在其中调用sleep()等与进程运行环境有关的函数。3.中断服务程序;又称为驱动程序的下半部,由Linux系统来接收硬件中断,再由系统调用中断服务子程序。在系统内部,I/O设备的存取通过一组固定的入口点来进行,入口点可以理解为对设备进行操作的基本函数。字符型设备驱动程序提供如下几个入口点:open入口点。打开设备,准备I/O操作。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。close入口点。关闭设备。当最后一次使用设备完成后,调用close子程序。read入口点。从设备上读数据。对于有缓冲区的I/O操作,一般是从缓冲区里读数据。对字符设备文件进行读操作将调用read子程序。write入口点。往设备上写数据。对于有缓冲区的I/O操作,一般是把数据写入缓冲区里。对字符设备文件进行写操作将调用write子程序。ioctl入口点。执行读、写之外的操作。select入口点。检查设备,看数据是否可读或设备是否可用于写数据。structfile_operations{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);int(*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,structdentry*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfile_lock*);ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);};lseek,移动文件指针的位置,只能用于可以随机存取的设备,如块设备。read,进行读操作,buf为存放读取结果的缓冲区,count为所要读取的数据长度。write,进行写操作,与read类似。select,进行选择操作。ioctl,进行读、写以外的其他操作。mmap,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。open,打开设备进行I/O操作。返回0表示成功,返回负数表示失败。release,即close操作。structinode称做索引节点数据结构,定义如下structinode{structlist_headi_hash;//指向哈希链表的指针structlist_headi_list;//指向索引结点链表的指针structlist_headi_dentry;//指向目录项链表的指针structlist_headi_dirty_buffers;//指向“脏”缓冲区链表的指针structlist_headi_dirty_data_buffers;//指向“脏”数据缓冲区链表的指针unsignedlongi_ino;//指向索引结点号atomic_ti_count;//当前使用该结点的进程数kdev_ti_dev;//设备类型umode_ti_mode;//文件类型nlink_ti_nlink;//与该结点建立链接的文件数uid_ti_uid;//文件拥有者的标识号gid_ti_gid;//文件拥有者所在组的标识号kdev_ti_rdev;//实际设备标识号loff_ti_size;//文件大小time_ti_atime;//文件最后访问的时间time_ti_mtime;//文件最后修改的时间time_ti_ctime;//结点最后修改的时间unsignedinti_blkbits;//位数unsignedlongi_blksize;//块大小unsignedlongi_blocks;//文件所占用的块数unsignedlongi_version;//版本号structsemaphorei_sem;//用于同步操作的信号量结构structsemaphorei_zombie;//索引结点的信号量structinode_operations*i_op;//索引结点操作structfile_operations*i_fop;//指向文件操作的指针structsuper_block*i_sb;//指向该文件系统超级块的指针wait_queue_head_ti_wait;//指向索引结点等待队列的指针structfile_lock*i_flock;//指向文件加锁链表的指针structaddress_space*i_mapping;//管理所有可交换的页面structaddress_spacei_data;//数据structdquot*i_dquot[MAXQUOTAS];//索引结点的磁盘限额structlist_headi_devices;//设备文件形成的链表structpipe_inode_info*i_pipe;//指向管道文件structblock_device*i_bdev;//指向块设备文件的指针structchar_device*i_cdev;//指向字符设备文件的指针unsignedlongi_dnotify_mask;//structdnotify_struct*i_dnotify;unsignedlongi_state;//索引结点状态标志unsignedinti_flags;//文件系统的安装标志unsignedchari_sock;//是否是套接字文件atomic_ti_writecount;//写进程的引用计数unsignedinti_attr_flags;//文件创建标志__u32i_generation;//保留union{//各个具体文件系统的索引结点structmin