CTP源码分析6 CTP链路质量估计层结构及源码分析(五)----源码实现解读(二)

来源:互联网 发布:cpda数据分析师 假 编辑:程序博客网 时间:2024/06/06 20:46
void updateNeighborTableEst(am_addr_t n) {    uint8_t i, totalPkt;    neighbor_table_entry_t *ne;    uint8_t newEst;    uint8_t minPkt;    minPkt = BLQ_PKT_WINDOW;    dbg("LI", "%s\n", __FUNCTION__);    for (i = 0; i < NEIGHBOR_TABLE_SIZE; i++) {      ne = &NeighborTable[i];      if (ne->ll_addr == n) {if (ne->flags & VALID_ENTRY) {  if (ne->inage > 0)    ne->inage--;  if (ne->outage > 0)    ne->outage--;  if ((ne->inage == 0) && (ne->outage == 0)) {    ne->flags ^= VALID_ENTRY;    ne->inquality = ne->outquality = 0;  } else {    dbg("LI", "Making link: %d mature\n", i);    ne->flags |= MATURE_ENTRY;    totalPkt = ne->rcvcnt + ne->failcnt;    dbg("LI", "MinPkt: %d, totalPkt: %d\n", minPkt, totalPkt);    if (totalPkt < minPkt) {      totalPkt = minPkt;    }    if (totalPkt == 0) {      ne->inquality = (ALPHA * ne->inquality) / 10;    } else {      newEst = (255 * ne->rcvcnt) / totalPkt;      dbg("LI,LITest", "  %hu: %hhu -> %hhu", ne->ll_addr, ne->inquality, (ALPHA * ne->inquality + (10-ALPHA) * newEst)/10);      ne->inquality = (ALPHA * ne->inquality + (10-ALPHA) * newEst)/10;    }    ne->rcvcnt = 0;    ne->failcnt = 0;  }  updateEETX(ne, computeBidirEETX(ne->inquality, ne->outquality));}else {  dbg("LI", " - entry %i is invalid.\n", (int)i);}      }    }  }

这个函数可以更新一个条目的EETX,在更新之前有一个对于入站链路质量的计算然后将入站质量和出站质量合起来计算新的EETX,然后再把EETX做一个更新。和inage以及outage相关的逻辑先不予以解释。

因为收到的LEEP帧的数量会有一个计数,通过LEEP帧序号的差值可以计算出丢失的帧数,两个数相加就可以知道邻居节点发送的所有LEEP帧数。知道了所有的LEEP帧数以及接收到的LEEP帧数,这样子入度链路质量就可以得知,最后结合从邻居节点传来的出度链路质量,双向EETX就可以得知然后基于LEEP帧的EETX就可以更新。

这个函数是基于LEEP帧的EETX更新。


上面这个函数更新链路质量估计表某一个条目和链路质量相关的字段。下面这个函数会在最后调用updateNeighborTableEst,并且在调用之前更新除了链路质量之外的信息。

void updateNeighborEntryIdx(uint8_t idx, uint8_t seq) {    uint8_t packetGap;    if (NeighborTable[idx].flags & INIT_ENTRY) {      dbg("LI", "Init entry update\n");      NeighborTable[idx].lastseq = seq;      NeighborTable[idx].flags &= ~INIT_ENTRY;    }    packetGap = seq - NeighborTable[idx].lastseq;    dbg("LI", "updateNeighborEntryIdx: prevseq %d, curseq %d, gap %d\n",NeighborTable[idx].lastseq, seq, packetGap);    NeighborTable[idx].lastseq = seq;    NeighborTable[idx].rcvcnt++;    NeighborTable[idx].inage = MAX_AGE;    if (packetGap > 0) {      NeighborTable[idx].failcnt += packetGap - 1;    }    if (packetGap > MAX_PKT_GAP) {      NeighborTable[idx].failcnt = 0;      NeighborTable[idx].rcvcnt = 1;      NeighborTable[idx].outage = 0;      NeighborTable[idx].outquality = 0;      NeighborTable[idx].inquality = 0;    }    if (NeighborTable[idx].rcvcnt >= BLQ_PKT_WINDOW) {      updateNeighborTableEst(NeighborTable[idx].ll_addr);    }  }

