DPDK实践一:技术介绍 + 编译运行

一、Intel® DPDK技术引入

网路的核心是报文的转发过程,linux网路是通过内核协议栈进行转发的,报文控制平面和数据转发平面没有分离,不适合处理大规模网络数据包,因为linux分为内核区和用户区,报文先进入内核区然后拷贝到用户区,供给上层应用程序处理。并且为了全面的支持用户空间的各个功能,协议栈中嵌入了大量用于对接的接口。如果能让应用程序直接接管网络数据包处理、内存管理以及CPU调度,那么性能可以得到一个质的提升。

如今的处理器都是多核,而且内存也越来越大,可以提高多核和大内存的扩展性,减少CPU多核之间任务的切换,内存cache miss,因为内存的访问速度永远也赶不上cache和cpu的频率,为了能让性能平行扩展,最好是少访问。

要提高网路报文转发,从如下几个方面着手:

1.控制层留给Linux做,其它数据层全部由应用程序来处理。
  2.减少系统调度、系统调用、系统中断,上下文切换等
  3.摒弃Linux内核协议栈,将数据包传输到用户空间定制协议栈
  4.使用多核编程技术替代多线程,将OS绑在指定核上运行
  5.针对SMP系统,使CPU尽量使用所在NUMA系统节点的内存,减少内存刷写
  6.使用大页面,减少访问
  7.采用无锁技术解竞争

Intel® DPDK全称Intel Data Plane Development Kit,是intel提供的数据平面开发工具集,主要应用x86通用平台,为用户空间高效的数据包处理提供库函数和驱动的支持。它不同于Linux系统以通用性设计为目的,而是专注于网络应用中数据包的高性能处理。

需要强调的是,DPDK应用程序是运行在用户空间上利用自身提供的数据平面库来收发数据包,绕过了Linux内核协议栈对数据包处理过程。Linux内核将DPDK应用程序看作是一个普通的用户态进程,包括它的编译、连接和加载方式和普通程序没有什么两样。DPDK程序启动后只能有一个主线程,然后创建一些子线程并绑定到指定CPU核心上运行。

二、DPDK架构

这里写图片描述

  • EAL(Environment Abstraction Layer)即环境抽象层,为应用提供了一个通用接口,隐藏了与底层库与设备打交道的相关细节。EAL实现了DPDK运行的初始化工作,基于大页表的内存分配,多核亲缘性设置,原子和锁操作,并将PCI设备地址映射到用户空间,方便应用程序访问。

  • Buffer Manager API通过预先从EAL上分配固定大小的多个内存对象,避免了在运行过程中动态进行内存分配和回收来提高效率,常常用作数据包buffer来使用。

  • Queue Manager API以高效的方式实现了无锁的FIFO环形队列,适合与一个生产者多个消费者、一个消费者多个生产者模型来避免等待,并且支持批量无锁的操作。

  • Flow Classification API通过Intel SSE基于多元组实现了高效的hash算法,以便快速的将数据包进行分类处理。该API一般用于路由查找过程中的最长前缀匹配中,安全产品中根据Flow五元组来标记不同用户的场景也可以使用。

  • PMD则实现了Intel 1GbE、10GbE和40GbE网卡下基于轮询收发包的工作模式,大大加速网卡收发包性能。

DPDK的优势:

这里写图片描述

DPDK拦截中断,不触发后续中断流程,并绕过协议栈,通过UIO技术将网卡收到的报文拷贝到应用层处理,报文不再经过内核协议栈。减少了中断,DPDK的包全部在用户控件使用内存池管理,内核控件与用户空间的内存交互不用进行拷贝,只做控制权转移,减少报文拷贝过程,提高报文的转发效率。

DPDK核心技术如下:

(1)通过UIO技术将报文拷贝到应用空间处理

(2)通过大页内存,降低cache miss ,提高命中率,进而cpu访问速度

(3)通过CPU亲和性,绑定网卡和线程到固定的core,减少cpu任务切换

(4)通过无锁队列,减少资源竞争

接下来深入学习总结一下dpdk所用到的技术,加深理解。

三、DPDK关键技术

1、hugepage: 使用大页缓存支持来提高内存访问效率。

