虚拟机dpdk环境搭建

一、准备环境

        磨刀不误砍柴工,要想更好的进行dpdk源码分析,需要搭建一套dpdk环境,观察数据包的转发流程。由于个人电脑条件有限,只能在vmware虚拟机环境下搭建dpdk环境。dpdk源码分析系列的所有文章都是基于这套环境来分析。

1、vmware虚拟机上安装ubuntun系统, ubuntu版本为12.04;  linux内核版本为3.13.0-32-generic。我的系统架构是32位,可以通过uname -a命令来查看。

root@apelife:/home/xyd/work/bin/dpdk# uname -a
Linux apelife 3.13.0-32-generic #57~precise1-Ubuntu SMP Tue Jul 15 03:50:54 UTC 2014 i686 i686 i386 GNU/Linux

x86_64表示64位系统, i686   i386表示32位系统。i686 只是i386的一个子集

2、dpdk版本为dpdk-1.8.0 

3、虚拟机物理内存2G; 4个cpu,单线程; 5个网卡,采用桥接模式,其中4个网卡用于dpdk, 另一个网卡用于与宿主机通信。

        物理机与虚拟机网络环境的搭建可以参考ubuntu网络配置(桥接模式, nat模式, host主机模式)这篇文章

二、dpdk环境的安装流程

        下载好dpdk代码后,需要将其进行解压。  例如将其解压到/home/xyd/work/bin/dpdk目录

root@apelife:/home/xyd/work/packet# tar -xvf dpdk-1.8.0.tar.xz -C /home/xyd/work/bin/dpdk

dpdk环境搭建流程,包含下面几个过程

1、设置环境变量

2、编译dpdk

3、设置大页内存

4、加载uio模块

5、将网卡绑定到uio驱动

        我们可以通过两种方式来搭建dpdk环境,一种方式是使用dpdk根目录下的tools/setup.sh脚本来安装, 另一种方式是通过命令行来安装。脚本安装方式已经帮我们封装好了安装流程,操作比较简单, 但不容易理解dpdk安装的内部细节, 而命令行方式操作麻烦些, 但可以对整个安装流程有个很好的理解。 先来介绍命令行安装方式,之后在介绍脚本安装方式。

三、使用命令行安装

1、设置环境变量

        /home/xyd/work/bin/dpdk是我dpdk的安装目录, 在这个目录下添加一个dpdkenv文件,当然文件名可以随便取,我就把这个文件命名为dpdkenv。 在这个文件里面添加下面三个环境变量。

root@apelife:/home/xyd/work/bin/dpdk# cat dpdkenv 
export RTE_SDK=`pwd`
export RTE_TARGET=i686-native-linuxapp-gcc
export EXTRA_CFLAGS="-O0 -g"

        其中RTE_SDK为dpdk的安装目录;  RTE_TARGET为编译器,由于我的操作系统为32位,因此RET_TARGET设置为i686-native-linuxapp-gcc。 如果系统是64位,则设置为x86_64-native-linuxapp-gcc;  EXTRA_CFLAGS意思是打开调试选项,开启了调试选项后,才能使用gdb调试。

        然后执行source dpdkenv,意思就是不用重启设备,让dpdkenv文件里面设置的所有命令立即生效。

root@apelife:/home/xyd/work/bin/dpdk# source dpdkenv

        为什么要添加dpdkenv这个文件名,搞这么复杂。 那是因为每次新打开一个终端时,之前设置的环境变量会失效,需要重新设置环境变量。因此把这个设置环境需要的命令放到文件中来,每次打开终端时,只需要执行source dpdkenv就好了,操作简单。

2、编译dpdk

        先在dpdk安装目录下make clean, 清除一些残留信息。 由于我的系统是32位的,因此使用make install T=i686-native-linuxapp-gcc开始编译。如果是64位操作系统,则使用make install T=x86_64-native-linuxapp-gcc