packetGap是存储没有接收到的LEEP帧的个数,如果这个值大于MAX_PKT_GAP,那么就说明有很多LEEP帧没有收到了,这个时候就要重新初始化和链路质量相关的信息。原因也是显而易见的,因为已经有大量LEEP帧没有收到了,那么直接收到的LEEP帧数量已经不能代表现在的链路质量了,所以需要把和链路质量相关的一切全部初始化。当然如果packetGap的值没有大于那个阈值,那么packetGap就是没有成功接收到的LEEP帧数,存在链路质量估计表中的failcnt字段下。


  void print_neighbor_table() {    uint8_t i;    neighbor_table_entry_t *ne;    for (i = 0; i < NEIGHBOR_TABLE_SIZE; i++) {      ne = &NeighborTable[i];      if (ne->flags & VALID_ENTRY) {dbg("LI,LITest", "%d:%d inQ=%d, inA=%d, outQ=%d, outA=%d, rcv=%d, fail=%d, biQ=%d\n",    i, ne->ll_addr, ne->inquality, ne->inage, ne->outquality, ne->outage,    ne->rcvcnt, ne->failcnt, computeBidirEETX(ne->inquality, ne->outquality));      }    }  }
  void print_packet(message_t* msg, uint8_t len) {    uint8_t i;    uint8_t* b;    b = (uint8_t *)msg->data;    for(i=0; i<len; i++)      dbg_clear("LI", "%x ", b[i]);    dbg_clear("LI", "\n");  }
这两个函数是用来打印一个链路质量估计表的,主要用于调试,没有其他的作用。


  void initNeighborTable() {    uint8_t i;    for (i = 0; i < NEIGHBOR_TABLE_SIZE; i++) {      NeighborTable[i].flags = 0;    }  }

这个函数是用来初始化一个表的,把flag设为0意味着这个条目不属于任何一个状态,这样子任何函数都没有办法访问这个条目。


  command error_t StdControl.start() {    dbg("LI", "Link estimator start\n");    return SUCCESS;  }  command error_t StdControl.stop() {    return SUCCESS;  }  command error_t Init.init() {    dbg("LI", "Link estimator init\n");    initNeighborTable();    return SUCCESS;  }
这三个函数分别是开启和关闭链路质量估计层,还有就是链路质量估计层的初始化函数,这个函数就是本质上就是重置一下链路质量估计表。


  command uint8_t LinkEstimator.getLinkQuality(am_addr_t neighbor) {    uint8_t idx;    idx = findIdx(neighbor);    if (idx == INVALID_RVAL) {      return INFINITY;    } else {      return NeighborTable[idx].eetx;    };  }  command uint8_t LinkEstimator.getReverseQuality(am_addr_t neighbor) {    uint8_t idx;    idx = findIdx(neighbor);    if (idx == INVALID_RVAL) {      return INFINITY;    } else {      return computeEETX(NeighborTable[idx].inquality);    };  }  command uint8_t LinkEstimator.getForwardQuality(am_addr_t neighbor) {    uint8_t idx;    idx = findIdx(neighbor);    if (idx == INVALID_RVAL) {      return INFINITY;    } else {      return computeEETX(NeighborTable[idx].outquality);    };  }
这一组函数就是链路质量的获取函数,eetx和两种quality变量不一样,eetx是由后者推算出来的,不是一个维度的数据。但是这三个函数,无论是获取双向质量还是单向的出度和入度质量,都是换算成eetx。具体是如何换算的,看函数computeEETX的实现。双向的eetx本来就已经算好放在链路质量估计表中,而出度和入度的eetx我们需要先从链路质量估计表中选出入度和出度质量,然后换算成eetx。


command error_t LinkEstimator.insertNeighbor(am_addr_t neighbor) {    uint8_t nidx;    nidx = findIdx(neighbor);    if (nidx != INVALID_RVAL) {      dbg("LI", "insert: Found the entry, no need to insert\n");      return SUCCESS;    }    nidx = findEmptyNeighborIdx();    if (nidx != INVALID_RVAL) {      dbg("LI", "insert: inserted into the empty slot\n");      initNeighborIdx(nidx, neighbor);      return SUCCESS;    } else {      nidx = findWorstNeighborIdx(BEST_EETX);      if (nidx != INVALID_RVAL) {dbg("LI", "insert: inserted by replacing an entry for neighbor: %d\n",    NeighborTable[nidx].ll_addr);signal LinkEstimator.evicted(NeighborTable[nidx].ll_addr);initNeighborIdx(nidx, neighbor);return SUCCESS;      }    }    return FAIL;  }

