CSAPP 第六章 存储器层次结构

存储器层次结构

课程引出——

到目前为止,在对系统的研究中,我们依赖于一个简单的计算机系统模型,CPU执行指令,而存储器系统为CPU存放指令和数据。在简单模型中,存储器系统是一个线性的字节数组,而CPU能够在一个常数时间内访问每个存储器位置。虽然迄今为止这都是一个有效的模型,但是它没有反映现代系统实际工作的方式。
实际上,存储器系统(memorysystem)是一个具有不同容量、成本和访问时间的存储设备的层次结构。CPU寄存器保存着最常用的数据。靠近CPU的小的、快速的高速缓存存储器(cache memory)作为一部分存储在相对慢速的主存储器(main memory)中数据和指令的缓冲区域。主存缓存存储在容量较大的、慢速磁盘上的数据,而这些磁盘常常又作为存储在通过网络连接的其他机器的磁盘或磁带上的数据的缓冲区域。
在本章中,我们会看看基本的存储技术——SRAM存储器、DRAM存储器、ROM存储器以及旋转的和固态的硬盘——并描述它们是如何被组织成层次结构的。特别地,我们将注意力集中在高速缓存存储器上,它是作为CPU和主存之间的缓存区域,因为它们对应用程序性能的影响最大。我们向你展示如何分析C程序的局部性,并且介绍改进你的程序中局部性的技术。你还会学到一种描绘某台机器上存储器层次结构的性能的有趣方法,称为“存储器山(memory mountain)”,它展示出读访问时间是局部性的一个函数

6.1 存储技术

6.1.1 随机访问存储器(RAM)

随机访问存储器(Random-Access Memory,RAM)分为两类**:静态的动态的**。
静态RAM(SRAM)比动态RAM(DRAM)更快,但也贵得多。SRAM用来作为高速缓存存储器,既可以在CPU芯片上,也可以在片下。
DRAM用来作为主存以及图形系统的缓冲区。典型地,一个桌面系统的SRAM不会超过几兆字节,但是DRAM却有几百或几千兆字节

1.静态 RAM

SRAM将每个位存储在一个双稳态的(bistable)存储器单元里。每个单元是用一个六个晶体管电路来实现的。这个电路有这样一个属性,它可以无限期地保持在两个不同的电压配置(configuration)或状态(state)之一。其他任何状态都是不稳定的——从不稳定状态开始,电路会迅速地转移到两个稳定状态中的一个。这样一个存储器单元类似于图6-1中画出的倒转的钟摆。
在这里插入图片描述

2.动态 RAM

DRAM将每个位存储为对一个电容的充电。这个电容非常小,通常只有大约30毫微法拉(femtofarad)–30X10-15法拉。不过,回想一下法拉是一个非常大的计量单位DRAM存储器可以制造得非常密集——每个单元由一个电容和一个访问晶体管组成。
但是,与SRAM不同,DRAM存储器单元对干扰非常敏感。当电容的电压被扰乱之后,它就永远不会恢复了。暴露在光线下会导致电容电压改变。实际上,数码照相机和摄像机中的传感器本质上就是DRAM单元的阵列。
很多原因会导致漏电,使得DRAM单元在10~100毫秒时间内失去电荷。

3.动静态随机存储器对比

只要有供电,SRAM就会保持不变与DRAM不同,它不需要刷新。SRAM的存取比DRAM快。SRAM对诸如光和电噪声这样的干扰不敏感。代价是SRAM单元比DRAM单元使用更多的晶体管,因而密集度低,而且更贵,功耗更大。
在这里插入图片描述

4.传统的 DRAM

