LtePdcp
TE的EnbRrc通过引用LtePdcpSapProvider将数据包发送到整个堆栈。为了实现任何类型的PDCP层,LtePdcpSapProvider是一个抽象接口。在本例中,仿真使用了lte/model/lte-pdcp.cc中提供的LtePdcp实现。
可以通过启用INFO级别的日志信息来查看LtePdcp日志组件:
+0.400000282s 0 LtePdcp:DoTransmitPdcpSdu(): [INFO ] Received PDCP SDU
+0.400000282s 0 LtePdcp:DoTransmitPdcpSdu(): [INFO ] Transmit PDCP PDU
根据LtePdcpSapProvider接口的定义,用于从上层接收传入数据包的内部方法被称为DoTransmitPdcpSdu(),在本例中被定义如下:
Ptr<Packet> p = params.pdcpSdu;
// Sender timestamp
PdcpTag pdcpTag(Simulator::Now());
LtePdcpHeader pdcpHeader;
pdcpHeader.SetSequenceNumber(m_txSequenceNumber);
m_txSequenceNumber++;
if (m_txSequenceNumber > m_maxPdcpSn)
{
m_txSequenceNumber = 0;
}
pdcpHeader.SetDcBit(LtePdcpHeader::DATA_PDU);
NS_LOG_LOGIC("PDCP header: " << pdcpHeader);
p->AddHeader(pdcpHeader);
p->AddByteTag(pdcpTag, 1, pdcpHeader.GetSerializedSize());
m_txPdu(m_rnti, m_lcid, p->GetSize());
LteRlcSapProvider::TransmitPdcpPduParameters txParams;
txParams.rnti = m_rnti;
txParams.lcid = m_lcid;
txParams.pdcpPdu = p;
m_rlcSapProvider->TransmitPdcpPdu(txParams);
小计:在协议体系里,下层协议是为上层协议提供服务(service)的,因此凡是上层发过来的或者发往上层的数据都叫SDU(Service Data Unit)。而上层协议(Protocol)实体对数据进行处理后,通过下层协议提供的服务继续往后面发送,因此凡是发给下层或从下层收到的数据叫做PDU(Protocol Data Unit),
正如可以观察到的,一个PDCP数据包标签(由PdcpTag标识)和其头部(由LtePdcpHeader表示)被创建并附加到SDU上。前者存储了当前模拟时间,而后者则设置了特定的序列号和D/C位。序列号通过m_txSequenceNumber属性在本地跟踪,而D/C位设置为DATA_PDU,表示这是一个数据包而不是控制包。此变化可以在下图中可视化表示。
最后,通过提供一些额外的参数(如RNTI和LCID)将数据包转发到RLC层,使用LteRlcSapProvider::TransmitPdcpPdu()方法,并在LteRlcSap-Provider::TransmitPdcpPduParameters结构下提供。
#1
数据包延迟
数据包在EnbEpcApplication中不能产生延迟。
#2丢包
数据包不会被丢弃。
LteRlcUm
遵循LtePdcpSapProvider模板类的相同设计及其实现LtePdcp, RLC层基于相同的类结构。在这里,LteRlcSapProvider提供程序是一个模板类,其中可以定义RLC的多个实现。在ns-3上,RLC层由LteRlcAm、LteRlcTm和LteRlcUm实现,根据PDCP PDU如何转移到UE,如果分别在确认模式(AM)、透明模式(TM)或未确认模式(UM)。该仿真采用了LteRlcUm,并在lte/model/lte-rlc-um.cc中实现。
如果在INFO级别启用了LteRlcUm日志组件,它将生成以下日志消息:
+0.400000282s 0 LteRlcUm:DoTransmitPdcpPdu(): [INFO ] Received RLC SDU
+0.400000282s 0 LteRlcUm:DoTransmitPdcpPdu(): [INFO ] New packet enqueued to the RLC␣
,
→Tx Buffer
可以推断,RLC SDU在LteRlcUm::Do传输pdcpPdu()上接收,其实现为:
if (m_txBufferSize + p->GetSize() <= m_maxTxBufferSize)
{
if (m_enablePdcpDiscarding)
// Ignored, as this flag is false by default
/** Store PDCP PDU */
LteRlcSduStatusTag tag;
tag.SetStatus(LteRlcSduStatusTag::FULL_SDU);
p->AddPacketTag(tag);
NS_LOG_INFO("New packet enqueued to the RLC Tx Buffer");
m_txBuffer.emplace_back(p, Simulator::Now());
m_txBufferSize += p->GetSize();
NS_LOG_LOGIC("NumOfBuffers = " << m_txBuffer.size());
NS_LOG_LOGIC("txBufferSize = " << m_txBufferSize);
}
else
{
// Discard full RLC SDU
NS_LOG_INFO("TxBuffer is full. RLC SDU discarded");
NS_LOG_LOGIC("MaxTxBufferSize = " << m_maxTxBufferSize);
NS_LOG_LOGIC("txBufferSize = " << m_txBufferSize);
NS_LOG_LOGIC("packet size = " << p->GetSize());
m_txDropTrace(p);
}
/** Report Buffer Status */
DoReportBufferStatus();
m_rbsTimer.Cancel();
m_txBuffer.size());///m_txBufferSize这两个变量值得注意,暗示缓冲区的大小变化。如果没有LteRlcSduStatusTag管理的新标签,则数据包保持不变,该标签跟踪RLC层完成的数据包的分片状态。如果DoTransmitPdcpPdu()没有对数据包进行分片,则相应将状态设置为LteRlcSduStatusTag::FULL_SDU。然后将数据包附加到一个名为m_txBuffer的缓冲区,这是一个矢量,其中TxPdu是一个结构,包含(i)指向数据包的指针,以及(ii)数据包被添加到缓冲区的时间,如m_txBuffer.emplace_back()指令中的Simulator::Now()调用所指示的。
在过程结束时,调用DoReportBufferStatus()来警告MAC层的队列大小,由LteMacSapProvider处理。缓冲区报告的重点是报告队列大小和它的HOL延迟,以及参考的RNTI和LCID,可以从下面的方法摘录中注意到:
Time holDelay(0);
uint32_t queueSize = 0;
if (!m_txBuffer.empty())
{
holDelay = Simulator::Now() - m_txBuffer.front().m_waitingSince;
queueSize =
m_txBufferSize + 2 * m_txBuffer.size(); // Data in tx queue + estimated␣
,
→headers size
}
LteMacSapProvider::ReportBufferStatusParameters r;
r.rnti = m_rnti;
r.lcid = m_lcid;
r.txQueueSize = queueSize;
r.txQueueHolDelay = holDelay.GetMilliSeconds();
r.retxQueueSize = 0;
r.retxQueueHolDelay = 0;
r.statusPduSize = 0;
NS_LOG_LOGIC("Send ReportBufferStatus = " << r.txQueueSize << ", " << r.
,
→txQueueHolDelay);
m_macSapProvider->ReportBufferStatus(r);
_ holDelay定义:当前时间减去队列中第一个数据包的到达时间_
2 * m_txBuffer.size()这里的2代表pdu在下层传输时的固定头部开销,这里需要提前估计
没有提到实际的m_txBuffer,它保存在RLC层,直到在MAC层找到传输机会,为此MAC调度器会对LteRlcUm::DoNotifyTxOpportunity()进行向上调用。//这句话暗示mac层出现调度机会时主动通知RLC层传输数据,实际上,INFO日志消息表明缓冲区在传输机会发生之前获得了两个RLC sdu:
+0.400000282s 0 LteRlcUm:DoTransmitPdcpPdu(): [INFO ] Received RLC SDU
+0.400000282s 0 LteRlcUm:DoTransmitPdcpPdu(): [INFO ] New packet enqueued to the RLC␣
,
→Tx Buffer
+0.400002262s 0 LteRlcUm:DoTransmitPdcpPdu(): [INFO ] Received RLC SDU
+0.400002262s 0 LteRlcUm:DoTransmitPdcpPdu(): [INFO ] New packet enqueued to the RLC␣
,
→Tx Buffer
+0.400062500s 0 LteRlcUm:DoNotifyTxOpportunity(): [INFO ] RLC layer is preparing data␣
,
→for the following Tx opportunity of 81 bytes for RNTI=2, LCID=4, CCID=0, HARQ ID=19,
,
→ MIMO Layer=0
LteRlcUm::DoNotifyTxOpportunity()以以下方式准备传输数据:
if (txOpParams.bytes <= 2)
{
// Stingy MAC: Header fix part is 2 bytes, we need more bytes for the data
NS_LOG_INFO("TX opportunity too small - Only " << txOpParams.bytes << " bytes");
return;
}
Ptr<Packet> packet = Create<Packet>();
LteRlcHeader rlcHeader;
// Build Data field
uint32_t nextSegmentSize = txOpParams.bytes - 2;
// ...
首先,检查机会窗口的大小,以确保我们有两个以上的字节,因为MAC头需要这个大小。接下来,将创建一个空数据包,称为packet。这样的数据包必须等于传输机会的大小,因此使用变量nextSegmentSize来了解m_txBuffer中包含的数据可以传输多少。在这种情况下,我们最多可以传输79字节,这是txOpParams的结果。设置为81字节,减去MAC报头大小的2字节。
Ptr<Packet> firstSegment = m_txBuffer.begin()->m_pdu->Copy();
Time firstSegmentTime = m_txBuffer.begin()->m_waitingSince;
// ...
while (firstSegment && (firstSegment->GetSize() > 0) && (nextSegmentSize > 0))
{
if ((firstSegment->GetSize() > nextSegmentSize) ||
// Segment larger than 2047 octets can only be mapped to the end of the␣
,
→Data field
(firstSegment->GetSize() > 2047))
{
// The packet 'firstSegment' must be fragmented to fit in our segment being␣
,
→prepared for MAC TX
}
else if ((nextSegmentSize - firstSegment->GetSize() <= 2) || m_txBuffer.empty())
{
// If the packet fits the segment and there are no other packets to TX, just␣
,
→add it without fragmenting it
}
else // (firstSegment->GetSize () < m_nextSegmentSize) && (m_txBuffer.size () > 0)
{
// If there are still other packets to TX, add the current packet
,
→'firstSegment' and update nextSegmentSize
}
}
从m_txBuffer的头部,第一个RLC SDU被占用,它被称为firstSegment,连同它的等待时间。直到我们有空间传输机会,由nextSegmentSize跟踪,while循环被执行。
当然,如果第一个分段太大而无法适应传输机会,那么它就是碎片化的。然后更新LteRlcSduStatusTag,根据数据包被分片的部分数量来检查该片段是FIRST_SEGMENT、MIDDLE_SEGMENT还是LAST_SEGMENT.
不适合机会窗口大小的片段被插入到m_txBuffer的头部。如果这样的片段不是最后一个,RLC头DATA_FIELD_FOLLOWS位设置为警告此数据包不包含整个PDCP数据包。这将有助于安排新的传输并确保正确的分组集成。
还可能发生其他情况,这些情况由while循环中的条件子句处理。如果缓冲区中只剩下一个数据包,并且它适合当前段,则将其放置到段中。此外,如果段上还有更多的空间,而其他数据包正在m_txBuffer中等待,则将当前数据包firstSegment放置在段中,并评估由nextSegmentSize跟踪的剩余空间,以便在下一个while循环迭代时在段中添加新数据包。
通过观察图5.6,可以更好地可视化该操作
在分段过程之后,利用LteRlcHeader数据结构设置了RLC头。序列号通过本地属性m_sequenceNumber进行设置和跟踪。此外,帧信息设置为声明段的最后一个字节是否对应于RLC SDU的结束。最后,添加了一个RlcTag字节标签,并将其链接到RLC头,以保存当前模拟时间。可以启用INFO日志信息来了解何时将数据包发送到MAC层。
+0.400062500s 0 LteRlcUm:DoNotifyTxOpportunity(): [INFO ] Forward RLC PDU to MAC Layer
一旦RLC PDU准备好,就会通过TransmitPdu()接口方法将其与RNTI、LCID、CCID、HARQ ID和MIMO层一起推送到MAC SAP。
#1
数据包延迟
在LteRlcUm中以及RLC层中,数据包确实会存在延迟,这取决于传输机会的发生时间以及缓冲区的大小。较小的缓冲区一方面可能改善延迟问题,但另一方面也有可能导致传输缓冲区满的情况发生,从而导致数据包丢失。
#2丢包
如果RLC缓冲区m_txBuffer已满或启用了m_enablePdcpDiscarding,则可能会丢弃数据包。启用m_enablePdcpDiscarding会分析RLC SDU的时序预算和条件,并可能导致其被丢弃。