Contiki OS 数据包发送流程

来源:互联网 发布:php的进程是什么 编辑:程序博客网 时间:2024/06/06 06:31

数据包发送过程


下面讨论发送数据包的流程。发数据包的过程相对复杂。用到了回调机制

仍旧是两步(如图1中所示):1、高层(应用层)传递消息事件(UDP_POLL、TCP_POLL)给tcpip_process

2、tcpip进程处理函数(即tcpip_process)依据该事件调用数据包输出函数

 

其中第二步又可分为:产生完整的数据包 和 发送数据包 两部分。下面分别说明:1、产生完整数据包  通过一系列的准备工作,包括填充数据包头等,向我们的自定义应用进程传递tcpip_event消息事件,然后应用进程通过uip_send()函数填充要发送的数据信息;2、发送数据包   tcpip_output()


 

图1:数据收发的大概流程

 

以doc/example-program.c 为例,该函数执行的事情是:周期性的广播发送hello。如图2所示:


图2:example-program.c流程

一、网络层UDP传输

PROCESS_THREAD(example_program_process, ev, data)

{

static struct uip_udp_conn *c;

PROCESS_BEGIN();

c = udp_broadcast_new(UIP_HTONS(4321), NULL); ------建立一个udp广播连接

while(1) {

etimer_set(&timer, CLOCK_SECOND); --- 设置定时器

PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&timer)); -----等待定时器到时

tcpip_poll_udp(c); ----向tcpip进程传递UDP_POLL事件消息

PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event);   ----等待回传的tcpip_event事件

uip_send("Hello", 5); ----填充要发送的数据

}

PROCESS_END();

}

首先解释:c=udp_broadcast_new(UIP_HTONS(4321), NULL);先创建UDP端口号是port=4321。

接下来从tcpip_poll_udp(c) 开始

void

tcpip_poll_udp(structuip_udp_conn*conn)

{

 process_post(&tcpip_process,UDP_POLL,conn);             // 给tcpip_process 传递消息 UDP_POLL

}

当tcpip_process 收到消息后,进行的处理如下:

static voideventhandler(process_event_tev, process_data_t data)   //core/net/tcpip.c

{

 switch(ev){

   casePROCESS_EVENT_EXITED:

   casePROCESS_EVENT_TIMER:

   caseTCP_POLL:

   case UDP_POLL:

     if(data!= NULL) {

        uip_udp_periodic_conn(data);  // 产生数据

   tcpip_ipv6_output(); //  IPv6  发送

     }

     break;

 };

}

其中uip_udp_periodic_conn 用于产生数据包tcpip_ipv6_output()用于发送

先看uip_udp_periodic_conn:

#defineuip_udp_periodic_conn(conn) do { uip_udp_conn =conn;   \

uip_process(UIP_UDP_TIMER);} while(0)

转到uip_process()。 这是uip的核心处理函数,其实数据接收和发送均有其完成

voiduip_process(u8_t flag)     \core\net\uip6.c

 if(flag== UIP_UDP_TIMER) {

   if(uip_udp_conn->lport!= 0) {

     uip_conn= NULL;

     uip_sappdata= uip_appdata = &uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN];

     uip_len= uip_slen = 0;

     uip_flags = UIP_POLL;

     UIP_UDP_APPCALL();  /* 产生应用层数据 */

      goto udp_send;

   }else {

     gotodrop;

}

udp_send:

/* 填充udp的包头*/

goto ip_send_nolen;

ip_send_nolen:

/* 填充ip层包头 , 校验和等*/

Return;

}

下面看 UIP_UDP_APPCALL 是如何产生数据的:

#define  UIP_UDP_APPCALL   tcpip_uipcall

Voidtcpip_uipcall(void)            \core\net\uip6.c

{

 registeruip_udp_appstate_t *ts;

 ts =&uip_udp_conn->appstate;

 if(ts->p!= NULL) {

   process_post_synch(ts->p,tcpip_event, ts->state);       \core\net\tcpip.c

  }

}

通过process_post_synch(ts->p,tcpip_event, ts->state) 调回到udp连接相关联的进程即我们最初的进程:example_program_process,向这个进程发送消息: tcpip_event,因此可以执行uip_send("Hello", 5);