DRAM 芯片中的单元(位)被分成d个超单元(super cell),每个超单元都由w个DRAM单元组成。一个dXw的 DRAM总共存储了dw位信息。超单元被组织成一个r行c列的长方形阵列,这里rc=d。每个超单元有形如(i,j)的地址,这里i表示行,而j表示列。例如,图6-3展示的是一个16X8的DRAM芯片的组织,有d=16个超单元,每个超单元有w=8位,r=4行,c=4列。带阴影的方框表示地址(2,1)处的超单元。信息通过称为引脚(pin)的外部连接器流人和流出芯片。每个引脚携带一个1位的信号。图6-3给出了两组引脚:8个data引脚它们能传送一个字节到芯片或从芯片传出一个字节,以及2个addr引脚,它们携带2位的行和列超单元地址。其他携带控制信息的引脚没有显示出来。
在这里插入图片描述
每个 DRAM 芯片被连接到某个称为内存控制器(memorycontroller)的电路,这个电路可以一次传送w位到每个DRAM芯片或一次从每个DRAM 芯片传出w位。为了读出超单元(i,i)的内容,内存控制器将行地址i发送到DRAM,然后是列地址j。DRAM 把超单元(i,j)的内容发回给控制器作为响应。行地址i称为RAS(Row Access Strobe,行访问选通脉冲)请求。列地址i称为CAS(ColumnAccessStrobe,列访问选通脉冲)请求。注意,RAS和CAS请求共享相同的DRAM地址引脚。

例如,要从图6-3中16X8的DRAM中读出超单元(2,1),内存控制器发送行地址2,如图6-4a所示。DRAM的响应是将行2的整个内容都复制到一个内部行缓冲区。接下来,内存控制器发送列地址1,如图6-4b所示。DRAM的响应是从行缓冲区复制出超单元(2,1)中的8位,并把它们发送到内存控制器
在这里插入图片描述

5.内存模块

DRAM 芯片封装在内存模块(memory module)中,它插到主板的扩展上。Corei7系统使用的240个引脚的双列直插内存模块(Dual Inline Memory Module,DIMM),它以64位为块传送数据到内存控制器和从内存控制器传出数据。
图6-5展示了一个内存模块的基本思想。示例模块用8个64Mbit的8MX8的DRAM芯片,总共存储64MB(兆字节),这8个芯片编号为0~7。每个超单元存储主存的一个字节,而用相应超单元地址为(i,j)的8个超单元来表示主存中字节地址A处的64位字。在图6-5的示例中,DRAM0存储第一个(低位)字节,DRAM1存储下一个字节,依此类推要取出内存地址A处的一个字,内存控制器将A转换成一个超单元地址(i,i),并将它发送到内存模块,然后内存模块再将i和i广播到每个DRAM。作为响应,每个DRAM输出它的(i,i)超单元的8位内容。模块中的电路收集这些输出,并把它们合并成一个64位字,再返回给内存控制器。
通过将多个内存模块连接到内存控制器,能够聚合成主存。在这种情况中,当控制器收到一个地址A时,控制器选择包含A的模块k,将A转换成它的(i,i)的形式,并将(i,j)发送到模块k。

在这里插入图片描述

6.增强的 DRAM

有许多种DRAM存储器,而生产厂商试图跟上迅速增长的处理器速度,市场上就会定期推出新的种类。每种都是基于传统的DRAM单元,并进行一些优化,提高访问基本DRAM单元的速度。

  • 快页模式DRAM(Fast Page Mode DRAM,FPM DRAM)。 传统的DRAM 将超单元的一整行复制到它的内部行缓冲区中,使用一个,然后丢弃剩余的。FPMDRAM允许对同一行连续地访问可以直接从行缓冲区得到服务,从而改进了这一点。例如,要从一个传统的DRAM的行i中读4个超单元,内存控制器必须发送4个RAS/CAS请求,即使是行地址i在每个情况中都是一样的。要从一个FPMDRAM
    的同一行中读取超单元,内存控制器发送第一个RAS/CAS请求,后面跟三个CAS请求。初始的RAS/CAS请求将行i复制到行缓冲区,并返回CAS寻址的那个超单元。接下来三个超单元直接从行缓冲区获得,因此返回得比初始的超单元更快。
  • 扩展数据输出 DRAM(Extended Data Out DRAM,EDO DRAM)。
    FPM DRAM 的一个增强的形式,它允许各个CAS信号在时间上靠得更紧密一点。
  • 同步 DRAM(SynchronouS DRAM,SDRAM)。 就它们与内存控制器通信使用一组显式的控制信号来说,常规的、FPM和EDODRAM都是异步的。SDRAM用与驱动内存控制器相同的外部时钟信号的上升沿来代替许多这样的控制信号。我们不会深入讨论细节,最终效果就是SDRAM能够比那些异步的存储器更快地输出它的超单元的内容。
  • 双倍数据速率同步DRAM(Double Data-Rate Synchronous DRAM,DDR SDRAM) DDRSDRAM是对SDRAM的一种增强,它通过使用两个时钟沿作为控制信号从而使DRAM的速度翻倍。不同类型的DDRSDRAM是用提高有效带宽的很小的预取缓冲区的大小来划分的:DDR(2位)、DDR2(4位)和DDR(8位)。
  • 视频RAM(VideORAM,VRAM)。 它用在图形系统的帧缓冲区中。VRAM的思想与FPMDRAM类似。两个主要区别是:
    1)VRAM的输出是通过依次对内部缓冲区的整个内容进行移位得到的;
    2)VRAM允许对内存并行地读和写。因此,系统可以在写下一次更新的新值(写)的同时,用缓冲区中的像素刷屏幕(读)。
