1 嵌入式Linux下的设备驱动 4.1 设备类型分类 字符设备(char device)。字符设备是Linux最简单的设备,可以向文件一样访问。 初始化字符设备时,它的设备驱动程序向Linux登记,并在字符设备向量表中增加一个device_struct数据结构条目,这个设备的主设备标识符用作这个向量表的索引。一个设备的主设备标识符是固定的。chrdevs向量表中的每一个条目,一个device_struct数据结构,包括两个元素:一个登记的设备驱动程序的名称的指针和一个指向一组文件操作的指针。参见include/linux/major.h. 块设备(block device)。是文件系统的物质基础,它也支持像文件一样被访问。 Linux用blkdevs向量表维护已经登记的块设备文件。它像chrdevs向量表一样,使用设备的主设备号作为索引.它的条目也是device_struct数据结构.与字符设备不同的是,块设备分为SCSI类和IDE类。类向Linux内核登记并向河心提供文件操作。一种块设备类的设备驱动程序向这种类提供和类相关的接口。参见fs/devices.c[10]。 每一个块设备驱动程序必须提供普通的文件操作接口和对于buffer cache的接口。每一个块设备驱动程序填充blk_dev向量表中的blk_dev_struct数据结构。这个向量表的索引还是设备的主设备号。这个blk_dev_struct数据结构包括一个请求例程的地址和一个指针,指向一个request数据结构的列表,每一个都表达buffer cache向设备读/写一块数据的一个请求。参见drivers/block/ll_rw_blk.c和include/linux/blkdev.h。 当buffer_cache从一个已登记的设备读/写一块数据,或者希望读写一块数据到其他位置时,他救灾blk_dev_struct中增加一个request数据结构。每个request数据结构都有一个指向一个或多个buffer_head数据结构的指针,每一个都是读/写一块数据的请求。如果buffer_head数据结构被锁定(buffer_cache),可能会有一个进程在等待这个缓冲区的阻塞进成完成。每一个request数据结构都是从all_request表中分配的。如果request增加到空的request列表,就调用驱动程序的request函数处理这个request队列,否则驱动程序只是简单的处理request队列中的每一个请求。 块设备驱动程序和字符设备驱动程序的主要区别是:在对字符设备发出读写请求时,实际的硬件I/O一般紧接着就发生了,块设备则不然,它利用一块系统内存作为缓冲区,当用户进程对设备请求能满足用户的要求时,就返回请求的数据,如果不能就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备的,以免耗费过多地CPU时间来等待。 设备驱动中关键数据结构: file_operations:内核内部通过file结构识别设备,通过file_operations数据结构提供文件系统的入口点函数.file_operations定义在中的函数指针表.这个结构的每一个成员的名字都对应着一个系统调用。从某种意义上说,写驱动程序的任务之一就是完成file_operations中的函数指针。如果在2.4版本内核下开发的驱动很可能在2.6版本中无法使用,需要进行移植。 inode:文件系统处理的文件所需要的信息在inode(索引节点)中。 file:主要用于与文件系统对应的设备驱动程序使用。 网络设备驱动程序开发: 网络设备使用网络接口管理表dev_base.dev_base是一个指向device结构的指针,因为网络设备是通过device数据结构来表示的。网络驱动程序必须解决的两个问题: (1)不是所有建立在Linux内核的网络设备驱动程序都会有控制的设备。 (2)系统中的以太网设备总是叫做/dev/eth0到/dev/eth7,而不管底层的设备驱动程序是什么。当驱动程序找到它的以太网设备时,它就填充它现在拥有的ethn的device数据结构。这时网络驱动程序也要初始化它控制的物理硬件,并找出它使用的IRQ,DMA等。一旦所有的8个标准的/dev/ethn都分配了,就不会再探测更多的以太网设备。 两个重要的数据结构: device:这个数据结构是在系统中每一个设备的代表,它提供了多个设备方法,供操作系统或协议层使用。这其中就包括设备初始化时调用的init函数,打开和关闭设备的open和stop函数,处理数据包发送的hard_start_xmit函数等。 sk_buff:Linux网络各层之间的数据传送都是通过sk_buff(套接字缓冲区)完成的。每个sk_buff包括一些控制方法和一块数据缓冲区,这个区域存放了网络传输的数据包.控制方法按功能分为两种类型,一种是控制整个buffer链的方法,另一种是控制数据缓冲区的方法。sk_buff组成双向链表的形式,对链表的操作主要是删除链表头的元素和添加到链表尾。sk_buff数据结构再include/linux/skbuff.h文件中定义。 内核的驱动程序接口: 打开函数net_open:执行"ifconfig eth0 up"命令时网络层调用它,把设备连到线路上并启用来接受/发送数据。open函数在网络设备驱动程序里是网络设备被激活的时候被调用的,即设备状态由down至up。所以实际上很多在初始化的工作可以放到这里来做。 关闭函数net_close:执行"ifconfig eth0 down"时使网卡进入一个清醒的状态。如果硬件许可那么它会释放中断和DMA通道,并完全关闭以节约能源。 探测函数probe/probe1:在启动时调用以检测网卡存在与否。如果可以通过读取内存等非强制手段进行检查最好,也可以从I/O端口读取。在探测函数的最后它会填充device结构各域。 发送函数:它与dev->hard_start_xmit()连接,在内核想通过设备传送数据时调用它。如果发送成功,hard_start_xmit()方法里释放sk_buff,返回0,否则返回1。如果dev->tbusy置为非0,则系统认为硬件忙,要等到dev->tbusy置0后才会再次发送;也可以不置dev->tbusy为非0,这样系统会不断尝试重发。tbusy的置0任务一般由中断完成,然后用mark_bh()调用通知系统可以再次发送。 接收函数:它把数据从硬件移出,放在sk_buff结构中,执行netif_rx(sk_buff)告诉内核数据所在位置。真正的处理是在中断返回之后,这样可以减少中断时间。在协议层,接收数据包的流程控制分两个层次:首先,netif_rx()函数限制了从物理层到协议层的数据帧的数量;然后,每一个套接字都有一个队列,限制从协议层到套接字层的数据帧的数量。 中断处理函数:需要了解相关的中断状态位以进行相应的操作的其他函数。 |