赵礼栋;陈娜
【摘 要】基于S3C2410开发板,在Linux操纵系统上对AX88796网卡驱动程序进行了详细分析.依托该实例,采用情景分析的方法,按照从应用层到物理层的顺序,具体的阐述了当嵌入式Linux在TCP/IP网络协议下进行数据的发送和接收时,数据在各层中的数据格式和流动路径. 【期刊名称】《兰州工业学院学报》 【年(卷),期】2011(018)003 【总页数】5页(P29-33)
【关键词】Linux;AX88796;网卡驱动;网络通信 【作 者】赵礼栋;陈娜
【作者单位】兰州工业高等专科学校继续教育中心,甘肃兰州730050;兰州工业高等专科学校软件工程系,甘肃兰州730050 【正文语种】中 文 【中图分类】TP311.56
当前,网络已经渗入到了我们生活的方方面面.由于人们生产和生活的需要,网络也在不断的演化和向前发展,传感器网络、移动互联网和物联网的出现都是社会现实需要推动的结果.不论是那种类型的网络,网络通信都具有举足轻重的作用,例如:物联网具有全面感知、可靠传送、智能处理3个主要特征.其中,“可靠传送”就是指通过各种通信网络与互联网融合,对接收到的物体信息进行实时远程传送,
以进行各种有效的处理.网络通信的重要地位,由此可见一斑.
嵌入式系统的发展,要求操作系统体积小、执行速度快、具有较好的可裁剪性和可移植性,同时嵌入式系统越来越追求数字化、网络化、智能化,因此,要求整个系统必须是开放的并提供标准的API,并且能够方便众多第三方的硬软件沟通,Linux完全符合以上条件,并具有开发源码、内核小、功能强大、源码易于裁剪、可靠、稳定、运行效率高、兼容性好、可移植性强等特性.这使得Linux在嵌入式领域得到了广泛的应用,并拥有广阔的前景.
一般情况下,网络通信程序开发主要集中在网卡驱动和网络应用程序开发,对网络协议方面很少涉及.然而,随着嵌入式时代的到来,对网络协议层的开发逐渐的增多,例如:在嵌入式开发中常需要对网络协议栈进行裁剪、移植.所以对网络通信各个层次的全面综合研究已显得十分必要.Linux网络通信子系统十分的复杂:有大量的网络协议;支持各种型号的网卡;一次网络通信中由于通信条件的不同,程序的流向很多.为了简化分析,使传输数据在网络各层中的格式和流向更加的清晰明了,论文做了以下限定:基于AX88796快速以太网卡;基于TCP/IP网络协议;只讲述数据的发送和接收时的数据格式和流动路径.
AX88796是一款高性能、高集成度和NE2000兼容的快速以太网控制器.内部集成有10/100 Mbps自适应的物理层收发器和8 K×16位的SRAM,可支持 ARM等多种 CPU总线类型.AX88796提供了基于IEEE802.3/IEEE802.3u局域网标准的10/100 M以太网控制功能和与IEEE802.3u兼容的媒介无关接口MII.AX88796结构框图如图1所示[1].
当AX88796进行报文发送时,远端DMA先将报文写入SRAM中的发送缓冲区.把报文写入发送缓冲区是通过以下操作来实现:把发送缓冲区的首地址写入远程起始地址寄存器对(RSAR0,RSAR1);把将要传送报文的长度写入远程字节计数寄存器(RBCR0,RBCR1);启动一个远程DMA写操作.紧接着本地DMA把报文从发送
缓冲区传送到MAC层,再经由内部的PHY层发送到网络.把报文从发送缓冲区传送到MAC层是通过以下操作实现:把发送缓冲区的首地址写入传送页面开始寄存器(TPSR);把要传送的报文的长度写入传送字节寄存器(TBCR0,1);启动发送传输帧命令.至此,完成了AX88796报文发送.
当AX88796进行报文接收时,本地DMA先将报文写入SRAM中的接收缓冲区.把报文写入接收缓冲区是通过以下操作实现:设置页起始地址寄存器(PSTART)、页结束地址寄存器(PSTOP)、当前页地址寄存器(CPR)、边界指针寄存器(BNRY);启动发送写命令.紧接着远端DMA把接收缓冲区中的报文传送到主机,把接收缓冲区中的报文传送到主机是通过以下操作实现:把接收缓冲区的首地址写入远程起始地址寄存器对(RSAR0,RSAR1);把将要传送报文的长度写入远程字节计数寄存器(RBCR0,RBCR1);启动一个远程DMA读操作.至此,完成了AX88796报文接收.
从编程者的角度可以把网络分成了以下几层:应用层、BSD Socket层、INET Socket层、IP 层、硬件驱动层、物理接口层.
应用层通过API函数调用内核层中的函数进行数据的收发.在BSD Socket层中,数据被存储在Msghdr{}数据结构中,通过操作Socket{}结构进行数据的操作,每一个Socket{}结构对应一个网络连接,Socket{}结构中的成员变量Proto_ops是和该连接相关的一些操作的函数集合,通过调用其中相应的函数,使数据从BSD Socket层传输到INET Socket层.在INET Socket层及该层以下的层中,数据存储在Sk_buff{}数据结构中,通过操作网络连接Sock{}来完成数据包的存放和调度.Sock{}结构中的成员变量Proto{}是关于该连接的控制函数集合.在硬件驱动层,每一个网络设备接口都对应一个Net_device{}数据结构,通过操纵该数据结构实现网络设备的驱动功能[2].
从应用层到物理层发送数据时,函数调用顺序如下:
[send>sys_send>sys_sendto>sock_sendmsg>inet_sendmsg>tcp_sendmsg>tcp_send_skb>tcp_transmit>ip_queue_xmit>ip_queue_xmit2>ip_output>ip_finish_output>ip_finish_output2>neigh_resolve_output> dev_queue_xmit> ei_start_xmit]
网络连接创建好以后,就可以进行AX88796网卡的数据发送了.在应用层,有两种发送数据的方法:一种是调用write()系统调用;一种是调用send()系统调用.两种调用方法殊途同归,现在采用send()系统调用开始进行数据的发送.send()系统调用通过调用内核过程sys_send()使传输的数据从应用层转到内核层(BSD Socket层).sys_send()调用sys_send to()继续数据的发送,在sys_sendto()函数中,把fd对应的Socket{}取出,准备用于数据的传输;初始化Msghdr{}结构体,通过iov.iov_base=buff把要传送的数据和Msghdr{}结构体联系起来;最后调用sock_sendmsg()函数发送数据.sock_sendmsg()函数通过err=sock->ops- > sendmsg(sock,msg,size,&scm)调用 INET Socket层的inet_sendmsg()函数.在这里Socket{}结构中的Proto_ops{}类型的指针ops已经初始化成了inet_stream_ops,ops指针中的sendmsg()函数对应着INET Socket层的inet_sendmsg()函数.
int inet_sendmsg(struct socket*sock,struct msghdr*msg,int size,struct scm_cookie*scm)
inet_sendmsg()函数把在 BSD Socket层对Socket{}结构的操作转换成在INET Socket层中对Sock{}结构的操作,该转换是通过struct sock*sk=sock->sk完成的.最后调用Sock{}结构的成员变量prot的sendmsg函数发送数据,该功能通过return sk- > prot- >sendmsg(sk,msg,size)来完成的.成员变量prot已经被初始化成了tcp_prot,所以sk->prot->sendmsg()对应的函数就是
tcp_sendmsg().tcp_sendmsg()函数中大部分是关于对TCP协议的处理,这里假
设这些处理的条件都满足.这个函数主要是把Msghdr{}结构中的数据转入到Sk_buff{}结构中,最后调用tcp_send_skb(sk,skb,queue_it,mss_now)函数继续数据包的传送.至此,对BSD Socket层的Msghdr{}结构和Socket{}结构的操作已经完全转变成了对Sk_buff{}结构和Sock{}结构的操作.tcp_send_skb()函数把数据加入发送队列,如果需要发送,就调用tcp_transmit_skb()进行发
送.tcp_transmit_skb()函数首先给Sk_buff中要传送的数据添加TCP头,然后调用函数指针tp->af_specific->queue_xmit(skb)将数据发送出去.这里的tp->af_specific已经被初始化成了ipv4_specific,queue_xmit()函数对应着ip_queue_xmit()函数.
从函数int ip_queue_xmit(struct sk_buff*skb)开始,所传送的数据到达IP层,该函数主要为发送的数据包寻找最佳路径,并加入IP头,最后调用
ip_queue_xmit2继续数据的发送.ip_queue_xmit2()函数根据路由过程得到发送数据的网络设备接口,增加IP头的一些信息,调用skb->dst->output()函数继续数据的发送.在这里,output函数已经被初始化为ip_output()函数.ip_output()函数调用ip_finish_output()继续数据的发送.在ip_finish_output()函数中把需要通过的网络接口存放在skb->dev中,初始化 skb->protocol为ETH_P_IP,调用 ip_finish_output2(),继续数据的发送.在ip_finish_output2()函数中,如果下一个发送目标地址dst_entry的成员变量neighbour不为空,调用该成员变量的传输函数,也就是dst->neighbour->output(),该函数指针对应于
neigh_resolve_output()函数.neigh_resolve_output()函数是在IP层数据包经过的最后一个函数,在该函数中找到管理这次发送的Neighbour{}数据结构,并调用neigh->ops->queue_xmit()函数指针,该函数指针已经被初始化成了dev_queue_xmit()函数.
从函数int dev_queue_xmit(struct sk_buff*skb)开始,从这个函数开始,发送数
据从IP层进入了驱动层.首先获取用来发送数据的网卡接口设备,然后调用该设备函数指针dev->hard_start_xmit()发送数据.对AX88796这类ne兼容网卡,该函数指针对应着ei_start_xmit()函数.在ei_start_xmit()函数中通过out_p(0x00,e8390_base+EN0_IMR)将8390芯片的所有中断都屏蔽掉,然后调用ei_local->block_output函数指针将数据存放到发送缓冲区中,block->output函数指针对应着ne_block_output()函数.在 ne_block_output()函数中,如果发送模式是字模式,并且发送的数据长度是奇数个字节,就把发送的数据长度加1.把报文写入发送缓冲区需要以下操作:把发送缓冲区的首地址写入远程起始地址寄存器对(RSAR0,RSAR1);把将要传送报文的长度写入远程字节计数寄存器(RBCR0,RBCR1);启动一个远程DMA写操作.代码如下:
把报文写入发送缓冲区后根据字模式还是字节模式,有选择的调用outsw()和outsb()函数.最后,调用NS8390_trigger_send()函数把数据从发送缓冲区送人MAC层.在NS8390_trigger_send()函数中把数据从发送缓冲区送入MAC层需要以下操作:把发送缓冲区的首地址写入传送页面开始寄存器(TPSR);把要传送的报文长度写入传送字节寄存器(TBCR0,1);启动发送命令.代码如下: 至此,完成了基于AX88796局域网卡的从应用层到物理层的数据发送.
数据接收时,传输数据在各层中的数据格式和数据流程和数据发送时差别不大,现在主要介绍驱动层的数据接收格式和数据流程.
当网络上的数据到达 AX88796网卡时,AX88796控制器产生中断,调用该中断的中断处理程序ei_interrupt().在这个函数中主要通过判断中断状态寄存器(ISR)的内容,调用相应的处理程序来接收数据.如果中断状态寄存器显示接收到数据或者接收到有错误的数据时,就调用ei_receive()函数进行数据的接收.
在ei_receive()函数中要正确设定边界指针寄存器(BNRY)和当前页寄存器(CPR)的内容.当BNRY或者CPR等于页停止寄存器(PSTOP)时,把它们的内容设置成页开
始寄存器(PSTART).当CPR=BNRY时,表示缓冲区全部被存满,数据没有被用户读走,这时网卡将停止往内存写数据,新收到的数据包将被丢弃不要,而不覆盖旧的数据;当CPR=BNRY+1时,表示网卡的接收缓冲区里没有数据,用户通过这个判断知道没有包可以读.当CPR!=BNRY+1时,表示接收到新的数据包,用户应该读取数据包,直到上述条件成立时,表示所有数据包已经读完,此时停止读取数据包.当申请Sk_buff{}数据结构成功时,调用宏ei_block_input(),将数据接收到sk_buff{}数据结构中去,该宏对应着ne_block_input()函数.最后调用函数netif_rx()将数据发送到数据接收队列中,将数据依次通过各层传输,最后交给应用层,完成数据的接收.
在ne_block_input中,从接收缓冲区读出数据需要以下几步操作:把接收缓冲区首地址写入远程起始地址寄存器对(RSAR0,RSAR1);把要传送报文的长度写入远程字节计数寄存器(RBCR0,RBCR1);启动一个远程DMA读操作.代码如下: 完成以上设置以后,根据是字模式还是字节模式,有选择的调用insw()和insb()函数,把得到的数据存放在buf[]数组中.在字模式时,如果接收的字节为奇数个,最后一个字节的接收应调用inb()获得.
在Linux操作系统中,如果把所有的功能组件和驱动程序都加入Linux内核,Linux内核就会过于庞大,影响系统的性能和稳定性;另外,如果每增加一个新的驱动,都要重新的配置和编译内核,那将是一个噩梦.鉴于此,Linux内核提出模块的概念,模块机制让Linux内核更加的紧凑,让内核开发更加的方便.当你需要安装驱动和增加新的功能模块,不需要重新编译内核,只需要编译要增加的模块,把它插入内核即可.
运用insmod命令把编译好的AX88796驱动模块加入内核.当AX88796驱动模块加入到内核以后,就调用命令ifconfig设置开发板的IP地址(开发板的IP地址和宿主机的IP地址应在同一个网段中),设置完成后,在开发板上运用ping命令向
宿主机发送信息,能够正确收发数据(如图2所示),网卡被驱动.
熟悉嵌入式Linux网络通信中通信数据在各层的数据流向和数据格式,建立起清晰的嵌入式网络通信脉络.是开发出高质量的网卡驱动程序和应用程序的必要条件,是对网络内核移植开发人员的基本要求.在物联网的建设中,一个好的网络通信系统也是物联网建设的基本条件.
【相关文献】
[1]李善平,刘文峰,李程远,王焕龙,王伟波.Linux内核2.4版源代码分析大全[M].北京:机械工业出版社,2002.
[2]Alessandro Rubini,Jonathan Corbet.Linux设备驱动程序[M].2版.北京:中国电力出版社,2002.
[3]毛德操,胡希明.Linux内核源代码情景分析[M].杭州:浙江大学出版社,2001. [4]Daniel P.Bovet& Marco Cesati.深入理解Linux内核[M].陈莉君,张琼声,张宏伟,译.3版.北京:中国电力出版社,2001.
[5]邹思轶.嵌入式Linux的设计与应用[M].北京:清华大学出版社,2002.
[6]李善平,陈文智.边干边学:LINUX内核指导[M].杭州:浙江大学出版社,2002. [7]Jean J Labrosse.嵌入式实时操作系统 uc/08-Ⅱ[M].邵贝贝,译.2版.北京:北京航空航天大学出版社,2003.
因篇幅问题不能全部显示,请点此查看更多更全内容