7.非易失性存储器(ROM)

如果断电,DRAM和SRAM会丢失它们的信息,从这个意义上说,它们是易失的(volatile)。另一方面,非易失性存储器(nonvolatile memory)即使是在关电后,仍然保存着它们的信息。现在有很多种非易失性存储器。由于历史原因,虽然ROM中有的类型既可以读也可以写,但是它们整体上都被称为只读存储器(Read-Only Memory,ROM)
ROM是以它们能够被重编程(写)的次数和对它们进行重编程所用的机制来区分的。
PROM(Programmable ROM,可编程ROM)只能被编程一次。PROM的每个存储器单元有一种熔丝(fuse),只能用高电流熔断一次。
可擦写可编程ROM(Erasable Programmable ROM,EPROM)有一个透明的石英窗口,允许光到达存储单元。紫外线光照射过窗口,EPROM单元就被清除为0。对EPROM编程是通过使用一种把1写人EPROM的特殊设备来完成的。EPROM能够被除和重编程的次数的数量级可以达到1000次。
电子可擦除PROM(ElectricallyErasablePROM,EEPROM)类似于EPROM,但是它不需要一个物理上独立的编程设备,因此可以直接在印制电路卡上编程。EEPROM能够被编程的次数的数量级可以达到10’次。
闪存(flash memory)是一类非易失性存储器,基于EEPROM,它已经成为了一种重要的存储技术。闪存无处不在,为大量的电子设备提供快速而持久的非易失性存储,包括数码相机、手机、音乐播放器、PDA和笔记本、台式机和服务器计算机系统。
在6.1.3节中,我们会仔细研究一种新型的基于闪存的磁盘驱动器,称为固态硬盘(Solid StateDisk,SSD),它能提供相对于传统旋转磁盘的一种更快速、更强健和更低能耗的选择。
存储在ROM设备中的程序通常被称为固件(firmware)。当一个计算机系统通电以后它会运行存储在ROM中的固件。一些系统在固件中提供了少量基本的输人和输出函数——例如PC的BIOS(基本输人/输出系统)例程。复杂的设备,像图形卡和磁盘驱动控制器,也依赖固件翻译来自CPU的I/O(输人/输出)请求。

8.ROM、RAM、磁盘、内存区别

RAM和ROM都是作为内存使用的,而不是作磁盘。RAM(Random Access Memory)是一种易失性存储器,用于临时存储计算机正在运行的数据和程序。它是计算机的主要工作内存,可以快速读写数据,但断电后数据会丢失。

ROM(Read-Only Memory)是一种只读存储器,用于存储固定的数据和程序。它通常用于存储计算机的基本输入/输出系统(BIOS)和其他固化的程序,这些程序在计算机启动时被加载。与RAM不同,ROM的内容在断电后仍然保持不变。

需要注意的是,磁盘是一种永久性存储设备,用于长期存储数据和程序。它与RAM和ROM不同,磁盘上的数据在断电后仍然保持不变。

9.访问主存

