Linux平台上DPDK入门指南

1. 简介

本文档包含DPDK软件安装和配置的相关说明。旨在帮助用户快速启动和运行软件。文档主要描述了在Linux环境下编译和 运行DPDK应用程序,但是文档并不深入DPDK的具体实现细节。

1.1. 文档地图

以下是一份建议顺序阅读的DPDK参考文档列表:

  • 发布说明 : 提供特性发行版本的信息,包括支持的功能,限制,修复的问题,已知的问题等等。此外,还以FAQ方式提供了常见问题及解答。
  • 入门指南(本文档):介绍如何安装和配置DPDK,旨在帮助用户快速上手。
  • 编程指南 :描述如下内容:
    • 软件架构及如何使用(实例介绍),特别是在Linux环境中的用法
    • DPDK的主要内容,系统构建(包括可以在DPDK根目录Makefile中来构建工具包和应用程序的命令)及应用移植细则。
    • 软件中使用的,以及新开发中需要考虑的一些优化。

还提供了文档使用的术语表。

  • API参考 :提供有关DPDK功能、数据结构和其他编程结构的详细信息。
  • 示例程序用户指南 :描述了一组例程。 每个章节描述了一个用例,展示了具体的功能,并提供了有关如何编译、运行和使用的说明。

2. 系统要求

本章描述了编译DPDK所需的软件包。

注意:假如在Intel公司的89xx通信芯片组平台上使用DPDK,请参阅文档 Intel® Communications Chipset 89xx Series Software for Linux Getting Started Guide。

2.1. X86上预先设置BIOS

对大多数平台,使用基本DPDK功能无需对BIOS进行特殊设置。然而,对于HPET定时器和电源管理功能,以及为了获得40G网卡上小包处理的高性能,则可能需要更改BIOS设置。可以参阅章节 Enabling Additional Functionality 以获取更为详细的信息。

2.2. 编译DPDK

工具集:

注意:
以下说明在Fedora 18上通过了测试。其他系统所需要的安装命令和软件包可能有所不同。有关其他Linux发行版本和测试版本的详细信息,请参阅DPDK发布说明。

  • GNU make.
  • coreutils: cmp, sed, grep, arch 等.
  • gcc: 4.9以上的版本适用于所有的平台。 在某些发布版本中,启用了一些特定的编译器标志和链接标志(例如-fstack-protector)。请参阅文档的发布版本和 gcc -dumpspecs.
  • libc 头文件,通常打包成 gcc-multilib (glibc-devel.i686 / libc6-dev-i386; glibc-devel.x86_64 / libc6-dev 用于Intel 64位架构编译; glibc-devel.ppc64 用于IBM 64位架构编译;)
  • 构建Linux内核模块所需要的头文件和源文件。(kernel - devel.x86_64; kernel - devel.ppc64)
  • 在64位系统上编译32位软件包额外需要的软件为:
    • glibc.i686, libgcc.i686, libstdc++.i686 及 glibc-devel.i686, 适用于Intel的i686/x86_64;
    • glibc.ppc64, libgcc.ppc64, libstdc++.ppc64 及 glibc-devel.ppc64 适用于 IBM ppc_64;
      注意:x86_x32 ABI目前仅在Ubuntu 13.10及以上版本或者Debian最近的发行版本上支持。编译器必须是gcc 4.9+版本。 Python, 2.7+ or 3.2+版本, 用于运行DPDK软件包中的各种帮助脚本。

可选工具:

  • Intel® C++ Compiler (icc). 安装icc可能需要额外的库,请参阅编译器安装目录下的icc安装指南。
  • IBM® Advance ToolChain for Powerlinux. 这是一组开源开发工具和运行库。允许用户在Linux上使用IBM最新POWER硬件的优势。具体安装请参阅IBM的官方安装文档。
  • libpcap 头文件和库 (libpcap-devel) ,用于编译和使用基于libcap的轮询模式驱动程序。默认情况下,该驱动程序被禁用,可以通过在构建时修改配置文件 CONFIG_RTE_LIBRTE_PMD_PCAP=y 来开启。
  • 需要使用libarchive 头文件和库来进行某些使用tar获取资源的单元测试。

2.3. 运行DPDK应用程序

要运行DPDK应用程序,需要在目标机器上进行某些定制。

2.3.1. 系统软件

需求:

  • Kernel version >= 2.6.34, 当前内核版本可以通过命令查看:

    uname -r
    
  • glibc >= 2.7 (方便使用cpuset相关特性), 版本信息通命令 ldd --version 查看。

  • Kernel configuration, 在 Fedora OS 及其他常见的发行版本中,如 Ubuntu 或 Red Hat Enterprise Linux,供应商提供的配置可以运行大多数的DPDK应用程序。
    对于其他内核构件,应为DPDK开启的选项包括:

    • UIO 支持
    • HUGETLBFS 支持
    • PROC_PAGE_MONITOR 支持
    • 如果需要HPET支持,还应开启 HPET and HPET_MMAP 配置选项。有关信息参考 High Precision Event Timer (HPET) Functionality 章节获取更多信息。

2.3.2. 在 Linux 环境中使用 Hugepages

用于数据包缓冲区的大型内存池分配需要 Hugepages 支持(如上节所述,必须在运行的内核中开启 HUGETLBFS 选项)。通过使用大页分配,程序需要更少的页面,性能增加,因为较少的TLB减少了将虚拟页面地址翻译成物理页面地址所需的时间。如果没有大页,标准大小4k的页面会导致频繁的TLB miss,性能下降。

2.3.2.1. 预留Hugepages给DPDK使用

大页分配应该在系统引导时或者启动后尽快完成,以避免物理内存碎片化。要在引导时预留大页,需要给Linux内核命令行传递一个参数。

对于2MB大小的页面,只需要将hugepages选项传递给内核。如,预留1024个2MB大小的page,使用:

hugepages=1024

对于其他大小的hugepage,例如1G的页,大小必须同时指定。例如,要预留4个1G大小的页面给程序,需要传递以下选项给内核:

