嵌入式Linux网络通信程序开发
赵礼栋;陈娜
【摘 要】基于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)
嵌入式linux开发书籍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()函数.
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论