数据流通过称为总线(bus)的共享电子电路在处理器和DRAM主存之间来来回回。每次CPU和主存之间的数据传送都是通过一系列步骤来完成的,这些步骤称为总线事务(bus transaction)。读事务(read transaction)从主存传送数据到CPU。写事务(write transaction)从CPU传送数据到主存。总线是一组并行的导线,能携带地址、数据和控制信号。取决于总线的设计,数据和地址信号可以共享同一组导线,也可以使用不同的。同时,两个以上的设备也能共享同一总线。控制线携带的信号会同步事务,并标识出当前正在被执行的事务的类型。
例如,当前关注的这个事务是到主存的吗?还是到诸如磁盘控制器这样的其他I/O设备?这个事务是读还是写?总线上的信息是地址还是数据项?
图6-6展示了一个示例计算机系统的配置。主要部件是CPU芯片、我们将称为I/O桥接器(I/O bridge)的芯片组(其中包括内存控制器),以及组成主存的 DRAM 内存模块。这些部件由一对总线连接起来,其中一条总线是系统总线(systembus),它连接CPU和I/0桥接器,另一条总线是内存总线(memorybus),它连接I/0桥接器和主存。I/O桥接器将系统总线的电子信号翻译成内存总线的电子信号。正如我们看到的那样,I/O桥也将系统总线和内存总线连接到I/O总线,像磁盘和图形卡这样的I/O设备共享I/O总线。不过现在,我们将注意力集中在内存总线上。
在这里插入图片描述
考虑当CPU执行一个如下加载操作时会发生什么movq A,%rax
这里,地址A的内容被加载到寄存器%rax中。CPU芯片上称为总线接口(bus interface)的电路在总线上发起读事务。
读事务是由三个步骤组成的。首先,CPU将地址A放到系统总线上。I/O桥将信号传递到内存总线(图6-7a)。接下来,主存感觉到内存总线上的地址信号,从内存总线读地址,从DRAM取出数据字,并将数据写到内存总线。I/0桥将内存总线信号翻译成系统总线信号,然后沿着系统总线传递(图6-7b)。最后,CPU感觉到系统总线上的数据,从总线上读数据,并将数据复制到寄存器rax(图6-7c)。
在这里插入图片描述
反过来,当CPU执行一个像下面这样的存储操作时
movq %rax,A
这里,寄存器%rax的内容被写到地址A,CPU发起写事务。同样,有三个基本步骤。首先CPU将地址放到系统总线上。内存从内存总线读出地址,并等待数据到达(图6-8a)。接下来,CPU将rax中的数据字复制到系统总线(图6-8b)。最后,主存从内存总线读出数据字,并且将这些位存储到DRAM中。

6.1.2 磁盘存储

磁盘是广为应用的保存大量数据的存储设备,存储数据的数量级可以达到几百到几千千兆字节,而基于RAM的存储器只能有几百或几千兆字节。不过,从磁盘上读信息的时间为毫秒级,比从DRAM读慢了10万倍,比从SRAM读慢了100万倍。

1.磁盘构造

磁盘是由盘片(platter)构成的。每个盘片有两面或者称为表面(surface),表面覆盖着磁性记录材料。盘片中央有一个可以旋转的主轴(spindle),它使得盘片以固定的旋转速率(rotational rate)旋转,通常是5400~15 000 转每分钟(Revolution Per Minute,RPM)。磁盘通常包含一个或多个这样的盘片,并封装在一个密封的容器内。
图6-9a展示了一个典型的磁盘表面的结构。每个表面是由一组称为磁道(track)的同心圆组成的。每个磁道被划分为一组扇区(sector)。每个扇区包含相等数量的数据位(通常是512字节),这些数据编码在扇区上的磁性材料中。扇区之间由一些间隙(gap)分隔开这些间隙中不存储数据位。间隙存储用来标识扇区的格式化位。磁盘是由一个或多个叠放在一起的盘片组成的,它们被封装在一个密封的包装里,如图6-9b所示。整个装置通常被称为磁盘驱动器(diskdrive),我们通常简称为磁盘(disk)有时,我们会称磁盘为旋转磁盘(rotatingdisk),以使之区别于基于闪存的固态硬盘(SSD),SSD是没有移动部分的。
磁盘制造商通常用术语柱面(cylinder)来描述多个盘片驱动器的构造,这里,面是所有盘片表面上到主轴中心的距离相等的磁道的集合。例如,如果一个驱动器有三个盘片和六个面,每个表面上的磁道的编号都是一致的,那么柱面k就是6个磁道k的集合。
在这里插入图片描述

2.磁盘操作

