说在开头:关于哲学
在《东邪西毒》电影里欧阳锋说:“看来你的年纪也有四十出头了,这四十多年来,总有些事你是不愿再提,或是有些人不想再见,有的人曾经对不起你,也许你想过杀了他们,但是你不敢。”
我们大部分人在经历了这么多年社会沉浮之后,总会有很多自己无法改变的不如意,举个栗子:发小从小就没你优秀,但是人家生来就是富二代,他的人生起点可能就是你人生的终点目标;同学上学时成绩一般,毕业后进入小公司,没想几年后公司上市发了大财;或则一起进公司且水平差不多的同事没过几年就当了你领导的领导等等。我们渐渐就会产生一个疑问:努力真的能改变个人的命运么?如果答案是肯定的,那为什么没我努力、聪明的人看起来得到了更好的结果呢?如果是否定的,那这一切都是命中注定的么?然后在亲历了同龄亲朋的离世之后,突然发现死亡距离我似乎已不远了,在一刹那感悟到了“人生无常”(或许这才是人生的本质呢?)。
在经历了种种痛苦和挫折之后,我们开始动摇原来信念,并追问我们的一生到底在追求什么?似乎所有的一切又只不过是过眼云烟,如果每个人都注定要死去,而且人死就如灯灭(没有佛祖、没有玉皇大帝,也没有上帝),那么我们就会回过头来思考:现在活着的意义又是什么呢?慢慢地这些困惑就不自觉爬上了我们的头脑。
当我们开始真正恐惧死亡(我们大多数人是无神论者)的时候,就不得不思考这些问题了。
我们希望通过思考和认知这个世界,来判断个人存在的意义:到这个世界是茫无目的(偶然的)还是带着使命的(必然的);是能够自由做出选择(自由意志)还是一切都已确定(决定论)。而我们长期以来所接受的科学和唯物主义教育,自然且必然地得到了虚无主义(人死如灯灭)和决定论(如果所有粒子的运动是受到严格的法则限制,那么宇宙大爆炸之后的任何事情是既定的,就像看一部电影,我们虽然还没看完,但是中间每个细节(任何运动甚至思想)和结局已经确定了)。我想这引起了我的不安(其实吧,这也不关我个人啥事,除了上帝谁还不一样哪~)。
后来我苦思冥想了些狗屁倒灶的东西,直到看了一些哲学介绍的书,发现我那些自以为逻辑自洽的想法都能在先贤们的哲学理论中找到类似的思想。我突然懵逼了下:啊,我有这么牛B么?但反过来想了下:不对啊,我对世界的认知,只不过是哲学先贤们思想的边边角角混合而成的大杂烩嘛。虽然我从没拜读过他们的大作,但是他们的思想却通过各种途径影响了我,我甚至还自以为是靠自己脑壳凭空想出来的,这个事情震惊了我:无知!
我们谈起几千年的古人,很多人想象到的是一群愚昧且低智的人,事实上古人的智力水平并不比现代人要低(我们只是站在了历史巨人的肩膀上),而且古代哲学家们对问题的思考往往更深入。我相信绝大多数(给古人留点余地,给自己留点希望)古代的思想家,哲学家的智力水平要远高于我,我们普通人绝大部分(给古人留点余地,给自己留点希望)关于世界和人生的想法,古代先贤们已经思考过,并形成了系统的理论。那我为什么还要这么焦虑的呢?先看一看人类几千年来那些最聪明的大脑袋们的思想,不比自己一个人瞎捉摸好上一万倍么(杨绛先生对我说:你的问题主要在于读书不多而想得太多)?如果他们也想不出完美的答案,我似乎也只能认命,但万一从中找到了我认为的完美答案,那不就赚大了。
关于命运,最后再分享一部动画片:《功夫熊猫1》是标准漂亮国电影套路的第一部:英雄的诞生(宿命)。如果“我”是命中注定的“神龙大侠”,就自然而然的能成为“神龙大侠”么?答案是否定的。那熊猫阿宝被乌龟指定为神龙大侠,就完全是概率事件么?答案也是否定的(《功夫熊猫3》有对这个问题的解答)。那命运或则宿命到底是什么?乌龟给了一个答案:无论你做了什么,那个种子还是会长成桃树,你可能想要苹果或桔子,可你只能得到桃子。当时 “小熊猫师傅”和荧幕前的我都一脸闷逼,没明白乌龟大师打的什么哑语,直到熊猫拿到了“神龙秘笈”:传说得到后能成为真正的“神龙大侠”。但是师傅、五侠以及残豹看了之后都是一头懵逼,只有熊猫阿宝窥得了秘笈的真谛:其他人看到的神龙秘笈都是“空白”,而只有熊猫阿宝看到的是他自己。不错,“神龙秘笈”的秘笈是要大家接受并相信自己:如果命中注定你是“神龙大侠”,那就去做最好的自己,你就是“神龙大侠”。
我相信我们每个人都有自己的命运,但我们并不知道自己是否会成为“神龙大侠”,但这并不重要。正如《功夫熊猫1》中乌龟大师所说:昨天已经过去,明天一切未知,而今天是上天给予的一个礼物,这就是为什么我们称今天(Present)叫做礼物(Present)。希望我们每一天都能做最好的自己,成为“我的世界”中的“神龙大侠”。
好,那么接下来我们先从2000多年前的古希腊开始,先看一看西方哲学家们的智慧(注:后续所有关于哲学的内容都只是搬运,莫问我,问自己。)
4,TLP总线事务
4.1 Non-Posted 事务
如下图所示为存储器读(Memory Read)请求,这个请求由EP发往系统存储器,在存储器读请求 TLP 中有一个很重要的部分,即目标地址:一个存储器请求的地址可以是 32 位或者 64 位的,这也决定了数据包的路由方式。在如下示例中,这个请求事务通过两个Switch的路由后向上转发给了目标设备:RC,而当RC对TLP请求包进行译码,并识别出了数据包中的地址指向了系统存储器,它将从系统存储器(Memory)中取出EP所请求的数据;同时为将从内存中取出来的这些数据返回给源设备,RC端口的事务层将生成足够多的TLP完成包(CplD),这些完成包足以将源设备所请求的全部数据都发送回去。
——PCIe 中规定一个数据包中数据荷载最大为 4KB,但是实际设计的设备中使用的数据负载一般会比 4KB 小,因此需要多个完成包(CplD)才能将较大的数据返回给源设备。
这些完成包(CplD)内包含了路由信息,用于将它们路由回到源设备,这是因为源设备在原先的请求包中就附带了需要返回的地址信息,所以目标设备就可以将该地址放在完成包中作为完成包的路由信息。这个“返回地址”就是 PCI 中定义的设备 ID:1,发起方所属 PCI 总线在系统中的 PCI 总线(Bus)号;2,发起方在所属 PCI 总线上的设备(Device)号;3,发起方在所属设备中的功能(Function)号。这样的总线号、设备号、功能号组合起来的信息(缩写为 BDF)就是完成包用来返回到发起方的路由信息。
源设备可以同时有多个正在进行的拆分事务,它必须能够将输入的完成包和正确的请求关联起来。为了便于实现这一点,源设备在请求包中加入了一个值: Tag,该 Tag 对于每一个请求而言都是独一无二的,即每一个未完成的请求都有一个与其他未完成的请求不同的 Tag 号。目标设备会将这个事务的 Tag 拷贝进完成包中,这样源设备就可以快速地通过完成包中的 Tag 来将这个完成包与正确的请求关联起来,也就是找到了这个完成包是用来服务哪个请求的。
4.2 锁定读(Locked Read)
锁定的内存读是为了支持原子读-修改-写操作,如上一章节所述,所谓原子操作就是一种不可中断的事务,处理器使用这种事务来进行测试以及设置信号标志等任务。当测试和设置信号标正在进行中时,不允许其他对信号标的访问发生,不允许发生竞争的情况。为了避免发生竞争情况,处理器使用一个锁定指示符,来阻止总线上的其他事务,直到这个锁定的事务完成。
在PCI 协议规范中要求总线要能支持处理器要完成的事情,比如锁定事务。然而PCI 很少使用这种方式,最终这种处理器总线支持的东西大部分都被丢弃了。不过锁定周期依然存在,用以支持一些特殊情况,在PCIe 规范中也保留了下来,以实现对一些遗留事务的支持。也许是为了加速向 PCIe 的迁移,在新的 PCIe 设备中禁止接收锁定请求,只有那些传统设备才能够使用它。在上图所示的栗子中,源设备发出了一个 MRdLk(锁定读)来发起事务。根据定义这样的请求仅允许来自 CPU,因此在 PCIe 中仅有RC的端口可以发起这种事务。
这个锁定请求使用目标内存地址作为路由信息,并最终到达了传统设备的端点;当数据包经过沿途的每个路由设备时,数据包的Express端口被锁定,这意味着在被解锁之前这条路径都不能通过其他的数据包。
4.3 I/O和配置写(Non-Posted事务)
如下图所示,为一个Non-Posted 的I/O 写事务。I/O 操作流程的目的设备只能是一个传统端点(Legacy Endpoint)。请求包使用 IO 地址作为路由信息在Swtich中被路由转发,直到它到达目标EP。当目标设备接收到这个写请求包,它接收请求包内的数据并返回一个单独的不含有数据的完成包(Cpl),以此来确认自己收到了这个写请求包。完成包中的状态标识区域将会指示出是否发生了错误,如果发生了错误,发起方的软件需要对错误进行处理。
如果完成包(Cpl)中显示并未出现错误,那么源设备就认为写数据被成功的送达目标设备并成功写入,可以允许针对这一个完成方的指令序列继续向下执行;即需要确认这个写请求成功了才允许继续执行下一条指令。
——Non-Posted写事务的目的:不能仅仅知道数据被送往了目标设备,还必须要知道数据真的到达了目标设备才行,否则在逻辑上不能继续执行下一步。
4.4 Posted Write
存储器写(Memory Write)请求是Posted类型的,它不需要返回完成包(Cpl);一旦这种写请求包被发出,源设备不需要等到任何反馈就可以继续去进行下一条请求,不需要在返回完成包这件事情上花费时间和带宽;因此Posted写会比Non-Posted写更加快速且高效,并很好的提升了系统的性能。如下图所示,请求包使用存储器地址作为路由信息在系统中进行路由转发,并最终到达目标设备。一旦成功地将这个请求发送过去,该事务在链路上就已经结束了,链路上就可以继续传输其他的数据包了。Posted事务执行方法在提升效率的同时也舍弃了一些东西:目标设备不需要发送完成包,所以这也意味着它无法将错误报告给发起方;如果完成方发生了错误,那么它可以记录下这个错误,并向 RC 发一个消息来通知系统软件这些情况,但是源设备对这些是完全不知情的。
消息写(Message Writes)不同于我们前面所说的那些请求事务,它有好几种路由方法(前面的都是通过内存地址、IO 地址中的一种),在消息内部专门有一个区域来标识使用的是哪一种路由方式。举个栗子:有一些消息是Posted写请求,其目的地为特定的目标设备;有一些是RC向所有EP广播的请求;还有一些是EP发出的要自动路由到RC的请求。
消息在 PCIe 中很有用,它可以使得 PCIe 达到减少器件管脚数量的设计目标;使PCIe 不再需要边带信号(Side-Band Signal)管脚:在 PCI 中则是需要用边带信号来报告中断、功耗管理事项以及错误信息;而在 PCIe 中使用消息(Message)则可以将这些信息使用数据包来进行报告,数据包是直接在一般数据路径上传输的,因此不再需要额外的边带信号。
4.5 QoS服务质量
PCIe 从一开始就被设计为能够支持时间敏感(time‐sensitive)事务,举个栗子:视频、音频等应用程序,对于这些应用程序来说数据必须被及时传输才能保证数据的有效;这就是所谓的 QoS了,如之前所述,它需要通过一些机制来保证:
1. 每个数据包都被软件分配了一个优先级,这个优先级是通过设置数据包内的一个 3 比特的字段区域来进行标识的,称之为 TC (Traffic class,流量类型);
——一般来说,给一个数据包分配一个编号较大的 TC 表示希望给与这个数据包一个更高的优先级。
2. 使用多缓冲区,称为 VC (Virtual Channels, 虚拟通道),将其构建在硬件的每一个端口中,数据包会根据其 TC 值,来被放入相应的 VC 中(相应的缓存区中);
3. 对于一个端口来说,在一个时刻将会有多个缓冲区都存在可以进行传输的数据包,因此需要有对 VC 进行选择的仲裁逻辑;
4. Switch必须在竞争的各个输入端口间做出选择,以便访问相应端口的 VC ;这被称为端口仲裁,它可以由硬件来进行分配或是由软件来进行编程配置。
——这与第三点的不同之处在于,第3点是在一个端口内对多个 VC 进行仲裁选择,而端口仲裁是指各个端口已经选择好了此次访问的 VC,需要由交换机仲裁选择优先访问哪一个端口。
5. 所有的这些硬件部件都必须要能够支持系统对数据包进行优先级排序,如果一个这样的系统被正确的配置,它可以为给定的路径提供有保障的服务。
用如下图栗子来阐述了 QoS 的概念,其中的视频摄像头以及 SCSI 设备都需要向系统 DRAM 发送数据。这二者的不同之处在于:
1. 摄像头是对时效性要求严格的,如果传输路径无法满足摄像头的带宽,那么将出现丢帧的现象;
——系统需要保障摄像头所需要的最小带宽,否则它捕捉的视频画面将会出现不稳定。
2. SCSI 数据需要正确无误的进行传输,但对它来说传输需要的时间长短并不是那么重要。
所以当视频数据和SCSI数据同时需要被发送时,视频数据流应当具有更高的优先级。QoS 指的是PCIe系统的一种能力,为数据包分配不同优先级并将这些数据包按照确定性的延时、带宽在系统拓扑中进行路由的能力。
4.6 Flow Control(流量控制)
串行传输所使用的一个典型协议是:要求发送端仅在对端有足够的缓冲区接收时才发送数据包。这样的规定删去了并行总线上浪费性能的操作事件,(举个栗子: PCI 中允许进行的断开与重试( disconnects and retries),这使得这类问题在传输中得到消除。)但这样做的代价是,接收方必须足够频繁的报告它的可用缓冲区空间来避免不必要的传输停顿,而且这样的报告也需要占用接收端的一点带宽。
在 PCIe 中可用缓冲区空间的报告是由 DLLP(Data Link Layer Packet)来完成的,我们将在下一章中具体讲 DLLP。而不使用 TLP 的原因是为了避免可能出现的死锁现象,如果使用TLP则可能出现:一个设备 A 作为发送端需要对接收端 B 的可用缓冲区空间进行更新,但是当A的接收缓冲区满导致它又无法接收来自B的可用缓冲区报告,这样就出现了一个死循环。而 DLLP 可以不管缓冲区状态就直接进行收发,这样就避免了死锁的问题;所以流量控制协议由硬件级进行自动管理,对于软件来说是透明的。
如下图所示,接收端内的 VC 缓冲区内缓存了接收到的 TLP,并将自己的缓冲区大小通过流量控制 DLLP 来告知发送方。发送端将会跟踪接收端的可用缓冲区空间的值,并且不允许发出大于这个可用空间的数据包。当接收端对缓冲区中的 TLP 进行了处理,并将这个 TLP 移出了缓冲区,此时缓冲区中就空闲出了新的可用空间,那么接收端将会定期的发送流量控制更新 DLLP ,保持发送端能够获取到最新的可用空间的值。
写在最后
PCIe总线事务层的TLP包是最重要的数据传输方式,对理解PCIe的工作非常重要(当然前面的以及后面写的关于PCIe的相关章节,也都非常重要,说了一句废话~ 任何协议都是一个整体,想要理解它就需要完整的去了解它。)。希望通过本章的学习,胖友们能够大致了解TLP的大致内容,以及其工作方式,如果在工作中碰到了问题,需要更加详细的内容,则请参考PCIe协议。
本章部分相关内容和图片参考自:王齐 -《PCI Express 体系结构导读》,Mike Jackson-《PCI Express Technology 3.0》,知乎- LogicJitterGibbs《译文:PCI Express Technology 3.0》,《PCI Express Base Specification Reversion 3.0》。下一章《PCIe总线基础-数据链路层》。