root@apelife:/home/xyd/work/bin/dpdk# make clean
root@apelife:/home/xyd/work/bin/dpdk# make install T=i686-native-linuxapp-gcc

        如果dpdk编译过程中出现了 cc1: out of memory allocating 33554432 bytes after a total of 19070976 bytes这种类型的错误,那就把虚拟机重启下吧。编译成功会提示Build complete

3、设置大页内存

        系统默认每个大页的大小是2M, 在这里设置256个大页,也就是大页内存一共512M。  大页内存尽量设置大一些,通常设置为512---1024M之间, 否则后续执行测试例子的时候有可能会报大页内存不足错误信息。 设置好大页内存后,挂载大页系统

root@apelife:/home/xyd/work/bin/dpdk# echo 256 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
root@apelife:/home/xyd/work/bin/dpdk# mkdir /mnt/huge
root@apelife:/home/xyd/work/bin/dpdk# mount -t hugetlbfs nodev /mnt/huge

之后可以在/proc/meminfo文件中看到大页内存信息

root@apelife:/home/xyd/work/bin/dpdk# cat /proc/meminfo | grep Huge

4、安装igb_uio驱动

        编译好dpdk后,会在dpdk的安装目录下生成uio驱动: i686-native-linuxapp-gcc/kmod/igb_uio.ko,需要把这个驱动加载到内核

root@apelife:/home/xyd/work/bin/dpdk# modprobe uio
root@apelife:/home/xyd/work/bin/dpdk# insmod i686-native-linuxapp-gcc/kmod/igb_uio.ko

        记得一定要先执行modprobe uio, 否则会找不到驱动。当然如果需要删除驱动,则执行rmmod  igb_uio

5、将物理网卡与igb_uio驱动进行绑定

        使用dpdk自带的脚本,查看网卡信息。从中可以看出我有5个千兆网卡, 每个网卡使用e1000驱动。

root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py --status

Network devices using DPDK-compatible driver
============================================
<none>

Network devices using kernel driver
===================================
0000:02:00.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth0 drv=e1000 unused=igb_uio *Active*
0000:02:04.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth1 drv=e1000 unused=igb_uio *Active*
0000:02:05.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth2 drv=e1000 unused=igb_uio *Active*
0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth3 drv=e1000 unused=igb_uio *Active*
0000:02:07.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth4 drv=e1000 unused=igb_uio *Active*

Other network devices
=====================
<none>

        现在想对eth0, eth1, eth2, eth3共4个网卡解除与e1000驱动绑定,从而与igb_uio驱动进行绑定。-b选项表示bind绑定的意思

root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:00.0
root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:04.0
root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:05.0
root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py -b igb_uio 0000:02:06.0

        再来查看下网卡信息, 可以看出eth0, eth1, eth2, eth4这四个网卡都与igb_uio驱动进行了绑定

root@apelife:/home/xyd/work/bin/dpdk# ./tools/dpdk_nic_bind.py --status

Network devices using DPDK-compatible driver
============================================
0000:02:00.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:04.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:05.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000

Network devices using kernel driver
===================================
0000:02:07.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth4 drv=e1000 unused=igb_uio *Active*

Other network devices
=====================
<none>

        如果绑定过程出现这样的错误Routing table indicates that interface 0000:02:00.0 is active. Not modifying。则说明网卡已经启用了,需要把这个网卡down掉, 例如ifconfig eth0 down

        当然如果网卡不再需要与igb_uio绑定,从而继续与e1000绑定,则可以使用-u选项解除绑定,然后再重新与e1000重新绑定。例如

./tools/dpdk_nic_bind.py -u  0000:02:00.0    解除网卡与igb_uio绑定

./tools/dpdk_nic_bind.py -b e1000  0000:02:00.0 网卡重新与e1000绑定

        需要注意的是lspci查看网卡信息,如果发现不是intel的e1000网卡,而是AMD的网卡,则关闭虚拟机,在虚拟机安装目录下找到vmx后缀的文件,例如ubuntu-new.vmx,在这个文件末尾添加下面的内容。 在这里我有5个网卡,因此在这个文件末尾加了五条信息。修改完这个文件后重新开启虚拟机。