磁盘用读/写头(read/writehead)来读写存储在磁性表面的位,而读写头连接到一个传动臂(actuator arm)一端,如图6-10a所示。通过沿着半径轴前后移动这个传动臂,驱动器可以将读/写头定位在盘面上的任何磁道上。这样的机械运动称为寻道(seek)。一旦读/写头定位到了期望的磁道上,那么当磁道上的每个位通过它的下面时,读/写头可以感知到这个位的值(读该位),也可以修改这个位的值(写该位)。有多个盘片的磁盘针对每个盘面都有一个独立的读/写头,如图6-10b所示。读/写头垂直排列,一致行动。在任何时刻,所有的读/写头都位于同一个柱面上。
在这里插入图片描述

4.逻辑磁盘块

正如我们看到的那样,现代磁盘构造复杂,有多个盘面,这些盘面上有不同的记录区。为了对操作系统隐藏这样的复杂性,现代磁盘将它们的构造呈现为一个简单的视图,一个B个扇区大小的逻辑块的序列,编号为0,1,…,B-1。磁盘封装中有一个小的硬件/固件设备,称为磁盘控制器,维护着逻辑块号实际(物理)磁盘扇区之间的映射关系。当操作系统想要执行一个 I/O操作时,例如读一个磁盘扇区的数据到主存,操作系统会发送一个命令到磁盘控制器,让它读某个逻辑块号。控制器上的固件执行一个快速表查找,将个逻辑块号翻译成一个(盘面,磁道,扇区)的三元组,这个三元组唯一地标识了对应的物理扇区。控制器上的硬件会解释这个三元组,将读/写头移动到适当的柱面,等待扇区移动到读/写头下,将读/写头感知到的位放到控制器上的一个小缓冲区中,然后将它们复制到主存中。

5.连接 l/0 设备

