# NUMA

NUMA来源于AMD Opteron微架构，处理器和本地内存之间有更小的延迟和更大的带宽；每个处理器还可以有自己的总线。处理器访问本地的总线和内存时延迟低，而访问远程资源时则要高。

![](/files/-LDZS6NrpcKegVGOcphd)

DPDK充分利用了NUMA的特点

* Per-core memory，每个核都有自己的内存，一方面是本地内存的需要，另一方面也是为了cache一致性
* 用本地处理器和本地内存处理本地设备上产生的数据

```c
q = rte_zmalloc_socket("fm10k", sizeof(*q), RTE_CACHE_LINE_SIZE, socket_id)
```

CPU核心的几个概念：

* 处理器核数（cpu cores）：每个物理CPUcore的个数
* 逻辑处理器核心数（siblings）：单个物理处理器超线程的个数
* 系统物理处理器封装ID（physical id）：也称为socket插槽，物理机处理器封装个数，物理CPU个数
* 系统逻辑处理器ID（processor）：逻辑CPU数，是物理处理器的超线程技术

**CPU亲和性**

将进程与CPU绑定，提高了Cache命中率，从而减少内存访问损耗。CPU亲和性的主要应用场景为

* 大量计算场景
* 运行时间敏感、决定性的线程，即实时线程

相关工具

* `sched_set_affinity()`、`sched_get_affinity()`内核函数
* taskset命令
* isolcpus内核启动参数：CPU绑定之后依然是有可能发生线程切换，可以借助isolcpus=2,3将cpu从内核调度系统中剥离。

**DPDK中的CPU亲和性**

DPDK中lcore实际上是EAL pthread，每个EAL pthread都有一个Thread Local Storage的`_lcore_id`，`_lcore_id`与CPU ID是一致的。注意虽然默认是1:1关系，但可以通过`--lcores='<lcore_set>@<cpu_set>'`来指定lcore的CPU亲和性，这样可以不是1:1的，也就是多个lcore还是可以亲和到同一个的核，这就需要注意调度的情况（以非抢占式无锁`rte_ring`为例）：

* 单生产者、单消费者模式不受影响
* 多生产者、多消费者模式，调度策略为`SCHED_OTHER`时，性能会有所影响
* 多生产者、多消费者模式，调度策略为`SCHED_FIFO/SCHED_RR`，会产生死锁

而在具体实现流程如下所示：

![](/files/-LDZS6OAWmAvf-2O7u0l)

* DPDK通过读取`/sys/devices/system/cpu/cpuX/`目录的信息获取CPU的分布情况，将第一个核设置为MASTER，并通过`eal_thread_set_affinity()`为每个SLAVE绑定CPU
* 不同模块要调用`rte_eal_mp_remote_launch()`将自己的回调函数注册到DPDK中（`lcore_config[].f`）
* 每个核最终调用`eal_thread_loop()->回调函数`来执行真正的逻辑

**指令并发**

借助SIMD（Single Instruction Multiple Data，单指令多数据）可以最大化的利用一级缓存访存的带宽，但对频繁的窄位宽数据操作就有比较大的副作用。DPDK中的`rte_memcpy()`在Intel处理器上充分利用了SSE/AVX的特点：优先保证Store指令存储的地址对齐，然后在每个指令周期指令2条Load的特新弥补一部分非对齐Load带来的性能损失。

## 数据同步与互斥

DPDK根据多核处理器的特点，遵循资源局部化的原则，解耦数据的跨核共享，使得性能可以有很好的水平扩展。但当面对实际应用场景，CPU核间的数据通信、数据同步、临界区保护等都是不得不面对的问题

**原子操作**

原子操作（atomic operation）：不可被中断的一个或一系列操作，多个线程执行一个操作时，其中任何一个线程要么完全执行完此操作，要么没有执行此操作的任何步骤，那么这个操作就是原子的。原子操作是其他内核同步方法的基石。CPU提供三种独立的原子锁机制：原子保证操作、加LOCK指令前缀和缓存一致性协议。