PROCESS_THREAD(example_program_process,ev,data)

staticstructuip_udp_conn *c;

PROCESS_BEGIN();

c = udp_broadcast_new(UIP_HTONS(4321), NULL); ------建立一个udp广播连接

while(1) {

etimer_set(&timer, CLOCK_SECOND); --- 设置定时器

PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&timer)); -----等待定时器到时

tcpip_poll_udp(c); ----向tcpip进程传递UDP_POLL事件消息

PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event);  ----等待回传的tcpip_event事件

uip_send("Hello", 5); ----填充要发送的数据

}

 PROCESS_END();

}

这时,example_program_process 将继续执行,调用 uip_send()

void  uip_send(const void *data, int len)     \core\net\uip.c

{

   memcpy(uip_sappdata,(data), uip_slen);

}

Uip_send 执行的功能就是把应用层的数据拷贝到数据包的对应位置,然后返回。

至此在eventhandler()函数的  uip_udp_periodic_conn(data)执行完毕,进入tcpip_ipv6_output()处理

staticvoid eventhandler(process_event_t ev, process_data_t data)  //core/net/tcpip.c

switch(ev){

   case UDP_POLL:

     if(data!= NULL) {

        uip_udp_periodic_conn(data);  // 产生数据

   tcpip_ipv6_output(); //  IPv6  发送也许有报头压缩

     }

 

voidtcpip_ipv6_output(void)    \core\net\tcpip.c

{

#ifUIP_CONF_IPV6_QUEUE_PKT

 

     if(uip_packetqueue_buflen(&nbr->packethandle)!= 0) {

     。。。。。。

       tcpip_output(&nbr->lladdr);

     }

#endif/*UIP_CONF_IPV6_QUEUE_PKT*/

}

 

至此,网络层的数据包就已经完全生成了。

 

 

二、IP层传输

下面看发包函数tcpip_output (/core/net/tcpip.c)

u8_t  tcpip_output(void)

{

 if(outputfunc!= NULL) {

   return    outputfunc();

  }

 Return  0;

}

下面转到outputfunc()。这里需要说明下,在main函数中启动了进程 uip_fw_procss,该进程声明如下:

PROCESS_THREAD(uip_fw_process,ev,data)   //  /core/net/uip_fw_drv.c

{

 PROCESS_BEGIN();

  tcpip_set_outputfunc(uip_fw_output);    即outputfunc =uip_fw_output

 PROCESS_WAIT_UNTIL(ev== PROCESS_EVENT_EXIT);

 PROCESS_END();

}

 

即:outputfunc= uip_fw_output

 

下面进入 uip_fw_output

u8_tuip_fw_output(void)      \core\net\uip-fw.c

{

 structuip_fw_netif  *netif;

 if(uip_len== 0) {

 returnUIP_FW_ZEROLEN;

 } 

  netif=find_netif();

 if(netif== NULL) {

   returnUIP_FW_NOROUTE;

  }

  Returnnetif->output();

}

该函数中先调用find_netif() 通过查路由表找到合适的网卡,然后调用该网卡的output函数。这就是IP层数据传输。

 

 

三、Mesh-under路由  (这有待于进一步验证

 

网卡在最开始的main函数中就定义过:SmeshLink\Platform\Mx2xxcc\contiki-mx2xxcc-main.c

Staticstruct uip_fw_netif meshif ={UIP_FW_NETIF(172,16,0,0,255,255,0,0, uip_over_mesh_send)};

所以,假设找到的网卡就是meshif,那么接下来就要调用uip_over_mesh_send函数。

 

uint8_tuip_over_mesh_send(void)   \core\net\Uip-over-mesh.c

{

 // uip_len = hc_compress(&uip_buf[UIP_LLH_LEN], uip_len);

 //看不懂用了什么压缩方式。这里不是用来报头压缩,别被忽悠了,其作用待研究。

 rt=route_lookup(&receiver);

 if(rt == NULL) {

。。。。。。

}else {

   route_decay(rt);

   send_data(&rt->nexthop);     //发数据到下一跳。

  }

 

接着进入send_data(&rt->nexthop)函数,看看6Lowpan是如何实现打包的。

staticvoid  send_data(rimeaddr_t *next)    \core\net\Uip-over-mesh.c

{

 PRINTF("uip-over-mesh: %d.%d: send_data with len%d\n",rimeaddr_node_addr.u8[0],rimeaddr_node_addr.u8[1],packetbuf_totlen());

  unicast_send(&dataconn,next);  //单播

}

 

再进入unicast_send(&dataconn,next)       

Intunicast_send(structunicast_conn *c, const rimeaddr_t*receiver)  \core\net\rime Unicast.c

{

 return broadcast_send(&c->c);

}

 

Intbroadcast_send(structbroadcast_conn *c)         \core\net\rimeBrocast.c

{

 packetbuf_set_addr(PACKETBUF_ADDR_SENDER,&rimeaddr_node_addr);

 return abc_send(&c->c);

}

Int abc_send(struct abc_conn*c)          \core\net\rime  Abc.c

{

 return rime_output(&c->channel);

}

 

Int  rime_output(struct channel *c)    \core\net\rime  Rime.c

{

RIMESTATS_ADD(tx);

  if(chameleon_create(c)) {

   packetbuf_compact();

  NETSTACK_MAC.send(packet_sent,c);

// NETSTACK_MAC=nullmac_driver     core\net\mac\Nullmac.c

//进入packet_sent函数有三种选择,有一种在\core\net\Sicslowpan.c中,该函数实现了报头压缩。

   return 1;

 }  }

 

 

四、6LowPan适配层

staticuint8_t  output(uip_lladdr_t *localdest)  //报头压缩函数调用体 \core\net\Sicslowpan.c

{

。。。。。。//报头压缩  支持分片。

send_packet(&dest);     //发送压缩的报文

}

 

staticvoid   send_packet(rimeaddr_t *dest) \core\net\Sicslowpan.c

{

NETSTACK_MAC.send(&packet_sent,NULL);  //据此进入Nullmac.C发送数据包

}

 

//检测邻居节点传输情况,是否可以传输

staticvoid  packet_sent(void *ptr, int status,inttransmissions)  \core\net\Sicslowpan.c

{

#if SICSLOWPAN_CONF_NEIGHBOR_INFO

  neighbor_info_packet_sent(status,transmissions);

#endif/*SICSLOWPAN_CONF_NEIGHBOR_INFO */

 if(callback != NULL) {

   callback->output_callback(status); //callback  回调函数输出数据

  }

 last_tx_status = status;

}

以上三个函数是6Lowpan适配层的接口函数。根据static void   send_packet(rimeaddr_t *dest) 函数的NETSTACK_MAC.send(&packet_sent,NULL);  已知NETSTACK_MAC=Nullmac  因此进入Nullmac.c文件的packet_sent函数,也就是进入了802.15.4 的MAC层了。

 

 

五:IEEE802.15.4  MAC 、PHY

进入nullmac 的发送函数

staticvoid  send_packet(mac_callback_t sent, void *ptr)   \core\net\Mac\nullmac.c 

{

NETSTACK_RDC.send(sent,ptr); // NETSTACK_RDC在 sicslowmac_driver \platform\mxusbstick\Contiki-conf.h

}

  

staticvoid   send_packet(mac_callback_t sent, void *ptr)

{

if(packetbuf_hdralloc(len))

{

frame802154_create(&params,packetbuf_hdrptr(),len);

ret= NETSTACK_RADIO.send(packetbuf_hdrptr(),packetbuf_totlen());// NETSTACK_RADIO  rf2xx_driver

}

NETSTACK_RADIO 网络协议的射频层使用rf2xx_driver 参见\platform\mxusbstick\Contiki-conf.h

至此进入NETSTACK_RADIO所指带的rf2xx_driver ,在该射频芯片下查找send 函数。

 

在\cpu\AVR\Radio\rf230bb\radio.c 里面包含了rf2xx_driver 所有函数。

staticint

rf230_send(constvoid *payload, unsigned short payload_len)

{

      ret =rf230_transmit(payload_len);

}

 

至此物理层数据发送完毕。


1 0