对RLC重排序窗口大小的一点讨论

在LTE协议栈的PDCP层和RLC层,都有一个重排序窗口(reordering window),主要用来保证数据的可靠传输,PDCP层的重排序窗口主要用于handover时保证数据的可靠传输,这里暂且不表,只讨论RLC层的重排序窗口。

对RLC层,在AM接收模式和UM接收模式下,UM接收实体/AM实体接收端有一个重排序窗口,当接收到的RLC PDU位于重排序窗口内,且之前没有被接收过时,接收端才会对该RLC PDU进行处理,重排序窗口大小无论是在UM模式还是AM模块下都是序列号(SN)取值范围的一半。例如,在AM模式下,假如SN长度为10bit,那么SN取值范围为0 ~ 2^10-1,即0 ~ 1023,则重排序窗口大小为512。

刚开始的时候,我一直不太明白,为什么重排序窗口要选择这么一个数?不能取大点或者取小点吗?我想了一下,没想明白,就囫囵吞枣地默认了这个事实,直到有一天,我在Andrew S. Tanenbaum写的Computer Networks一书中才偶然发现了答案。As an aside,这里推荐一下Tanenbaum写的另外一本书——Modern operating systems,这本书对现代操作系统里面基本的元素和概念都进行了比较详细的阐述,虽然有些地方略有晦涩,并且似乎有点far-fetched之嫌,但是不深究里面的code snippet,仅从其对操作系统一些思想的论述来理解操作系统的设计的话,仍然大有裨益。

回到前面的讨论,在AM模式下,RLC PDU不一定要按序接收,假如收到的RLC PDU不是期望接收到的下一帧RLC PDU(i.e. 其SN不等于VR(R)变量的值),但是却位于重排序窗口内,那么接收端仍然会将该RLC PDU缓存下来,这种非顺序接收(Nonsequential receive)方法相比只能按序接收的协议会引入一个问题,而这个问题恰好可以通过对重排序窗口大小的设置来优雅地解决掉。这里我直接引用Tanenbaum的Computer Networks一书中3.4.3节的例子来阐述这个问题。

假设现在RLC PDU的SN号长度为3bit,初始时刻,发送端和接收端的窗口如图所示。图中,a图是初始时刻发送端和接收端窗口的情况。发送端发送窗口为0~6,假设发送端将发送窗口内的RLC PDU全都发送出去了,接收端成功地接收到了SN0~6 RLC PDU,那么接收端就会将窗口往右挪,并将VR(MS)更新为7,此时接收窗口变为7、0~5,如b图所示。同时,接收端会给发送端发ACK,通知发送端它已经接收到了SN0 ~6 RLC PDU,发送端可以发送新的RLC PDU了。不幸的是,接收端给发送端回复的ACK全都丢失了,发送端一个都没收着,其发送窗口仍然保持不动,这种情况被称为window stalling(窗口停滞)。就这样,一段时间过后,发送端的t-Pol 大专栏  对RLC重排序窗口大小的一点讨论lRetransmit timer将会超时,此时发送端还没有收到0~6 RLC PDU的ACK,它会认为对方可能没有收到这7个RLC PDU,于是又重传SN0 RLC PDU,并且将该RLC PDU header里的Poll位置1,询问对方是不是没有收着它刚发出去的7个RLC PDU。当SN0 RLC PDU到达接收端时,接收端检查其是否位于接收窗口内,此时接收窗口为7、0~5,如b图所示。很不幸,SN0 RLC PDU正好位于其中,接收端认为这是一帧新的RLC PDU,于是很愉快地接收下这帧RLC PDU,然后回了一帧status report(因为它收到的RLC PDU的P位为1),ACK_SN为7,告诉发送端SN 0~6 RLC PDU都已经接收到了。发送端这会终于收到ACK了(i.e. status report),知道SN 0~6 RLC PDU已经被对方成功接收了,于是很愉快地把发送窗口往前移动,发送窗口变为7、0~5。发送端继续发送SN7、0 ~ 5 RLC PDU给对方,接收端收到SN7、0~5RLC PDU后,发现接收buffer里面已经有SN0 RLC PDU了,就认为新接收到的SN0 RLC PDU是duplicate packet,于是就把新接收到的SN0 RLC PDU给丢弃了,然后对接收buffer里的旧的SN0 RLC PDU连同新接收到的SN7、SN1~5 RLC PDU一起解析,再向上提交给PDCP层。显然,PDCP会得到错误的packet,原因就在于RLC层把旧的SN0 RLC PDU当成了新的RLC PDU,而把真正的新的RLC PDU当成了duplicate packet给丢弃了,通信就此出错。