ethernet0.virtualDev = "e1000"
ethernet1.virtualDev = "e1000"
ethernet2.virtualDev = "e1000"
ethernet3.virtualDev = "e1000"
ethernet4.virtualDev = "e1000"

        到此为止,dpdk环境已经搭建成功了,恭喜你。下面来执行下dpdk的测试程序,感受下。

四、dpdk测试程序的执行

1、测试下pmd驱动是否成功。由于我有4个cpu, 因此对应的掩码为0xf;  -n 2表示内存通道为2

root@apelife:/home/xyd/work/bin/dpdk/i686-native-linuxapp-gcc/app# ./testpmd -c 0xf -n 2 -- -i
EAL: Cannot read numa node link for lcore 0 - using physical package id instead
EAL: Detected lcore 0 as core 0 on socket 0
EAL: Cannot read numa node link for lcore 1 - using physical package id instead
EAL: Detected lcore 1 as core 0 on socket 2
EAL: Cannot read numa node link for lcore 2 - using physical package id instead
EAL: Detected lcore 2 as core 0 on socket 4
EAL: Cannot read numa node link for lcore 3 - using physical package id instead
EAL: Detected lcore 3 as core 0 on socket 6
EAL: Support maximum 128 logical core(s) by configuration.
...................................
...................................忽略剩余过程
Port 0 Link Up - speed 1000 Mbps - full-duplex
Port 1 Link Up - speed 1000 Mbps - full-duplex
Port 2 Link Up - speed 1000 Mbps - full-duplex
Port 3 Link Up - speed 1000 Mbps - full-duplex
Done
testpmd>quit

       如果执行testpmd过程中出现EAL: Error reading from file descriptor 13: Input/output error错误信息,不要担心,这个只在虚拟机环境下才会出现,物理机是不会出现这个错误的。 需要在dpdk/lib/librte_eal/linuxapp/igb_uio/igb_uio.c文件中将if (pci_intx_mask_supported(dev))修改为

if (pci_intx_mask_supported(dev) || true), 然后重新编译dpdk


509         case RTE_INTR_MODE_LEGACY:
510                 if (pci_intx_mask_supported(dev) || true) {
511                         dev_dbg(&dev->dev, "using INTX");
512                         udev->info.irq_flags = IRQF_SHARED;
513                         udev->info.irq = dev->irq;
514                         udev->mode = RTE_INTR_MODE_LEGACY;
515                         break;
516                 }

2、测试下hello程序

        在./examples/目录下有许多例子,其中有一个hellowoeld路径。进入helloworld路径直接make之后会在当前helloworld路径下生成build文件夹。 build文件夹中存在helloworld可执行程序。由于我有4个cpu, 因此对应的掩码为0xf;  -n 2表示内存通道为2

root@apelife:/home/xyd/work/bin/dpdk/examples/helloworld/build# ./helloworld -c 0x0f -n 2 -- -i
EAL: Cannot read numa node link for lcore 0 - using physical package id instead
EAL: Detected lcore 0 as core 0 on socket 0
EAL: Cannot read numa node link for lcore 1 - using physical package id instead
EAL: Detected lcore 1 as core 0 on socket 2
EAL: Cannot read numa node link for lcore 2 - using physical package id instead
EAL: Detected lcore 2 as core 0 on socket 4
EAL: Cannot read numa node link for lcore 3 - using physical package id instead
EAL: Detected lcore 3 as core 0 on socket 6
EAL: Support maximum 128 logical core(s) by configurati
.............................
.............................忽略剩余过程
hello from core 1
hello from core 2
hello from core 3
hello from core 0

3、测试下l2fwd二层转换的例子
        由于我有4个cpu, 因此对应的掩码为0xf;  -n 2表示内存通道为2; -q表示每个cpu有两个接收队列,两个发送队列。 -p 用于指定网卡, 在这里我的eth0,eth1,eth2,eth3共四个网卡绑定了igb_uio驱动,因此掩码为0xf

