您好,欢迎访问三七文档
《LINUX设备驱动开发详解》作者:华清远见第16章Linux网络设备驱动专业始于专注卓识源于远见 ‐ 2 ‐Linux网络设备驱动的结构Linux网络设备驱动程序的体系结构如图16.1所示,从上到下可以划分为4层,依次为网络协议接口层、网络设备接口层、提供实际功能的设备驱动功能层以及网络设备与媒介层,这4层的作用如下所示。网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接收数据。这一层的存在使得上层协议独立于具体的设备。网络设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。设备驱动功能层各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数物理上驱动。对于Linux系统而言,网络设备和媒介都可以是虚拟的。16.1Linux在设计具体的网络设备驱动程序时,我们需要完成的主要工作是编写设备驱动功能层的相关函数以填充net_device数据结构的内容并将net_device注册入内核。16.1.1网络协议接口层网络协议接口层最主要的功能是给上层协议提供了透明的数据包发送和接收接口。当上层ARP或IP协议需要发送数据包时,它将调用网络协议接口层的dev_queue_xmit()函数发送该数据包,同时需传递给该函数一个指向structsk_buff数据结构的指针。dev_queue_xmit()函数的原型为:dev_queue_xmit(structsk_buff*skb);同样地,上层对数据包的接收也通过向netif_rx()函数传递一个structsk_buff数据结构的指针来完成。netif_rx()函数的原型为:intnetif_rx(structsk_buff*skb);sk_buff结构体非常重要,它的含义为“套接字缓冲区”,用于在Linux网络子系统中的各层之间传递数据,是Linux网络子系统数据传递的“中枢神经”。当发送数据包时,Linux内核的网络处理模块必须建立一个包含要传输的数据包的sk_buff,然后将sk_buff递交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从专业始于专注卓识源于远见 ‐ 3 ‐网络媒介上接收到数据包后,它必须将接收到的数据转换为sk_buff数据结构并传递给上层,各层剥去相应的协议头直至交给用户。1.套接字缓冲区成员 参看linux/skbuff.h中的源代码,sk_buff结构体包含的主要成员如下(1)各层协议头h、nh和mac。sk_buff结构体中定义了3个协议头以对应于网络协议的不同层次,这3个协议头为传输层TCP/UDP(及ICMP和IGMP)协议头h、网络层协议头nh和链路层协议头mac。这3个协议头数据结构都被定义为联合体,如代码清单16.1所示。代码清单16.1sk_buff结构体协议头1union{2structtcphdr*th;/*TCP头部*/3structudphdr*uh;/*UDP头部*/4structicmphdr*icmph;/*ICMP头部*/5structigmphdr*igmph;/*IGMP头部*/6structiphdr*ipiph;/*IP头部*/7structipv6hdr*ipv6h;/*IPv6头部*/8unsignedchar*raw;/*数据链路层头部*/9}h;1011union{12structiphdr*iph;/*IP头部*/13structipv6hdr*ipv6h;/*IPv6头部*/14structarphdr*arph;/*ARP头部*/15unsignedchar*raw;/*数据链路层头部*/16}nh;1718union{19unsignedchar*raw;/*数据链路层头部*/20}mac;(2)数据缓冲区指针head、data、tail和end。Linux内核必须分配用于容纳数据包的缓冲区,sk_buff结构体定义了4个指向这片缓冲区不同位置的指针head、data、tail和end。head指针指向内存中已分配的用于承载网络数据的缓冲区的起始地址,sk_buff和相关数据块在分配之后,该指针的值就被固定了。data指针则指向对应当前协议层有效数据的起始地址。每个协议层的有效数据含义并不相同,各层的有效数据信息包含的内容如下。对于传输层而言,用户数据和传输层协议头属于有效数据。对于网络层而言,用户数据、传输层协议头和网络层协议头是其有效数据。对于数据链路层而言,用户数据、传输层协议头、网络层协议头和链路层头部都属于有效数据。因此,data指针的值需随着当前拥有sk_buff的协议层的变化进行相应的移动。tail指针则指向对应当前协议层有效数据负载的结尾地址,与data指针对应。end指针指向内存中分配的数据缓冲区的结尾,与head指针对应。和head指针一样,sk_buff被分配之后,end指针的值也就固定不变了。很显然,有效数据必须位于分配的数据缓冲区内,即(data,tail)区间位于(head,end)区间内,如图16.2所示。因此,head、data、tail和end这4个指针间存在如下关系:head-data-tail-end。从图16.2中可以看出,end指针所指地址数据缓冲区的末尾还包括一个skb_shared_info结构体的空间,这个结构体存放分隔存储的数据片段,意味着可以将数据包的有效数据分成几片存储在不同的内存空间中。图中的frags为分片数组,每一个分片的16.2headdatatailend专业始于专注卓识源于远见 ‐ 4 ‐长度上限是一页。(3)长度信息len、data_len、truesize。sk_buff结构体中定义的len是指数据包有效数据的长度,包括协议头和负载(Payload)。为了支持数据包的分片存放,sk_buff中增加了data_len这个成员,它记录分片的数据长度。truesize表示缓存区的整体长度,置为sizeof(structsk_buff)加上传入alloc_skb()函数或dev_alloc_skb()函数(下文将要介绍)的长度,但不包括结构体skb_shared_info的长度。2.套接字缓冲区操作 下面我们来分析套接字缓冲区涉及到的操作函数,Linux套接字缓冲区支持分配、释放、指针移动等功能函数。(1)分配。Linux内核用于分配套接字缓冲区的函数有:structsk_buff*alloc_skb(unsignedintlen,intpriority);structsk_buff*dev_alloc_skb(unsignedintlen);alloc_skb()函数分配一个套接字缓冲区和一个数据缓冲区,参数len为数据缓冲区的空间大小,以16字节对齐,参数priority为内存分配的优先级。dev_alloc_skb()函数只是以GFP_ATOMIC优先级(代表分配过程不能被中断)调用上面的alloc_skb()函数,并保存skb-head和skb-data之间的16个字节。分配成功之后,因为还没有存放具体的网络数据包,所以sk_buff的data、tail指针都指向存储空间的起始地址head,而len的大小则为0。(2)释放。Linux内核用于释放套接字缓冲区的函数有:voidkfree_skb(structsk_buff*skb);voiddev_kfree_skb(structsk_buff*skb);voiddev_kfree_skb_irq(structsk_buff*skb);voiddev_kfree_skb_any(structsk_buff*skb);上述函数用于释放被alloc_skb()函数分配的套接字缓冲区和数据缓冲区。Linux内核内部使用kree_skb()函数,而网络设备驱动程序中则必须用dev_kfree_skb()、dev_kfree_skb_irq()或dev_kfree_skb_any()函数进行套接字缓冲区的释放。其中,dev_kfree_skb()函数用于非中断上下文,dev_kfree_skb_irq()函数用于中断上下文,而dev_kfree_skb_any()函数则在中断和非中断上下文中皆可采用。(3)指针移动。Linux套接字缓冲区中的数据缓冲区指针移动操作包括put(放置)、push(推)、pull(拉)、reserve(保留)等。①put操作数据缓冲区指针put操作以下列函数完成:unsignedchar*skb_put(structsk_buff*skb,unsignedintlen);unsignedchar*__skb_put(structsk_buff*skb,unsignedintlen);上述函数将tail指针下移,增加sk_buff的len值,并返回skb-tail的当前值。skb_put()和__skb_put()的区别在于前者会检测放入缓冲区的数据,而后者不会检查。这两个函数主要用于在缓冲区尾部添加数据。②push操作数据缓冲区指针push操作以下列函数完成:unsignedchar*skb_push(structsk_buff*skb,unsignedintlen);unsignedchar*__skb_push(structsk_buff*skb,unsignedintlen);与skb_put()和__skb_put()不同,skb_push()和__skb_push()会将data指针上移,因此也要增加sk_buff的len值。push操作在存储空间的头部增加一段可以存储网络数据包的空间,而put操作则在存储空间的尾部增加一段可以存储网络数据包的空间,因此主要用于在数据包发送时添加头部。skb_push()与_专业始于专注卓识源于远见 ‐ 5 ‐_skb_push()的区别和skb_put()和__skb_put()的区别类似。③pull操作数据缓冲区指针pull操作以下列函数完成:unsignedchar*skb_pull(structsk_buff*skb,unsignedintlen);skb_pull()函数将data指针下移,并减小skb的len值。这个操作一般用于下层协议向上层协议移交数据包,使data指针指向上一层协议的协议头。④reserve操作数据缓冲区指针reserve操作以下列函数完成:voidskb_reserve(structsk_buff*skb,unsignedintlen);skb_reserve()函数将data指针和tail指针同时下移,这个操作主要用于在存储空间的头部预留len长度的空隙。下面我们以一个具体的UDP数据包接收的Linux处理流程为例来说明sk_buff的操作过程,这一过程的绝大部分工作都由Linux内核完成,驱动工程师只需完成涉及的数据链路层部分。假设以太网适配器(以太网卡)收到了一个UDP数据包,Linux从底层到应用层处理这一数据包的流程如下。(1)网卡收到一个UDP数据包后,驱动程序需要创建一个sk_buff结构体和数据缓冲区,将收到的数据全部复制到data指向的空间,并将skb-mac.raw指向data。此时,有效数据的开始位置是一个以太网头,skb-mac.raw指向链路层的以太网头部。代码清单16.2演示了驱动程序在接收到数据包后分配sk_buff和数据缓冲区并将数据包复制到缓冲区的过
本文标题:【华清远见10年特献】《Linux设备驱动开发详解》第16章、 Linux网络设备驱动
链接地址:https://www.777doc.com/doc-5321873 .html