为实现物理地址到虚拟地址的转换,Linux一般通过查找TLB来进行快速映射,如果在查找TLB没有命中,就会触发一次缺页中断,将访问内存来重新刷新TLB页表。Linux下默认页大小为4K,当用户程序占用4M的内存时,就需要1K的页表项,如果使用2M的页面,那么只需要2条页表项,这样有两个好处:

第一是使用hugepage的内存所需的页表项比较少,对于需要大量内存的进程来说节省了很多开销,像oracle之类的大型数据库优化都使用了大页面配置;

第二是TLB冲突概率降低,TLB是cpu中单独的一块高速cache,一般只能容纳100条页表项,采用hugepage可以大大降低TLB miss的开销。

DPDK目前支持了2M和1G两种方式的hugepage。通过修改默认/etc/grub.conf中hugepage配置为:

default_hugepagesz=1G hugepagesz=1G hugepages=32 isolcpus=0-22
  • 1

然后通过

mount –t hugetlbfs nodev /mnt/huge
  • 1

就将hugepage文件系统hugetlbfs挂在/mnt/huge目录下,然后用户进程就可以使用mmap映射hugepage目标文件来使用大页面了。测试表明应用使用大页表比使用4K的页表性能提高10%~15%

2、UIO,PMD,用户态轮询驱动,可以减小上下文切换开销,方便实现虚拟机和主机零拷贝zero copy。

当前Linux操作系统都是通过中断方式通知CPU来收发数据包,我们假定网卡每收到10个数据包触发一次软中断,一个CPU核心每秒最多处理2w次中断,那么当一个核每秒收到20w个包时就占用了100%,此刻它无法做其它任何操作。

DPDK针对Intel网卡实现了基于轮询方式的PMD(Poll Mode Drivers)驱动,该驱动由API、用户空间运行的驱动程序构成,该驱动使用无中断方式直接操作网卡的接收和发送队列(除了链路状态通知仍必须采用中断方式以外)。目前PMD驱动支持Intel的大部分1G、10G和40G的网卡。

PMD驱动从网卡上接收到数据包后,会直接通过DMA方式传输到预分配的内存中,同时更新无锁环形队列中的数据包指针,不断轮询的应用程序很快就能感知收到数据包,并在预分配的内存地址上直接处理数据包,这个过程非常简洁。

如果要是让Linux来处理收包过程,首先网卡通过中断方式通知协议栈对数据包进行处理,协议栈先会对数据包进行合法性进行必要的校验,然后判断数据包目标是否本机的socket,满足条件则会将数据包拷贝一份向上递交给用户socket来处理,不仅处理路径冗长,还需要从内核到应用层的一次拷贝过程。

dpdk利用uio支持,提供应用空间下PMD驱动程序的支持,也就是说网卡驱动是运行在用户空间的,减下了报文在用户空间和应用空间的多次拷贝。

3、 利用LINUX亲和性支持,把控制面线程及各个数据面线程绑定到不同的CPU核,节省了 线程在各个CPU核来回调度。

多线程的初衷是提高整体应用程序的性能,但是如果不加注意,就会将多线程的创建和销毁开销,锁竞争,访存冲突,cache失效,上下文切换等诸多消耗性能的因素引入进来。

为了进一步提高性能,就必须仔细考虑线程在CPU不同核上的分布情况,这也就是常说的多核编程。多核编程和多线程有很大的不同:多线程是指每个CPU上可以运行多个线程,涉及到线程调度、锁机制以及上下文的切换;而多核则是每个CPU核一个线程,核心之间访问数据无需上锁。为了最大限度减少线程调度的资源消耗,需要将Linux绑定在特定的核上,释放其余核心来专供应用程序使用。

同时还需要考虑CPU特性和系统是否支持NUMA架构,如果支持的话,不同插槽上CPU的进程要避免访问远端内存,尽量访问本端内存。

4、 提供内存池和无锁环形缓存管理,加快内存访问效率。多个收发包集中到一个cache line,在内存池中实现,无需反复申请和释放。

模块分解如下:
这里写图片描述

模块分解:

这里写图片描述

这里写图片描述

在最底部的内核态DPDK有几个模块:

  • KNI:提供给用户一个使用Linux内核态的协议栈以及传统的Linux网络工具(如ethtool、ifconfig);
  • IGB_UIO(igb_uio.ko和kni.ko.IGB_UIO):借助UIO技术,在初始化工程中将网卡硬件寄存器映射到用户态