root@apelife:/home/xyd/work/bin/dpdk/examples/l2fwd/build# ./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf
Port statistics ====================================
Statistics for port 0 ------------------------------
Packets sent:                        0
Packets received:                    0
Packets dropped:                     0
Statistics for port 1 ------------------------------
Packets sent:                        0
Packets received:                    0
Packets dropped:                     0
Statistics for port 2 ------------------------------
Packets sent:                        0
Packets received:                    0
Packets dropped:                     0
Statistics for port 3 ------------------------------
Packets sent:                        0
Packets received:                    0
Packets dropped:                     0
Aggregate statistics ===============================
Total packets sent:                  0
Total packets received:              0
Total packets dropped:               0
====================================================

       其它例子的执行也是相同的,就不再举例了。在dpdk下有个example目录下有很多实例, 需要调试哪个则进入对应目录执行make就好了。至于每个例子要带什么参数,那就分析源码或者查找文档吧。

五、使用dpdk脚本方式安装

        dpdk自带了一个安装脚本setup.sh, 路径为dpdk根目录下的toos目录下。使用这个脚本可以完成一系列安装过程。在学会了如何使用命令行安装后,后续为了方便,还是使用脚本方式来搭建dpdk环境。来看下这个脚本有什么功能。

root@apelife:/home/xyd/work/bin/dpdk# ./tools/setup.sh 
------------------------------------------------------------------------------
 RTE_SDK exported as /home/xyd/work/bin/dpdk
------------------------------------------------------------------------------
----------------------------------------------------------
 Step 1: Select the DPDK environment to build
----------------------------------------------------------
[1] i686-native-linuxapp-gcc
[2] i686-native-linuxapp-icc
[3] ppc_64-power8-linuxapp-gcc
[4] x86_64-ivshmem-linuxapp-gcc
[5] x86_64-ivshmem-linuxapp-icc
[6] x86_64-native-bsdapp-clang
[7] x86_64-native-bsdapp-gcc
[8] x86_64-native-linuxapp-clang
[9] x86_64-native-linuxapp-gcc
[10] x86_64-native-linuxapp-icc

----------------------------------------------------------
 Step 2: Setup linuxapp environment
----------------------------------------------------------
[11] Insert IGB UIO module
[12] Insert VFIO module
[13] Insert KNI module
[14] Setup hugepage mappings for non-NUMA systems
[15] Setup hugepage mappings for NUMA systems
[16] Display current Ethernet device settings
[17] Bind Ethernet device to IGB UIO module
[18] Bind Ethernet device to VFIO module
[19] Setup VFIO permissions

----------------------------------------------------------
 Step 3: Run test application for linuxapp environment
----------------------------------------------------------
[20] Run test application ($RTE_TARGET/app/test)
[21] Run testpmd application in interactive mode ($RTE_TARGET/app/testpmd)

----------------------------------------------------------
 Step 4: Other tools
----------------------------------------------------------
[22] List hugepage info from /proc/meminfo

----------------------------------------------------------
 Step 5: Uninstall and system cleanup
----------------------------------------------------------
[23] Uninstall all targets
[24] Unbind NICs from IGB UIO driver
[25] Remove IGB UIO module
[26] Remove VFIO module
[27] Remove KNI module
[28] Remove hugepage mappings

[29] Exit Script

Option: 

1、环境变量的设置

        这个过程和上面使用命令行方式下环境变量的设置是一模一样的,可以参考上面的设置。

2、编译dpdk

        由于我的系统是32位,因此选择[1]  i686-native-linuxapp-gcc; 如果是64位系统,选择[9]  x86_64-native-linuxapp-gcc。 编译完成后会出现Build complete

3、安装igb_uio驱动

        选择[11] Insert IGB UIO module, 使得内核加载这个uio驱动