default_hugepagesz=1G hugepagesz=1G hugepages=4

注意:CPU支持的hugepage大小可以从Intel架构上的CPU标志位确定。如果存在pse,则支持2M个hugepages,如果page1gb存在,则支持1G的hugepages。
在IBM Power架构中,支持的hugepage大小为16MB和16GB。

注意:对于64位程序,如果平台支持,建议使用1GB的hugepages。
在双插槽NUMA的系统上,在启动时预留的hugepage数目通常在两个插槽之间评分(假设两个插槽上都有足够的内存)。

有关这些和其他内核选项的信息,请参阅Linux源代码目录中/kernel-parameter.txt文件。

特例:
对于2MB页面,还可以在系统启动之后再分配,通过向 /sys/devices/ 目录下的nr_hugepages文件写入hugepage数目来实现。 对于单节点系统,使用的命令如下(假设需要1024个页):

echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

在NUMA设备中,分配应该明确指定在哪个节点上:

echo 1024 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 1024 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages

注意:对于1G页面,系统启动之后无法预留页面内存。

2.3.2.2. DPDK使用Hugepages

一旦预留了hugepage内存,为了使内存可用于DPDK,请执行以下步骤:

mkdir /mnt/huge
mount -t hugetlbfs nodev /mnt/huge

通过将一下命令添加到 /etc/fstab 文件中,安装点可以在重启时永久保存:

nodev /mnt/huge hugetlbfs defaults 0 0

对于1GB内存,页面大小必须在安装选项中指定:

nodev /mnt/huge_1GB hugetlbfs pagesize=1GB 0 0

2.3.3. Linux环境中Xen Domain0支持

现有的内存管理实现是基于Linux内核的hugepage机制。在Xen虚拟机管理程序中,对于DomainU客户端的支持意味着DPDK程序与客户一样正常工作。
但是,Domain0不支持hugepages。为了解决这个限制,添加了一个新的内核模块rte_dom0_mm用于方便内存的分配和映射,通过 IOCTL (分配) 和 MMAP (映射).

2.3.3.1. DPDK中使能Xen Dom0模式

默认情况下,DPDK构建时禁止使用Xen Dom0模式。要支持Xen Dom0,CONFIG_RTE_LIBRTE_XEN_DOM0设置应该改为 “y”,编译时弃用该模式。
此外,为了允许接收错误套接字ID,CONFIG_RTE_EAL_ALLOW_INV_SOCKET_ID也必须设置为 “y”。

2.3.3.2. 加载DPDK rte_dom0_mm模块

要在Xen Dom0下运行任何DPDK应用程序,必须使用rsv_memsize选项将rte_dom0_mm 模块加载到运行的内核中。该模块位于DPDK目标目录的kmod子目录中。应该使用insmod命令加载此模块,如下所示:

sudo insmod kmod/rte_dom0_mm.ko rsv_memsize=X

X的值不能大于4096(MB).

2.3.3.3. 配置内存用于DPDK使用

在加载rte_dom0_mm.ko内核模块之后,用户必须配置DPDK使用的内存大小。这也是通过将内存大小写入到目录 /sys/devices/ 下的文件memsize中来实现的。 使用以下命令(假设需要2048MB):

echo 2048 > /sys/kernel/mm/dom0-mm/memsize-mB/memsize

用户还可以使用下面命令检查已经使用了多少内存:

cat /sys/kernel/mm/dom0-mm/memsize-mB/memsize_rsvd

Xen Domain0不支持NUMA配置,因此 --socket-mem 命令选项对Xen Domain0无效。

注意:
memsize的值不能大于rsv_memsize。

2.3.3.4. 在Xen Domain0上运行DPDK程序

要在Xen Domain0上运行DPDK程序,需要一个额外的命令行选项 --xen-dom0。

3. 使用源码编译DPDK目标文件

注意:这个过程的部分工作可以通过章节“使用脚本快速构建”描述的脚本来实现。

3.1. 安装DPDK及源码

首先,解压文件并进入到DPDK源文件根目录下:

tar xJf dpdk-<version>.tar.xz
cd dpdk-<version>

DPDK源文件由几个目录组成:

  • lib: DPDK 库文件
  • drivers: DPDK 轮询驱动源文件
  • app: DPDK 应用程序 (自动测试)源文件
  • examples: DPDK 应用例程
  • config, buildtools, mk: 框架相关的makefile、脚本及配置文件

3.2. DPDK目标环境安装

DPDK目标文件的格式为:

ARCH-MACHINE-EXECENV-TOOLCHAIN

其中:

  • ARCH 可以是: i686, x86_64, ppc_64
  • MACHINE 可以是: native, power8
  • EXECENV 可以是: linuxapp, bsdapp
  • TOOLCHAIN 可以是: gcc, icc
    目标文件取决于运行环境是32位还是64位设备。可以在DPDK的 /config 目录中找到可用的目标,不能使用defconfig_前缀。

注意:
配置文件根据 RTE_MACHINE 优化级别不同分别提供。在配置文件内部,RTE_MACHINE 配置为 native,
意味着已编译的软件被调整到其构建的平台上。有关此设置的更多信息,请参阅 DPDK 编程指南。
当使用Intel® C++ 编译器 (icc)时,对64位和32位,需要使用以下命令进行调整。 注意,shell脚本会更新 $PATH 值,因此不能再同一个会话中执行。 此外,还应该检查编译器的安装目录,因为可能不同。

source /opt/intel/bin/iccvars.sh intel64
source /opt/intel/bin/iccvars.sh ia32

在顶级目录中使用 make install T=<target> 来生成目标文件。

例如,为了使用icc编译生成64位目标文件,运行如下命令:

make install T=x86_64-native-linuxapp-icc

为了使用gcc编译生成32位目标文件,命令如下:

make install T=i686-native-linuxapp-gcc

如果仅仅只是生成目标文件,并不运行,比如,配置文件改变需要重新编译,使用 make config T=<target> 命令:

make config T=x86_64-native-linuxapp-gcc