核心组件:

1、 环境抽象层(EAL):为DPDK其它组件和应用程序提供一个屏蔽具体平台特性的统一接口,EAL提供的功能主要有:DPDK加载和启动;支持多核或多线程执行类型;CPU核亲和性处理;原子操作和锁操作接口;时钟参考;PCI总线访问接口;跟踪和调试接口;CPU特性采集接口;中断和告警接口等。

2、 堆内存管理组件(MALLOC):堆内存管理组件为应用程序提供从大页内存分配堆内存的接口。当需要分配大量内存小块时(如用于存储列表中每个表项指针的内存),使用这些接口可以减少TLB缺页。

3、 网络报文缓存块管理组件(Mbuf):提供应用程序创建和释放用于存储报文信息的缓存块的接口,这些MBUF存储在一内存池中。提供两种类型的MBUF,一种用于存储一般信息,一种用于存储报文数据。

4、 内存池管理组件(Mempool):为应用程序和其它组件提供分配内存池的接口,内存池是一个由固定大小的多个内存块组成的内存容器,可用于存储相同对像实体,如报文缓存块等。内存池由内存池的名称(一个字符串)来唯一标识,它由一个环缓中区和一组核本地缓存队列组成,每个核从自已的缓存队列分配内存块,当本地缓存队列减少到一定程度时,从内存环缓冲区中申请内存块来补充本地队列。

5、 环缓冲区管理组件(RING):环缓冲区管理组件为应用程序和其它组件提供一个无锁的多生产者多消费者FIFO队列API。

6、 定时器组件(Timer):提供一些异步周期执行的接口(也可以只执行一次),可以指定某个函数在规定的时间异步的执行,就像LIBC中的timer定时器,但是这里的定时器需要应用程序在主循环中周期调用rte_timer_manage来使定时器得到执行,使用起来没有那么方便。定时器组件的时间参考来自EAL层提供的时间接口。

除了以上六个核心组件外,DPDK还提供以下功能:
1、 以太网轮询模式驱动(PMD)架构:把以太网驱动从内核移到应用层,采用同步轮询机制而不是内核态的异步中断机制来提高报文的接收和发送效率。
2、 报文转发算法支持:Hash 库和LPM库为报文转发算法提供支持。
3、 网络协议定义和相关宏定义:基于FreeBSD IP协议栈的相关定义如:TCP、UDP、SCTP等协议头定义。
4、 报文QOS调度库:支持随机早检测、流量整形、严格优先级和加权随机循环优先级调度等相关QOS功能。
5、 内核网络接口库(KNI):提供一种DPDK应用程序与内核协议栈的通信的方法,类似普通LINUX的TUN/TAP接口,但比TUN/TAP接口效率高。每个物理网口可以虚拟出多个KNI接口。

四、组件代码

这里写图片描述

RTE:Run-Time Environment

EAL:Environment Abstraction Layer

PMD: Poll-Mode Driver

Memory Manager (librte_malloc,内存管理器)

Librte_malloc 库提供一组API,用于从hugepages创建的memzones中分配内存而不是在堆中分配。这有助于改善Linux用户空间环境下典型的从堆中大量分配4KB页面而容易引起TLB不命中。

Memory Pool Manager (librte_mempool,内存池管理器)

内存池管理器负责分配的内存中的pool对象。一个pool由名称标识,并使用一个ring来存储空闲对象。它提供了其他一些可选的服务,例如每个core的对象缓存和对齐方式帮助器,以确保将填充的对象在所有内存通道上均匀分布。

Ring Manager (librte_ring,环形队列管理器)

在一个有限大小的表中,ring结构提供了一个无锁的多生产者多消费者的FIFO API。相较于无锁队列,它有一些的优势 ;更容易实现,适应于大容量操作和速度更快。一个ring用在内存池管理器(librte_mempool),也可用作cores和(或)在一个逻辑core之上的连接在一起的执行块的通用沟通机制。

Network Packet Buffer Management (librte_mbuf,网络报文缓冲管理)

