• 1. Linux下网络驱动程序编写及相关基础知识
  • 2. 目录 1.设备驱动概述 3. Linux内核及内核编程 5. Linux网络设备驱动 4. Linux内核模块 2.驱动设计的硬件基础
  • 3. 设备驱动概述设备驱动的作用 任何一个计算机系统的运行都是系统中软硬件协作的结果,没有硬件的软件是空中楼阁,而没有软件的硬件则只是一堆废铁。硬件是底层基础,是所有软件得以运行的平台,代码最终会落实为硬件上的组合逻辑与时序逻辑;软件则实现了具体应用,它按照各种不同的业务需求而设计,满足了用户的需求。硬件较固定,软件则很灵活,可以适应各种复杂多变的应用。可以说,计算机系统的软硬件互相成就了对方。 但是,软硬件之间同样存在着悖论,那就是软件和硬件不应该互相渗透到对方的领地。例如,应用软件工程师在调用套接字发送和接收数据包的时候,他不必关心网卡上的中断、寄存器、存储空间、I/O 端口、片选以及其他任何硬件词汇;在使用printf()函数输出信息的时候,他不用知道底层究竟是怎样把相应的信息输出到屏幕或串口。 也就是说,应用软件工程师需要看到一个没有硬件的纯粹的软件世界,硬件必须被透明地呈现给他们。谁来实现硬件对应用软件工程师的透明?这个任务是由驱动工程师来完成。
  • 4. 设备驱动的作用 对设备驱动最通俗的解释就是“驱使硬件设备行动”。设备驱动与底层硬件直接打交道,按照硬件设备的具体工作方式读写设备寄存器,完成设备的轮询、中断处理、DMA 通信,进行物理内存向虚拟内存的映射,最终使通信设备能够收发数据,使显示设备能够显示文字和画面,使存储设备能够记录文件和数据。 由此可见,设备驱动充当了和应用软件之间的纽带,它使得应用软件只需要调用系统软件的应用编程接口(API)就可让硬件去完成要求的工作。在系统中没有操作系统的情况下,工程师可以根据硬件设备的特点自行定义接口。而在有操作系统的情况下,设备驱动的架构则由相应的操作系统定义,驱动工程师必须按照相应的架构设计设备驱动,这样,设备驱动才能良好地整合到操作系统的内核中。 驱动程序沟通着硬件和应用软件,而驱动工程师则沟通着硬件工程师和应用软件工程师。随着通信、电子行业的迅速发展,全世界每天都会有大量的新芯片被生产,大量的新电路板被设计,因此,也会有大量设备驱动需要开发。这些设备驱动,或运行在简单的单任务环境中,或运行在VxWorks、Linux、Windows等多任务操作系统环境中,发挥硬件着不可替代的作用。
  • 5. 无操作系统时的设备驱动 并不是任何一个计算机系统都一定要运行操作系统,在许多情况下操作系统是不必要的。对于功能比较单一、控制并不复杂的系统,如公交车刷卡机、电冰箱、微波炉、简单的手机和小灵通等,并不需要多任务调度、文件系统、内存管理等复杂功能,用单任务架构完全可以很好地支持它们的工作。一个无限循环中夹杂对设备中断的检测或者对设备的轮询是这种系统中软件的典型架构。
  • 6. 单任务软件典型架构1 int main(int argc, char* argv[]) 2 { 3 while (1) 4 { 5 if (serialInt == 1) 6 /*有串口中断*/ 7 { 8 ProcessSerialInt(); /*处理串口中断*/ 9 serialInt = 0; /*中断标志变量清零*/ 10 } 11 if (keyInt == 1) 12 /*有按键中断*/ 13 { 14 ProcessKeyInt(); /*处理按键中断*/ 15 keyInt = 0; /*中断标志变量清零*/ 16 }
  • 7. 单任务软件典型架构17 status = CheckXXX(); 18 switch (status) 19 { 20 ... 21 } 22 ... 23 } 24 }
  • 8. 有操作系统时的设备驱动系统中存在操作系统的时候,设备驱动变成了连接硬件和内核的桥梁。硬件、设备驱动、操作系统和应用程序的关系之间关系如下图:
  • 9. 有操作系统时的设备驱动用户应用程序操作系统API操作系统设备驱动中独立于设备的接口设备驱动中硬件操作硬件
  • 10. 操作系统的存在势必要求设备驱动附加更多的代码和功能,把单一的“驱使硬件设备行动”变成了操作系统内与硬件交互的模块,它对外呈现为操作系统的API,不再给应用软件工程师直接提供接口。 请问:有了操作系统之后,设备驱动反而变得复杂,那要操作系统干什么?有操作系统时的设备驱动
  • 11. 一个复杂的软件系统需要处理多个并发的任务,没有操作系统,想完成多任务并发是很困难的。 操作系统给我们提供内存管理机制。一个典型的例子是,对于多数含MMU的处理器而言,Windows、Linux 等操作系统可以让每个进程都独立地访问4GB的内存空间。有操作系统时的设备驱动
  • 12. 上述优点似乎并没有体现在设备驱动身上,操作系统的存在给设备驱动究竟带来了什么好处呢?有操作系统时的设备驱动
  • 13. 简而言之,操作系统通过给设备驱动制造烦来达到给上层应用提供便利的目的。如果设备驱动都按照操作系统给出的独立于设备的接口而设计,应用程序将可使用统一的系统调用接口来访问各种设备。对于UNIX的VxWorks、Linux等操作系统而言,应用程序通过write()、read()等函数读写文件就可以访问各种字符设备和块设备,而不用管设备的具体类型和工作方式,是非常方便的。有操作系统时的设备驱动
  • 14. Linux设备驱动设备的分类及特点 Linux 将存储器和外设分为3 个基础大类: 1.字符设备 2.块设备 3.网络设备 字符设备指那些必须以串行顺序依次进行访问的设备,请例出几种字符设备?
  • 15. Linux设备驱动触摸屏 磁带驱动器 鼠标 问:键盘是字符设备么? 块设备可以用任意顺序进行访问,以块为单位进行操作,如硬盘、软驱等。 字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲。但是,字符设备和块设备并没有明显的界限,如Flash 设备符合块设备的特点,但是我们仍然可以把它作为一个字符设备来访问。
  • 16. Linux设备驱动字符设备和块设备的驱动设计呈现出很大的差异,但是对于用户而言,他们都使用文件系统的操作接口open()、close()、read()、write()等函数进行访问。 在 Linux 系统中,网络设备面向数据包的接收和发送而设计,它并不对应于文件系统的节点。内核与网络设备的通信和内核与字符设备、块设备的通信方式完全不同。
  • 17. Linux设备驱动Linux设备驱动与整个软硬件系统的关系 除网络设备外,字符设备与块设备都被映射到Linux 文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()等函数即可访问字符设备和块设备。
  • 18. Linux设备驱动与整个软硬件系统的关系
  • 19. 问:在Linux内核和驱动程序中能不能使用c语言中的printf函数?
  • 20. 目录 1.设备驱动概述 3. Linux内核及内核编程 5. Linux网络设备驱动 4. Linux内核模块 2.驱动设计的硬件基础
  • 21. 驱动设计的硬件基础了解微控制器、微处理器、数字信号处理器以及应用于特定领域的处理器各自的特点。 了解嵌入式系统中所使用的各类存储器与CPU的接口、应用领域及特点。 了解常见的外设接口与总线的工作方式,包括串口、I2C、USB、以太网接口、ISA、PCI和cPCI等。 嵌入式系统硬件电路中经常会使用CPLD 和FPGA,了解CPLD 和FPGA在电路中的作用。 学会实际项目开发过程中硬件分析的方法,包括如何进行原理图分析、时序分析以及如何快速地从芯片手册获取有效信息。 了解调试过程中常用仪器、仪表的使用方法,涉及万用表、示波器和逻辑分析仪。 这些各人可根据兴趣可查阅相关资料,这里就不多讲了。
  • 22. 目录 1.设备驱动概述 3. Linux内核及内核编程 5. Linux网络设备驱动 4. Linux内核模块 2.驱动设计的硬件基础
  • 23. Linux内核及内核编程Linux内核的组成部分 Linux内核主要由进程调度(SCHED)、内存管理(MM)、虚拟文件系统(VFS)、网络接口(NET)和进程间通信(IPC)等5 个子系统组成。 Linux内核空间与用户空间 X86 处理器包含4 个不同的特权级,称为Ring0~Ring3。Ring0 下,可以执行特权级指令,对任何I/O 设备都有访问权等,而Ring3 则有很多操作限制。 Linux 系统充分利用CPU的这一硬件特性,但它只使用了两级。在Linux 系统中,内核可进行任何操作,而应用程序则被禁止对硬件的直接访问和对内存的未授权访问。若使用X86处理器,则用户代码运行在特权级3,而系统内核代码则运行在特权级0。
  • 24. Linux内核及内核编程内核空间和用户空间这两个名词被用来区分程序执行的这两种不同状态,它们使用不同的地址空间。Linux 系统只能通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。 问:驱动程序运行在Ring0~Ring3的哪个级别上?
  • 25. 目录 1.设备驱动概述 3. Linux内核及内核编程 5. Linux网络设备驱动 4. Linux内核模块 2.驱动设计的硬件基础
  • 26. Linux内核模块以Hello,World模块说明Linux内核模块的基本要点。 代码清单 一个最简单的Linux 内核模块 1 #include 2 #include 3 MODULE_LICENSE("Dual BSD/GPL"); 4 static int hello_init(void) 5 { 6 printk(KERN_ALERT " Hello World enter\n"); 7 return 0; 8 } 9 static void hello_exit(void) 10 { 11 printk(KERN_ALERT " Hello World exit\n "); 12 }
  • 27. Linux内核模块13 module_init(hello_init); 14 module_exit(hello_exit); 15 16 MODULE_AUTHOR("Song Baohua"); 17 MODULE_DESCRIPTION("A simple Hello World Module"); 18 MODULE_ALIAS("a simplest module"); 当使用insmod命令加载该模块时会执行hello_init这个函数,当用rmmod命令卸载该模块时会执行hello_exit这个函数。
  • 28. 目录 1.设备驱动概述 3. Linux内核及内核编程 5. Linux网络设备驱动 4. Linux内核模块 2.驱动设计的硬件基础
  • 29. Linux网络设备驱动网络设备是完成用户数据包在网络媒介上发送和接收的设备,它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送,并将接收到的数据包传递给上层协议。 与字符设备和块设备不同,网络设备并不对应/dev目录下的文件,应用程序最终使用套接字(socket)完成与网络设备的接口。因而在网络设备身上并不能体现出”一切都是文件”的思想。 Linux 系统对网络设备驱动定义了4 个层次,这4 个层次为网络协议接口层、网络设备接口层、提供实际功能的设备驱动功能层和网络设备与媒介层。
  • 30. Linux网络设备驱动的结构
  • 31. Linux网络设备驱动的结构Linux 网络设备驱动程序的体系结构如上图 所示,从上到下可以划分为4 层,依次为网络协议接口层、网络设备接口层、提供实际功能的设备驱动功能层以及网络设备与媒介层,这4层的作用如下所示。 网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。这一层的存在使得上层协议独立于具体的设备。 网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。
  • 32. Linux网络设备驱动的结构设备驱动功能层各函数是网络设备接口层net_device 数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。 网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数物理上驱动。对于Linux 系统而言,网络设备和媒介都可以是虚拟的。 在设计具体的网络设备驱动程序时,我们需要完成的主要工作是编写设备驱动功能层的相关函数以填充net_device数据结构的内容并将net_device注册入内核。
  • 33. 网络协议接口层网络协议接口层 网络协议接口层最主要的功能是给上层协议提供了透明的数据包发送和接收接口。当上层ARP 或IP 协议需要发送数据包时,它将调用网络协议接口层的dev_queue_xmit()函数发送该数据包,同时需传递给该函数一个指向struct sk_buff数据结构的指针。dev_queue_xmit()函数的原型为: dev_queue_xmit (struct sk_buff * skb ); 同样地,上层对数据包的接收也通过向netif_rx()函数传递一个struct sk_buff数据 结构的指针来完成。netif_rx()函数的原型为: int netif_rx(struct sk_buff *skb); sk_buff 结构体非常重要,它的含义为“套接字缓冲区”,用于在Linux 网络子系统中的各层之间传递数据。
  • 34. 网络协议接口层当发送数据包时,Linux 内核的网络处理模块必须建立一个包含要传输的数据包的sk_buff,然后将sk_buff 递交给下层,各层在sk_buff 中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网络媒介上接收到数据包后,它必须将接收到的数据转换为sk_buff 数据结构并传递给上层,各层剥去相应的协议头直至交给用户。 套接字缓冲区成员: sk_buff结构体包含的主要成员如下: 1.各层协议头h、nh和mac sk_buff结构体中定义了3个协议头以对应于网络协议的不同层次,这3 个协议头为传输层TCP/UDP(及ICMP和IGMP)协议头h、网络层协议头nh和链路层协议头mac。这3个协议头数据结构都被定义为联合体,如 下代码清单所示。
  • 35. sk_buff结构体1 union{ 2 struct tcphdr *th; /* TCP头部*/ 3 struct udphdr *uh; /* UDP头部*/ 4 struct icmphdr *icmph; /* ICMP头部*/ 5 struct igmphdr *igmph; /* IGMP头部*/ 6 struct iphdr *ipiph; /* IP头部*/ 7 struct ipv6hdr *ipv6h; /* IPv6头部*/ 8 unsigned char *raw; /* 数据链路层头部*/ 9 }h; 10 问:sizeof(h)=??? 11 union{ 12 struct iphdr *iph; /* IP头部*/ 13 struct ipv6hdr *ipv6h; /* IPv6头部*/ 14 struct arphdr *arph; /*ARP头部*/ 15 unsigned char *raw; /* 数据链路层头部*/ 16 }nh; 17 问:sizeof(nh)=??? 18 union{ 19 unsigned char *raw; /* 数据链路层头部*/ 20 } mac;
  • 36. sk_buff结构体2. 数据缓冲区指针head、data、tail和end。 Linux 内核必须分配用于容纳数据包的缓冲区,sk_buff结构体定义了4 个指向这片缓冲区不同位置的指针head、data、tail和end。 套接字缓冲区操作 1.分配套接字缓冲区 Linux 内核用于分配套接字缓冲区的函数有: struct sk_buff *alloc_skb(unsigned int len,int priority); struct sk_buff *dev_alloc_skb(unsigned int len); 2.释放配套接字缓冲区 Linux 内核用于释放套接字缓冲区的函数有: void kfree_skb(struct sk_buff *skb); void dev_kfree_skb(struct sk_buff *skb);
  • 37. 网络设备接口层网络设备接口层的主要功能是为千变万化的网络设备定义了统一、抽象的数据结构net_device结构体,以不变应万变,实现多种硬件在软件层次上的统一。 net_device 结构体在内核中指代一个网络设备,网络设备驱动程序只需通过填充net_device的具体成员并注册net_device即可实现硬件操作函数与内核的挂接。 net_device 本身是一个巨型结构体,包含网络设备的属性描述和操作接口。当我们编写网络设备驱动程序时,只需要了解其中的一部分。 下面就对net_device中比较重要的字段进行简单 的介绍。 (1)全局信息 char name[IFNAMESIZ]; name是网络设备的名称。
  • 38. 网络设备接口层int (*init)(struct net_device *dev); init 为设备初始化函数指针,如果这个指针被设置了,则网络设备被注册时将调用该函数完成对net_device 结构体的初始化。但是,设备驱动程序可以不实现这个函数并将其赋值为NULL。 (2)硬件信息。 unsigned long mem_end; unsigned long mem_start; mem_start和mem_end 分别定义了设备所使用的共享内存的起始和结束地址。 unsigned long base_addr; unsigned char irq; unsigned char if_port; unsigned char dma; base_addr 为网络设备I/O 基地址。
  • 39. 网络设备接口层irq为设备使用的中断号。 if_port指定多端口设备使用哪一个端口,该字段仅针对多端口设备。例如,如果设备同时支持IF_PORT_10BASE2(同轴电缆)和IF_PORT_10BASET(双绞线),则可使用该字段。 dma 指定分配给设备的DMA通道。 (3)接口信息。 unsigned short hard_header_len; hard_header_len 是网络设备的硬件头长度,在以太网设备的初始化函数中,该成员被赋为ETH_HLEN,即14。 unsigned short type; type是接口的硬件类型。 unsigned mtu; mtu指最大传输单元(MTU)。 unsigned char dev_addr[MAX_ADDR_LEN]; unsigned char broadcast[MAX_ADDR_LEN];
  • 40. 网络设备接口层dev_addr[ ]、broadcast[ ]无符号字符数组,分别用于存放设备的硬件地址和广播地址。对于以太网而言,这两个地址的长度都为6 个字节。以太网设备的广播地址为6个0xFF,而MAC地址需由驱动程序从硬件上读出并填充到dev_addr[ ]中。 (4)设备操作函数。 int (*open)(struct net_device *dev); int (*stop)(struct net_device *dev); open()函数的作用是打开网络接口设备,获得设备需要的I/O 地址、IRQ、DMA通道等。stop()函数的作用是停止网络接口设备,与open()函数的作用相反。 int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev); hard_start_xmit() 函数会启动数据包的发送,当系统调用驱动程序的hard_start_xmit()函数时,需要向其传入一个sk_buff 结构体指针,以使得驱动程序能获取从上层传递下来的数据包。
  • 41. 设备驱动功能层net_device 结构体的成员(属性和函数指针)需要被设备驱动功能层的具体数值和函数赋予。对于具体的设备xxx,应该编写设备驱动功能层的函数,这些函数形如xxx_open()、xxx_stop()、xxx_tx()、xxx_ hard_header()、xxx_get_stats()、xxx_tx_timeout()、xxx_poll()等。 由于网络数据包的接收可由中断引发,设备驱动功能层中另一个主体部分将是中断处理函数,它负责读取硬件上接收的数据包并传送给上层协议,可能包含xxx_interrupt()和xxx_rx()函数,前者完成中断类型判断等基本的工作,后者则需完成数据包的生成和递交上层等复杂工作。 在后面会给出上述xxx_xxx()函数进行详细地分析并给出参考设计模板。
  • 42. 网络设备与媒介层网络设备与媒介层直接对应于实际的硬件设备。为了给设备的物理配置和寄存器操作一个更一般的描述,可以定义一组宏和一组访问设备内部寄存器的函数,具体的宏和函数与特定的硬件紧密相关。代码清单如下 所示为相应的设计范例。 代码清单 网络设备底层硬件操作 1 /* 寄存器定义*/ 2 #define DATA_REG 0x0004 3 #define CMD_REG 0x0008 4 5 /* 寄存器读写函数*/ 6 static u16 xxx_readword(u32 base_addr, int portno) 7 { 8 ... /*读取寄存器的值并返回*/ 9 } 10 11 static void xxx_writeword(u32 base_addr, int portno, u16 value) 12 { 13 ... /*向寄存器写入数值*/ 14 }
  • 43. 网络设备驱动的注册与注销网络设备驱动的注册与注销使用成对出现的register_netdev()和unregister_netdev()函数完成,这两个函数的原型为: int register_netdev(struct net_device *dev); void unregister_netdev(struct net_device *dev); 这两个函数都接收一个net_device 结构体指针为参数。 net_device 的生成和成员的赋值并非一定要由逐个亲自动手完成,可以利用下面的函数帮助我们填充: struct net_device *alloc_netdev(int sizeof_priv…); struct net_device *alloc_etherdev(int sizeof_priv); alloc_netdev()函数生成一个net_device 结构体,对其成员赋值并返回该结构体的指针。
  • 44. 网络设备驱动的注册与注销alloc_etherdev()是alloc_netdev()针对以太网的“快捷”函数,这从alloc_etherdev()的源代码可以看出,如下面代码清单所示。 代码清单 alloc_etherdev()函数 1 struct net_device *alloc_etherdev(int sizeof_priv) 2 { 3 /* 以ether_setup为alloc_netdev的setup参数*/ 4 return alloc_netdev(sizeof_priv, "eth%d", ether_setup); 5 } 完成与 alloc_enetdev()和alloc_etherdev()函数相反功能,即释放net_device结构体的函数为: void free_netdev(struct net_device *dev);
  • 45. 网络设备驱动的注册与注销net_device 结构体的分配和网络设备驱动注册需在网络设备驱动程序的模块加载函数中进行,而net_device 结构体的释放和网络设备驱动的注销则需在模块卸载函数中完成,如下面代码清单所示。 代码清单 网络设备驱动程序的模块加载函数模板 1 int xxx_init_module(void) 2 { 3 ... 4 /* 分配net_device结构体并对其成员赋值*/ 5 xxx_dev = alloc_netdev(sizeof(struct xxx_priv), "sn%d",xxx_init); 6 if (xxx_dev == NULL) 7 ... /* 分配net_device失败*/ 8 9 /* 注册net_device结构体*/ 10 if ((result = register_netdev(xxx_dev)))
  • 46. 网络设备驱动的注册与注销11 ... 12 } 13 14 void xxx_cleanup(void) 15 { 16 ... 17 /* 注销net_device结构体*/ 18 unregister_netdev(xxx_dev); 19 /* 释放net_device结构体*/ 20 free_netdev(xxx_dev); 21 }
  • 47. 网络设备的初始化 网络设备的初始化主要需要完成如下几个方面的工作: 1.进行硬件上的准备工作,检查网络设备是否存在,如果存在,则检测设备所使用的硬件资源。 2.进行软件接口上的准备工作,分配net_device 结构体并对其数据和函数指针成员赋值。 3.获得设备的私有信息指针并初始化其各成员的值。 对 net_device 结构体成员及私有数据的赋值都可能需要与硬件初始化工作协同进行,即硬件检测出了相应的资源,需要根据检测结果填充net_device 结构体成员和私有数据。 一个网络设备驱动初始化函数的模板如下面代码清单所示,具体的设备驱动初始化函数并不一定完全和该模板一样,但是其本质过程是一致的。
  • 48. 网络设备的初始化代码清单 网络设备驱动的初始化函数模板 1 void xxx_init(struct net_device *dev) 2 { 3 /*设备的私有信息结构体*/ 4 struct xxx_priv *priv; 5 6 /* 检查设备是否存在和设备所使用的硬件资源*/ 7 xxx_hw_init(); 8 9 /* 初始化以太网设备的公用成员*/ 10 ether_setup(dev); 11 12 /*设置设备的成员函数指针*/ 13 dev->open = xxx_open; 14 dev->stop = xxx_release; 15 dev->set_config = xxx_config;
  • 49. 16 dev->hard_start_xmit = xxx_tx; 17 dev->do_ioctl = xxx_ioctl; 18 dev->get_stats = xxx_stats; 19 dev->change_mtu = xxx_change_mtu; 20 dev->rebuild_header = xxx_rebuild_header; 21 dev->hard_header = xxx_header; 22 dev->tx_timeout = xxx_tx_timeout; 23 dev->watchdog_timeo = timeout; 24 25 /*如果使用NAPI,设置pool函数*/ 26 if (use_napi) 27 { 28 dev->poll = xxx_poll; 29 } 30 31 /* 取得私有信息,并初始化它*/ 32 priv = netdev_priv(dev); 33 ... /* 初始化设备私有数据区*/ 34 }
  • 50. 网络设备的打开与释放 网络设备的打开函数需要完成如下工作。 使能设备使用的硬件资源,申请I/O 区域、中断和DMA通道等。 调用Linux 内核提供的netif_start_queue()函数,激活设备发送队列。 网络设备的关闭函数需要完成如下工作。 调用Linux 内核提供的netif_stop_queue()函数,停止设备传输包。 释放设备所使用的I/O 区域、中断和DMA资源。 Linux 内核提供的netif_start_queue()和netif_stop_queue()两个函数的原型为: void netif_start_queue(struct net_device *dev); void netif_stop_queue (struct net_device *dev); 下面列出 网络设备打开和释放函数的模板。
  • 51. 网络设备的打开与释放代码清单 网络设备打开和释放函数模板 1 int xxx_open(struct net_device *dev) 2 { 3 /* 申请端口、IRQ等,类似于fops->open */ 4 ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev); 5 ... 6 netif_start_queue(dev); 7 ... 8 } 9 10 int xxx_release(struct net_device *dev) 11 { 12 /* 释放端口、IRQ等,类似于fops->close */ 13 free_irq(dev->irq, dev); 14 ... 15 netif_stop_queue(dev); /* can't transmit any more */ 16 ... 17 }
  • 52. 数据发送流程从 网络设备驱动程序的结构分析可知,Linux 网络子系统在发送数据包时,会调用驱动程序提供的hard_start_transmit()函数,该函数用于启动数据包的发送。在设备初始化的时候,这个函数指针需被初始化指向设备的xxx_tx()函数。 网络设备驱动完成数据包发送的流程如下。 (1)网络设备驱动程序从上层协议传递过来的sk_buff 参数获得数据包的有效数据和长度,将有效数据放入临时缓冲区。 (2)对于以太网,如果有效数据的长度小于以太网冲突检测所要求数据帧的最小长度ETH_ZLEN,则给临时缓冲区的末尾填充0。 (3)设置硬件的寄存器,驱使网络设备进行数据发送操作。 完成以上3个步骤的网络设备驱动程序的数据包发送函数的模板如下面代码清单所示。
  • 53. 数据发送流程代码清单 网络设备驱动程序的数据包发送函数模板 1 int xxx_tx(struct sk_buff *skb, struct net_device *dev) 2 { 3 int len; 4 char *data, shortpkt[ETH_ZLEN]; 5 /* 获得有效数据指针和长度*/ 6 data = skb->data; 7 len = skb->len; 8 if (len < ETH_ZLEN) 9 { 10 /* 如果帧长小于以太网帧最小长度,补0 */ 11 memset(shortpkt, 0, ETH_ZLEN); 12 memcpy(shortpkt, skb->data, skb->len); 13 len = ETH_ZLEN; 14 data = shortpkt; 15 }
  • 54. 数据发送流程16 17 dev->trans_start = jiffies; /* 记录发送时间戳*/ 18 19 /* 设置硬件寄存器让硬件把数据包发送出去*/ 20 xxx_hw_tx(data, len, dev); 21 ... 22 } 当数据传输超时时,意味着当前的发送操作失败,此时,数据包发送超时处理函数xxx_tx_ timeout()将被调用。这个函数需要调用Linux内核提供的netif_wake_queue()函数重新启动设备发送队列,如下面代码清单所示。 代码清单 网络设备驱动程序的数据包发送超时函数模板 1 void xxx_tx_timeout(struct net_device *dev) 2 { 3 ... 4 netif_wake_queue(dev); /* 重新启动设备发送队列*/ 5 }
  • 55. 数据接收流程网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,如果为接收中断,则读取接收到的数据,分配sk_buffer 数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,并调用netif_rx()函数将sk_buffer 传递给上层协议。代码清单如下 所示为完成这一过程的函数模板。 代码清单 网络设备驱动的中断处理函数模板 1 static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs) 2 { 3 ... 4 switch (status &ISQ_EVENT_MASK) 5 { 6 case ISQ_RECEIVER_EVENT: 7 /* 获取数据包*/ 8 xxx_rx(dev);
  • 56. 数据接收流程9 break; 10 /* 其他类型的中断*/ 11 } 12 } 13 static void xxx_rx(struct xxx_device *dev) 14 { 15 ... 16 length = get_rev_len (...); 17 /* 分配新的套接字缓冲区*/ 18 skb = dev_alloc_skb(length + 2); 19 20 skb_reserve(skb, 2); /* 对齐*/ 21 skb->dev = dev; 22 23 /* 读取硬件上接收到的数据*/ 24 insw(ioaddr + RX_FRAME_PORT, skb_put(skb, length), length >> 1);
  • 57. 数据接收流程25 if (length &1) 26 skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT); 27 28 /* 获取上层协议类型*/ 29 skb->protocol = eth_type_trans(skb, dev); 30 31 /* 把数据包交给上层*/ 32 netif_rx(skb); 33 34 /* 记录接收时间戳*/ 35 dev->last_rx = jiffies; 36 ... 37 }
  • 58. 谢谢 Thank You!!!