炼数成金 门户 大数据 存储 查看内容

大话Queue、Buffer、Cache

2018-9-27 10:01| 发布者: 炼数成金_小数| 查看: 27216| 评论: 0|原作者: 冬瓜哥|来自: 大话存储

摘要: 队列用于两个模块(或者硬件模块,或者软件模块)之间传递消息,一般采用FIFO(先进先出)方式。下文中会解释这些消息里都是什么。在芯片内部,两个硬件模块(或者是CPU+固件,或者直接是组合逻辑电路)之间通常采用 ...

网络 Hadoop 计算机 硬件 芯片

这个问题似乎跨越了十几年甚至更长。而且跨越了IT领域后端前端,跨越了硬件和软件。

Queue
队列用于两个模块(或者硬件模块,或者软件模块)之间传递消息,一般采用FIFO(先进先出)方式。下文中会解释这些消息里都是什么。在芯片内部,两个硬件模块(或者是CPU+固件,或者直接是组合逻辑电路)之间通常采用寄存器~寄存器对连的方式来传递数据/信号,但是寄存器对连的话,每次只能往寄存器里放一条数据,如果两端步调不一致,你处理快我处理慢的话,自然就有需求形成一个队列,那就是排布多个寄存器形成一列,然后再加上用于记录这一列寄存器中数据保存到什么位置的队列指针寄存器。生产者将消息从队列尾部入队,更新写指针,消费者从队列头部读走消息,更新读指针。有限的队列槽位形成一个虚拟的环形,不断生产消费,当写指针追赶上读指针时,队列满,有专门寄存器的控制位记录这个状态,有些设计还会产生一个中断来通知生产者。

如果生产者和消费者各自处于不同时钟域,对寄存器的信号采样时刻点不同,会采到错误的值,此时需要使用异步FIFO。异步FIFO的关键是必须采用格雷码来编码队列指针,格雷码可以保证每次只翻转1bit,保证消费者读取指针时不会产生误判。关于同步异步FIFO、格雷码等更详细内容请见《大话计算机》第1章。

如果Queue容量较大,一般不采用寄存器来充当,因为寄存器成本太高,存储1bit需要几十个晶体管,寄存器是为了响应时钟下边沿将输入端数据瞬间锁定并输送到输出端的,内部逻辑比较复杂。而成本低一些,响应速度也可以做到一个时钟周期的另一种存储器则是SRAM。SRAM无法充当寄存器因为它不具备下沿锁定并输出的能力,无法用于组合逻辑之间的隔离。CPU内部的各级缓存其实都是SRAM,有些L4缓存甚至用DRAM。

芯片内两个模块之间到底在传些什么东西?举例来讲。Raid控制器是一个芯片,其内部有通用CPU+固件代码在运行总控逻辑,芯片内部后端有多个SAS通道控制器,固件控制着DMA控制器从Host端主存的队列(软队列,下文讲)中取回对应的指令包(由Raid卡驱动准备好,当然最终是app发起I/O调用,沿着bio、scsi协议栈一路下来,生成了i/o request,并通过queue_command()调用到Raid卡驱动注册的request_fn()回调函数,后者将这个request封装到一个大包中,包中同时还描述了该指令对应的数据所在的主存位置等信息),固件解析该指令包内容,提取出scsi指令,并通过芯片内部queue将指令入队,然后通知后端硬件模块处理该指令,假设该scsi指令是读指令,则通过队列下发给后端SAS通道控制器,后者封装sas帧并传送给硬盘执行,写回来的数据在被DMA到主存。

网卡、显卡同样是这种套路,网卡拿到的是以太网帧,显卡拿到的是drawcall指令包,FPGA也是这么玩,都一样。I/O套路详见请见《大话计算机》第7章。

其他硬件队列还有很多,比如CPU的取指令单元取回的指令就在一个FIFO队列中等待被译码执行。芯片内部可以说到处都是队列。

软件队列,那就必须位于SDRAM里,比如刚才说的,各种I/O设备的驱动初始化时一般都会初始化至少两个队列,一个用于下发I/O,一个用于接收I/O完成回执(由I/O设备亲自写入完成队列并中断)。在驱动里你会发现总有一个函数是类似init_queue、init_queuepair等名字的。底层基本上就是kmalloc之类分配一段连续的内核内存,并将队列基地址写入I/O设备相关寄存器中,让后者知道去哪找这个队列。每次下发I/O之后驱动将队列的写指针更新到I/O控制器的相关寄存器让后者知道host端准备了多少i/o了。

