Documentos de Académico
Documentos de Profesional
Documentos de Cultura
simétrico)
Play Linux en el sistema SMP
El rendimiento del sistema Linux se puede mejorar de varias maneras, y el más popular es
mejorar el rendimiento del procesador. Una solución obvia es usar un procesador con una
frecuencia de reloj más rápida, pero hay un límite físico para cualquier técnica en
particular, y la frecuencia del reloj tiene tal límite. Al alcanzar ese límite, puede usar el
multiprocesador utilizando un método "Más". Desafortunadamente, el rendimiento de los
multiprocesadores no es una relación lineal con un solo rendimiento del procesador.
Historial multi-proceso
Algunas empresas se originaron a mediados de la década de 1950, algunas de las cuales
puede saber, mientras que otras pueden no recordar (IBM, Digital Equipment Corporation,
Control Data Corporation). A principios de la década de 1960, Burroghs Corporation
introdujo un multiprocesador simétrico MIMD con cuatro CPU y accesibles hasta dieciséis
módulos de memoria (primera arquitectura SMP) por interruptores de cruce. En 1964, se
introdujo CDC 6600, y su uso fue exitoso y popular, que proporcionó una CPU con diez
sub-procesadores (unidades de procesamiento periférico). A fines de la década de 1960,
Honeywell lanzó su primer sistema de múltiples múltiples, que es otro sistema simétrico de
multiprocesamiento con ocho CPU.
Amdahl
Gene Amdahl es un arquitecto informático, el personal de IBM, en IBM, Amdahl
Corporation (un negocio nombrado en su nombre) y otras compañías involucradas en el
desarrollo de la arquitectura informática. Pero lo más famoso es su regla, que se utiliza para
predecir la mejoría máxima del sistema esperada después del sistema mejorado. Se utiliza
principalmente para calcular la mayor mejora del rendimiento en el uso teórico de los
multiprocesadores (consulte la Figura 1).
Multi-proceso y PC
Arquitectura SMP: dos o más los mismos procesadores están conectados entre sí a través de
una memoria compartida. Cada procesador puede acceder a la memoria compartida por
igual (con el mismo retardo de acceso de espacio de memoria). Esta arquitectura se puede
comparar con la arquitectura de acceso a la memoria (NUMA) no uniformes. Por ejemplo,
cada procesador tiene su propia memoria y tiene diferentes retrasos de acceso al acceder a
la memoria compartida.
Acoplamiento flojo
El primer sistema Linux SMP está suelto acoplando múltiples sistemas de procesadores.
Estos sistemas se construyen con múltiples sistemas individuales interconectados de alta
velocidad (como 10G Ethernet, canal de fibra o infiniband). Dichas arquitecturas también
se conocen como grupos (véase la Figura 3), y el proyecto Linux Beowulf es una solución
popular para dicha arquitectura. El clúster Linux Beowulf se puede construir utilizando el
hardware normal y las interconexiones de red típicas (como Ethernet).
En CMP, las CPU múltiples están conectadas a la memoria compartida (2 caché) a través
del bus compartido. Cada procesador también tiene su propia memoria rápida (caché de
nivel 1). La esencia de CMP está estrechamente acoplada a la distancia física entre el
procesador y la memoria, por lo que puede proporcionar el retardo de acceso de memoria
más pequeño y un mayor rendimiento. Dicha arquitectura está funcionando bien en
aplicaciones multi-roscadas, que se pueden asignar a múltiples procesadores en dichas
aplicaciones para implementar operaciones paralelas. Este método se denomina paralelo a
nivel de rosca (TLP).
2.6 El kernel presenta un nuevo programador O (1), que contiene un mejor soporte del
sistema SMP. La clave es que el saldo de carga se puede realizar entre las CPU disponibles
mientras se mantiene la afinidad para aumentar la eficiencia del caché. Revise la Figura 4,
cuando la tarea está asociada con una única CPU, si lo mueve a otra CPU, debe vaciar el
caché para la tarea. Esto aumenta el retraso de acceso a la memoria de la tarea, que se
utiliza para mover sus datos a la memoria de la nueva CPU.
2.6 El kernel sirve a dos ratones para cada procesador (luminiscente expirado y activo).
Cada lejaduras admite 140 prioridades, el frente 100 se utiliza para tareas en tiempo real,
mientras que 40 de los siguientes cuatro se utilizan para las tareas de usuario. Plantillas de
división de tareas, cuando se utilizan las películas de tiempo asignadas, estas tareas se
mueven desde la lejada activa hasta la batralue caducada. Esto proporciona un acceso justo
a la CPU para todas las tareas (solo bloqueadas de acuerdo con cada CPU).
Con la cola de tareas de cada CPU, se puede realizar el balance de carga de acuerdo con
todas las cargas de la CPU en el sistema. Cada 200 milisegundos, el programador realiza un
ajuste de equilibrio de carga para reasignar la carga de tareas y mantener el equilibrio entre
el procesador. Para obtener más información sobre los programadores de Linux 2.6,
veaReferencia sección.
El hilo de la interfaz del sistema operativo portátil (POSIX) es una buena manera de
construir una aplicación roscada que puede aprovechar SMP. El hilo POSIX proporciona
un mecanismo de roscado y memoria compartida. Cuando el programa de llamadas crea
varios hilos, cada hilo obtiene su propia pila (variables y estado locales), pero compartió el
espacio de datos del hilo principal. Todos los hilos creados compartieron este mismo
espacio de datos, pero el problema está aquí.
Concluir
Cuando la frecuencia del procesador alcanza su límite, un método de rendimiento de mejora
popular es agregar más procesadores. En los primeros días, esto significa agregar más
procesadores a la placa base o establece múltiples clústeres de computación independientes.
Ahora, el procesamiento de múltiples procesos de chip puede proporcionar más CPU en un
solo chip, lo que resulta en un mayor rendimiento debido a la reducción de los retrasos en la
memoria.
Encontrará que el sistema SMP existe no solo en el servidor, sino también en el escritorio,
especialmente después de introducir la virtualización. Como la mayoría de las tecnologías
avanzadas, Linux proporciona soporte SMP. El kernel es responsable de completar la
optimización de la carga entre las CPU disponibles (desde hilos hasta los sistemas
operativos de virtualización). Lo único que debe hacer es asegurarse de que la aplicación
pueda estar completamente multi-roscada para usar SMP.
Si queremos que nuestro programa empiece a ejecutar varias cosas "a la vez", tenemos dos
opciones. Por una parte podemos crear un nuevo proceso y por otra, podemos crear un nuevo
hilo de ejecución (un thread). En realidad nuestro ordenador, salvo que tenga varias cpu, no
ejecutará varias cosas a la vez. Cuando digo "a la vez", me refiero a que el sistema operativo
irá ejecutando cachos de programa por turnos (por rodajas de tiempo) de forma muy rápida,
dando la sensación de simultaneidad.
Dentro de un proceso puede haber varios hilos de ejecución (varios threads). Eso quiere decir
que un proceso podría estar haciendo varias cosas "a la vez". Los hilos dentro de un proceso
comparten todos la misma memoria. Eso quiere decir que si un hilo toca una variable, todos
los demás hilos del mismo proceso verán el nuevo valor de la variable. Esto hace
imprescindible el uso de semáforos o mutex (EXclusión MUTua, que en inglés es al revés,
funciones pthread_mutex...) para evitar que dos threads accedan a la vez a la misma
estructura de datos. También hace que si un hilo "se equivoca" y corrompe una zona de
memoria, todos los demás hilos del mismo proceso vean la memoria corrompida. Un fallo en
un hilo puede hacer fallar a todos los demás hilos del mismo proceso.
Un proceso es, por tanto, más costoso de lanzar, ya que se necesita crear una copia de toda
la memoria de nuestro programa. Los hilos son más ligeros.
En cuanto a complejidad, en los hilos, al compartir la memoria y los recursos, es casi obligado
el uso de mutex o semáforos, así que su programación suele ser más complicada y se
necesita ser más cuidadoso. Un proceso, en el momento de lanzarlo, se hace independiente
del nuestro, así que no deberíamos tener ningún problema, salvo que necesitemos
comunicación entre ellos, que nos liaríamos a programar memorias compartidas (con sus
correspondientes semáforos), colas de mensajes, sockets o cualquier otro mecanismo de
comunicación entre procesos unix.
¿Qué elegimos? ¿Un proceso o un hilo?. Depende de muchos factores, pero yo (y es cosa
mia, que tengo un PC obsoleto con linux), suelo elegir procesos cuando una vez lanzado el
hijo no requiero demasiada comunicación con él. Elijo hilos cuando tienen que compartir y
actualizarse datos. Por ahí he leido que para gestionar entradas/salidas es mejor procesos
(atender simultaneamente a varias entradas de sockets, por ejemplo) y que para hacer
programas con muchos cálculos en paralelo con varias cpu es mejor hilos, siempre y cuando
el sistema operativo sea capaz de repartir automáticamente los hilos en las distintas cpu en
función de su carga de trabajo.
Ejemplo de programación de procesos
La función C que crea un nuevo proceso es fork(). ¡Qué suerte!, tiene mucha miga, pero no
lleva parámetros
Cuando llamamos a fork(), en algún lugar dentro de la función, se duplican los procesos y
empiezan a correr por separado. Cuando llega el momento de retornar de dicha función (y ya
tenemos dos procesos), al proceso original le devuelve un identificador del proceso recien
creado (el mismo numerito que vemos con el comando ps de una shell). Al proceso recien
creado le devuelve 0. Échale un ojo a la figura para entenderlo mejor.
A partir de aquí ya podemos programar normalmente. Hay que tener en cuenta que se ha
duplicado todo el espacio de memoria. Por ello, ambos procesos tienen todas las variables
repetidas, pero distintas. Si el proceso original toca la variable "contador", el proceso hijo no
verá reflejado el cambio en su versión de "contador".
Otro tema a tener en cuenta es que si antes del fork() tenemos, por ejemplo, un fichero
abierto (un fichero normal, un socket, una pipa o cualquier otra cosa), después
del fork() ambos procesos tendrán abierto el mismo fichero y ambos podrán escribir en él. Es
más, uno de los procesos puede cerrar el fichero mientras que el otro lo puede seguir teniendo
abierto.
switch (fork())
{
case -1:
/* Código de error */
...
break;
case 0:
/* Código del proceso hijo */
...
break;
default:
...
/* Código del proceso original */
}
Una vez lanzado el proceso hijo, el padre puede "esperar" que el hijo termine. Para ello
tenemos la función wait(). A la función wait() se le pasa la dirección de un entero para que
nos lo devuelva relleno. La función wait() deja dormido al proceso que la llama hasta que
alguno de sus procesos hijo termina, es decir, si llamamos a wait(), nos quedamos ahí
parados hasta que el hijo termine. A la salid, en el entero tendremos guardada información de
cómo ha terminado el hijo (ha llamado a un exit(), alguien le ha matado, se ha caido, etc).
int estadoHijo;
...
wait (&estadoHijo);
... el proceso original (padre), se queda dormido hasta que el nuevo proceso (hijo) termina.
Una vez que salimos del wait(), tenemos unas macros que nos permiten evaluar el contenido
de estadoHijo. Hay varias, pero un par de ellas más o menos útiles son:
if ( WIFEXITED(estadoHijo) != 0)
{
printf ("Mi hijo ha hecho exit (%d)\n", WEXITSTAUS (estadoHijo));
}
Como ejemplo de hilos, vamos a hacer un programa que cree un hilo. Luego, tanto el
programa principal como el hilo se meterán en un bucle infinito. El primero se dedicará a
incrementar un contador y escribir su valor en pantalla. El hilo decrementará el mismo
contador y escribirá su valor en pantalla. El resultado es que veremos en pantalla el contador
incrementándose y decrementándose a toda velocidad.
Mas adelante veremos cómo hacer que un hilo espere a otro o como sincronizarlos para
acceso a estructuras de datos.
Hay un pequeño detalle a tener en cuenta. En linux de PC todo esto funciona correctamente
(al menos en el mio). Sin embargo, hay otros microprocesadores/sistemas operativos que
requieren que los threads "colaboren" entre ellos. Un ejemplo es Sparc/Solaris. El sistema
operativo pone en marcha uno de los threads y no le quita el control hasta que él lo diga. Los
demás threads quedan parados hasta que el primero "ceda el control". Para ceder el control
suele haber funciones estilo yield(), thr_yield(), pthread_delay_pn(), etc. También se cede el
control si se hacen llamadas que dejen dormido al thread en espera de algo,
como wait(), sleep(), etc. Así que ya sabes, si ejecutas el ejemplo y ves que el contador sólo
se incrementa o sólo se decrementa, quizás haya que poner usleep (1) dentro de los bucles,
después del printf().