Breve análisis de la tecnología SPDK.

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 SPDKla aplicación es magistral y I/Oel 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 DPDKArquitectura y SPDKSimilitud. Si quieres aprender este libro, te recomiendo leer "En profundidad y simple DPDK" .

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 NVMedispositivo del controlador del kernel nativo

Este es un error común que indica que /dev/nvme1n1está en uso, por lo que si desea desvincular el dispositivo, debe umountformatearlo
. El controlador de usuario usa uio/vfiola función para asignar el dispositivo PCI BARal proceso actual, lo que permite que el controlador se ejecute directamente MMIO( adoptado por defecto uio)

(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 2048MBpá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 SPDKse centra principalmente en las tres ventajas de


Controlador de modo de usuario

Después VVMede la aparición de, el software se ha convertido en I/Oun 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 bypasstoda la operación quede atrapada en el kernel, es decir, esta solución de aceleración de almacenamientoI/OSPDK

IntelSu definición es una biblioteca de aceleración que utiliza métodos de modo de usuario , asíncronos y de sondeoNVMe para acelerar NVMe SSDel software de aplicación utilizado como almacenamiento de back-end.

NVMeEl protocolo es una SSDespecificació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 a NVMe-oFprotocolos de red para admitir InfiniBandfibra óptica o Ethernet. Entre las soluciones de almacenamiento en red, actualmente se encuentran principalmente DAS, NASySAN

SPDKSe basa UIOo VFIOadmite el método de asignar directamente el espacio de direcciones del dispositivo de almacenamiento al espacio de la aplicación, y utiliza NVMeespecificaciones para inicializar NVMe SSDel dispositivo e implementar I/Ooperaciones 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 SPDKla solución impulsada por el modo de usuario, este comportamiento se reemplaza por un sondeo asincrónico. A través del CPUsondeo 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 SPDKla aplicación se utiliza para vincular CPUsubprocesos y CPUnúcleos, y se diseña un modelo de subproceso.Desde el momento en que la aplicación recibe I/Ola 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 SPDKla arquitectura del modelo de subprocesos, cada CPUnúcleo tiene un subproceso del núcleo e inicializará unreactor

Cada reactorhilo puede contener de cero a múltiples SPDKhilos de modo de usuario ligeros y abstractos spdk_thread. Para mejorar reactorla eficiencia de la comunicación y la sincronización entre ellos, SPDKse 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_ringspdk_threadpoller

Insertar descripción de la imagen aquí

uso de SPDK

inicio en segundo plano rpc

SPDKA partir de v20.xla versión, se ha cambiado el formato del archivo de configuración.Al jsonejecutar el programa ejecutable, --jsonel archivo de configuración json se puede pasar como parámetro. Cuando se especifica un archivo SPDKal iniciar una aplicación , el proceso de inicialización se realizará en el modo y se realizarán las operaciones especificadas en el archivo .jsonSPDKrpcsubsystemsjson

rpcEs un método bien conocido para realizar operaciones de forma dinámica y flexible después de iniciar el programa. Se utiliza principalmente para unix sockettransferir datos de mensajes entre el cliente y el servidor,
SPDKy también integra o implementa rpccanales interactivos que pueden admitir operaciones dinámicas.

Servidor

SPDKrpcEl servidor se spdk_rpc_initializeinicializa 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. rpcEsta es la dirección de escucha predeterminada que se utiliza cuando el cliente accede. Cada rpcmódulo o función que deba llamarse puede SPDK_RPC_REGISTER registrar su función de prestación de servicios en g_rpc_methodsla lista vinculada. La función jsonrpc_handleres el punto de entrada para procesar todas las solicitudes del cliente. La g_rpc_methodsfunción de procesamiento específica coincidirá de acuerdo con la solicitud de la lista vinculada.

cliente

SPDKrpcLa mayoría de las funciones proporcionadas por el cliente se llaman ./spdk/scripts/rpc.pyutilizando scripts como punto de entrada. El script incluirá scripts ./spdk/python/spdk/rpcen 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.pythonrpcpython

Si desea consultar qué funciones de llamada rpc compatibles son, puede ejecutar ./rpc.py -hla consulta directamente

Si desea agregar nuevas rpcfunciones, debe SPDK_RPC_REGISTER registrar las nuevas funciones y rpcagregar pythonla lógica de script correspondiente en el cliente.

Análisis de mecanismos básicos.

SPDKLas funciones de programación, sin bloqueo y paralelismo del subnúcleo Run to completionse componen principalmente de los mecanismos reactor、events、polleryio channel


reactores

DPDKCuando se ejecuta la función, creará subprocesos en cada núcleo disponible especificado, rte_eal_initexcepto el núcleo que se está ejecutando actualmente .CPU main cpuCPU

Y vincúlelo para que se ejecute en el CPUnúcleo correspondiente modificando el parámetro de afinidad del hilo. La función de ejecución de cada hilo es eal_thread_loopesperar a que se reciban datos pipede é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");
	}
	// ...
}