注意:
任何需要运行的内核模块,如 igb_uio, kni, 必须在与目标文件编译相同的内核下进行编译。如果DPDK未在目标设备上构建,则应使用 RTE_KERNELDIR 环境变量将编译指向要在目标机上使用的内核版本的副本(交叉编译的内核版本)。
创建目标环境之后,用户可以移动到目标环境目录,并继续更改代码并编译。用户还可以通过编辑build目录中的.config文件对DPDK配置进行修改。 (这是顶级目录中defconfig文件的本地副本)。

cd x86_64-native-linuxapp-gcc
vi .config
make

此外,make clean命令可以用于删除任何现有的编译文件,以便后续完整、干净地重新编译代码。

3.3. Browsing the Installed DPDK Environment Target

一旦目标文件本创建,它就包含了构建客户应用程序所需的DPDK环境的所有库,包括轮询驱动程序和头文件。 此外,test和testpmd应用程序构建在build/app目录下,可以用于测试。 还有一个kmod目录,存放可能需要加载的内核模块。

3.4. 加载模块启动DPDK环境需要的UIO功能

要运行任何的DPDK应用程序,需要将合适的uio模块线加载到当前内核中。在多数情况下,Linux内核包含了标准的 uio_pci_generic 模块就可以提供uio能力。 该模块可以使用命令加载

sudo modprobe uio_pci_generic

区别于 uio_pci_generic ,DPDK提供了一个igb_uio模块(可以在kmod目录下找到)。可以通过如下方式加载:

sudo modprobe uio
sudo insmod kmod/igb_uio.ko

注意:
对于一下不支持传统中断的设备,例如虚拟功能(VF)设备,必须使用 igb_uio 来替代 uio_pci_generic 模块。
由于DPDK 1.7版本提供VFIO支持,所以,对于支持VFIO的平台,可选则UIO,也可以不用。

3.5. 加载VFIO模块

DPDK程序选择使用VFIO时,需要加载 vfio-pci 模块:

sudo modprobe vfio-pci

注意,要使用VFIO,首先,你的平台内核版本必须支持VFIO功能。 Linux内核从3.6.0版本之后就一直包含VFIO模块,通常是默认存在的。不够请查询发行文档以确认是否存在。
此外,要使用VFIO,内核和BIOS都必须支持,并配置为使用IO虚拟化 (如 Intel® VT-d)。
为了保证非特权用户运行DPDK时能够正确操作VFIO,还应设置正确的权限。这可以通过DPDK的配置脚本(dpdk-setup.sh文件位于usertools目录中)。

3.6. 网络端口绑定/解绑定到内核去顶模块

从版本1.4开始,DPDK应用程序不再自动解除所有网络端口与原先内核驱动模块的绑定关系。 相反的,DPDK程序在运行前,需要将所要使用的端口绑定到 uio_pci_generic, igb_uio 或 vfio-pci 模块上。 任何Linux内核本身控制的端口无法被DPDK PMD驱动所使用。

注意:
默认情况下,DPDK将在启动时不再自动解绑定内核模块与端口的关系。DPDK应用程序使用的任何端口必须与Linux无关,并绑定到 uio_pci_generic, igb_uio 或 vfio-pci 模块上。
将端口从Linux内核解绑,然后绑定到 uio_pci_generic, igb_uio 或 vfio-pci 模块上供DPDK使用,可以使用脚本dpdk_nic _bind.py(位于usertools目录下)。 这个工具可以用于提供当前系统上网络接口的状态图,绑定或解绑定来自不同内核模块的接口。 以下是脚本如何使用的一些实例。通过使用 --help or --usage 选项调用脚本,可以获得脚本的完整描述与帮助信息。 请注意,要将接口绑定到uio或vfio的话,需要先将这两个模块加载到内核,再运行 dpdk-devbind.py 脚本。

注意:
由于VFIO的工作方式,设备是否可用VFIO是有明确限制的。大部分是由IOMMU组的功能决定的。
任何的虚拟设备可以独立使用VFIO,但是物理设备则要求将所有端口绑定到VFIO,或者其中一些绑定到VFIO,而其他端口不能绑定到任何其他驱动程序。
如果你的设备位于PCI-to-PCI桥之后,桥接器将成为设备所在的IOMMU组的一部分。因此,桥接驱动程序也应该从端口解绑定。

注意:
虽然任何用户都可以运行dpdk-devbind.py脚本来查看网络接口的状态,但是绑定和解绑定则需要root权限。
查看系统中所有网络接口的状态:

./usertools/dpdk-devbind.py --status

Network devices using DPDK-compatible driver
============================================
0000:82:00.0 '82599EB 10-GbE NIC' drv=uio_pci_generic unused=ixgbe
0000:82:00.1 '82599EB 10-GbE NIC' drv=uio_pci_generic unused=ixgbe

Network devices using kernel driver
===================================
0000:04:00.0 'I350 1-GbE NIC' if=em0  drv=igb unused=uio_pci_generic *Active*
0000:04:00.1 'I350 1-GbE NIC' if=eth1 drv=igb unused=uio_pci_generic
0000:04:00.2 'I350 1-GbE NIC' if=eth2 drv=igb unused=uio_pci_generic
0000:04:00.3 'I350 1-GbE NIC' if=eth3 drv=igb unused=uio_pci_generic

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

绑定设备 eth1,04:00.1, 到 uio_pci_generic 驱动:

./usertools/dpdk-devbind.py --bind=uio_pci_generic 04:00.1
或者

./usertools/dpdk-devbind.py --bind=uio_pci_generic eth1

恢复设备 82:00.0 到Linux内核绑定状态:

./usertools/dpdk-devbind.py --bind=ixgbe 82:00.0

4. 编译和运行简单应用程序

本章介绍如何在DPDK环境下编译和运行应用程序。还指出应用程序的存储位置。
注意:此过程的部分操作也可以使用脚本来完成。参考 使用脚本快速构建 章节描述。

4.1. 编译一个简单应用程序