这个函数是将一个邻居节点的信息放到链路质量估计表中。如果这个邻居节点的地址已经在链路质量估计表中了,那就直接返回SUCCESS。如果发现这个邻居节点还不在链路质量估计表中,那么能做的就只有在链路质量估计表中找一个空的条目。如果空的条目也没有,那么能做的就是找到一个链路质量最烂并且烂到一定程度的节点。并且重新写这个条目。



command error_t LinkEstimator.pinNeighbor(am_addr_t neighbor) {    uint8_t nidx = findIdx(neighbor);    if (nidx == INVALID_RVAL) {      return FAIL;    }    NeighborTable[nidx].flags |= PINNED_ENTRY;    return SUCCESS;  }
command error_t LinkEstimator.unpinNeighbor(am_addr_t neighbor) {    uint8_t nidx = findIdx(neighbor);    if (nidx == INVALID_RVAL) {      return FAIL;    }    NeighborTable[nidx].flags &= ~PINNED_ENTRY;    return SUCCESS;  }

使用这个函数可以绑定和取消绑定一个节点,这个“绑定”过程很有意思,就是把这个节点地址对应的条目找出来,然后再叠加一个PINNED状态。进入“绑定”状态的节点不能从链路质量估计表中剔除。为什么有节点需要绑定?因为有些节点如果从链路质量估计表中去除就会导致严重的通信问题,比如根节点和父节点。每一个节点都要和自己的父节点通信,如果父节点从链路质量估计表中剔除,那么就意味着这个节点不再知道自己父节点的地址,那么信息的发送和转发就无从谈起。根节点必须绑定在链路质量估计表中的原因也是类似,如果根节点从所有其他节点的链路质量估计表中消失,那么“汇聚树”的“信息向根节点汇聚”的功能就无从谈起。


command error_t LinkEstimator.txAck(am_addr_t neighbor) {    neighbor_table_entry_t *ne;    uint8_t nidx = findIdx(neighbor);    if (nidx == INVALID_RVAL) {      return FAIL;    }    ne = &NeighborTable[nidx];    ne->data_success++;    ne->data_total++;    if (ne->data_total >= DLQ_PKT_WINDOW) {      updateDEETX(ne);    }    return SUCCESS;  }  command error_t LinkEstimator.txNoAck(am_addr_t neighbor) {    neighbor_table_entry_t *ne;    uint8_t nidx = findIdx(neighbor);    if (nidx == INVALID_RVAL) {      return FAIL;    }    ne = &NeighborTable[nidx];    ne->data_total++;    if (ne->data_total >= DLQ_PKT_WINDOW) {      updateDEETX(ne);    }    return SUCCESS;  }
这两个函数和基于数据包发送成功率的链路质量估计有关。支持ACK帧的转发引擎会将数据包是否成功发送的结果通过这两个函数告知链路质量估计层。这里要解释的就是DLQ,并不是每次数据包发送的反馈都会直接更新链路质量,而是先累计一定数量之后计算一个链路质量估计值,然后这个链路质量估计值会以2、8开的比例更新链路质量。DLQ_PKT_WINDOW这个值在TOS的实现中被定为5。


command error_t LinkEstimator.clearDLQ(am_addr_t neighbor) {    neighbor_table_entry_t *ne;    uint8_t nidx = findIdx(neighbor);    if (nidx == INVALID_RVAL) {      return FAIL;    }    ne = &NeighborTable[nidx];    ne->data_total = 0;    ne->data_success = 0;    return SUCCESS;  }
在父节点改变的时候激活的函数,因为父节点改变了,所以之前的基于数据包发送成功率的链路质量估计值也就没有意义了。这个函数做的就是一个重置的操作


  command error_t Send.send(am_addr_t addr, message_t* msg, uint8_t len) {    uint8_t newlen;    newlen = addLinkEstHeaderAndFooter(msg, len);    dbg("LITest", "%s packet of length %hhu became %hhu\n", __FUNCTION__, len, newlen);    dbg("LI", "Sending seq: %d\n", linkEstSeq);    print_packet(msg, newlen);    return call AMSend.send(addr, msg, newlen);  }  event void AMSend.sendDone(message_t* msg, error_t error ) {    return signal Send.sendDone(msg, error);  }  command uint8_t Send.cancel(message_t* msg) {    return call AMSend.cancel(msg);  }