DPDKrte_mempoolLos mecanismos y se proporcionan rte_ringpara soportar los requisitos de memoria.

Cada rte_mempoolinstancia 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_getdirectamente cache bufferde 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_ringEs una cola que se utiliza para entregar mensajes. Cada rte_ringunidad pasada es un puntero de memoria. Consulte spdk_thread_send_msgel uso en la función.

rte_ringSe utiliza un modelo de cola sin bloqueo para admitir el modelo de múltiples productores y múltiples consumidores, actualmente SPDKse utiliza el modelo de múltiples productores y un solo consumidor.

SPDKCPUDespués del inicio, se ejecutará uno en cada núcleo disponible especificado y en los núcleos reactordistintos del núcleo principal , existe una correspondencia uno a uno entre yCPUSPDKreactoreal_thread_loop

reactor->eventsSe rte_ringimplementa en base a y puede usarse para reactorspasar mensajes entre llamadas spdk_event_allocatey spdk_event_callpuede enviar los mensajes necesarios a cualquier núcleo spdkutilizado CPUpara ejecutar operaciones lógicas privadas.

Escenarios de uso para esta operación

  • CPUDebe realizar una acción retrasada en el núcleo actual , pero no hay ninguna correspondiente spdk_threaddisponible.
  • Es necesario realizar una determinada acción en otros núcleosSPDK , pero no hay ninguno asociado disponible.CPUspdk_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 \
	{
      
       \
	}

SPDKLa política de programación predeterminada es statictipo, es decir, reactory threadambas se ejecutan en pollingmodo


Modelo de hilo de SPDK

spdk_threadNo 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 reactorejecutan en la función de ejecución.

spdk_threadreactorLa relación entre y es N:1una relación correspondiente, es decir, reactorpuede haber muchos en cada uno spdk_thread, pero cada uno spdk_threaddebe pertenecer y solo puede pertenecer a uno específico.reactor


Encuesta

Cada registro spdk_pollerse almacena en spdk_thread->timed_pollersuna estructura de árbol rojo-negro o en spdk_thread->active_pollersuna lista vinculada. Entonces, si desea usarlo poller, primero debe crear unspdk_thread

Una vez que lo tengas spdk_thread, podrás spdk_pollerejecutar una función de forma repetida o periódica registrándola. Si pollerel ciclo se especifica al registrarse 0, entonces se llamará pollera la función de ejecución correspondiente reactoren cada ciclo; si el ciclo no lo es 0, entonces cada reactorciclo 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_msgfunciones para ejecutar funciones específicas. Al seleccionar la apropiada, spdk_threadpuede realizar operaciones en el CPUnúcleo actual u otros núcleos SPDKusados .CPU

Lo que se pasa en esta función se asigna msgdesde g_spdk_msg_mempool (una instancia) y la cola sin bloqueo se usa al pasarlo.rte_mempoolrte_ring

En general, las colas sin bloqueos se coordinan mediante la comunicación Reactorentre diferentes núcleos o el mismo núcleo .event(rte_ring)cpuspdk_threadcpuspdk_threadMessage(rte_ring)


canal_io

IO channelCPUEs 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 controllerDevolución de llamada después de encontrar
  • attach_cb: NVMeSe 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_probeimplementa 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.

HOSTEs NVMeel sistema en el que se inserta la tarjeta.La interacción entre ·HOST· y ·Controller· se realiza a través de ·Qpair·

pairDividido en IO Qpairy Admin Qpair, como su nombre indica, Admin Qpairse utiliza para la transmisión de comandos de control, mientras que IO Qpairpara IOla transmisión de comandos

QpairPara 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, CQ64216232DoorbellHead DoorbellTail Doorbell

HOSTCuando necesite NVMeescribir datos, debe especificar la dirección de los datos en la memoria y NVMela ubicación donde se escriben . Lo mismo ocurre con la lectura de datos del esclavo. Debe especificar HOSTla 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 esNVMeNVMeHOSTNVMePRPSGL

PRPApunta a una página de memoria física. PRPSimilar al direccionamiento normal, la dirección base más la dirección de desplazamiento. PRPApunta a una página de dirección física


SPDKEnviar al proceso del dispositivo I/OlocalPCIe

Comprométase con el dispositivo construyendo un 64comando 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/ONVMeTail DoorbellSQDoorbell

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. DMATransferir 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. CQCada elemento contiene una fase que cambia entre y Phase Tagen 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.01HOST CQSPDKCQ

Es un poco como io_uringel mecanismo.

E/S de bicicleta

Asíncrono se usa ampliamente aquí I/Oy generalmente se Linuxusa de forma predeterminada enAIOio_uring

Entre ellos, io_uringse compensaron aioalgunas deficiencias

io_uringaveces 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:

Supongo que te gusta

Origin blog.csdn.net/qq_48322523/article/details/128653182
Recomendado
Clasificación