mbuf 库提供了创建和销毁缓冲区,英特尔 ® DPDK 应用程序可能用来存储消息缓冲。创建消息缓冲区在启动时间和存储在 mempool,并使用英特尔 ® DPDK mempool 库。

此库提供一组 API,用于分配或释放 mbufs,操纵控制消息缓冲区(ctrlmbuf) ——普通的消息缓冲区,还操作数据包缓冲区 (pktmbuf) ­——用来进行网络数据包。

Timer Manager (librte_timer,定时器管理)

timer库向英特尔 ® DPDK 执行单位提供定时器服务,保证以异步方式执行函数的能力。它可以是定期调用,或只是一次性调用。它使用环境抽象层 (EAL) 提供的到的 HPET 接口来获取精确时间的引用,并根据需求在每个核心启动。

代码目录:
这里写图片描述

这里写图片描述

五、运行环境搭建

在root权限下:

1)配置大页内存

echo 128 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages 
mkdir /mnt/huge 
mount -t hugetlbfs nodev /mnt/huge  
cat /proc/meminfo | grep Huge

2)编译dpdk+helloworld

dpdk源码下载:http://www.dpdk.org/browse/dpdk/refs/tags?h=releases

进入dpdk主目录,输以下命令进行编译

1,设置环境变量:
export RTE_SDK=/home/dpdk-20.08
export RTE_TARGET=x86_64-native-linuxapp-gcc
export KERNELDIR=/lib/modules/3.10.0-862.14.1.6_97.x86_64/build/

ps:可以通过env命令查看已设置的环境变量

2,设置编译生成动态库so和igb_uio:
vim config/common_base
CONFIG_RTE_BUILD_SHARED_LIB=y
CONFIG_RTE_EAL_IGB_UIO=y

3,编译
make config T=x86_64-native-linuxapp-gcc
make install T=x86_64-native-linuxapp-gcc RTE_KERNELDIR=/lib/modules/3.10.0-862.14.1.6_97.x86_64/build/ -j 8 'TOOLCHAIN_CFLAGS=-msse4.2 -g -Wall -Werror -fstack-protector-strong -fPIC' 'EXTRA_LDFLAGS=-z relro -z now -z noexecstack' MAKE_PAUSE=n

编译成功后可以在 x86_64-native-linuxapp-gcc/lib/ 目录下看到对应的so, 拷贝到/lib64目录下
cp x86_64-native-linuxapp-gcc/lib/lib* /lib64/

在DPDK的examples路径下面有许多示例应用,可以在examples路径下面运行make,将这些应用全都编译好,然后执行,从helloword开始:

# cd examples
# make
# ./helloworld/x86_64-native-linuxapp-gcc/helloworld -c 0xf -n 2
EAL: Detected 56 lcore(s)
EAL: Detected 2 NUMA nodes
EAL: Multi-process socket /var/run/dpdk/rte/mp_socket
EAL: Selected IOVA mode 'VA'
EAL: Probing VFIO support...
EAL: VFIO support initialized
EAL: No legacy callbacks, legacy socket not created
hello from core 1
hello from core 0
hello from core 3
hello from core 2

该程序自动检测逻辑核,0核为主逻辑核,默认运行结果如上述打印。
执行该程序时默认加的参数,具体含义如下:

