uip协议栈uip_process函数工作流程详解
uip协议栈,下载

竭诚为您提供优质文档/双击可除uip协议栈,下载篇一:uip之udp应用笔记千兆网项目中,移植了uip到mcu中,采用udp通信方式,主要用来做一些控制协议的处理。
刚开始接手的时候,并没有做过网络方面的应用,而且对tcp/ip及udp通信又不太熟悉。
好在网上有一些文档,加上仔细阅读uip_process 代码,一边用抓包软件一边调试,总算把uip很好的应用了起来,而且还针对项目某些应用的特殊性,对uip源码进行了一些修改。
本文前半部分对uip源码的一些重要函数进行介绍,后半部分将对修改的部分做个记录,以备往后查阅。
本次使用的是uip-1.0,抓包软件用的wireshark1.6.7,这个软件真的很不错,居然支持gigevision,这点真的很意外。
一、一个完整的udp数据报文格式其实uip就是将你要发送到网络上的数据加上报头,好让它被成功发送到目的主机。
所以我们要先搞清楚一个完整的数据报文,才能搞清楚uip到底在做些什么。
ethernetheader:由目标mac和本机mac及type组成,共14byte,当目标mac全为ff时,表示是udp广播。
type=0x0800表示是ip。
在uip中,ethernetheader结构体定义如下:ipheader:0x45表示version=4,headerlength=20byte;0028表示ipheader+udpheader+userdata长度为40byte;6c14为包的id,每发一个包,这个id会自加1。
80的意义是timetolive,表示这个包的存活时间,路由每转发一次,就会对它自减1。
17表示通信协议类型为udp,4a0a为ipheader的校验码。
再后面就是源ip和目的ip地址了。
udpheader:0aaa表示srcport为2730;0f74表示dstprot为3956;14表示udpheader+userdata长度为20byte,c477表示udpheader的校验码,在一般的情况下,这个可以为0。
uip