解决这个问题的方法就是要确保接收窗口在往右移动的过程中,不会把原来的窗口给覆盖掉(即窗口移进来的部分不会与窗口移出去的部分发生重叠),上面的例子之所以会出错,就是因为接收窗口往右移动的过程中,新的窗口右边缘为SN5,刚好落入到旧的窗口里面(SN0~6),新的窗口把旧的窗口的一部分给覆盖掉了(覆盖了SN0 ~ 5)。为了保证新的窗口不会覆盖到旧的窗口,窗口的大小最大不能超过序列号范围的一半。以图c和图d为例,序列号的范围仍然为0~7,发送窗口大小变为4,任何时刻最多只能有4帧没有被确认的RLC PDU。这种情况下,当接收端接收到SN0 ~ 3 RLC PDU之后,将会往右移动接收窗口,允许接收SN 4 ~ 7 RLC PDU,这时候,接收端可以明确地区分出发送端发过来的是重传的RLC PDU(SN0 ~ 3)还是新的RLC PDU(SN4 ~ 7)。一般来说,接收窗口和发送窗口的大小为(MAX_SEQ+1)/2,MAX_SEQ为SN的最大取值。上面的例子中,MAX_SEQ为7,因此窗口大小应该设置为4。当然了,窗口大小也可以取小一点,(MAX_SEQ+1)/2只是一个上界,极端一点的话,甚至可以把窗口大小设为1,但是没人会这么干,因为这样的话每次发送都只能发送一帧RLC PDU,然后又要等上老半天,等接收到对方的回复的确认才能发下一帧,采用这种通信方式效率会非常低。所以,这就是为什么RLC层窗口的大小要设置为序列号一半的原因。

原文地址:https://www.cnblogs.com/lijianming180/p/12041459.html

时间: 2024-10-21 21:44:30

对RLC重排序窗口大小的一点讨论的相关文章

深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则[转]

在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念. 在Java Concurrency in Practice中是这样定义线程安全的: 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的. 显然只有资源竞争时才会导致线程不安全,因此无状态对象永远是线程安全的. 原子操作的描述是: 多个线程执行一个操作时,其

指令重排序 as-if-serial

笔者认为看完一本书或刚要了解完一个知识点  最好自己先运行一些DEMO 自己尝试着去了解下各种意思  这样知识点最终一定是你的.靠死记硬背的讨论或简单的粗暴的看下资料 脑子里肯定还是一团浆糊. p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco } p.p2 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Monaco; min-height: 15.0px } p.p3 { ma

深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则

转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念. 在Java Concurrency in Practice中是这样定义线程安全的: 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替运行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安

volotile关键字的内存可见性及重排序

在理解volotile关键字的作用之前,先粗略解释下内存可见性与指令重排序. 1. 内存可见性 Java内存模型规定,对于多个线程共享的变量,存储在主内存当中,每个线程都有自己独立的工作内存,并且线程只能访问自己的工作内存,不可以访问其它线程的工作内存.工作内存中保存了主内存中共享变量的副本,线程要操作这些共享变量,只能通过操作工作内存中的副本来实现,操作完毕之后再同步回到主内存当中,其JVM内存模型大致如下图. 而JAVA内存模型规定工作内存与主内存之间的交互协议,其中包括8种原子操作: 1)

Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专家又往往建议我们远离它.比如Thread这个很基础的类,其中很重要的线程状态字段,就是用volatile来修饰,见代码 /* Java thread status for tools, * initialized to indicate thread 'not yet started' */   p

数组重排序方法

今天在复习的过程中,发现自己对数组的重排序方法sort()一直存在一个误区,重新捋顺,在此记录... 数组中存在两个可以直接用来重排序的方法:reverse()和sort(),reverse()方法就是反转数组项的顺序.在默认情况下,sort()方法按升序排列数组项,在这个过程中sort方法会调用每个数组项的toString()转型方法,然后比较得到的字符串,然后确定排序.之前一直没有注意到这一点,一直以为调用sort()方法过后会直接对一个数组进行正确排序,其实并不是这样,而是对得到的字符串进

J.U.C JMM. pipeline.指令重排序,happen-before(续)

前面已经介绍硬件平台Cache Coherence问题和解决办法,下面来看看Java虚拟机平台的相关知识.硬件平台处理器,高速缓存,主存之间的交互关系如下: Java内存模型(JMM)        Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model, JMM)来屏蔽掉底层各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果. 主内存和工作内存       Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟

为了防止程序重排序,慎用volatile

之前在InfoQ看到一篇关于java重排序的一篇文章,觉得里面有些知识写得太绝对了,于是想通过实际程序来说明一下: 关于java重排序,这里就不做介绍了,我们知道JVM底层封装了与OS的交互,它内部有自己的一套类似于OS的内存模型,程序重排序的设计思路基本上是来源于OS.下面直接入正题吧! 我们知道JVM给每个线程分配了自己的内存空间,也就是说在变量存储方面,分为主内存和线程工作内存,也就是说,所有线程共享主内存,每个线程都有自己的工作内存.程序执行的时候是去工作内存里面取值还是去主内存里面取值

java指令重排序的问题

转载自于:http://my.oschina.net/004/blog/222069?fromerr=ER2mp62C 指令重排序是个比较复杂.觉得有些不可思议的问题,同样是先以例子开头(建议大家跑下例子,这是实实在在可以重现的,重排序的概率还是挺高的),有个感性的认识 /** * 一个简单的展示Happen-Before的例子. * 这里有两个共享变量:a和flag,初始值分别为0和false.在ThreadA中先给 a=1,然后flag=true. * 如果按照有序的话,那么在ThreadB