EAL common options:
  -c COREMASK         Hexadecimal bitmask of cores to run on
  -l CORELIST         List of cores to run on
                      The argument format is <c1>[-c2][,c3[-c4],...]
                      where c1, c2, etc are core indexes between 0 and 128
  --lcores COREMAP    Map lcore set to physical cpu set
                      The argument format is
                            '<lcores[@cpus]>[<,lcores[@cpus]>...]'
                      lcores and cpus list are grouped by '(' and ')'
                      Within the group, '-' is used for range separator,
                      ',' is used for single number separator.
                      '( )' can be omitted for single element group,
                      '@' can be omitted if cpus and lcores have the same value
  -s SERVICE COREMASK Hexadecimal bitmask of cores to be used as service cores
  --master-lcore ID   Core ID that is used as master
  --mbuf-pool-ops-name Pool ops name for mbuf to use
  -n CHANNELS         Number of memory channels
  -m MB               Memory to allocate (see also --socket-mem)
  -r RANKS            Force number of memory ranks (don't detect)
  -b, --pci-blacklist Add a PCI device in black list.
                      Prevent EAL from using this PCI device. The argument
                      format is <domain:bus:devid.func>.
  -w, --pci-whitelist Add a PCI device in white list.
                      Only use the specified PCI devices. The argument format
                      is <[domain:]bus:devid.func>. This option can be present
                      several times (once per device).
                      [NOTE: PCI whitelist cannot be used with -b option]
  --vdev              Add a virtual device.
                      The argument format is <driver><id>[,key=val,...]
                      (ex: --vdev=net_pcap0,iface=eth2).
  --iova-mode   Set IOVA mode. 'pa' for IOVA_PA
                      'va' for IOVA_VA
  -d LIB.so|DIR       Add a driver or driver directory
                      (can be used multiple times)
  --vmware-tsc-map    Use VMware TSC map instead of native RDTSC
  --proc-type         Type of this process (primary|secondary|auto)
  --syslog            Set syslog facility
  --log-level=<int>   Set global log level
  --log-level=<type-match>:<int>
                      Set specific log level
  --trace=<regex-match>
                      Enable trace based on regular expression trace name.
                      By default, the trace is disabled.
                      User must specify this option to enable trace.
  --trace-dir=<directory path>
                      Specify trace directory for trace output.
                      By default, trace output will created at
                      $HOME directory and parameter must be
                      specified once only.
  --trace-bufsz=<int>
                      Specify maximum size of allocated memory
                      for trace output for each thread. Valid
                      unit can be either 'B|K|M' for 'Bytes',
                      'KBytes' and 'MBytes' respectively.
                      Default is 1MB and parameter must be
                      specified once only.
  --trace-mode=<o[verwrite] | d[iscard]>
                      Specify the mode of update of trace
                      output file. Either update on a file can
                      be wrapped or discarded when file size
                      reaches its maximum limit.
                      Default mode is 'overwrite' and parameter
                      must be specified once only.
  -v                  Display version information on startup
  -h, --help          This help
  --in-memory   Operate entirely in memory. This will
                      disable secondary process support
  --base-virtaddr     Base virtual address
  --telemetry   Enable telemetry support (on by default)
  --no-telemetry   Disable telemetry support

EAL options for DEBUG use only:
  --huge-unlink       Unlink hugepage files after init
  --no-huge           Use malloc instead of hugetlbfs
  --no-pci            Disable PCI
  --no-hpet           Disable HPET
  --no-shconf         No shared config (mmap'd files)

EAL Linux options:
  --socket-mem        Memory to allocate on sockets (comma separated values)
  --socket-limit      Limit memory allocation on sockets (comma separated values)
  --huge-dir          Directory where hugetlbfs is mounted
  --file-prefix       Prefix for hugepage filenames
  --create-uio-dev    Create /dev/uioX (usually done by hotplug)
  --vfio-intr         Interrupt mode for VFIO (legacy|msi|msix)
  --vfio-vf-token     VF token (UUID) shared between SR-IOV PF and VFs
  --legacy-mem        Legacy memory mode (no dynamic allocation, contiguous segments)
  --single-file-segments Put all hugepage memory in single files
  --match-allocations Free hugepages exactly as allocated

3)转发实践

1,安装igb_uio驱动
modprobe uio
rmmod igb_uio.ko
insmod x86_64-native-linuxapp-gcc/kmod/igb_uio.ko

2,绑定网卡
先看一下当前网卡的状态:
./usertools/dpdk-devbind.py --status

再进行绑定:
./usertools/dpdk-devbind.py -b igb_uio 0000:02:06.0 
./usertools/dpdk-devbind.py -b igb_uio 0000:02:06.1 

ps:如果网卡有接口名,如eth1, eth2, 也可以在-b igb_uio后面使用接口名, 而不使用pci地址

DPDK搭建环境完成后,网卡绑定到相应IGB_UIO驱动接口上,所有的网络数据包都会到DPDK,例如examples/l2fwd程序,网卡接收网络数据包,再从另一个网卡转发出去。

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 腾讯的开源协议栈f-stack.pdf是在DPDK技术峰会上推出的一项重要技术创新。DPDK技术峰会是一个技术交流平台,旨在推动数据平面开发工具包(DPDK技术的发展和应用。f-stack.pdf是腾讯开源的一套协议栈,它通过与DPDK集成,提供了高性能的网络数据包处理能力。 f-stack.pdf具有以下几个特点:高性能、轻量级和易使用。它基于零拷贝技术,能够以卓越的性能处理数据包。同时,f-stack.pdf采用了轻量级设计,只依赖于DPDK的核心功能,避免了不必要的复杂性,使得使用起来更加简单和灵活。 f-stack.pdf在应用层提供了一系列网络协议的支持,例如TCP、UDP和IP等。它提供了高度可扩展和定制化的接口,使得开发者可以根据具体需求进行定制和优化。另外,f-stack.pdf还提供了丰富的功能和工具,方便开发者进行网络应用的开发和调试。 该协议栈不仅适用于云服务器、网络设备和高速存储等领域,还可以广泛应用于物联网、5G通信和金融等行业。它的高性能和低延迟使得在大规模并发场景下的数据处理更加高效和稳定。 腾讯的开源协议栈f-stack.pdf在DPDK技术峰会上的推出,标志着中国企业在高性能网络技术领域的一次重要突破。它的出现将有助于促进我国网络技术的创新和发展,并提升我国在全球高性能网络领域的竞争力。 ### 回答2: 腾讯的开源协议栈f-stack是DPDK技术峰会上推出的一项重要的技术成果。该技术是基于DPDK(Data Plane Development Kit)开发的一套网络数据包处理框架,旨在提供高性能的网络数据包处理和协议栈功能。 f-stack的特点可以从以下几个方面来描述。首先,它采用了用户态的网络协议栈设计,将协议处理功能从内核态迁移到用户态,避免了内核态和用户态的频繁切换,提高了数据包处理的效率。其次,f-stack提供了一套完整的网络协议栈功能,包括TCP/IP协议栈、套接字接口、事件驱动机制等,可以支持常见的网络应用开发。此外,f-stack还提供了网络收发模块、队列管理、内存池等高性能的数据结构和算法,以进一步提升数据包处理的吞吐量和延迟。 通过使用f-stack,开发者可以轻松地构建高性能的网络应用,无论是在云计算、大数据分析还是边缘计算等场景中,都能够获得更好的性能和响应速度。与传统的网络协议栈相比,f-stack具有更低的延迟、更高的吞吐量和更好的可扩展性,能够更好地满足现代网络应用对于高性能数据处理的需求。 最后,开源协议栈f-stack的推出也体现了腾讯在网络技术领域的创新能力和开放合作的精神。通过开源的方式,腾讯可以与其他行业的开发者和研究机构共同探索和推进网络技术的发展,形成合力并推动整个行业的进步。 总的来说,腾讯的开源协议栈f-stack在DPDK技术峰会上的推出,为高性能网络数据包处理和协议栈开发提供了一个有力的工具和平台,对于促进网络技术的创新和发展具有重要意义。 ### 回答3: 腾讯的开源协议栈f-stack.pdf是关于dpdk技术峰会的一个重要文档。DPDK技术峰会是一个旨在促进DPDK技术发展和交流的会议,汇集了众多DPDK技术领域的专家和爱好者。 在f-stack.pdf中,腾讯团队分享了他们开发的开源协议栈f-stack的技术细节和应用场景。f-stack是一个高性能、可扩展的协议栈,基于DPDK技术开发,旨在提供更好的网络性能和更高的数据包处理能力。 f-stack的设计目标主要包括:轻量级、高性能、可扩展和易用性。它采用了一系列的优化技术,如零拷贝、多队列和事件驱动等,以提高数据包的处理效率。f-stack支持多种协议,如TCP、UDP和IP等,可以满足不同应用场景的需求。 此外,f-stack还提供了一些额外的功能,如高效的内存管理、负载均衡和安全性等。这些功能使得f-stack在网络应用程序的开发中更加方便和灵活。 f-stack已经在腾讯内部得到广泛的应用和验证,取得了良好的性能和稳定性。它已经成为了腾讯云和腾讯游戏等业务的核心组件。 总的来说,f-stack的出现充分展示了腾讯在DPDK技术领域的创新能力和技术实力。它为广大开发者提供了一个高效、可扩展的协议栈解决方案,有助于进一步推动DPDK技术的应用和发展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值