一个DPDK目标环境创建完成时(如 x86_64-native-linuxapp-gcc),它包含编译一个应用程序所需要的全部库和头文件。
当在Linux* 交叉环境中编译应用程序时,以下变量需要预先导出:

  • RTE_SDK - 指向DPDK安装目录。
  • RTE_TARGET - 指向DPDK目标环境目录。

以下是创建helloworld应用程序实例,该实例将在DPDK Linux环境中运行。这个实例可以在目录${RTE_SDK}/examples找到。

该目录包含 main.c 文件。该文件与DPDK目标环境中的库结合使用时,调用各种函数初始化DPDK环境,然后,为每个要使用的core启动一个入口点(调度应用程序)。 默认情况下,二进制文件存储在build目录中。

cd examples/helloworld/
export RTE_SDK=$HOME/DPDK
export RTE_TARGET=x86_64-native-linuxapp-gcc

make
    CC main.o
    LD helloworld
    INSTALL-APP helloworld
    INSTALL-MAP helloworld.map

ls build/app
    helloworld helloworld.map

注意:
在上面的例子中, helloworld 是在DPDK的目录结构下的。 当然,也可以将其放在DPDK目录之外,以保证DPDK的结构不变。 下面的例子, helloworld 应用程序被复制到一个新的目录下。

export RTE_SDK=/home/user/DPDK
cp -r $(RTE_SDK)/examples/helloworld my_rte_app
cd my_rte_app/
export RTE_TARGET=x86_64-native-linuxapp-gcc

make
  CC main.o
  LD helloworld
  INSTALL-APP helloworld
  INSTALL-MAP helloworld.map

4.2. 运行一个简单的应用程序

注意: UIO驱动和hugepage必须在程序运行前设置好。

注意:应用程序使用的任何端口,必须绑定到合适的内核驱动模块上,如章节 网络端口绑定/解绑定到内核去顶模块 描述的那样。
应用程序与DPDK目标环境的环境抽象层(EAL)库相关联,该库提供了所有DPDK程序通用的一些选项。

以下是EAL提供的一些选项列表:

./rte-app -c COREMASK [-n NUM] [-b <domain:bus:devid.func>] \
          [--socket-mem=MB,...] [-m MB] [-r NUM] [-v] [--file-prefix] \
          [--proc-type <primary|secondary|auto>] [-- xen-dom0]

选项描述如下:

  • -c COREMASK: 要运行的内核的十六进制掩码。注意,平台之间编号可能不同,需要事先确定。
  • -n NUM: 每个处理器插槽的内存通道数目。
  • -b <domain:bus:devid.func>: 端口黑名单,避免EAL使用指定的PCI设备。
  • --use-device: 仅使用指定的以太网设备。使用逗号分隔 [domain:]bus:devid.func 值,不能与 -b 选项一起使用。
  • --socket-mem: 从特定插槽上的hugepage分配内存。
  • -m MB: 内存从hugepage分配,不管处理器插槽。建议使用 --socket-mem 而非这个选项。
  • -r NUM: 内存数量。
  • -v: 显示启动时的版本信息。
  • --huge-dir: 挂载hugetlbfs的目录。
  • --file-prefix: 用于hugepage文件名的前缀文本。
  • --proc-type: 程序实例的类型。
  • --xen-dom0: 支持在Xen Domain0上运行,但不具有hugetlbfs的程序。
  • --vmware-tsc-map: 使用VMware TSC 映射而不是本地RDTSC。
  • --base-virtaddr: 指定基本虚拟地址。
  • --vfio-intr: 指定要由VFIO使用的中断类型。(如果不支持VFIO,则配置无效)。
    其中 -c 是强制性的,其他为可选配置。

将DPDK应用程序二进制文件拷贝到目标设备,按照如下命令运行(我们假设每个平台处理器有4个内存通道,并且存在core0~3用于运行程序):

./helloworld -c f -n 4

注意:选项 --proc-type 和 --file-prefix 用于运行多个DPDK进程。请参阅 “多应用程序实例” 章节及 DPDK 编程指南 获取更多细节。

4.2.1. 应用程序使用的逻辑Core

对于DPDK应用程序,coremask参数始终是必须的。掩码的每个位对应于Linux提供的逻辑core ID。 由于这些逻辑core的编号,以及他们在NUMA插槽上的映射可能因平台而异,因此建议在选择每种情况下使用的coremaks时,都要考虑每个平台的core布局。

在DPDK程序初始化EAL层时,将显示要使用的逻辑core及其插槽位置。可以通过读取 /proc/cpuinfo 文件来获取系统上所有core的信息。例如执行 cat /proc/cpuinfo。 列出来的physical id 属性表示其所属的CPU插槽。当使用了其他处理器来了解逻辑core到插槽的映射时,这些信息很有用。

注意:可以使用另一个Linux工具 lstopo 来获取逻辑core布局的图形化信息。在Fedora Linux上, 可以通过如下命令安装并运行工具:

sudo yum install hwloc
./lstopo

注意:逻辑core在不同的电路板上可能不同,在应用程序使用coremaks时需要先确定。

4.2.2. 应用程序使用的Hugepage内存

当运行应用程序时,建议使用的内存与hugepage预留的内存一致。如果运行时没有 -m 或 --socket-mem 参数传入,这由DPDK应用程序在启动时自动完成。

如果通过显示传入 -m 或 --socket-mem 值,但是请求的内存超过了该值,应用程序将执行失败。 但是,如果用户请求的内存小于预留的hugepage-memory,应用程序也会失败,特别是当使用了 -m 选项的时候。 因为,假设系统在插槽0和插槽1上有1024个预留的2MB页面,如果用户请求128 MB的内存,可能存在64个页不符合要求的情况:

内核只能在插槽1中将hugepage-memory提供给应用程序。在这种情况下,如果应用程序尝试创建一个插槽0中的对象,例如ring或者内存池,那么将执行失败 为了避免这个问题,建议使用 --socket-mem 选项替代 -m 选项。
这些页面可能位于物理内存中的任意位置,尽管DPDK EAL将尝试在连续的内存块中分配内存,但是页面可能是不连续的。在这种情况下,应用程序无法分配大内存。
使用socket-mem选项可以为特定的插槽请求特定大小的内存。通过提供 --socket-mem 标志和每个插槽需要的内存数量来实现的,如 --socket-mem=0,512 用于在插槽1上预留512MB内存。 类似的,在4插槽系统上,如果只能在插槽0和2上分配1GB内存,则可以使用参数–socket-mem=1024,0,1024 来实现。 如果DPDK无法在每个插槽上分配足够的内存,则EAL初始化失败。

4.3. 其他示例程序

其他的一些示例程序包含在${RTE_SDK}/examples 目录下。这些示例程序可以使用本手册前面部分所述的方法进行构建运行。另外,请参阅 DPDK示例程序用户指南 了解应用程序的描述、编译和执行的具体说明以及代码解释。

4.4. 附加的测试程序

此外,还有两个在创建库时构建的应用程序。这些源文件位于 DPDK/app目录下,称为test和testpmd程序。创建库之后,可以在build目录中找到。

  • test程序为DPDK中的各种功能提供具体的测试。
  • testpmd程序提供了许多不同的数据包吞吐测试,例如,在Intel® 82599 10 Gigabit Ethernet Controller中如何使用Flow Director。

5. 启用附加功能

5.1. 高精度事件定时器(HPET)功能

5.1.1. BIOS支持

要使用HPET功能时,必须先在平台BIOS上开启高精度定时器。否则,默认情况下使用时间戳计数器(TSC)。通常情况下,起机时按F2 可以访问BIOS。然后用户可以导航到HPET选项。在Crystal Forest平台BIOS上,路径为:Advanced -> PCH-IO Configuration -> High Precision Timer -> (如果需要,将Disabled 改为 Enabled )。

在已经起机的系统上,可以使用以下命令来检查HPET是否启用

grep hpet /proc/timer_list

如果没有条目,则必须在BIOS中启用HPET,镔铁重新启动系统。

5.1.2. Linux内核支持

DPDK通过将定时器计数器映射到进程地址空间来使用平台的HPET功能,因此,要求开启 HPET_MMAP 系统内核配置选项。
注意:在Fedora或者其他常见的Linux发行版本(如Ubuntu)中,默认不会启用 HPET_MMAP 选项。要重新编译启动此选项的内核,请参阅发行版本的相关说明。

5.1.3. DPDK中使能HPET

默认情况下,DPDK配置文件中是禁用HPET功能的。要使用HPET,需要将CONFIG_RTE_LIBEAL_USE_HPET设置为y来开启编译。
对于那些使用 rte_get_hpet_cycles()及rte_get_hpet_hz()API接口的应用程序,并且选择了HPET作为rte_timer库的默认时钟源,需要在初始化时调用 rte_eal_hpet_init()API。这个API调用将保证HPET可用,如果HPET不可用(例如,内核没有开启 HPET_MMAP 使能),则向程序返回一个错误值。 如果HPET在运行时不可用,应用程序可以方便的采取其他措施。
注意:对于那些仅需要普通定时器API,而不是HPET定时器的应用程序,建议使用 rte_get_timer_cycles() 和 rte_get_timer_hz() API调用,而不是HPET API。 这些通用的API兼容TSC和HPET时钟源,具体时钟源则取决于应用程序是否调用 rte_eal_hpet_init()初始化,以及运行时系统上可用的时钟。

5.2. 没有Root权限情况下运行DPDK应用程序

虽然DPDK应用程序直接使用了网络端口及其他硬件资源,但通过许多小的权限调整,可以允许除root权限之外的用户运行这些应用程序。 为了保证普通的Linux用户也可以运行这些程序,需要调整如下Linux文件系统权限:

  • 所有用于hugepage挂载点的文件和目录,如 /mnt/huge
  • /dev 中的UIO设备文件,如 /dev/uio0, /dev/uio1 等
  • UIO系统配置和源文件,如 uio0:
    • /sys/class/uio/uio0/device/config
    • /sys/class/uio/uio0/device/resource*
  • 如果要使用HPET,那么 /dev/hpet 目录也要修改

注意:在某些Linux 安装中, /dev/hugepages 也是默认创建hugepage挂载点的文件。

5.3. 电源管理和节能功能

如果要使用DPDK的电源管理功能,必须在平台BIOS中启用增强的Intel SpeedStep® Technology。否则,sys文件夹下 /sys/devices/system/cpu/cpu0/cpufreq 将不存在,不能使用基于CPU频率的电源管理。请参阅相关的BIOS文档以确定如何访问这些设置。

例如,在某些Intel参考平台上,开启Enhanced Intel SpeedStep® Technology 的路径为:

Advanced
  -> Processor Configuration
  -> Enhanced Intel SpeedStep® Tech

此外,C3 和 C6 也应该使能以支持电源管理。C3 和 C6 的配置路径为:

Advanced
  -> Processor Configuration
  -> Processor C3 Advanced
  -> Processor Configuration
  -> Processor C6

5.4. 使用Linux Core隔离来减少上下文切换

虽然DPDK应用程序使用的线程固定在系统的逻辑核上,但Linux调度程序也可以在这些核上运行其他任务。为了防止在这些核上运行额外的工作负载,可以使用 isolcpus Linux 内核参数来将其与通用的Linux调度程序隔离开来。

例如,如果DPDK应用程序要在逻辑核2,4,6上运行,应将以下内容添加到内核参数表中:

isolcpus=2,4,6

5.5. 加载 DPDK KNI 内核模块

要运行DPDK Kernel NIC Interface (KNI) 应用程序,需要将一个额外的内核模块(kni模块)加载到内核中。 该模块位于DPDK目录kmod子目录中。与 igb_uio 模块加载类似,(假设当前目录就是DPDK目录):

insmod kmod/rte_kni.ko

注意:相关的详细信息,可以参阅 “Kernel NIC Interface Sample Application” 章节和 DPDK 示例程序用户指南 。

5.6. Linux IOMMU Pass-Through使用Intel® VT-d运行DPDK