原子操作在DPDK代码中的定义都在rte\_atomic.h文件中，主要包含两部分：内存屏蔽和原16、32和64位的原子操作API。

* `rte_mb（）`：内存屏障读写API
* `rte_wmb（）`：内存屏障写API
* `rte_rmb（）`：内存屏障读API
* `rte_atomic64_add()`：原子操作API

**读写锁**

读写锁实际是一种特殊的自旋锁，它把对共享资源的访问操作划分成读操作和写操作，读操作只对共享资源进行读访问，写操作则需要对共享资源进行写操作。这种锁相对于自旋锁而言，能提高并发性，因为在多处理器系统中，它允许同时有多个读操作来访问共享资源，最大可能的读操作数为实际的逻辑CPU数。

1）互斥。任意时刻读者和写者不能同时访问共享资源（即获得锁）；任意时刻只能有至多一个写者访问共享资源。 2）读者并发。在满足“互斥”的前提下，多个读者可以同时访问共享资源。 3）无死锁。如果线程A试图获取锁，那么某个线程必将获得锁，这个线程可能是A自己；如果线程A试图但是永远没有获得锁，那么某个或某些线程必定无限次地获得锁。

DPDK读写锁的定义在rte\_rwlock.h文件中，主要用于在查找空闲的memory segment的时候，使用读写锁来保护memseg结构；LPM表创建、查找和释放；Memory ring的创建、查找和释放；ACL表的创建、查找和释放；Memzone的创建、查找和释放等。

**自旋锁**

自旋锁必须基于CPU的数据总线锁定，它通过读取一个内存单元（spinlock\_t）来判断这个自旋锁是否已经被别的CPU锁住。如果否，它写进一个特定值，表示锁定了总线，然后返回。如果是，它会重复以上操作直到成功，或者spin次数超过一个设定值。锁定数据总线的指令只能保证一个指令操作期间CPU独占数据总线。（自旋锁在锁定的时侯，不会睡眠而是会持续地尝试）。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠，所以自旋锁的效率远高于互斥锁。虽然自旋锁的效率比互斥锁高，但是它也有些不足之处：

1）自旋锁一直占用CPU，它在未获得锁的情况下，一直运行——自旋，所以占用着CPU，如果不能在很短的时间内获得锁，这无疑会使CPU效率降低。 2）在用自旋锁时有可能造成死锁，当递归调用时有可能造成死锁，调用有些其他函数（如copy\_to\_user（）、copy\_from\_user（）、kmalloc（）等）也可能造成死锁。

DPDK中自旋锁API的定义在rte\_spinlock.h文件中，其中`rte_spinlock_lock()`, `rte_spinlock_unlock()`被广泛的应用在告警、日志、中断机制、内存共享和link bonding的代码中，用于临界资源的保护。

**无锁机制**

在多核环境下，需要把重要的数据结构从锁的保护下移到无锁环境，以提高软件性能。现在无锁机制变得越来越流行，在特定的场合使用不同的无锁队列，可以节省锁开销，提高程序效率。Linux内核中有无锁队列的实现，可谓简洁而不简单（kfifo是一种“First In First Out”数据结构，它采用了前面提到的环形缓冲区来实现，提供一个无边界的字节流服务。采用环形缓冲区的好处是，当一个数据元素被用掉后，其余数据元素不需要移动其存储位置，从而减少拷贝，提高效率。更重要的是，kfifo采用了并行无锁技术，kfifo实现的单生产/单消费模式的共享队列是不需要加锁同步的）。

无锁队列中单生产者——单消费者模型中不需要加锁，定长的可以通过读指针和写指针进行控制队列操作，变长的通过读指针、写指针、结束指针控制操作。

基于无锁环形缓冲的的原理，Intel DPDK提供了一套无锁环形缓冲区队列管理代码，支持单生产者产品入列，单消费者产品出列；多名生产者产品入列，多名消费者出列操作。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sdn.feisky.xyz/wang-luo-ji-chu/index-3/numa.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
