在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层窗口的大小要设置为序列号一半的原因。

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

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

    转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ...

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

    在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念. 在Java Concurrency in Practice中是这样定义线程安全的: ...

  3. 指令重排序 as-if-serial

    笔者认为看完一本书或刚要了解完一个知识点  最好自己先运行一些DEMO 自己尝试着去了解下各种意思  这样知识点最终一定是你的.靠死记硬背的讨论或简单的粗暴的看下资料 脑子里肯定还是一团浆糊. p.p ...

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

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

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

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  6. Java内存访问重排序笔记

    >>关于重排序 重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段. 重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境. > ...

  7. 指令重排序及Happens-before法则随笔

    指令重排序 对主存的一次访问一般花费硬件的数百次时钟周期.处理器通过缓存(caching)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定内存操作的顺序.也就是说,程序的读写操作不一定会按 ...

  8. 深入浅出Java并发包—指令重排序

    前面大致提到了JDK中的一些个原子类,也提到原子类是并发的基础,更提到所谓的线程安全,其实这些类或者并发包中的这么一些类,都是为了保证系统在运行时是线程安全的,那到底怎么样才算是线程安全呢? Java ...

  9. 轻松学JVM(二)——内存模型、可见性、指令重排序

    上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...

随机推荐

  1. Maven--Eclipse maven相关配置

    选择自己安装的 Maven 版本: 更改配置文件路径,这里选择自己安装的 Maven 下的配置文件,方便配置及统一控制:

  2. Python笔记_第一篇_面向过程_第一部分_7.文件的操作(.txt)

    在平时,我们不光要对程序内的代码进行输入和输出的操作,还要对程序外的文件进行和语言之间的交换.操作和运算.在基础部分,先讲解对于外部的.txt文件的操作. 第一部分 基本内容讲解 1.   什么是文件 ...

  3. HDU - 1754 线段树

    #include <algorithm> #include <iostream> #include<sstream> #include<cstring> ...

  4. python获取当前时间戳

    import time # 获取当前时间戳print(int(time.time()))

  5. ESLint javascript格式要求

    首行缩进2个空格 eslint: indent functionhello (name) { console.log('hi', name) } 字符串使用单引号(除了避免转义) eslint: qu ...

  6. SQL触发器笔记

    触发器(Trigger)是在对表进行插入.更新.删除等操作时自动执行的存储过程. 触发器是一种特殊的存储过程,它在执行语言事件时自动生效,采用事件驱动机制.当某个触发事件发生时,定义在触发器中的功能将 ...

  7. Keywords|Result|Final check

    科研论文写作 风格最好是excited,不要过于谦虚. Reference不要过多引用自己的paper,可以多引用本刊物的paper. Acknowledgement:感谢帮助input的人员,可以n ...

  8. ubuntu或者raspbian清理软件使用痕迹

    拿最常用的nginx举例 删除nginx–purge包括配置文件 sudo apt-get --purge remove nginx 开始使用上面这条,后来发现还是有很多相关联没有删除 首先需要停止n ...

  9. java 之断言

    今天用idea的智能提示冒出一个assert关键字,愣是没看懂!!!还是太菜了.上网查了一下,这个关键字是断言. 什么是断言? 我也说不清楚,反正就是对jvm的操作.java的错误分为两种,一种叫er ...

  10. hdu2876 Connections between cities(LCA倍增)

    图不一定联通,所以用并查集找各个联通块的祖先分别建图,之后就和LCA的步骤差不多了 #include<iostream> #include<cstring> #include& ...