4、设置大页内存

        选择[14]  Setup hugepage mappings for non-NUMA systems开始设置大页内存。系统默认每个大页的大小是2M, 在这里设置256个大页,也就是大页内存一共512M。  大页内存尽量设置大一些,通常设置为512---1024M之间, 否则后续执行测试例子的时候有可能会报大页内存不足错误信息。 设置后大页内存后,挂载大页系统。

Option: 14

Removing currently reserved hugepages
.echo_tmp: 2: .echo_tmp: cannot create /sys/devices/system/node/node?/hugepages/hugepages-2048kB/nr_hugepages: Directory nonexistent
Unmounting /mnt/huge and removing directory

  Input the number of 2MB pages
  Example: to have 128MB of hugepages available, enter '64' to
  reserve 64 * 2MB pages
Number of pages: 256
Reserving hugepages
Creating /mnt/huge and mounting as hugetlbfs

        可以选择[22]  List hugepage info from /proc/meminfo查看下大页内存的信息

Option: 22

AnonHugePages:     28672 kB
HugePages_Total:     256
HugePages_Free:      256
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB

5、将网卡与uio驱动进行绑定

        选择[17] Bind Ethernet device to IGB UIO module,将物理网卡与uio驱动进行绑定操作。现在想对eth0, eth1, eth2, eth3共4个网卡解除与e1000驱动绑定,从而与igb_uio驱动进行绑定。 在绑定操作之前需要将eth0, eth1, eth2, eth3禁用掉,例如ifconfig eth0 down, 否则会报Routing table indicates that interface 0000:02:00.0 is active. Not modifying错误

<none>

Enter PCI address of device to bind to IGB UIO driver: 0000:02:00.0
OK

        将eth0, eth1, eth2, eth3绑定好后,选择[16]  Display current Ethernet device settings可以看到网卡的绑定信息。从中可以看出这四个网卡与igb_uio驱动进行了绑定。

Option: 16


Network devices using DPDK-compatible driver
============================================
0000:02:00.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:04.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:05.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000
0000:02:06.0 '82545EM Gigabit Ethernet Controller (Copper)' drv=igb_uio unused=e1000

Network devices using kernel driver
===================================
0000:02:07.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth4 drv=e1000 unused=igb_uio *Active*

Other network devices
=====================
<none>

        此时通过脚本方式dpdk环境也搭建好了,下面来测试下pmd的例子

        选择[21] Run testpmd application in interactive mode ($RTE_TARGET/app/testpmd)

Option: 21   


  Enter hex bitmask of cores to execute testpmd app on
  Example: to execute app on cores 0 to 7, enter 0xff
bitmask: 0xf
Port 0 Link Up - speed 1000 Mbps - full-duplex
Port 1 Link Up - speed 1000 Mbps - full-duplex
Port 2 Link Up - speed 1000 Mbps - full-duplex
Port 3 Link Up - speed 1000 Mbps - full-duplex
Done

输入start开始发包,输入stop停止发包

testpmd> start
  io packet forwarding - CRC stripping disabled - packets/burst=32
  nb forwarding cores=1 - nb forwarding ports=4
  RX queues=1 - RX desc=128 - RX free threshold=32
  RX threshold registers: pthresh=8 hthresh=8 wthresh=0
  TX queues=1 - TX desc=512 - TX free threshold=0
  TX threshold registers: pthresh=32 hthresh=0 wthresh=0
  TX RS bit threshold=0 - TXQ flags=0x0