要在Linux内核中启用Intel® VT-d,必须配置一系列内核选项,包括:

IOMMU_SUPPORT
IOMMU_API
INTEL_IOMMU

另外,要使用Intel® VT-d运行DPDK,使用igb_uio驱动时必须携带iommu=pt参数。这使得主机可以直接通过DMA重映射查找。另外,如果内核中没有设置 INTEL_IOMMU_DEFAULT_ON 参数,那么也必须使用 intel_iommu=on 参数。这可以确保 Intel IOMMU 被正确初始化。

请注意,对于igb_uio 驱动程序,使用iommu = pt是必须的,vfio-pci驱动程序实际上可以同时使用iommu = pt和iommu = on。

5.7. 40G NIC上的小包处理高性能

由于在最新版本中可能提供用于性能提升的固件修复,因此最好进行固件更新以获取更高的性能。 请和 Intel’s Network Division 工程师联系以进行固件更新。 用户可以参考DPDK版本发行说明,以使用 i40e 驱动程序识别NIC的已验证固件版本。

5.7.1. 使用16B大小的RX描述符

由于 i40e PMD 支持16B和32B的RX描述符,而16B大小的描述符可以帮助小型数据包提供性能,因此,配置文件中 CONFIG_RTE_LIBRTE_I40E_16BYTE_RX_DESC 更改为使用16B大小的描述符。

5.7.2. 高性能和每数据包延迟权衡

由于硬件设计,每个数据包描述符回写都需要NIC内部的中断信号。中断的最小间隔可以在编译时通过配置文件中的 CONFIG_RTE_LIBRTE_I40E_ITR_INTERVAL 指定。 虽然有默认配置,但是该配置可以由用户自行调整,这取决于用户所关心的内容,整体性能或者每数据包延迟。

6. 使用脚本快速构建

usertools目录中的dpdk-setup.sh脚本,向用户提供了快速执行如下任务功能:

  • 构建DPDK库
  • 加载/卸载DPDK IGB_UIO内核模块
  • 加载/卸载VFIO内核模块
  • 加载/卸载DPDK KNI内核模块
  • 创建/删除NUMA 或 non-NUMA平台的hugepages
  • 查看网络端口状态和预留给DPDK应用程序使用的端口
  • 设置非root用户使用VFIO的权限
  • 运行test和testpmd应用程序
  • 查看meminfo中的hugepages
  • 列出在 /mnt/huge 中的hugepages
  • 删除内置的DPDK库
    对于其中一个EAL目标,一旦完成了这些步骤,用户就可以编译自己的在EAL库中链接的应用程序来创建DPDK映像。

6.1. 脚本组织

dpdk-setup.sh脚本在逻辑上组织成用户按顺序执行的一系列步骤。每个步骤都提供了许多选项来指导用户完成所需的任务。以下是每个步骤的简单介绍:

Step 1: Build DPDK Libraries

最开始,用户必须指定tagert的类型以便编译正确的库。

如本入门指南前面的章节描述,用户必须在此之前就安装好所有的库、模块、更新和编译器。

Step 2: Setup Environment

用户需要配置Linux* 环境以支持DPDK应用程序的运行。 可以为NUMA 或non-NUMA系统分配Hugepages。任何原来已经存在的hugepages将被删除。 也可以在此步骤中插入所需的DPDK内核模块,并且可以将网络端口绑定到此模块供DPDK使用。

Step 3: Run an Application

一旦执行了其他步骤,用户就可以运行test程序。该程序允许用户为DPDK运行一系列功能测试。也可以运行支持数据包接收和发送的testpmd程序。

Step 4: Examining the System

此步骤提供了一些用于检查Hugepage映射状态的工具。

Step 5: System Cleanup

最后一步具有将系统恢复到原始状态的选项。

6.2. Use Cases

以下是使用dpdk-setup.sh的示例。脚本应该使用source命令运行。脚本中的某些选项在继续操作之前提示用户需要进一步的数据输入。

注意:必须与root权限运行dpdk-setup.sh。

source usertools/dpdk-setup.sh

------------------------------------------------------------------------

RTE_SDK exported as /home/user/rte

------------------------------------------------------------------------

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-native-bsdapp-clang

[5] x86_64-native-bsdapp-gcc

[6] x86_64-native-linuxapp-clang

[7] x86_64-native-linuxapp-gcc

[8] 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:

以下选项演示了 “x86_64-native-linuxapp-gcc“ DPDK库的创建。

Option: 9

================== Installing x86_64-native-linuxapp-gcc

Configuration done
== Build lib
...
Build complete
RTE_TARGET exported as x86_64-native-linuxapp-gcc

以下选项用于启动DPDK UIO驱动程序。

Option: 25

Unloading any existing DPDK UIO module
Loading DPDK UIO module

以下选项演示了在NUMA系统中创建hugepage。为每个node分配1024个2MB的页。 应用程序应该使用 -m 4096 来启动,以便访问这两个内存区域。(如果没有 -m 选项,则自动完成)。
注意:如果显示提示以删除临时文件,请输入’y’。

Option: 15

Removing currently reserved hugepages
mounting /mnt/huge and removing directory
Input the number of 2MB pages for each node
Example: to have 128MB of hugepages available per node,
enter '64' to reserve 64 * 2MB pages on each node
Number of pages for node0: 1024
Number of pages for node1: 1024
Reserving hugepages
Creating /mnt/huge and mounting as hugetlbfs

以下操作说明了启动测试应用程序以在单个core上运行

Option: 20

Enter hex bitmask of cores to execute test app on
Example: to execute app on cores 0 to 7, enter 0xff
bitmask: 0x01
Launching app
EAL: coremask set to 1
EAL: Detected lcore 0 on socket 0
...
EAL: Master core 0 is ready (tid=1b2ad720)
RTE>>

6.3. 应用程序

一旦用户运行和dpdk-setup.sh脚本,构建了目标程序并且设置了hugepages,用户就可以继续构建和运行自己的应用程序或者源码中提供的示例。