uIP的ARP协议代码分析之一ARP请求对于一个设备用的ARP协议而言,重要的东西包括三方面:1.一个本地的IP与MAC地址的缓存表.以有对应的更新和查询操作.2.一个发送ARP请求的函数.3.一个处理ARP回复的函数.下面我们来看uIP中是如何实现的(代码见uip_arp.c:首先,定义一个缓存表的数据结构,99行起:struct arp_entry {u16_t ipaddr[2];struct uip_eth_addr ethaddr;u8_t time;};只有三个项,很简单第一项是ip地址,16*2=4*8位的,保存四个八位组.第二项是MAC地址.第三项是缓存更新时间.下来是ARP请求发送函数:/*-----------------------------------------------------------------------------------*//*** Prepend Ethernet header to an outbound IP packet and see if we need* to send out an ARP request.*为传出的IP包添加以太网头并看是否需要发送ARP请求.* This function should be called before sending out an IP packet. The* function checks the destination IP address of the IP packet to see* what Ethernet MAC address that should be used as a destination MAC* address on the Ethernet.*此函数应该在发送IP包时调用,它会检查IP包的目的IP地址,看看以太网应该使用什么目的MAC地址.* If the destination IP address is in the local network (determined* by logical ANDing of netmask and our IP address), the function* checks the ARP cache to see if an entry for the destination IP* address is found. If so, an Ethernet header is prepended and the* function returns. If no ARP cache entry is found for the* destination IP address, the packet in the uip_buf[] is replaced by* an ARP request packet for the IP address. The IP packet is dropped* and it is assumed that they higher level protocols (e.g., TCP)* eventually will retransmit the dropped packet.*如果目的IP地址是在局域网中(由IP地址与子网掩码的与逻辑决定),函数就会从ARP缓存表中查找有*无对应项.若有,就取对应的MAC地址,加上以太网头,并返回,否则uip_buf[]中的数据包会被替换成一个*目的IP在址的ARP请求.原来的IP包会被简单的仍掉,此函数假设高层协议(如TCP)会最终重传扔掉的包.* If the destination IP address is not on the local network, the IP* address of the default router is used instead.*如果目标IP地址并非一个局域网IP,则会使用默认路由的IP地址.* When the function returns, a packet is present in the uip_buf[]* buffer, and the length of the packet is in the global variable* uip_len.函数返回时,uip_buf[]中已经有了一个包,其长度由uip_len指定.*//*-----------------------------------------------------------------------------------*/voiduip_arp_out(void){struct arp_entry *tabptr;/* Find the destination IP address in the ARP table and constructthe Ethernet header. If the destination IP addres isn't on thelocal network, we use the default router's IP address instead.//在ARP表中找到目的IP地址,构成以太网头.如果目的IP地址不在局域网中,则使用默认路由的IP.If not ARP table entry is found, we overwrite the original IPpacket with an ARP request for the IP address. *///如果ARP表中找不到,则将原来的IP包替换成一个ARP请求./* First check if destination is a local broadcast. 首先检查目标是不是广播*/if(uip_ipaddr_cmp(IPBUF->destipaddr, broadcast_ipaddr)) {memcpy(IPBUF->ethhdr.dest.addr, broadcast_ethaddr.addr, 6);} else {/* Check if the destination address is on the local network. 检查目标地址是否在局域网内 */if(!uip_ipaddr_maskcmp(IPBUF->destipaddr, uip_hostaddr, uip_netmask)) {/* Destination address was not on the local network, so we need touse the default router's IP address instead of the destinationaddress when determining the MAC address. 目的地址不在局域网内,所以保用默认路由器的地址来确在MAC地址*/uip_ipaddr_copy(ipaddr, uip_draddr);} else {/* Else, we use the destination IP address. 否则,使用目标IP地址*/uip_ipaddr_copy(ipaddr, IPBUF->destipaddr);}for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {//这里遍历表,对比目的IP与ARP缓存表中的IP.tabptr = &arp_table;if(uip_ipaddr_cmp(ipaddr, tabptr->ipaddr)) {break;}}if(i == UIP_ARPTAB_SIZE) {/* The destination address was not in our ARP table, so weoverwrite the IP packet with an ARP request. 如果遍历到头没找到,将原IP包替换为ARP请求并返回*/memset(BUF->ethhdr.dest.addr, 0xff, 6);memset(BUF->dhwaddr.addr, 0x00, 6);memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);uip_ipaddr_copy(BUF->dipaddr, ipaddr);uip_ipaddr_copy(BUF->sipaddr, uip_hostaddr);BUF->opcode = HTONS(ARP_REQUEST); /* ARP request. */BUF->hwtype = HTONS(ARP_HWTYPE_ETH);BUF->protocol = HTONS(UIP_ETHTYPE_IP);BUF->hwlen = 6;BUF->protolen = 4;BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP);uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN];uip_len = sizeof(struct arp_hdr);return;}/* Build an ethernet header. 如果是在局域网中,且在ARP缓存中找到了(如果没找到进行不到这一步,在上面就返回了),则构建以太网头*/memcpy(IPBUF->ethhdr.dest.addr, tabptr->ethaddr.addr, 6);}memcpy(IPBUF->ethhdr.src.addr, uip_ethaddr.addr, 6);IPBUF->ethhdr.type = HTONS(UIP_ETHTYPE_IP);uip_len += sizeof(struct uip_eth_hdr);}以上内容自325行起.下面再总结一下其基本顺序:用IPBUF->ethhdr.dest.addr来存储目的IP地址,它有可能是局域网内一主机IP,也可能是路由器IP(如果是发往外网,即原来的目的IP不在局域网内),还有可能是广播专用的IP. 先看是不是在广播:如果是广播,将IPBUF->ethhdr.dest.addr设为广播IP.再看是不是在局域网内:如果不是,则将IPBUF->ethhdr.dest.addr设为路由器IP.如果在局域网内,查看是否已经存在于ARP缓存表中:如果不在,将要发送的换成ARP请求,返回.如果已存在,则查找使用查找到的MAC地址为IP包添加以太网头.这里还要解释一些细节问题,主要是:1.如何在IP包上添加以太网头2.如果将IP包替换成ARP请求,ARP请求的格式是什么.3.广播地址这些问题将在二楼来说.将IP包替换成ARP请求部分代码(实际上IP包是放在uip_buf[]里的,这里只是将uip_buf[]填充上ARP请求即可),于uip_arp.c的388行:/* The destination address was not in our ARP table, so weoverwrite the IP packet with an ARP request. */memset(BUF->ethhdr.dest.addr, 0xff, 6);memset(BUF->dhwaddr.addr, 0x00, 6);memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);uip_ipaddr_copy(BUF->dipaddr, ipaddr);uip_ipaddr_copy(BUF->sipaddr, uip_hostaddr);BUF->opcode = HTONS(ARP_REQUEST); /* ARP request. */BUF->hwtype = HTONS(ARP_HWTYPE_ETH);BUF->protocol = HTONS(UIP_ETHTYPE_IP);BUF->hwlen = 6;BUF->protolen = 4;BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP);uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN];uip_len = sizeof(struct arp_hdr);return;首先解释这里的BUF(于uip_arp.c的116行):#define BUF ((struct arp_hdr *)&uip_buf[0])可见这里的BUF就是uip_buf[],只不过这里将它取做一个struct arp_hdr的结构体:struct arp_hdr {struct uip_eth_hdr ethhdr;u16_t hwtype; //硬件类型u16_t protocol; //协议类型u8_t hwlen;u8_t protolen;u16_t opcode; //操作码struct uip_eth_addr shwaddr; //源以太网地址u16_t sipaddr[2]; //源IP地址struct uip_eth_addr dhwaddr; //目的以太网地址u16_t dipaddr[2]; //目的IP地址};struct uip_eth_hdr {struct uip_eth_addr dest;struct uip_eth_addr src;u16_t type;};这是arp_hdr的第一个成员ethhdr的类型定义,对应图片中的前三项:6+6+2,目的以太网地址,源以太网地址,2字节数据类型(ARP请求和应答为0x0806).struct arp_hdr的第二个成员u16_t hwtype,对应图片中第三项,2字节硬件类型(值为1表示以太网).struct arp_hdr的第三个成员u16_t protocol,对应图片中第四项,2字节要映射的协议地址类型(ip地址为0x0800).struct arp_hdr的第四个成员u8_t hwlen,对应图片中第五项,1字节硬件地址长度(对MAC地址来说为6).struct arp_hdr的第五个成员u8_t protolen,对应图片中第六项,1字节协议地址长度(对IP地址来说为4).struct arp_hdr的第六个成员u16_t opcode,对应图片中第七项,2字节操作码(1 ARP请求,2 ARP应答,3 RARP请求,4 RARP应答,必须字段).struct arp_hdr的第七个成员struct uip_eth_addr shwaddr,对应图片中第八项,6字节源以太网地址.struct arp_hdr的第八个成员u16_t sipaddr[2];,对应图片中第九项,2字节源IP地址.struct arp_hdr的第九个成员struct uip_eth_addr dhwaddr,对应图片中第十项,6字节目标以太网地址.struct arp_hdr的第十个成员u16_t dipaddr[2];,对应图片中第十一项,2字节目标IP地址.上面绿色的表示已经详解的,红字的表示要进一步说明的.这就是一个ARP帧的结构,可以看到,里面的源和目的以太网地址都是重复的.我们再看函数中的代码:memset(BUF->ethhdr.dest.addr, 0xff, 6);memset(BUF->dhwaddr.addr, 0x00, 6);memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);这里四个memset,重复的源和目的以太网地址一起设置了,四个memset对应图片中的1,10,2,8项.但是:对1和10两项,都是源以太网地址,但置的值是不同的,分别为0xff*6和0x00*6.为什么会这样呢?因为他们的用处不一样,见:【相关资料】ARP分组格式(帧格式,报文格式)6+6–以太网的源地址和目的地址,目的地址为全1的为广播地址注意这里说,目的地址为全为1的广播地址.什么意思?当你发送一个ARP请求是,你是想知道一个IP对应的以太网地址(即MAC地址),但是你现在不知道目的主机的以太网地址,你问谁啊?不知道问谁,这种情况下,只能是广播一下了,0xff*6就是广播地址.从图片中可以看到,ARP包是分成两部分的,前6+6+2叫做"以太网首部",它的意义就是"分组是从谁(源地址)发给谁(目的地址)的什么类型(帧类型,请求和应答为0x0806)",第二部分则是内容.来看这个例子:请求帧如下(为了清晰在每行的前面加了字节计数,每行16个字节):以太网首部(14字节)0000: ff ff ff ff ff ff 00 05 5d 61 58 a8 08 06ARP帧(28字节)0000: 00 010010: 08 00 06 04 00 01 00 05 5d 61 58 a8 c0 a8 00 370020: 00 00 00 00 00 00 c0 a8 00 02填充位(18字节)0020: 00 77 31 d2 50 100030: fd 78 41 d3 00 00 00 00 00 00 00 00以太网首部:目的主机采用广播地址,源主机的MAC地址是00:05:5d:61:58:a8,上层协议类型0x0806表示ARP。
UIP中文文档第二 uIP初始化函数

1. void uip_init(void)此函数用于在启动时初始化uIP的TCP/IP栈。
应用示例:example-mainloop-with-arp.c, and example-mainloop-without-arp.c.定义于uip.c的379行。
2. void uip_setipid(u16_t id)此函数用于启动时设置初始的ip_id.此函数定义于uip.c的181行。
1. void uip_init(void)代码分析1.void2.uip_init(void)3.{4.for(c = 0; c < UIP_LISTENPORTS; ++c) {5.uip_listenports[c] = 0;6.} //将uip_listenports数组全部清零7.for(c = 0; c < UIP_CONNS; ++c) {8.uip_conns[c].tcpstateflags = UIP_CLOSED;9.} //将所有连接的tcpstateflags状态标志设为关闭,表示此连接为关闭状态。
10.#if UIP_ACTIVE_OPENstport = 1024;12.#endif /* UIP_ACTIVE_OPEN */13.//上面的段不知什么意思。
14.#if UIP_UDP15.for(c = 0; c < UIP_UDP_CONNS; ++c) {16.uip_udp_conns[c].lport = 0;17.}18.#endif /* UIP_UDP *///如果定义了UIP_UDP则将uip_udp_conns的lport清零。
19.20.21./* IPv4 initialization. */22.#if UIP_FIXEDADDR == 023./* uip_hostaddr[0] = uip_hostaddr[1] = 0;*/24.#endif /* UIP_FIXEDADDR *///如果主机地址要为固定的,在上面这里赋值。
osip工作原理和过程

OSIP工作原理和工作过程雷/wcl0715感谢OSIP代码整理小组的工作。
一、概述:首先说明一个概念:OSIP是一个开原的标准C的sip 3261的CORE,实际上是一个SIP 的信令实现,从另一个角度说,它是SIP的一个信令解释器,任务是负责生成和解析SIP信令,仅此而已,其它的事情,比如收包,发包,建立RTP流的过程等等和OSIP没有任何必然关系。
理论上OSIP可以应用在任何可以编译C语言的系统上。
二、工作原理OSIP实现的核心是状态机,为了便于保持逻辑的清晰和代码模块化的实现,OSIP分成两对状态机,分别用来处理正常的CALL流程和其它非CALL流程,对应的每对状态机又分成out和in两个状态,因此OSIP共有四个状态机。
详细的状态机部分文挡请参考OSIP 的状态机分析,在我们的资源连接里你可以找到它,你也可以登陆我的BLOG来寻找它。
对不同的状态OSIP相应的用不同的状态机处理,在这些状态机下,OSIP对本身或者来自对方的消息进行处理,从而推动自身状态的改变,这也就是OSIP的核心工作原理。
和其它的SIP协议栈的实现一样,OSIP采用CALLBACK函数的方法来对用户程序提供接口,当系统有事件发生,或者状态改变的时候,OSIP调用这些CALLBACK函数,来完成用户的要求(比如收到对方180消息后,本地要响铃,实现方法就是在OSIP的收到180消息的CALLBACK函数里实现响铃代码),因此OSIP用户需要自己编写这些CALLBACK 函数,实现自己所需要的功能,然后在系统初始化过程中,和系统callback函数挂接,这样当系统调用CALLBACK的时候就会执行你的函数,这也就是OSIP初始化的时候,要设定一大堆CALLBACK函数的原因,在接触协议初期,也许你觉得烦琐,但尽可能多的让用户能对事件进行处理,才能保证协议栈的可用性这样,这在系统越来越复杂的情况下,或者应用比较复杂的情况下,尤为重要。
uip_process流程

uip_process(u8_t flag)(1)if(flag ==UIP_UDP_SEND_CONN),若是则goto udp_send;不是则向下执行;(2)if(flag == UIP_POLL_REQUEST){if(tcpstateflags== UIP_ESTABLISHED &&!uip_outstanding(uip_connr))如果处于稳定连接状态且没有数据在缓存中等待确认则:{①uip_flags = UIP_POLL;②UIP_APPCALL();③goto appsend;}goto drop;}else if(flag == UIP_TIMER){uip_len = 0;uip_slen = 0;如果连接处于等待超时关闭状态则增加超时计数器,如果到达超时期限则关闭当前连接tcpstateflags = UIP_CLOSED;if(tcpstateflags != UIP_CLOSED) 如果连接不处于关闭状态{if(uip_outstanding(uip_connr)&&(timer-- == 0)) 已经发送的数据包还未接收到对其的ACK,超时计数器减一且超时计数器值为0 {①如果到达所设定的重发次数则:1、tcpstateflags = UIP_CLOSED;关闭当前连接2、uip_flags = UIP_TIMEDOUT;通知应用程序超时;3、UIP_APPCALL();4、设置RST+ACK终止连接标志5、goto tcp_send_nodata;②没有到达设定的重发次数则重传数据:1、重置重传计数器2、 switch(tcpstateflags)根据连接处的不同状态重发不同的数据包case UIP_SYN_RCVD:goto tcp_send_synack;重新发送先前发送的SYN+ACKcase UIP_SYN_SENT:goto tcp_send_syn;重发SYN请求连接case UIP_ESTABLISHED:uip_flags = UIP_REXMIT;UIP_APPCALL(); 调用上层应用程序,通知重新生成数据重发goto apprexmit;进入重发阶段case UIP_FIN_WAIT_1:case UIP_CLOSING:case UIP_LAST_ACK:goto tcp_send_finack;重发FIN+ACK关闭连接}else if(tcpstateflags) == UIP_ESTABLISHED) 处于稳定连接状态且上次发送的数据接收到正确的ACK,可以继续发送新数据{①uip_flags = UIP_POLL;询问应用程序是否有数据要发送②UIP_APPCALL();调用应用程序产生数据③goto appsend;发送数据}}goto drop;}if(flag == UIP_UDP_TIMER){当前连接的本地端口不为0则{①uip_len = uip_slen = 0;②uip_flags = UIP_POLL;询问应用程序是否有数据要发送③UIP_UDP_APPCALL();调用应用程序产生数据④goto udp_send;}本地端口为0,表明没有建立DUP连接,则{goto drop;}}(3)检查IP帧头中的IP版本及IP头长度是否符合要求:①不符合:goto drop;丢弃此包②符合继续向下执行(4)检查目的IP地址是否为本机地址:①不是,goto drop;丢弃此包②是,向下继续执行(5)if(BUF->proto == UIP_PROTO_TCP)IP上层协议是否为TCP协议①是,goto tcp_input;进入TCP数据处理模块②不是,继续向下执行(6)if(BUF->proto == UIP_PROTO_UDP)IP上层协议是否为UDP协议①是,goto udp_input;进入UDP数据处理模块②不是,继续向下执行(7)if(BUF->proto != UIP_PROTO_ICMP) 不是TCP不是UDP也不是ICMP协议①goto drop;本机只处理UDP、TCP、ICMP数据包,其它包都将丢弃(8)运行到此处,表明接收到的是ICMP数据包,继续向下执行;———————————————————————————————————————icmp_input:此处为ICMP数据包处理部分,比较简单不做详解。
uip协议栈

uIP协议栈分析uIP特性uIP协议栈往掉了完整的TCP/IP中不常用的功能,简化了通讯流程,但保存了网络通讯必须使用的协议,设计重点放在了IP/TCP/ICMP/UDP/ARP这些网络层和传输层协议上,保证了其代码的通用性和结构的稳定性。
由于uIP协议栈专门为嵌进式系统而设计,因此还具有如下优越功能:(1)代码非常少,其协议栈代码不到6K,很方便阅读和移植。
(2)占用的内存数非常少,RAM占用仅几百字节。
(3)其硬件处理层、协议栈层和应用层共用一个全局缓存区,不存在数据的拷贝,且发送和接收都是依靠这个缓存区,极大的节省空间和时间。
(4)支持多个主动连接和被动连接并发。
(5)其源代码中提供一套实例程序:web服务器,web客户端,电子邮件发送程序(SMTP 客户端),Telnet服务器,DNS主机名解析程序等。
通用性强,移植起来基本不用修改就可以通过。
(6)对数据的处理采用轮循机制,不需要操纵系统的支持。
由于uIP对资源的需求少和移植轻易,大部分的8位微控制器都使用过uIP协议栈, 而且很多的著名的嵌进式产品和项目(如卫星,Cisco路由器,无线传感器网络)中都在使用uIP协议栈。
uIP架构uIP相当于一个代码库,通过一系列的函数实现与底层硬件和高层应用程序的通讯,对于整个系统来说它内部的协议组是透明的,从而增加了协议的通用性。
uIP协议栈与系统底层和高层应用之间的关系如图2-1所示。
从上图可以看出,uIP协议栈主要提供了三个函数供系统底层调用。
即uip_init(), uip_input() 和uip_periodic()。
其与应用程序的主要接口是UIP_APPCALL( )。
uip_init()是系统初始化时调用的,主要初始化协议栈的侦听端口和默认所有连接是封闭的。
当网卡驱动收到一个输进包时,将放进全局缓冲区uip_buf中,包的大小由全局变量uip_len约束。
同时将调用uip_input()函数,这个函数将会根据包首部的协议处理这个包和需要时调用应用程序。
processing的if和else函数 -回复

processing的if和else函数-回复Processing是一种强大的编程语言和开发环境,广泛应用于艺术、设计和交互媒体等领域。
在Processing中,if和else函数是用来控制程序流程的重要工具。
本文将一步一步解释如何使用if和else函数在Processing 中实现条件分支。
首先,我们需要了解if语句的基本语法和用法。
if语句用于判断某个条件是否为真,如果条件为真,则执行if代码块中的语句,否则跳过if代码块。
其基本语法结构如下:if (condition) {if代码块执行此处的语句}其中,`condition`代表一个逻辑表达式,当其结果为真时,执行if代码块中的语句。
如果我们想要执行多个语句,可以使用花括号`{}` 来包围多条语句。
下面是一个简单的例子,演示了如何使用if语句来判断某个变量是否大于10,并在控制台输出不同的信息:int num = 15; 初始化一个变量num为15if (num > 10) {如果num大于10print("变量num大于10"); 输出信息}在上述例子中,由于变量`num`的值为15,所以条件`num > 10`为真,因此if代码块中的语句被执行,控制台将输出"变量num大于10"。
如果我们把变量`num`的值改为8,则控制台不会输出任何信息,因为条件`num > 10`为假。
接下来,我们可以介绍else语句的使用。
else语句紧跟在if语句之后,它用于在条件不满足的情况下执行一组语句。
基本语法如下:if (condition) {if代码块执行此处的语句} else {else代码块执行此处的语句}当if条件满足时,执行if代码块中的语句;当if条件不满足时,执行else 代码块中的语句。
以下是一个例子,演示了如何使用if-else语句来判断一个数是奇数还是偶数:int num = 7; 初始化一个变量num为7if (num 2 == 0) {如果num是偶数print("变量num是偶数");} else {如果num是奇数print("变量num是奇数");}在上述例子中,由于变量`num`除以2的余数为1,表明它是一个奇数。
IRP原理以及派遣函数基本工作流程

I/O Request Packet(IRP)IRP基本数据结构:IRP是由I/O管理器发出的,I/O管理器是用户态与内核态之间的桥梁,当用户态进程发出I/O请求时,I/O管理器就捕获这些请求,将其转换为IRP请求,发送给驱动程序。
I/O 管理器无疑是非常重要的,具有核心地位。
它负责所有I/O请求的调度和管理工作,根据请求的不同内容,选择相应的驱动程序对象,设备对象,并生成、发送、释放各种不同的IRP。
整个I/O处理流程是在它的指挥下完成的。
一个IRP是从非分页内存中分配的可变大小的结构,它包括两部分:IRP首部和辅助请求参数数组,如图1所示。
这两部分都是由I/O管理器建立的。
图1IRP简单结构图IRP首部中包含了指向IRP输入输出缓冲区指针、当前拥有IRP的驱动指针等。
紧接着首部的是一个IO_STACK_LOCATION结构的数组。
它的大小由设备栈中的设备数确定。
IO_STACK_LOCATION结构中保存了一个I/O请求的参数及代码、请求当前对应的设备指针、完成函数指针(IoCompletion)等。
IRP运行流程:操作系统用设备对象(device object)表示物理设备,每一个物理设备都有一个或多个设备对象与之相关联,设备对象提供了在设备上的所有操作。
也有一些设备对象并不表示物理设备。
一个唯软件驱动程序(software-only driver,处理I/O请求,但是不把这些请求传递给硬件)也必须创建表示它的操作的设备对象。
设备常常由多个设备对象所表示,每一个设备对象对应一个驱动程序来管理设备的I/O 请求。
一个设备的所有设备对象被组织成一个设备栈(device stack)。
而且,IO_STACK_LOCATION数组中的每个元素和设备栈中的每个设备是一一对应的,一般情况下,只允许层次结构中的每个设备对象访问它自己对应的IO_STACK_LOCATION。
无论何时,一个请求操作都在一个设备上被完成,I/O管理器把IRP请求传递给设备栈中顶部设备的驱动程序(IRP是传递给设备对象的,通过设备对象的DriverObject成员找到驱动程序)。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
uip_len = 0; uip_slen = 0; 如果连接处于等待超时关闭状态则增加超时计数器,如果到达超时期限则关闭当前连 接 tcpstateflags = UIP_CLOSED; if(tcpstateflags != UIP_CLOSED) 如果连接不处于关闭状态 {
case UIP_ESTABLISHED: (1)接收到远方主机的 FIN 请求:
① uip_flags |= UIP_CLOSE;关闭 TCP 连接 ②如果接收到的数据包中还包含有数据则 uip_flags |= UIP_NEWDATA; ③调用 UIP_APPCALL()处理刚关闭的连接和新接收到数据; ④发送 TCP_FIN +TCP_ACK,关闭连接; (2)如果接收到的数据状态为 UIP_NEWDATA | UIP_ACKDATA 则: ①调用 UIP_APPCALL();处理接收到的包; ② appsend: (1)如果(uip_flags & UIP_ABORT)终止连接则
①tcpstateflags = UIP_CLOSED;将连接置为关闭状态 ②uip_flags = UIP_CLOSE;调用 UIP_APPCALL();通知应用程序连接已经断开;
case UIP_FIN_WAIT_1: (1)此时本机已经关闭连接等待对方关闭连接,如果接收到数据并不处理,仅仅将接收到数 据包数目加一; (2)如果接收到 FIN 请求:
reset: (1)接收到的是 RST 断开连接包,则直接丢包,返回; (2)设置 RST+ACK 标志,填充适当的 TCP 帧头; (3)goto tcp_send_noconn;发送 TCP 数据包;
found_listen: (1)从链接列表中找出一个空链接或剩余生存时间最短的连接; (2)将找到的链接列表根据接收到的 TCP 数据包进行初始化; (3)设置 TCP 状态为 UIP_SYN_RCVD; (4)向下执行,发送 ACK tcp_send_synack: (1)设置 ACK 标志 (2)向下执行 tcp_send_syn: (1)设置 SYN 标志 (2)填充 TCP 选项中最大报文段长度 MSS (3) goto tcp_send;
———————————————————————————————————————
tcp_input: (1)检查 TCP 校验和,若正确向下继续,若错误则丢弃此包直接返回; (2)在 TCP 连接列表 uip_conns 中轮询,检查接收到的 TCP 数据包是否已经建立连接(通过逐 个比较源端口、目的端口和源 IP 是否与链接列表中的相同)。
(3)检查 IP 帧头中的 IP 版本及 IP 头长度是否符合要求: ①不符合:goto drop;丢弃此包 ②符合继续向下执行
(4)检查目的 IP 地址是否为本机地址: ①不是,goto drop;丢弃此包 ②是,向下继续执行
(5)if(BUF->proto == UIP_PROTO_TCP)IP 上层协议是否为 TCP 协议 ①是,goto tcp_input;进入 TCP 数据处理模块 ②不是,继续向下执行
——————————————————————————————————————— udp_input: (1)根据要求校验 UDP 数据 (2)在 UDP 连接列表中寻找接收到的数据包是否属于列表中的连接,若是则 goto udp_found;
如果不是则 goto drop;
udp_found: (1)接收到数据数设置 uip_flags = UIP_NEWDATA; 将 uip_sappdata ,uip_appdata 指向接收 到的 UDP 包的数据部分。 (2)调用 UIP_UDP_APPCALL();使应用程序处理接收到的数据; (3)继续向下执行 udp_send: (1)如果 uip_slen == 0 表明没有数据要发送,则直接 goto drop; (2)计算 UDP 数据包长度,填充 UDP、IP 帧头中的数据长度及相关选项; (3)根据要求计算校验和; (4)goto ip_send_nolen;发送 UDP 数据包;
case UIP_SYN_RCVD: goto tcp_send_synack;重新发送先前发送的 SYN+ACK
case UIP_SYN_SENT: goto tcp_send_syn;重发 SYN 请求连接
case UIP_ESTABLISHED: uip_flags = UIP_REXMIT; UIP_APPCALL(); 调用上层应用程序,通知重新生成数据重发 goto apprexmit;进入重发阶段
found: (1)若接收到的是 RST 数据包,则将本连接状态置为 UIP_CLOSED,uip_flags = UIP_ABORT;, 调用 UIP_APPCALL()通知应用程序处理连接断开请求。然后丢弃此包,直接返回; (2)检查接收到的数据包中的数据编号是否为自己等在等待的数据编号,若不是则 goto tcp_send_ack;发送自己期望的数据编号的数据,即请求重传。若是则继续向下; (3)检查接收到的数据包中是否包含 ACK,
case UIP_FIN_WAIT_1: case UIP_CLOSING:
case UIP_LAST_ACK: goto tcp_send_finack;重发 FIN+ACK 关闭连接 } else if(tcpstateflags) == UIP_ESTABLISHED) 处于稳定连接状态且上次发送的数据
case UIP_SYN_SENT: (1)如果接收到 ACK 且为 SYN+ACK 则:
①检查 TCP 扩展选项,如果有扩展选项从中取出 MSS 信息; ②tcpstateflags = UIP_ESTABLISHED;进入 ESTABLISHED 状态 ③设置接收编号,uip_flags = UIP_CONNECTED | UIP_NEWDATA;调用 UIP_APPCALL()处 理刚建立的连接和新接收到数据; ④goto appsend; (2)没有接收到 ACK 且为 SYN+ACK 则: ①uip_flags = UIP_ABORT;终止连接调用 UIP_APPCALL(); ②tcpstateflags = UIP_CLOSED;关闭 TCP 连接 ③goto reset;
①tcpstateflags = UIP_CLOSED;关闭 TCP 连接; ②发送 RST+ACK 关闭连接; (2)如果(uip_flags & UIP_CLOSE)正常关闭连接则: ①tcpstateflags = UIP_FIN_WAIT_1;进入等待关闭状态 ②发送 FIN+ACK 告知对方关闭连接; (3)如果 uip_slen > 0 有数据要发送则设置发送数据的长度
若找到 goto found; 没有找到则检查接收到的 TCP 数据包中是否含有 SYN 请求建立连接标志:
若没有则 goto reset;发送 RST+ACK 断开连接; 若有则检查 uip_listenports 监听列表,若 TCP 数据包目的端口在监听列表中则 goto found_listen;若不在监听列表中则向下执行,进入 reset;发送 RST+ACK 断开连接;
(6)if(BUF->proto == UIP_PROTO_UDP)IP 上层协ቤተ መጻሕፍቲ ባይዱ是否为 UDP 协议 ①是,goto udp_input;进入 UDP 数据处理模块 ②不是,继续向下执行
(7)if(BUF->proto != UIP_PROTO_ICMP) 不是 TCP 不是 UDP 也不是 ICMP 协议
apprexmit: (1)如果(uip_slen > 0 && uip_connr->len > 0)则发送 PSH_ACK 数据包; (2)如果(uip_flags & UIP_NEWDATA)仅仅是发送 ACK,没有数据要发送则发送对接收
到数据的 ACK;
(3) goto drop;
case UIP_LAST_ACK: (1)如果 uip_flags & UIP_ACKDATA 接收到对本机发送的 FIN 的 ACK 确认则:
若是则: ①更新发送数据序列的编号,使之可以发送后续数据; ②计算 RTT 时间,重新设置 RTT 时间; ③uip_flags = UIP_ACKDATA;表明接收到 ACK ④uip_connr->len = 0;表明等待 ACK 的数据长度为 0,即可以发送其它数据 ⑤继续向下;
若不是:继续向下;
①goto drop;本机只处理 UDP、TCP、ICMP 数据包,其它包都将丢弃
(8)运行到此处,表明接收到的是 ICMP 数据包,继续向下执行; ——————————————————————————————————————— icmp_input:
此处为 ICMP 数据包处理部分,比较简单不做详解。 此部分仅仅接收 ECHO 命令,若接收到别的命令,则将数据包丢弃。若接收到的是 ECHO 命令则返回包含 ECHO_REPLY 的 ICMP 数据包给远方主机,主要是用来响应 ping 命令。
接收到正确的 ACK,可以继续发送新数据 {
①uip_flags = UIP_POLL;询问应用程序是否有数据要发送 ②UIP_APPCALL();调用应用程序产生数据 ③goto appsend;发送数据 } } goto drop; } if(flag == UIP_UDP_TIMER) { 当前连接的本地端口不为 0 则 { ①uip_len = uip_slen = 0; ②uip_flags = UIP_POLL;询问应用程序是否有数据要发送 ③UIP_UDP_APPCALL();调用应用程序产生数据 ④goto udp_send; } 本地端口为 0,表明没有建立 DUP 连接,则 { goto drop; } }