在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. Linux-父进程wait回收子进程

    1.wait工作原理 (1).子进程结束时,系统向其父进程发送SIGCHILD信号 (2).父进程调用wait函数后阻塞 (3).父进程被SIGCHILD信号唤醒然后去回收僵尸子进程 (4).父子进程 ...

  2. \_\_module\_\_和\_\_class\_\_

    目录 __module__和__class__ 一.__module__ 二.通过字符导入模块 三.__class__ __module__和__class__ # lib/aa.py class C ...

  3. 7.docker file 语法

    详细文档 : https://docs.docker.com/engine/reference/builder/ 1. FROM   尽量使用官方的 image 作为 base image FROM ...

  4. 有关iOS热更新

    iOS热更新的几篇文章,看完这几篇,自己集成一下.下面说一下我集成时遇到的问题. 这是原作者的JSPatch的讲解的文章:<JSPatch – 动态更新iOS APP>.<JSPat ...

  5. MFC修改系统托盘的图标

    最近开始学习MFC,发现程序在任务栏,窗口和exe都使用的默认图标,那么,我们想使用自己的图标该如何做? 第一种方法: 1.我们将自己要使用的icon的图标导入项目中. 资源视图-->xx.rc ...

  6. Durandal入门

    参考:http://www.360doc.com/content/14/1223/11/13819_435123743.shtml 示例代码下载地址:http://durandaljs.com/ver ...

  7. Angular(二)

    Angular开发者指南(二)概念概述   template(模板):带有附加标记的模板HTMLdirectives(指令):使用自定义属性和元素扩展HTMLmodel(模型):用户在视图中显示的数据 ...

  8. BZOJ 2318: Spoj4060 game with probability Problem (概率dp)(博弈论)

    2318: Spoj4060 game with probability Problem Description Alice和Bob在玩一个游戏.有n个石子在这里,Alice和Bob轮流投掷硬币,如果 ...

  9. [LC] 22. Generate Parentheses

    Given n pairs of parentheses, write a function to generate all combinations of well-formed parenthes ...

  10. Leetcode9_回文数

    哈哈哈哈哈哈哈太开心了,今天的代码耗时和内存消耗比官方少了一半哈哈 (因为官方用C#写的,我用C++,手动狗头) 题目 判断一个整数是否是回文数.回文数是指正序(从左向右)和倒序(从右向左)读都是一样 ...