/examples 目录中提供的示例程序为了解DPDK提供了很好的起点。 以下命令显示了helloworld应用程序的构建和运行方式。 按照4.2.1节,”应用程序使用的逻辑Core”描述,当选择用于应用程序的coremask时,需要确定平台的逻辑core的布局。

cd helloworld/
make
  CC main.o
  LD helloworld
  INSTALL-APP helloworld
  INSTALL-MAP helloworld.map

sudo ./build/app/helloworld -c 0xf -n 3
[sudo] password for rte:

EAL: coremask set to f
EAL: Detected lcore 0 as core 0 on socket 0
EAL: Detected lcore 1 as core 0 on socket 1
EAL: Detected lcore 2 as core 1 on socket 0
EAL: Detected lcore 3 as core 1 on socket 1
EAL: Setting up hugepage memory...
EAL: Ask a virtual area of 0x200000 bytes
EAL: Virtual area found at 0x7f0add800000 (size = 0x200000)
EAL: Ask a virtual area of 0x3d400000 bytes
EAL: Virtual area found at 0x7f0aa0200000 (size = 0x3d400000)
EAL: Ask a virtual area of 0x400000 bytes
EAL: Virtual area found at 0x7f0a9fc00000 (size = 0x400000)
EAL: Ask a virtual area of 0x400000 bytes
EAL: Virtual area found at 0x7f0a9f600000 (size = 0x400000)
EAL: Ask a virtual area of 0x400000 bytes
EAL: Virtual area found at 0x7f0a9f000000 (size = 0x400000)
EAL: Ask a virtual area of 0x800000 bytes
EAL: Virtual area found at 0x7f0a9e600000 (size = 0x800000)
EAL: Ask a virtual area of 0x800000 bytes
EAL: Virtual area found at 0x7f0a9dc00000 (size = 0x800000)
EAL: Ask a virtual area of 0x400000 bytes
EAL: Virtual area found at 0x7f0a9d600000 (size = 0x400000)
EAL: Ask a virtual area of 0x400000 bytes
EAL: Virtual area found at 0x7f0a9d000000 (size = 0x400000)
EAL: Ask a virtual area of 0x400000 bytes
EAL: Virtual area found at 0x7f0a9ca00000 (size = 0x400000)
EAL: Ask a virtual area of 0x200000 bytes
EAL: Virtual area found at 0x7f0a9c600000 (size = 0x200000)
EAL: Ask a virtual area of 0x200000 bytes
EAL: Virtual area found at 0x7f0a9c200000 (size = 0x200000)
EAL: Ask a virtual area of 0x3fc00000 bytes
EAL: Virtual area found at 0x7f0a5c400000 (size = 0x3fc00000)
EAL: Ask a virtual area of 0x200000 bytes
EAL: Virtual area found at 0x7f0a5c000000 (size = 0x200000)
EAL: Requesting 1024 pages of size 2MB from socket 0
EAL: Requesting 1024 pages of size 2MB from socket 1
EAL: Master core 0 is ready (tid=de25b700)
EAL: Core 1 is ready (tid=5b7fe700)
EAL: Core 3 is ready (tid=5a7fc700)
EAL: Core 2 is ready (tid=5affd700)
hello from core 1
hello from core 2
hello from core 3
hello from core 0

7. 如何获取Intel平台上网卡的最佳性能

本文档一步一步教你如何在Intel平台上运行DPDK程序以获取最佳性能。

7.1. 硬件及存储需求

为了获得最佳性能,请使用Intel Xeon级服务器系统,如Ivy Bridge,Haswell或更高版本。

确保每个内存通道至少插入一个内存DIMM,每个内存通道的内存大小至少为4GB。 Note: 这对性能有最直接的影响。

可以通过使用 dmidecode 来检查内存配置:

dmidecode -t memory | grep Locator

Locator: DIMM_A1
Bank Locator: NODE 1
Locator: DIMM_A2
Bank Locator: NODE 1
Locator: DIMM_B1
Bank Locator: NODE 1
Locator: DIMM_B2
Bank Locator: NODE 1
...
Locator: DIMM_G1
Bank Locator: NODE 2
Locator: DIMM_G2
Bank Locator: NODE 2
Locator: DIMM_H1
Bank Locator: NODE 2
Locator: DIMM_H2
Bank Locator: NODE 2

上面的示例输出显示共有8个通道,从 A 到 H,每个通道都有2个DIMM。

你也可以使用 dmidecode 来确定内存频率:

dmidecode -t memory | grep Speed

Speed: 2133 MHz
Configured Clock Speed: 2134 MHz
Speed: Unknown
Configured Clock Speed: Unknown
Speed: 2133 MHz
Configured Clock Speed: 2134 MHz
Speed: Unknown
...
Speed: 2133 MHz
Configured Clock Speed: 2134 MHz
Speed: Unknown
Configured Clock Speed: Unknown
Speed: 2133 MHz
Configured Clock Speed: 2134 MHz
Speed: Unknown
Configured Clock Speed: Unknown

输出显示2133 MHz(DDR4)和未知(不存在)的速度。这与先前的输出一致,表明每个通道都有一个存储。

7.1.1. 网卡需求

使用 DPDK supported http://dpdk.org/doc/nics 描述的高端NIC,如Intel XL710 40GbE。

确保每个网卡已经更新最新版本的NVM/固件。

使用PCIe Gen3 插槽,如 Gen3 x8 或者 Gen3 x16 ,因为PCIe Gen2 插槽不能提供2 x 10GbE或更高的带宽。 可以使用 lspci 命令来检查PCI插槽的速率:

lspci -s 03:00.1 -vv | grep LnkSta

LnkSta: Speed 8GT/s, Width x8, TrErr- Train- SlotClk+ DLActive- ...
LnkSta2: Current De-emphasis Level: -6dB, EqualizationComplete+ ...

当将NIC插入PCI插槽时,需要查看屏幕输出,如 CPU0 或 CPU1,以指示连接的插槽。

同时应该注意NUMA,如果使用不同网卡的2个或更多端口,最好确保这些NIC在同一个CPU插槽上,下面进一步展示了如何确定这一点。

