Tabla de contenido
La razón para escribir este artículo es porque leí el artículo TriCache: A User-Transparent Block Cache Enabling High-Performance Out-of-Core Processing with In-Memory Programs , en el que SPDK
la aplicación es magistral y I/O
el rendimiento se eleva a un nuevo nivel. nivel.
Conceptos básicos de SPDK
SPDK Storage Performance Development Kit
, el kit de desarrollo de rendimiento de almacenamiento, proporciona un conjunto de herramientas y bibliotecas para escribir aplicaciones de almacenamiento en modo de usuario escalables y de alto rendimiento.
Informacion relevante:
Enlace |
---|
Introducción al kit de desarrollo de rendimiento de almacenamiento (SPDK) |
documentación del sitio web oficial de spdk |
código fuente de spdk GitHub spdk/spdk |
artículos técnicos spdk |
También existe una tecnología llamada
DPDK
Arquitectura ySPDK
Similitud. Si quieres aprender este libro, te recomiendo leer "En profundidad y simpleDPDK
" .
ventaja:
- Mueva todos los controladores necesarios al espacio del usuario, lo que evita llamadas al sistema y permite el acceso sin copia para las aplicaciones.
- Sondear el hardware para verificar su finalización, en lugar de depender de la prevención de interrupciones, reduce la latencia general y la variación de la latencia.
- Evite todos los bloqueos en la ruta de E/S y, en su lugar, confíe en el paso de mensajes (se utiliza una cola de mensajes sin bloqueos)
compilar e instalar
git clone https://github.com/spdk/spdk
cd spdk
git submodule update --init # 拉取子模块
sudo scripts/pkgdep.sh # 安装依赖性
./configure
make
Ejecute la prueba unitaria, lo siguiente significa que la prueba fue exitosa
(base) root@nizai8a-desktop:~/tt/spdk# ./test/unit/unittest.sh
=====================
All unit tests passed
=====================
WARN: lcov not installed or SPDK built without coverage!
WARN: neither valgrind nor ASAN is enabled!
Asigne páginas grandes y desvincule cualquier NVMe
dispositivo del controlador del kernel nativo
Este es un error común que indica que
/dev/nvme1n1
está en uso, por lo que si desea desvincular el dispositivo, debeumount
formatearlo
. El controlador de usuario usauio/vfio
la función para asignar el dispositivoPCI BAR
al proceso actual, lo que permite que el controlador se ejecute directamenteMMIO
( adoptado por defectouio
)
(base) root@nizai8a-desktop:~/tt/spdk# sudo scripts/setup.sh
0000:81:00.0 (144d a808): Active mountpoints on nvme1n1:nvme1n1, so not binding PCI dev
0000:01:00.0 (15b7 5009): nvme -> uio_pci_generi
¿Por qué asignar páginas enormes?
Principio: principio de memoria de página grande dpdk
- Todas las páginas y tablas de páginas grandes se almacenan en la memoria compartida y nunca se intercambiarán a la partición de intercambio del disco debido a que no hay suficiente memoria.
- Dado que todos los procesos comparten una tabla de páginas grande, la sobrecarga de la tabla de páginas se reduce y el espacio de memoria ocupado se reduce prácticamente, lo que permite que el sistema admita más procesos ejecutándose al mismo tiempo.
- Reducir la presión sobre TLB
- Reducir la presión de buscar en la memoria.
De forma predeterminada, el script asigna 2048MB
páginas grandes. Para cambiar este número, especifiqueHUGEMEM
sudo HUGEMEM = 4096 scripts/setup.sh
Verifique los tipos de páginas grandes admitidas por el sistema
(base) root@nizai8a-desktop:/sys/kernel/mm/hugepages# ls
hugepages-1048576kB hugepages-2048kB
Arquitectura SPDK
Esta sección SPDK
se centra principalmente en las tres ventajas de
Controlador de modo de usuario
Después VVMe
de la aparición de, el software se ha convertido en I/O
un cuello de botella en escenarios intensivos, existen dos métodos para optimizar el kernel.
io_ring
: Proporciona un nuevo conjunto de llamadas al sistema, basado en la optimización de las rutas de llamadas del sistema.- Sin pasar por el kernel , no es necesario que
kernel bypass
toda la operación quede atrapada en el kernel, es decir, esta solución de aceleración de almacenamientoI/O
SPDK
Intel
Su definición es una biblioteca de aceleración que utiliza métodos de modo de usuario , asíncronos y de sondeoNVMe
para acelerar NVMe SSD
el software de aplicación utilizado como almacenamiento de back-end.
NVMe
El protocolo es unaSSD
especificación definida para una comunicación más rápida entre unidades de estado sólido y hosts y extiende su rendimiento de almacenamiento local aNVMe-oF
protocolos de red para admitirInfiniBand
fibra óptica o Ethernet. Entre las soluciones de almacenamiento en red, actualmente se encuentran principalmenteDAS
,NAS
ySAN
SPDK
Se basa UIO
o VFIO
admite el método de asignar directamente el espacio de direcciones del dispositivo de almacenamiento al espacio de la aplicación, y utiliza NVMe
especificaciones para inicializar NVMe SSD
el dispositivo e implementar I/O
operaciones básicas para construir un controlador en modo de usuario, por lo que no es necesario atrapar todo el proceso. en el núcleo.
En SPDK
la solución impulsada por el modo de usuario, este comportamiento se reemplaza por un sondeo asincrónico. A través del CPU
sondeo continuo, una vez que se completa la consulta, la función de devolución de llamada se activa inmediatamente y se entrega al programa de usuario superior, para que el programa de usuario pueda enviar mensajes a pedido. Múltiples solicitudes para mejorar el rendimiento.
Otro beneficio obvio de eliminar las interrupciones del sistema es evitar cambios de contexto.
Además, la afinidad de SPDK
la aplicación se utiliza para vincular CPU
subprocesos y CPU
núcleos, y se diseña un modelo de subproceso.Desde el momento en que la aplicación recibe I/O
la operación de este núcleo hasta el final de la operación, se completa en este núcleo, de modo que se puede utilizar de manera más eficiente caché, al mismo tiempo que evita problemas de sincronización de memoria entre múltiples núcleos
Al mismo tiempo, la gestión de recursos de memoria en un solo núcleo utiliza un gran almacenamiento de páginas para acelerar
hilo
En SPDK
la arquitectura del modelo de subprocesos, cada CPU
núcleo tiene un subproceso del núcleo e inicializará unreactor
Cada reactor
hilo puede contener de cero a múltiples SPDK
hilos de modo de usuario ligeros y abstractos spdk_thread
. Para mejorar reactor
la eficiencia de la comunicación y la sincronización entre ellos, SPDK
se abandona el método de bloqueo tradicional. En su lugar, se utiliza el método abstracto para enviar mensajes a cada hilo reactor
. La posesión inferior se utiliza para registrar funciones de usuario.spdk_ring
spdk_thread
poller
uso de SPDK
inicio en segundo plano rpc
SPDK
A partir de v20.x
la versión, se ha cambiado el formato del archivo de configuración.Al json
ejecutar el programa ejecutable, --json
el archivo de configuración json se puede pasar como parámetro. Cuando se especifica un archivo SPDK
al iniciar una aplicación , el proceso de inicialización se realizará en el modo y se realizarán las operaciones especificadas en el archivo .json
SPDK
rpc
subsystems
json
rpc
Es un método bien conocido para realizar operaciones de forma dinámica y flexible después de iniciar el programa. Se utiliza principalmente paraunix socket
transferir datos de mensajes entre el cliente y el servidor,
SPDK
y también integra o implementarpc
canales interactivos que pueden admitir operaciones dinámicas.
Servidor
SPDK
rpc
El servidor se spdk_rpc_initialize
inicializa en la función. Si no se especifica ninguna dirección de escucha, se utilizará la dirección de escucha predeterminada /var/tmp/spdk.sock
. rpc
Esta es la dirección de escucha predeterminada que se utiliza cuando el cliente accede. Cada rpc
módulo o función que deba llamarse puede SPDK_RPC_REGISTER
registrar su función de prestación de servicios en g_rpc_methods
la lista vinculada. La función jsonrpc_handler
es el punto de entrada para procesar todas las solicitudes del cliente. La g_rpc_methods
función de procesamiento específica coincidirá de acuerdo con la solicitud de la lista vinculada.
cliente
SPDK
rpc
La mayoría de las funciones proporcionadas por el cliente se llaman ./spdk/scripts/rpc.py
utilizando scripts como punto de entrada. El script incluirá scripts ./spdk/python/spdk/rpc
en el directorio , el procesamiento del cliente de cada función y las funciones públicas para interactuar con el servidor se definen en estos scripts incluidos. La lógica de procesamiento correspondiente del cliente en la función rpc proporcionada por cada módulo se recopila en un archivo denominado con el nombre del módulo.python
rpc
python
Si desea consultar qué funciones de llamada rpc compatibles son, puede ejecutar
./rpc.py -h
la consulta directamente
Si desea agregar nuevas
rpc
funciones, debeSPDK_RPC_REGISTER
registrar las nuevas funciones yrpc
agregarpython
la lógica de script correspondiente en el cliente.
Análisis de mecanismos básicos.
SPDK
Las funciones de programación, sin bloqueo y paralelismo del subnúcleo Run to completion
se componen principalmente de los mecanismos reactor、events、poller
yio channel
reactores
DPDK
Cuando se ejecuta la función, creará subprocesos en cada núcleo disponible especificado, rte_eal_init
excepto el núcleo que se está ejecutando actualmente .CPU main cpu
CPU
Y vincúlelo para que se ejecute en el CPU
núcleo correspondiente modificando el parámetro de afinidad del hilo. La función de ejecución de cada hilo es eal_thread_loop
esperar a que se reciban datos pipe
de él y ejecutar
/* Launch threads, called at application init(). */
int
rte_eal_init(int argc, char **argv)
{
// ...
RTE_LCORE_FOREACH_WORKER(i) {
/*
* create communication pipes between main thread
* and children
*/
if (pipe(lcore_config[i].pipe_main2worker) < 0)
rte_panic("Cannot create pipe\n");
if (pipe(lcore_config[i].pipe_worker2main) < 0)
rte_panic("Cannot create pipe\n");
lcore_config[i].state = WAIT;
/* create a thread for each lcore */
// 创建线程,执行函数为eal_thread_loop
ret = pthread_create(&lcore_config[i].thread_id, NULL,
eal_thread_loop, NULL);
if (ret != 0)
rte_panic("Cannot create thread\n");
/* Set thread_name for aid in debugging. */
snprintf(thread_name, sizeof(thread_name),
"lcore-worker-%d", i);
rte_thread_setname(lcore_config[i].thread_id, thread_name);
// 增加线程亲和性
ret = pthread_setaffinity_np(lcore_config[i].thread_id,
sizeof(rte_cpuset_t), &lcore_config[i].cpuset);
if (ret != 0)
rte_panic("Cannot set affinity\n");
}
// ...
}
DPDK
rte_mempool
Los mecanismos y se proporcionan rte_ring
para soportar los requisitos de memoria.
Cada rte_mempool
instancia es un grupo de memoria compuesto por una gran memoria de páginas y está organizado en una estructura de datos específica, en la que cada unidad asignada y utilizada se puede utilizar para almacenar los datos de la persona que llama.
Cuando se crea rte_mempool
, se creará en cada CPU disponible al mismo tiempo cache buffers
, de modo que se pueda obtener rte_mem_get
directamente cache buffer
de ella cuando se llame, acelerando el proceso de asignación (algo así como per-cpu cache
)
/**
* The RTE mempool structure.
*/
struct rte_mempool {
/*
* Note: this field kept the RTE_MEMZONE_NAMESIZE size due to ABI
* compatibility requirements, it could be changed to
* RTE_MEMPOOL_NAMESIZE next time the ABI changes
*/
char name[RTE_MEMZONE_NAMESIZE]; /**< Name of mempool. */
RTE_STD_C11
union {
void *pool_data; /**< Ring or pool to store objects. */
uint64_t pool_id; /**< External mempool identifier. */
};
void *pool_config; /**< optional args for ops alloc. */
const struct rte_memzone *mz; /**< Memzone where pool is alloc'd. */
unsigned int flags; /**< Flags of the mempool. */
int socket_id; /**< Socket id passed at create. */
uint32_t size; /**< Max size of the mempool. */
uint32_t cache_size;
/**< Size of per-lcore default local cache. */
uint32_t elt_size; /**< Size of an element. */
uint32_t header_size; /**< Size of header (before elt). */
uint32_t trailer_size; /**< Size of trailer (after elt). */
unsigned private_data_size; /**< Size of private data. */
/**
* Index into rte_mempool_ops_table array of mempool ops
* structs, which contain callback function pointers.
* We're using an index here rather than pointers to the callbacks
* to facilitate any secondary processes that may want to use
* this mempool.
*/
int32_t ops_index;
struct rte_mempool_cache *local_cache; /**< Per-lcore local cache */
uint32_t populated_size; /**< Number of populated objects. */
struct rte_mempool_objhdr_list elt_list; /**< List of objects in pool */
uint32_t nb_mem_chunks; /**< Number of memory chunks */
// 内存池
struct rte_mempool_memhdr_list mem_list; /**< List of memory chunks */
} __rte_cache_aligned;
rte_ring
Es una cola que se utiliza para entregar mensajes. Cada rte_ring
unidad pasada es un puntero de memoria. Consulte spdk_thread_send_msg
el uso en la función.
rte_ring
Se utiliza un modelo de cola sin bloqueo para admitir el modelo de múltiples productores y múltiples consumidores, actualmente SPDK
se utiliza el modelo de múltiples productores y un solo consumidor.
SPDK
CPU
Después del inicio, se ejecutará uno en cada núcleo disponible especificado y en los núcleos reactor
distintos del núcleo principal , existe una correspondencia uno a uno entre yCPU
SPDK
reactor
eal_thread_loop
reactor->events
Se rte_ring
implementa en base a y puede usarse para reactors
pasar mensajes entre llamadas spdk_event_allocate
y spdk_event_call
puede enviar los mensajes necesarios a cualquier núcleo spdk
utilizado CPU
para ejecutar operaciones lógicas privadas.
Escenarios de uso para esta operación
CPU
Debe realizar una acción retrasada en el núcleo actual , pero no hay ninguna correspondientespdk_thread
disponible.- Es necesario realizar una determinada acción en otros núcleos
SPDK
, pero no hay ninguno asociado disponible.CPU
spdk_thread
DEFINE_STUB(spdk_event_allocate, struct spdk_event *, (uint32_t core, spdk_event_fn fn, void *arg1,
void *arg2), NULL);
/* DEFINE_STUB is for defining the implmentation of stubs for SPDK funcs. */
#define DEFINE_STUB(fn, ret, dargs, val) \
bool ut_ ## fn ## _mocked = true; \
ret ut_ ## fn = val; \
ret fn dargs; \
ret fn dargs \
{
\
return MOCK_GET(fn); \
}
DEFINE_STUB_V(spdk_event_call, (struct spdk_event *event));
/* DEFINE_STUB_V macro is for stubs that don't have a return value */
#define DEFINE_STUB_V(fn, dargs) \
void fn dargs; \
void fn dargs \
{
\
}
SPDK
La política de programación predeterminada es static
tipo, es decir, reactor
y thread
ambas se ejecutan en polling
modo
Modelo de hilo de SPDK
spdk_thread
No es un hilo en el sentido convencional, en realidad es un concepto lógico, no tiene una función de ejecución específica y todas sus operaciones relacionadas se reactor
ejecutan en la función de ejecución.
spdk_thread
reactor
La relación entre y es N:1
una relación correspondiente, es decir, reactor
puede haber muchos en cada uno spdk_thread
, pero cada uno spdk_thread
debe pertenecer y solo puede pertenecer a uno específico.reactor
Encuesta
Cada registro spdk_poller
se almacena en spdk_thread->timed_pollers
una estructura de árbol rojo-negro o en spdk_thread->active_pollers
una lista vinculada. Entonces, si desea usarlo poller
, primero debe crear unspdk_thread
Una vez que lo tengas spdk_thread
, podrás spdk_poller
ejecutar una función de forma repetida o periódica registrándola. Si poller
el ciclo se especifica al registrarse 0
, entonces se llamará poller
a la función de ejecución correspondiente reactor
en cada ciclo; si el ciclo no lo es 0
, entonces cada reactor
ciclo verificará si cumple con el ciclo de ejecución antes de ejecutarlo.
struct spdk_poller {
TAILQ_ENTRY(spdk_poller) tailq;
/* Current state of the poller; should only be accessed from the poller's thread. */
enum spdk_poller_state state;
uint64_t period_ticks;
uint64_t next_run_tick;
uint64_t run_count;
uint64_t busy_count;
spdk_poller_fn fn;
void *arg;
struct spdk_thread *thread;
int interruptfd;
spdk_poller_set_interrupt_mode_cb set_intr_cb_fn;
void *set_intr_cb_arg;
char name[SPDK_MAX_POLLER_NAME_LEN + 1];
};
Una vez creado spdk_thread
, puede usar spdk_thread_send_msg
funciones para ejecutar funciones específicas. Al seleccionar la apropiada, spdk_thread
puede realizar operaciones en el CPU
núcleo actual u otros núcleos SPDK
usados .CPU
Lo que se pasa en esta función se asigna msg
desde g_spdk_msg_mempool
(una instancia) y la cola sin bloqueo se usa al pasarlo.rte_mempool
rte_ring
En general, las colas sin bloqueos se coordinan mediante la comunicación Reactor
entre diferentes núcleos o el mismo núcleo .event(rte_ring)
cpu
spdk_thread
cpu
spdk_thread
Message(rte_ring)
canal_io
IO channel
CPU
Es un mecanismo abstracto para realizar la misma operación de forma independiente en cada núcleo disponible . No se resumirá.
backendvhost
Hola
Escanee el dispositivo y vincúlelo al controlador y lea y escriba datos
probe_cb
:NVMe controller
Devolución de llamada después de encontrarattach_cb
:NVMe
Se llama una vez que el controlador se ha conectado al controlador del espacio de usuario.
El proceso de encontrar el dispositivo y vincular el controlador se spdk_nvme_probe
implementa en la función
En el código, escaneamos los dispositivos en el bus especificándolos, guardamos los controladores y dispositivos a través de dos listas enlazadas globales, recorremos los controladores y relacionamos los dispositivos y controladores encontrados transport id
.PCI
int
spdk_nvme_probe(const struct spdk_nvme_transport_id *trid, void *cb_ctx,
spdk_nvme_probe_cb probe_cb, spdk_nvme_attach_cb attach_cb,
spdk_nvme_remove_cb remove_cb)
{
struct spdk_nvme_transport_id trid_pcie;
struct spdk_nvme_probe_ctx *probe_ctx;
if (trid == NULL) {
memset(&trid_pcie, 0, sizeof(trid_pcie));
spdk_nvme_trid_populate_transport(&trid_pcie, SPDK_NVME_TRANSPORT_PCIE);
trid = &trid_pcie;
}
probe_ctx = spdk_nvme_probe_async(trid, cb_ctx, probe_cb,
attach_cb, remove_cb);
if (!probe_ctx) {
SPDK_ERRLOG("Create probe context failed\n");
return -1;
}
/*
* Keep going even if one or more nvme_attach() calls failed,
* but maintain the value of rc to signal errors when we return.
*/
return nvme_init_controllers(probe_ctx);
}
Centrarse en el proceso de lectura y escritura.
HOST
Es NVMe
el sistema en el que se inserta la tarjeta.La interacción entre ·HOST· y ·Controller· se realiza a través de ·Qpair·
pair
Dividido enIO Qpair
yAdmin Qpair
, como su nombre indica,Admin Qpair
se utiliza para la transmisión de comandos de control, mientras queIO Qpair
paraIO
la transmisión de comandos
Qpair
Para una cola circular de número fijo que consta de una cola de envío ( Submission Queue, SQ
) y una cola de finalización ( ), la cola de envío es una matriz de comandos que consta de un número fijo de bytes, más un número entero (índice inicial y final). La cola de finalización es una cola circular compuesta por un número fijo de comandos de bytes más un número entero (índice inicial y final). También hay dos registros de bits ( ), yCompletion Queue, CQ
64
2
16
2
32
Doorbell
Head Doorbell
Tail Doorbell
HOST
Cuando necesite NVMe
escribir datos, debe especificar la dirección de los datos en la memoria y NVMe
la ubicación donde se escriben . Lo mismo ocurre con la lectura de datos del esclavo. Debe especificar HOST
la dirección y la dirección de la memoria. para que sepa dónde buscar los datos. Después de buscar, dónde colocar los datos, hay dos formas de expresar la dirección de datos, una es y la otra esNVMe
NVMe
HOST
NVMe
PRP
SGL
PRP
Apunta a una página de memoria física. PRP
Similar al direccionamiento normal, la dirección base más la dirección de desplazamiento. PRP
Apunta a una página de dirección física
SPDK
Enviar al proceso del dispositivo I/O
localPCIe
Comprométase con el dispositivo construyendo un 64
comando de un byte, colocándolo en la cola de confirmación en la posición actual del índice de la cola de la cola de confirmación y luego escribiendo la cola de confirmación en el nuevo índice de la cola de la cola de confirmación . También puede escribir varios comandos y luego enviarlos todos escribiéndolos sólo una vez .I/O
NVMe
Tail Doorbell
SQ
Doorbell
El comando en sí describe la operación y también describe la ubicación en la memoria del host que contiene los datos de la memoria del host asociados con el comando, es decir, la ubicación en la memoria donde queremos escribir los datos o colocar los datos leídos en la memoria. DMA
Transferir datos hacia o desde esta dirección a través de
La cola de finalización funciona de manera similar, el dispositivo escribe mensajes de respuesta al comando CQ
. CQ
Cada elemento contiene una fase que cambia entre y Phase Tag
en cada ciclo de todo el anillo . El dispositivo recibe notificaciones de las actualizaciones mediante interrupciones , pero no habilita las interrupciones y en su lugar sondea los bits de fase para detectar actualizaciones.0
1
HOST CQ
SPDK
CQ
Es un poco como
io_uring
el mecanismo.
E/S de bicicleta
Asíncrono se usa ampliamente aquí I/O
y generalmente se Linux
usa de forma predeterminada enAIO
io_uring
Entre ellos, io_uring
se compensaron aio
algunas deficiencias
io_uring
aveces llamadoaio_ring,io_ring,ring_io
uno pequeñoexample
/**
* 读取文件
**/
#include <bits/stdc++.h>
#include <liburing.h>
#include <unistd.h>
char buf[1024] = {
0};
int main() {
int fd = open("1.txt", O_RDONLY, 0);
io_uring ring;
io_uring_queue_init(32, &ring, 0); // 初始化
auto sqe = io_uring_get_sqe(&ring); // 从环中得到一块空位
io_uring_prep_read(sqe, fd, buf, sizeof(buf), 0); // 为这块空位准备好操作
io_uring_submit(&ring); // 提交任务
io_uring_cqe* res; // 完成队列指针
io_uring_wait_cqe(&ring, &res); // 阻塞等待一项完成的任务
assert(res);
std::cout << "read bytes: " << res->res << " \n";
std::cout << buf << std::endl;
io_uring_cqe_seen(&ring, res); // 将任务移出完成队列
io_uring_queue_exit(&ring); // 退出
return 0;
}
Io_uring
Hay tres cosas: cola de envío, cola de finalización y entidad de tarea
Artículo de referencia: