concurrency - pagina - ¿Cuál es el mejor mecanismo de bloqueo de kernel de Linux para un escenario específico?
para que sirve debian (3)
Si el trabajo que realiza mientras mantiene presionado el candado es pequeño, puede intentar con un mutex normal, sin lector-escritor. Es más eficiente.
Necesito resolver un problema de bloqueo para este escenario:
- Un sistema de CPU múltiple.
- Todas las CPU usan un recurso común (software).
- El acceso de solo lectura al recurso es muy común. (Procesamiento de paquetes de red entrantes)
- El acceso de escritura es mucho menos frecuente. (Casi cambios de configuración solamente).
Actualmente uso el read_lock_bh
, write_lock_bh
(spinlocks). El problema es que cuantas más CPU hay, más obtengo bloqueos suaves en un contexto de escritor.
Leí el capítulo de concurrencia en este libro , pero no pude entender si el lector o el escritor tendrán prioridad al usar bloqueos de giro.
Entonces las preguntas son:
- ¿El mecanismo de spinlock de Linux le da prioridad al lector / escritor / ninguno de ellos?
- ¿Existe un mecanismo mejor que pueda utilizar para evitar esos bloqueos suaves en mi escenario, o quizás una forma de dar prioridad al escritor cada vez que intente obtener el bloqueo, mientras uso mi solución actual?
Gracias, Nir
¿No es este el tipo de caso de uso que RCU está diseñado para manejar? Ver http://lwn.net/Articles/262464/ para una buena redacción en su uso.
Aquí hay una cita directa de los controladores de dispositivos esenciales de Linux que podría ser lo que estás buscando. Parece que la parte relacionada con RCU al final puede ser lo que le interesa.
Cerraduras Reader-Writer
Otro mecanismo especializado de regulación de concurrencia es una variante de lector-escritor de spinlocks. Si el uso de una sección crítica es tal que los subprocesos separados leen o escriben en una estructura de datos compartida, pero no hacen ambas cosas, estos bloqueos son naturales. Múltiples hilos de lectura están permitidos dentro de una región crítica simultáneamente. Los spinlocks del lector se definen de la siguiente manera:
rwlock_t myrwlock = RW_LOCK_UNLOCKED;
read_lock(&myrwlock); /* Acquire reader lock */
/* ... Critical Region ... */
read_unlock(&myrwlock); /* Release lock */
Sin embargo, si un hilo de escritor entra en una sección crítica, otros hilos de lector o escritor no están permitidos dentro. Para usar spinlocks del escritor, debes escribir esto:
rwlock_t myrwlock = RW_LOCK_UNLOCKED;
write_lock(&myrwlock); /* Acquire writer lock */
/* ... Critical Region ... */
write_unlock(&myrwlock); /* Release lock */
Mire el código de enrutamiento IPX presente en net/ipx/ipx_route.c
para un ejemplo real de un spinlock lector-escritor. Un bloqueo de lector-escritor llamado ipx_routes_lock
protege la tabla de enrutamiento IPX del acceso simultáneo. Los subprocesos que necesitan buscar la tabla de enrutamiento para reenviar paquetes solicitan bloqueos de lector. Los subprocesos que necesitan agregar o eliminar entradas de la tabla de enrutamiento adquieren bloqueos de escritor. Esto mejora el rendimiento porque generalmente hay muchas más instancias de búsquedas en la tabla de enrutamiento que las actualizaciones de la tabla de enrutamiento.
Al igual que los spinlocks normales, los bloqueos lector-escritor también tienen variantes irq correspondientes, es decir, read_lock_irqsave()
, read_lock_irqrestore()
, write_lock_irqsave()
y write_lock_irqrestore()
. La semántica de estas funciones es similar a la de los spinlocks normales.
Los bloqueos de secuencia o seqlocks, introducidos en el kernel 2.6, son bloqueos lector-escritor donde los escritores son preferidos a los lectores. Esto es útil si las operaciones de escritura en una variable superan con creces los accesos de lectura. Un ejemplo es la variable jiffies_64
discutida anteriormente en este capítulo. Los hilos del escritor no esperan a los lectores que puedan estar dentro de una sección crítica. Debido a esto, los hilos del lector pueden descubrir que su entrada dentro de una sección crítica ha fallado y es posible que deba volver a intentarlo:
u64 get_jiffies_64(void) /* Defined in kernel/time.c */
{
unsigned long seq;
u64 ret;
do {
seq = read_seqbegin(&xtime_lock);
ret = jiffies_64;
} while (read_seqretry(&xtime_lock, seq));
return ret;
}
Los escritores protegen las regiones críticas usando write_seqlock()
y write_sequnlock()
.
El núcleo 2.6 introdujo otro mecanismo llamado Read-Copy Update (RCU) , que produce un mejor rendimiento cuando los lectores superan con creces a los escritores . La idea básica es que los hilos de lectura pueden ejecutarse sin bloqueo. Los hilos del escritor son más complejos. Realizan operaciones de actualización en una copia de la estructura de datos y reemplazan el puntero que ven los lectores. La copia original se mantiene hasta que el siguiente contexto encienda todas las CPU para garantizar la finalización de todas las operaciones de lectura en curso. Tenga en cuenta que el uso de RCU es más complicado que utilizar las primitivas discutidas hasta el momento y debe usarse solo si está seguro de que es la herramienta adecuada para el trabajo. Las estructuras de datos de RCU y las funciones de interfaz se definen en include/linux/rcupdate.h
. Hay una amplia documentación en Documentation/RCU/*
.
Para un ejemplo de uso de RCU , mire fs/dcache.c
. En Linux, cada archivo está asociado con información de entrada de directorio (almacenada en una estructura llamada dentry), información de metadatos (almacenada en un inodo) y datos reales (almacenados en bloques de datos). Cada vez que opera en un archivo, los componentes en la ruta del archivo se analizan y se obtienen las anotaciones correspondientes. Las dentries se guardan en caché en una estructura de datos denominada dcache, para acelerar las operaciones futuras. En cualquier momento, el número de búsquedas de dcache es mucho más que las actualizaciones de dcache, por lo que las referencias a dcache están protegidas utilizando primitivas de RCU.