7.1.2. BIOS 设置

以下是关于BIOS设置的一些建议。不同的平台可能会有不同的名字,因此如下仅用于参考:

  • 开始之前,请考虑将所有BIOS设置为默认值
  • 禁用所有省电选项,如电源性能调整、CPU P-State, CPU C3 Report and CPU C6 Report。
  • 选择 Performance 作为CPU电源及性能策略。
  • 禁用Turbo Boost以确保性能缩放随着内核数量的增加而增加。
  • 将内存频率设置为最高可用的值,NOT auto。
  • 当测试NIC的物理功能时,禁用所有的虚拟化选项,如果要使用VFIO,请打开 VT-d if you wants to use VFIO.

7.1.3. Linux引导选项

以下是GRUB启动选项的一些建议配置:

  • 使用默认的grub文件作为起点
  • 通过grub配置保留1G的hugepage。例如,保留8个1G大小的页面:
default_hugepagesz=1G hugepagesz=1G hugepages=8

隔离将用于DPDK的CPU core.如:

isolcpus=2,3,4,5,6,7,8

如果要使用VFIO,请使用以下附加的grub参数:

iommu=pt intel_iommu=on

7.2. 运行DPDK前的配置

构建目标文件,预留hugepage。参阅前面 在 Linux 环境中使用 Hugepages 描述。

以下命令为具体过程:

# Build DPDK target.
cd dpdk_folder
make install T=x86_64-native-linuxapp-gcc -j

# Get the hugepage size.
awk '/Hugepagesize/ {print $2}' /proc/meminfo

# Get the total huge page numbers.
awk '/HugePages_Total/ {print $2} ' /proc/meminfo

# Unmount the hugepages.
umount `awk '/hugetlbfs/ {print $2}' /proc/mounts`

# Create the hugepage mount folder.
mkdir -p /mnt/huge

# Mount to the specific folder.
mount -t hugetlbfs nodev /mnt/huge

使用命令 cpu_layout 来检查CPU布局:

cd dpdk_folder
usertools/cpu_layout.py

或者运行 lscpu 检查每个插槽上的core。
检查NIC ID和插槽ID:

列出所有的网卡的PCI地址及设备ID.

lspci -nn | grep Eth

例如,假设你的输入如下:

82:00.0 Ethernet [0200]: Intel XL710 for 40GbE QSFP+ [8086:1583]
82:00.1 Ethernet [0200]: Intel XL710 for 40GbE QSFP+ [8086:1583]
85:00.0 Ethernet [0200]: Intel XL710 for 40GbE QSFP+ [8086:1583]
85:00.1 Ethernet [0200]: Intel XL710 for 40GbE QSFP+ [8086:1583]

检测PCI设备相关联的NUMA节点:

cat /sys/bus/pci/devices/0000\:xx\:00.x/numa_node

通常的,0x:00.x 表示在插槽0,而 8x:00.x 表示在插槽1。 Note: 为了说去最佳性能,请保证core和NIC位于同一插槽中。 在上面的例子中 85:00.0 在插槽1,因此必须被插槽1上的core使用才能获得最佳性能。
将测试端口绑定到DPDK兼容的驱动程序,如igb_uio。例如,将两个端口绑定到兼容DPDK的驱动程序并检查状态:

绑定端口 82:00.0 和 85:00.0 到DPDK驱动

./dpdk_folder/usertools/dpdk-devbind.py -b igb_uio 82:00.0 85:00.0

检查端口驱动状态

./dpdk_folder/usertools/dpdk-devbind.py --status

运行 dpdk-devbind.py --help 以获取更多信息。
有关DPDK设置和Linux内核需求的更多信息,请参阅 使用源码编译DPDK目标文件 。

7.3. 网卡最佳性能实践举例

以下是运行DPDK l3fwd 例程并获取最佳性能的例子。使用 Intel 服务平台和Intel XL710 NICs。具体的40G NIC配置请参阅i40e NIC指南。

本例场景是通过两个Intel XL710 40GbE端口获取最优性能。请参阅 Fig. 7.1 用于性能测试设置。

Fig. 7.1 性能测试搭建

将两个Intel XL710 NIC添加到平台,并使用每个卡一个端口来获得最佳性能。使用两个NIC的原因是克服PCIe Gen3的限制,因为它不能提供80G带宽。 对于两个40G端口,但两个不同的PCIe Gen3 x8插槽可以。 请参考上面的示例NIC输出,然后我们可以选择 82:00.0 及 85:00.0 作为测试端口:

82:00.0 Ethernet [0200]: Intel XL710 for 40GbE QSFP+ [8086:1583]
85:00.0 Ethernet [0200]: Intel XL710 for 40GbE QSFP+ [8086:1583]

将端口连接到打流机,对于高速测试,最好有专用的打流设备。
检测PCI设备的numa节点,并获取该插槽id上的core。 在本例中, 82:00.0 和 85:00.0 都在插槽1上,插槽1上的core id为18-35 和 54-71。
注意: 不要在同一个core上使用两个逻辑核(e.g core18 有两个逻辑核core18 and core54),而是使用来自不同core的两个逻辑核。
将这两个端口绑定到igb_uio。
对于XL710 40G 端口,我们需要至少两个队列来实现最佳性能,因此每个端口需要两个队列,每个队列将需要专用的CPU内核来接收/发送数据包。
使用DPDK示例程序 l3fwd 做性能测试,两个端口进行双向转发,使用默认的lpm模式编译 l3fwd sample。
运行l3fwd的命令如下所示:

./l3fwd -c 0x3c0000 -n 4 -w 82:00.0 -w 85:00.0 \
        -- -p 0x3 --config '(0,0,18),(0,1,19),(1,0,20),(1,1,21)'

命令表示应用程序使用(core18,port0,队列0),(core19,port0,队列1), (core20,port1,队列0),(core18,port1,队列1)。
配置打流机用于发包

创建流
设置报文类型为Ethernet II type to 0x0800。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容