这几个函数都设计链路质量估计层更下层的信息发送模块,AMSend就是这个模块向外提供的接口。Send接口就是链路质量估计层向路由引擎提供的接口。当路由引擎打算发送路由帧的时候就会调用链路质量估计器的Send.send函数,这个函数会把路由帧封装成LEEP帧,然后调用更底层的AMSend进行发送。后面分别是发送成功之后激活的函数和取消发送的函数。sendDone这个函数主要是TOS更底层的模块激活,但是业务逻辑的处理还是放在了路由引擎的实现上。cancel这个函数是用来取消数据包的发送的,主要还是路由引擎来激活的。


command uint8_t Send.maxPayloadLength() {    return call Packet.maxPayloadLength();  }  command void* Send.getPayload(message_t* msg) {    return call Packet.getPayload(msg, NULL);  }
这两个函数都是获得payload有关的东西,其中第一个函数获得的是最大的payload大小,也就是LEEP帧的最大payload大小。第二个函数获得的是LEEP帧payload部分的一个指针。


void processReceivedMessage(message_t* msg, void* payload, uint8_t len) {    uint8_t nidx;    uint8_t num_entries;    dbg("LI", "LI receiving packet, buf addr: %x\n", payload);    print_packet(msg, len);    if (call SubAMPacket.destination(msg) == AM_BROADCAST_ADDR) {      linkest_header_t* hdr = getHeader(msg);      linkest_footer_t* footer;      am_addr_t ll_addr;      ll_addr = call SubAMPacket.source(msg);      dbg("LI", "Got seq: %d from link: %d\n", hdr->seq, ll_addr);      num_entries = hdr->flags & NUM_ENTRIES_FLAG;      print_neighbor_table();            nidx = findIdx(ll_addr);      if (nidx != INVALID_RVAL) {dbg("LI", "Found the entry so updating\n");updateNeighborEntryIdx(nidx, hdr->seq);      } else {nidx = findEmptyNeighborIdx();if (nidx != INVALID_RVAL) {  dbg("LI", "Found an empty entry\n");  initNeighborIdx(nidx, ll_addr);  updateNeighborEntryIdx(nidx, hdr->seq);} else {  nidx = findWorstNeighborIdx(EVICT_EETX_THRESHOLD);  if (nidx != INVALID_RVAL) {    dbg("LI", "Evicted neighbor %d at idx %d\n",NeighborTable[nidx].ll_addr, nidx);    signal LinkEstimator.evicted(NeighborTable[nidx].ll_addr);    initNeighborIdx(nidx, ll_addr);  } else {    dbg("LI", "No room in the table\n");  }}      }      if ((nidx != INVALID_RVAL) && (num_entries > 0)) {dbg("LI", "Number of footer entries: %d\n", num_entries);footer = (linkest_footer_t*) ((uint8_t *)call SubPacket.getPayload(msg, NULL)      + call SubPacket.payloadLength(msg)      - num_entries*sizeof(linkest_footer_t));{  uint8_t i, my_ll_addr;  my_ll_addr = call SubAMPacket.address();  for (i = 0; i < num_entries; i++) {    dbg("LI", "%d %d %d\n", i, footer->neighborList[i].ll_addr,footer->neighborList[i].inquality);    if (footer->neighborList[i].ll_addr == my_ll_addr) {      updateReverseQuality(ll_addr, footer->neighborList[i].inquality);    }  }}      }      print_neighbor_table();    }  }