testpmd> 
testpmd> stop
Telling cores to stop...
Waiting for lcores to finish...

  ---------------------- Forward statistics for port 0  ----------------------
  RX-packets: 597120         RX-dropped: 0             RX-total: 597120
  TX-packets: 597152         TX-dropped: 0             TX-total: 597152
  ----------------------------------------------------------------------------

  ---------------------- Forward statistics for port 1  ----------------------
  RX-packets: 597120         RX-dropped: 0             RX-total: 597120
  TX-packets: 597152         TX-dropped: 0             TX-total: 597152
  ----------------------------------------------------------------------------

  ---------------------- Forward statistics for port 2  ----------------------
  RX-packets: 597120         RX-dropped: 0             RX-total: 597120
  TX-packets: 597152         TX-dropped: 0             TX-total: 597152
  ----------------------------------------------------------------------------

  ---------------------- Forward statistics for port 3  ----------------------
  RX-packets: 597120         RX-dropped: 0             RX-total: 597120
  TX-packets: 597152         TX-dropped: 0             TX-total: 597152
  ----------------------------------------------------------------------------

  +++++++++++++++ Accumulated forward statistics for all ports+++++++++++++++
  RX-packets: 2388480        RX-dropped: 0             RX-total: 2388480
  TX-packets: 2388608        TX-dropped: 0             TX-total: 2388608
  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Done.
testpmd> quit

        输入start开始发包测试的时候,如果出现这个错误Warning! Cannot handle an odd number of ports with the current port topology. Configuration must be changed to have an even number of ports, or relaunch application with --port-topology=chained那是因为网卡个数不能是奇数,而应该是偶数个。

         此时top查看cpu信息,发现testpmd占用率高达100%,设备非常卡,这是正常的。cpu全速运转,处理报文的转发操作。

六、gdb调试dpdk源码

        还记得上面的那个dpdkenv,在里面设置了环境变量export EXTRA_CFLAGS="-O0 -g"。 编译好了dpdk后,就可以使用gdb调试了。但在实际分析源码过程中,可能会多次的修改源码,例如添加日志来加深对代码的理解。  那修改了源码后如何重新编译源码以便能够打印出日志与gdb调试呢? 有两种方式可以实现, 任意选择一种就好了,下面分别看下这两种方式。 

1. 方式1: 直接编译i686-native-linuxapp-gcc

        如果修改了lib目录下的源码,可以在dpdk根目录里面的i686-native-linuxapp-gcc目录下直接make,然后在想要调试的例子: 例如example/helloworld目录下执行make,接下里就可以使用gdb设置断点调试。

root@apelife:/home/xyd/work/bin/dpdk/i686-native-linuxapp-gcc# make
root@apelife:/home/xyd/work/bin/dpdk/examples/helloworld# make

        gdb设置断点开始调试,其中r 后面的参数为hellworld程序需要的参数。

root@apelife:/home/xyd/work/bin/dpdk/examples/helloworld/build# gdb helloworld
(gdb) b rte_eal_init
Breakpoint 1 at 0x80e85b2: file /home/xyd/work/bin/dpdk/lib/librte_eal/linuxapp/eal/eal.c, line 703.
(gdb) r -c 0xf -n 2 -- -i
Breakpoint 1, rte_eal_init (argc=7, argv=0xbffff694) at /home/xyd/work/bin/dpdk/lib/librte_eal/linuxapp/eal/eal.c:703
703		struct shared_driver *solib = NULL;
(gdb) c

        通过这种方式,不需要重新加载uio; 也不需要重新绑定网卡到uio驱动;也不需重新设置大页内存

2、方式2: 重新安装dpdk

        直接运行dpdk/tools/setup.sh脚本,输入选项1进行重新编译dpdk

root@apelife:/home/xyd/work/bin/dpdk# ./tools/setup.sh 
------------------------------------------------------------------------------
 RTE_SDK exported as /home/xyd/work/bin/dpdk
------------------------------------------------------------------------------
----------------------------------------------------------
 Step 1: Select the DPDK environment to build
----------------------------------------------------------
[1] i686-native-linuxapp-gcc
Option: 1

        在想要调试的例子: 例如example/helloworld目录下执行make, 接下里就可以使用gdb设置断点调试。

        通过这种方式,不需要重新加载uio; 也不需要重新绑定网卡到uio驱动;也不需重新设置大页内存。 好了, dpdk编译环境已经搭建好了,接下来一系列文章将会对源码进行分析

  • 7
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值