1、第第7章章 嵌入式嵌入式Linux的设备驱动的设备驱动l Linux系统驱动程序开发简介l常用的系统支持常用的系统支持 lLinux系统网络设备驱动程序系统网络设备驱动程序 l编写编写Linux网络驱动程序中需要注意的问题网络驱动程序中需要注意的问题 7.1 Linux系统驱动程序开发简介lLinux中设备被抽象出来,所有设备都看成文件如:系统中第一个IDE硬盘被表示成/dev/hda l设备的读写和普通文件一样 l设备驱动程序主要完成这些功能: 探测设备和初始化设备 从设备接收数据并提交给内核 从内核接收数据送到设备 检测和处理设备错误Linux设备驱动程序分类 lLinux系统的设备分为字
2、符设备(char device),块设备(block device)和网络设备(network device)三种 l字符设备是指存取时没有缓存的设备。 如:系统的串口设备/dev/cua0和/dev/cua1 l块设备的读写都有缓存来支持,并且块设备必须能够随机存取(random access),且不管块位于设备中何处都可以对其进行读写。块设备可以通过其设备相关文件进行访问,但更为平常的访问方法是通过文件系统。只有块设备才能支持可安装文件系统。如硬盘 l用mknod命令创建的块设备特殊文件与字符设备特殊文件 l网络设备在Linux里做专门的处理 ,可以通过BSD套接口访问。 1、核心代码、核
3、心代码 设备驱动是核心的一部分,象核心中其它代码一样,出错将导致系统的严重损伤。一个编写奇差的设备驱动甚至能使系统崩溃并导致文件系统的破坏和数据丢失。 2、核心接口、核心接口 设备驱动必须为Linux核心或者其从属子系统提供一个标准接口。例如终端驱动为Linux核心提供了一个文件I/O接口而SCSI设备驱动为SCSI子系统提供了一个SCSI设备接口,同时此子系统为核心提供了文件I/O和buffer cache接口。 Linux核心中的设备驱动具有的共性:3、核心机制与服务、核心机制与服务 设备驱动可以使用标准的核心服务如内存分配、中断发送和等待队列等等。 4、动态可加载、动态可加载 多数Lin
4、ux设备驱动可以在核心模块发出加载请求时加载,同时在不再使用时卸载。这样核心能有效地利用系统资源。 5、可配置、可配置 Linux设备驱动可以连接到核心中。6、动态性、动态性 当系统启动及设备驱动初始化时将查找它所控制的硬件设备。如果某个设备的驱动为一个空过程并不会有什么问题。此时此设备驱动仅仅是一个冗余的程序,它除了会占用少量系统内存外不会对系统造成什么危害。驱动程序的几个概念1、轮询与中断轮询方式意味着需要经常读取设备的状态,一直到设备状态表明请求已经完成为止。如果设备驱动被连接进入核心,这时使用轮询方式将会带来灾难性后果:核心将在此过程中无所事事,直到设备完成此请求。但是轮询设备驱动可以
5、通过使用系统定时器,使核心周期性调用设备驱动中的某个例程来检查设备状态。 定时器过程可以检查命令状态及Linux软盘驱动的工作情况。使用定时器是轮询方式中最好的一种,但更有效的方法是使用中断。 基于中断的设备驱动会在它所控制的硬件设备需要服务时引发一个硬件中断。如以太网设备驱动从网络上接收到一个以太数据报时都将引起中断。Linux核心需要将来自硬件设备的中断传递到相应的设备驱动。这个过程由设备驱动向核心注册其使用的中断来协助完成。此中断处理例程的地址和中断号都将被记录下来。在/proc/interrupts文件中你可以看到设备驱动所对应的中断号及类型: 0: 727432 timer 1: 2
6、0534 keyboard 2: 0 cascade 3: 79691 + serial 4: 28258 + serial 5: 1 sound blaster 11: 20868 + aic7xxx 13: 1 math error 14: 247 + ide0 15: 170 + ide1 2、直接内存访问 (DMA) DMA控制器可以在不受处理器干预的情况下在设备和系统内存之间高速传输数据。 设备驱动使用DMA时必须十分小心。首先DMA控制器没有任何虚拟内存的概念,它只存取系统中的物理内存。同时用作DMA传输缓冲的内存空间必须是连续物理内存块。这意味着不能在进程虚拟地址空间内直接使用D
7、MA。但是你可以将进程的物理页面加锁以防止在DMA操作过程中被交换到交换设备上去。另外DMA控制器所存取物理内存有限。DMA通道地址寄存器代表DMA地址的高16位而页面寄存器记录的是其余8位。所以DMA请求被限制到内存最低16M字节中。 Linux通过dma_chan(每个DMA通道一个)数组来跟踪DMA通道的使用情况。dma_chan结构中包含有两个域,一个是指向此DMA通道拥有者的指针,另一个指示DMA通道是否已经被分配出去。当敲入cat /proc/dma打印出来的结果就是dma_chan结构数组。 3、 内存 设备驱动必须谨慎使用内存。由于它属于核心,所以不能使用虚拟内存。Linux为
8、设备驱动提供了一组核心内存分配与回收过程。核心内存以2的次幂大小的块来分配。如512或128字节,此时即使设备驱动的需求小于这个数量也会分配这么多。所以设备驱动的内存分配请求可得到以块大小为边界的内存。这样核心进行空闲块组合更加容易。 请求分配核心内存时Linux需要完成许多额外的工作。如果系统中空闲内存数量较少,则可能需要丢弃些物理页面或将其写入交换设备。一般情况下Linux将挂起请求者并将此进程放置到等待队列中直到系统中有足够的物理内存为止。 4、 设备驱动与核心的接口 Linux核心与设备驱动之间必须有一个以标准方式进行互操作的接口。每一类设备驱动:字符设备、块设备 及网络设备都提供了通
9、用接口以便在需要时为核心提供服务。这种通用接口使得核心可以以相同的方式来对待不同的设备及设备驱动。如SCSI和IDE硬盘的区别很大但Linux对它们使用相同的接口。Linux动态性很强。每次Linux核心启动时如遇到不同的物理设备将需要不同的物理设备驱动。 4.1 字符设备 字符设备是Linux设备中最简单的一种 字符设备初始化时,它的设备驱动通过在device_struct 结构的chrdevs数组中添加一个入口来将其注册到Linux核 心上。设备的主设备标志符用来对此数组进行索引(如对tty设备的索引4) 4.2 块设备 块设备也支持以文件方式访问。Linux在blkdevs数组中维护所有
10、已注册的块设备。象chrdevs数组一样,blkdevs也使用设备的主设备号进行索引。其入口也是device_struct结构。和字符设备不同的是系统有几类块设备。SCSI设备是一类而IDE设备则是另外一类。和普通文件操作接口一样, 每个块设备驱动必须为buffer cache提供接口。每个块设备驱动将填充其在blk_dev数组中的blk_dev_struct结构入口。数组的索引值还是此设备的主设备号。 buffer cache块设备请求 上图表示每个请求有指向一个或多个buffer_hear结构的指针,每个请求读写一块数据。 一旦设备驱动完成了请求则它必须将每个buffer_heard结构从
11、request结构中清除。 4.3 网络设备 网络设备,即Linux的网络子系统,是一个发送与接收数据包的实体。它一般是一个象以太网卡的物理设备。 每个网络设备都用一个device结构来 表示,所有传输与接收到的网络数据用一个sk_buff结构 来表示 。 这些数据结构使得网络协议头可以更容易的添加与删除。 网络设备特殊文件仅在于系统 网络设备发现与初始化时建立。 它们使用标准的命名方法,每个名字代表一种类型的设备。多个 相同类型设备将从0开始记数。这样以太网设备被命名为/dev/eth0,/dev/eth1,/dev/eth2 等等。 一些常见的网络设备如下: /dev/ethN 以太网设备
12、 /dev/slN SLIP设备 /dev/pppN PPP 设备 /dev/lo Loopback 设备 编写驱动程序的一些基本概念 l读写 几乎所有设备都有输入和输出。每个驱动程序要负责本设备的读写操作。操作系统的其他不需要知道对设备的具体读写操作怎样进行,这些都由驱动程序屏蔽掉了。操作系统定义好一些读写接口,由驱动程序完成具体的功能。在驱动程序初始化时,需要把具有这种接口的读写函数注册进操作系统。l中断中断在现代计算机结构中有重要的地位。操作系统必须提供驱动程序响应中断的能力。一般是把一个中断处理程序注册到系统中去。操作系统在硬件中断发生后调用驱动程序的处理程序。Linux支持中断的共享
13、,即多个设备共享一个中断。l时钟在实现驱动程序时,很多地方会用到时钟。如某些协议里的超时处理,没有中断机制的硬件的轮询等。操作系统应为驱动程序提供定时机制。一般是在预定的时间过了以后回调注册的时钟函数。常用的系统支持常用的系统支持 l内存申请和释放 l中断l时钟 lI/O l中断打开关闭 l打印信息l注册驱动程序 内存申请和释放 linclude/linux/kernel.h里声明了kmalloc()和kfree()。用于在内核模式下申请和释放内存。l与用户模式下的malloc()不同,kmalloc()申请空间有大小限制。长度是2的整次方。可以申请的最大长度也有限制。另外kmalloc()有
14、priority参数 lKfree()释放的内存必须是kmalloc()申请的 申请中断和释放中断 lrequest_irq()、free_irq() 是驱动程序申请中断和释放中断的调用。l在include/linux/sched.h里声明 时钟 l时钟的处理类似中断,也是登记一个时间处理函数,在预定的时间过后,系统会调用这个函数。l在include/linux/timer.h里声明l使用时钟,先声明一个timer_list结构,调用init_timer对它进行初始化。Time_list结构里expires是标明这个时钟的周期,单位采用jiffies的单位。l jiffy 指连续微处理器时钟周
15、期间的时间长度 I/O lI/O端口的存取使用:linline unsigned int inb(unsigned short port);linline unsigned int inb_p(unsigned short port);linline void outb(char value, unsigned short port);linline void outb_p(char value, unsigned short port);l在include/adm/io.h里定义 中断打开关闭 l系统提供给驱动程序开放和关闭响应中断的能力 l是在include/asm/system.h l#
16、define cli() _asm_ _volatile_ (cli:)l#define sti() _asm_ _volatile_ (sti:) 打印信息 l驱动程序要输出信息使用printk() linclude/linux/kernel.h里声明 注册驱动程序 l如果使用模块(module)方式加载驱动程序,需要在模块初始化时把设备注册到系统设备表里去,不再使用时,把设备从系统中卸除 l定义在drivers/net/net_init.h里的两个函数完成这个工作 Int register_netdev(struct device *dev); void unregister_netdev
17、(struct device *dev); 网络驱动程序的结构 l所有的Linux网络驱动程序遵循通用的接口 l设计时采用的是面向对象的方法 l一个设备就是一个对象(device 结构),它内部有自己的数据和方法 l一个网络设备最基本的方法有初始化、发送和接收 网络驱动程序的基本方法 l初始化(initialize) l打开(open) l关闭(stop )l发送(hard_start_xmit) l接收(reception) l硬件帧头(hard_header) l地址解析(xarp) l参数设置和统计数据网络驱动程序中用到的数据结构 l最重要的是网络设备的数据结构。定义在include/linux/netdevice.h lsk_buff Linux网络各层之间的数据传送都是通过sk_buff 编写编写Linux网络驱动程序中需要注意网络驱动程序中需要注意的问题的问题 l中断共享 l硬件发送忙时的处理 l流量控制(flow control) l调试