这个函数是链路质量估计器最重要的函数之一。这个函数会在收到一个LEEP帧的时候激活,首先查看这个数据帧是不是一个广播帧,如果不是一个广播帧的话就不用进行接下来的逻辑了。如果是一个广播帧就要,就要声明三个变量:hdr(指向LEEP帧帧头的指针),footer(指向广播帧的尾部队列),ll_addr(广播帧的原地址)。ll_addr这个变量可以找到链路质量估计表中的固定条目的索引值,方便对链路质量估计表做修改。hdr头部记录了尾部其他节点出度质量信息的个数。footer是一个指向尾部队列的指针。一开始首先获得的是hdr,然后就是ll_addr。接下来就是使用ll_addr这个东西去获得一个链路质量估计表中的索引值。如果成功找到了源节点地址的索引值,那么就意味着,这个节点之前就已经登记到链路质量估计表中,所以说我们进行更新链路质量估计表的操作。如果发现链路质量估计表中之前没有登记过这个节点,也就是没有找到对应的索引值,那么就要看看这个节点的信息是不是要记录到链路质量估计表中。

这个过程分为很多种情况,首先就是看看链路质量估计表中有没有空闲的位置,如果有空闲位置,那么就直接把这个新的节点放到链路质量估计表中,如果没有空闲位置,那么就看看能不能挤掉其他节点。要挤掉的节点都是链路质量比较差的,如果没有发现链路质量足够差以至于可以被替换,那么这个节点就不能记入到表格中。

如果在之前就已经记录了节点的信息,或者这个节点的信息被允许插入到链路质量估计表中,那么就可以更新LEEP帧的发送节点和当前节点之间的出度链路质量。


  void initNeighborTable() {    uint8_t i;    for (i = 0; i < NEIGHBOR_TABLE_SIZE; i++) {      NeighborTable[i].flags = 0;    }  }
这个函数用来初始化链路质量估计表,实际的工作就是把链路质量估计表的flags位置为0。


event message_t* SubReceive.receive(message_t* msg,      void* payload,      uint8_t len) {    dbg("LI", "Received upper packet. Will signal up\n");    processReceivedMessage(msg, payload, len);    return signal Receive.receive(msg,  call Packet.getPayload(msg, NULL),  call Packet.payloadLength(msg));  }  command void* Receive.getPayload(message_t* msg, uint8_t* len) {    return call Packet.getPayload(msg, len);  }  command uint8_t Receive.payloadLength(message_t* msg) {    return call Packet.payloadLength(msg);  }  command void Packet.clear(message_t* msg) {    call SubPacket.clear(msg);  }
第一个函数是底层接收到数据包的响应函数,链路质量估计层把这个数据包的LEEP帧头和帧尾取了出来,然后进行链路质量估计表的更新,然后把中间负载的部分以激活路由引擎事件的方式交给上层处理。


command uint8_t Packet.payloadLength(message_t* msg) {    linkest_header_t *hdr;    hdr = getHeader(msg);    return call SubPacket.payloadLength(msg)      - sizeof(linkest_header_t)      - sizeof(linkest_footer_t)*(NUM_ENTRIES_FLAG & hdr->flags);  }  command void Packet.setPayloadLength(message_t* msg, uint8_t len) {    linkest_header_t *hdr;    hdr = getHeader(msg);    call SubPacket.setPayloadLength(msg,    len    + sizeof(linkest_header_t)    + sizeof(linkest_footer_t)*(NUM_ENTRIES_FLAG & hdr->flags));  }  command uint8_t Packet.maxPayloadLength() {    return call SubPacket.maxPayloadLength() - sizeof(linkest_header_t);  }  command void* Packet.getPayload(message_t* msg, uint8_t* len) {    uint8_t* payload = call SubPacket.getPayload(msg, len);    linkest_header_t *hdr;    hdr = getHeader(msg);    if (len != NULL) {      *len = *len - sizeof(linkest_header_t) - sizeof(linkest_footer_t)*(NUM_ENTRIES_FLAG & hdr->flags);    }    return payload + sizeof(linkest_header_t);  }}
这里是链路质量估计层处理数据包的几个函数,第一个函数主要是获取LEEP帧payload长度,SubPacket是链路质量估计层更下一层的接口,更下一层的数据包的负载部分就是LEEP帧,把这个LEEP帧的长度掐头去尾就知道了LEEP帧负载的长度。其实这几个函数都是一个道理,获取TOS底层数据包的负载部分然后掐头去尾得到自己想要的东西。















0 0
原创粉丝点击