所以,队列,就是一个队列,如其名一样,这个没有什么歧义。FIFO队列,顺着来,也有不顺着来的,不过一般不那么用。关于队列还有很多高级内容,比如VOQ(virtual output queue)、Virtual Channel等Qos方面概念,就不展开讲了,这些在《大话计算机》中第1/6/7章都有场景和原理介绍。

Buffer
Buffer,缓冲,不要叫它缓存,就是缓冲。就像不要把Fibre Channel叫做光纤网络或者光网络一样,说出去丢人。以太网也可以用光纤,光网络是指OTN,有些名词不要乱用。

缓冲缓的什么冲?就是两个模块之间生产和消费速度不匹配,导致积压。Queue不就是干这个的么?没错,很多时候,Queue就是Buffer,Buffer就是Queue,Buffer可以认为是一个队列深度很高的队列,能容纳相当量的数据。缓冲里的内容将会在一定时间内迅速消耗掉,而不是长期呆在里面,否则就成了缓存了。

Cache
缓存本质上是一块存储器,追求速度的硬件中一般采用SRAM来充当,比如CPU的各级缓存。不追求速度的可以用DRAM来盛放,比如Raid卡上的SDRAM缓存。缓存可以兼顾缓冲的作用,数据往缓存里仍,也可以匹配两边处理速度的差异,那是当然。但是缓存还有另一个作用,就是提升命中率。假设有个i/o写入0号扇区,而0好扇区的数据此时正在缓存里还没有被写入硬盘,那么缓存中的0扇区数据将被替换为写下来的这份,并继续留在缓存里。如果是缓冲,则缓冲队列头部假设已经存在之前写过的0扇区数据,又写了一次的话,那么之前的数据不会被删掉,会继续写到硬盘,后来新写入的0扇区数据也会被再写入硬盘一次。或许有些buffer有高级功能,能够发现这种重复而剔除掉之前的写数据只保留的,但是的数据一定不会长久待在buffer赖着不走。而缓存最关键的区别就在于数据会赖着不走,为什么,因为可能马上会被访问到。

既然如此,缓存容量有限,让谁待在里面,谁不待在里面,就是个需要决策的问题,让频繁访问的数据待在缓存会提升命中率,缓存就是看命中率,为命中率而生。于是有各种缓存替换算法比如LRU等,各类替换算法,以及缓存更高级的知识比如多核心缓存一致性等详见《大话计算机》第6章。(冬瓜哥总结19个关于缓存的知识点,写成了19节,美其名曰  “缓存19式”)。

有人说,“写buffer,读cache”,意思是指生产者将数据写到哪里?一般是写到buffer里,消费者从哪里读数据?一般是从cache读,也就是说buffer用于写加速,而cache用于读加速。这个说法并不准确,buffer一样用于读加速,经典的比如CPU内部的取指令缓冲,取指令单元从下层cache中取回若干条指令放到这个缓冲FIFO队列中,用于译码单元载入,这就是一段缓冲,但是确是用于读加速的,因为直接从cache中读还是不方便,速度也慢,做不到一个周期就载入指令,目前L1缓存起码也得经过两三个周期才能把数据读上来。

那么再看看,cache一定就是加速读操作的么?那可不一定。CPU算出数值之后,store指令可以将其存回主存,这个数据其实是被存到了L1 cache中存放,这个过程是典型的写加速,当然这个数据也用于后续访问的读加速。

所以,buffer和cache的区别并不在于读还是写,而是看它的目的只是为了缓冲,匹配速率不一致,还是为了缓存,提升命中率。

声明:文章收集于网络,如有侵权,请联系小编及时处理,谢谢!

欢迎加入本站公开兴趣群
软件开发技术群
兴趣范围包括:Java,C/C++,Python,PHP,Ruby,shell等各种语言开发经验交流,各种框架使用,外包项目机会,学习、培训、跳槽等交流
QQ群:26931708

Hadoop源代码研究群
兴趣范围包括:Hadoop源代码解读,改进,优化,分布式系统场景定制,与Hadoop有关的各种开源项目,总之就是玩转Hadoop
QQ群:288410967 
1

鲜花

握手

雷人

路过

鸡蛋

刚表态过的朋友 (1 人)

相关阅读

最新评论

热门频道

  • 大数据
  • 商业智能
  • 量化投资
  • 科学探索
  • 创业

即将开课

热门文章

     

    GMT+8, 2018-12-17 01:41 , Processed in 0.154164 second(s), 24 queries .