例如图形卡、监视器、鼠标、键盘和磁盘这样的输人/输出(I/O)设备,都是通过 I/O总线,例如 Intel的外围设备互连(Peripheral Component Interconnect,PCI)总线连接到CPU和主存的。系统总线和内存总线是与CPU相关的,与它们不同,诸如PCI这样的I/0总线设计成与底层CPU无关。例如,PC和Mac都可以使用PCI总线。图6-11展示了一个典型的I/O总线结构,它连接了CPU、主存和I/O设备。虽然I/O总线比系统总线和内存总线慢,但是它可以容纳种类繁多的第三方I/O设备。例如,在图6-11中,有三种不同类型的设备连接到总线——

  • 通用串行总线(Universal Serial Bus,USB)
    USB控制器是一个连接到 USB 总线的设备的中转机构,USB总线是一个广泛使用的标准,连接各种外围I/0设备,包括键盘、鼠标、调制解调器、数码相机、游戏操纵杆、打印机、外部磁盘驱动器和固态硬盘。USB 3.0总线的最大带宽为625MB/S。USB3.1总线的最大带宽为1250MB/s。

  • 图形卡(或适配器)包含硬件和软件逻辑,它们负责代表CPU在显示器上画像素

  • 主机总线适配器
    主机总线适配器将一个或多个磁盘连接到I/O总线,使用的是一个特别的主机总线接口定义的通信协议。两个最常用的这样的磁盘接口是SCSI(读作“scuzzy”)和SATA(读作“sat-uh”。SCSI磁盘通常比SATA驱动器更快但是也更贵。SCSI主机总线适配器(通常称为SCSI控制器)可以支持多个磁盘驱动器,与SATA适配器不同,它只能支持一个驱动器。
    在这里插入图片描述
    其他的设备,例如网络适配器,可以通过将适配器插入到主板上空的扩展槽中,从而连接到I/O总线,这些插槽提供了到总线的直接电路连接。

6.访问磁盘

虽然详细描述I/O设备是如何工作的以及如何对它们进行编程超出了我们讨论的范围,但是我们可以给你一个概要的描述。例如,图6-12总结了当CPU从磁盘读数据时发生的步骤。

CPU使用一种称为内存映射I/O(memory-mappedI/O)的技术来向I/O设备发射命令(图6-12a)。在使用内存映射I/0的系统中,地址空间中有一块地址是为与I/O设备通信保留的。每个这样的地址称为一个I/〇端口(1/Oport)。当一个设备连接到总线时,它与一个或多个端口相关联(或它被映射到一个或多个端口)。
在这里插入图片描述
来看一个简单的例子,假设磁盘控制器映射到端口0xa0。随后,CPU可能通过执行三个对地址 0xa0的存储指令,发起磁盘读:第一条指令是发送一个命令字,告诉磁盘发起一个读,同时还发送了其他的参数,例如当读完成时,是否中断CPU(我们会在8.1节中讨论中断)。第二条指令指明应该读的逻辑块号。第三条指令指明应该存储磁盘扇区内容的主存地址。当CPU发出了请求之后,在磁盘执行读的时候,它通常会做些其他的工作。回想一下,一个1GHz的处理器时钟周期为1ns,在用来读磁盘的16ms时间里,它潜在地可能执行1600万条指令。在传输进行时,只是简单地等待,什么都不做,是一种极大的浪费。在磁盘控制器收到来自CPU的读命令之后,它将逻辑块号翻译成一个扇区地址,读该扇区的内容,然后将这些内容直接传送到主存,不需要CPU的涉(图6-12b)。
设备可以自己执行读或者写总线事务而不需要CPU干涉的过程,称为直接内存访问(Direct Memory Access,DMA)。这种数据传送称为DMA传送(DMA transfer)。在DMA传送完成,磁盘扇区的内容被安全地存储在主存中以后,磁盘控制器通过给CPU发送一个中断信号来通知CPU(图6-12c)。基本思想是中断会发信号到CPU芯片的一个外部引脚上。这会导致CPU暂停它当前正在做的工作,跳转到一个操作系统例程,这个程序会记录下I/O已经完成,然后将控制返回到CPU被中断的地方。

6.1.3 固态硬盘

固态硬盘(Solid State Disk,SSD)是一种基于闪存的存储技术 ,在某些情况下是传统旋转磁盘的极有吸引力的替代产品。图6-13展示了它的基本思想。SSD封装插到 I/O总线上标准硬盘插槽(通常是USBSATA)中,行为就和其他硬盘一样,处理来自CPU的读写逻辑磁盘块的请求。一个SSD封装由一个或多个闪存芯片和闪存翻译层(flashtranslationlayer)组成,闪存芯片替代传统旋转磁盘中的机械驱动器,而闪存翻译层是一个硬件/固件设备,扮演与磁盘控制器相同的角色,将对逻辑块的请求翻译成对底层物理设备的访问。
在这里插入图片描述

6.2局部性

编写良好的计算机程序常常具有良好的局部性(locality)。也就是,它们倾向于引用邻近于其他最近引用过的数据项的数据项,或者最近引用过的数据项本身。这种倾向性,被称为局部性原理(principleoflocality),是一个持久的概念,对硬件和软件系统的设计和性能都有着极大的影响。
局部性通常有两种不同的形式:时间局部性(temporallocality)和空间局部性(spatiallocality)。在一个具有良好时间局部性的程序中,被引用过一次的内存位置很可能在不远的将来再被多次引用。在一个具有良好空间局部性的程序中,如果一个内存位置被引用了一次,那么程序很可能在不远的将来引用附近的一个内存位置。程序员应该理解局部性原理,因为一般而言,有良好局部性的程序比局部性差的程序运行得更快。

6.2.3局部性小结

在这一节中,我们介绍了局部性的基本思想,还给出了量化评价程序中局部性的一些简单原则:

  • 重复引用相同变量的程序有良好的时间局部性。
  • 对于具有步长为k的引用模式的程序,步长越小,空间局部性越好。具有步长为的引用模式的程序有很好的空间局部性。在内存中以大步长跳来跳去的程序空间局部性会很差。
  • 对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好。

6.3存储器层次结构

在这里插入图片描述

6.3.1 存储器层次结构中的缓存

6.3.2存储器层次结构概念小结

6.4高速缓存存储器

6.4.1 通用的高速缓存存储器组织结构

6.4.2直接映射高速缓存

6.4.3组相联高速缓存

6.4.4 全相联高速缓存

6.4.5有关写的问题

6.4.6一个真实的高速缓存层次结构的解剖

6.4.7 高速缓存参数的性能影响

6.5 编写高速缓存友好的代码

6.6综合:高速缓存对程序性能的影啊

6.6.1 存储器山

6.6.2重新排列循环以提高空间局部性

6.6.3 在程序中利用局部性

6.7 小结

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值