\newpage
%===========================================================================
%       x.4 > Arquitecturas
%===========================================================================
\pagestyle{empty}
\begin{latexonly}
\ifpdf
 \begin{figure}[htbp]
       \includegraphics[bb = -150 150 0 0, scale=1]{./TuXes/tux-mac.png}
 \end{figure}
 \vspace{5in}
\else
 \begin{figure}
       \includegraphics[-160,600][0,0]{./TuXes/tux-mac}
 \end{figure}
\fi
\hfill\line(1,0){400}
\end{latexonly}
\topmargin 1in
\section{ARQUITECTURAS}
\begin{latexonly}
\line(1,0){400}
\newpage
\end{latexonly}
\topmargin 0in
\pagestyle{fancy}
%========================cita===============================================
\begin{latexonly}
\hspace{1in}\textcolor{blue_omosix}{{\em It is true greatness to have in one}}

\hspace{1in}\textcolor{blue_omosix}{{\em the frailty of a man and the security
  of a god.}}

\hspace{4in}\textcolor{blue_omosix}{{\em Seneca}}
\vspace{0.5in}

\end{latexonly}
%===========================================================================
En este tema se tratar\'an tanto las arquitecturas hardware como software
 que se han desarrollado para conseguir hacer trabajo paralelo o distribuido.
 Estas arquitecturas fueron inventadas para superar los problemas de rendimiento
 y responden a distintos enfoques para conseguir
 sacar m\'as rendimiento gracias al paralelismo. Algunas arquitcturas pretenden
 tener una mejor relaci\'on velocidad/precio y otras ser las
 m\'aquinas m\'as r\'apidas del mercado.

Las soluciones hardware fueron las primeras en aparecer, las soluciones software tuvieron que esperar
 hasta que el hardware diese la potencia necesaria y en el caso de los sistemas
 distribuidos se tuvo que esperar a que en los a\~{n}os 70 se desarrollaran
 las redes de \'area local.
 En el cap\'\i tulo {\em Sistemas operativos} se explicar\'a  con detalle el software distribuido a nivel de sistema operativo.
%===========================================================================
\subsection{Soluciones hardware}
Las soluciones hardware han estado en el mercado de la
 computaci\'on desde sus inicios.
 Para muchas empresas la \'unica manera de crear mejores m\'aquinas
 era crear arquitecturas paralelas a partir de las que ya pose\'\i an.

Como el n\'umero de estos tipos de m\'aquinas es elevado, existen numerosas
 y diversas soluciones. Quiz\'as la divisi\'on m\'as conocida y
 b\'asica sea la taxonom\'\i a de Flint. Flint dividi\'o las soluciones hardware en cuatro categor\'\i as:
\begin{itemize}
  \item SISD: un flujo de instrucciones \'unico trabaja sobre un flujo de datos \'unico,
 a esta categor\'\i a pertenecen las CPUs simples y las superescalares.

  \item SIMD: un flujo de instrucciones \'unico trabaja sobre un flujo de datos m\'ultiple,
 en esta categor\'\i a tenemos los computadores matriciales.

  \item MISD: un flujo de instrucciones m\'ultiple trabaja sobre un flujo de datos
 \'unico, resultado te\'orico de la clasificaci\'on, el modelo que m\'as se acerca
 son los computadores sist\'olicos.

  \item MIMD: un flujo de instrucciones m\'ultiple trabaja sobre un flujo de datos
 m\'ultiple, estos son los multiprocesadores y multicomputadores.
\end{itemize}
Se ir\'a viendo c\'omo se explota el paralelismo en el hardware a varios
 niveles: desde lo m\'as peque\~{n}o (procesadores) hasta lo m\'as
 grande (multicomputadores).

\subsubsection*{SISD}
Un procesador se dice superescalar cuando acepta varias instrucciones a
 la vez, por tanto se ejecutan a la vez.
 Todo microprocesador destinado a computadores actuales es superescalar,
 para que un microprocesador sea superescalar necesita tener redundancia
 de unidades funcionales, esto no se debe confundir con los computadores
 SIMD.
 Estas CPUs pueden realmente ejecutar {\tt N} instrucciones a la vez (son superescalar
es grado {\tt N}) solamente si no hubiera dependencia entre los datos.
 Hay tres formas en las que podemos ejecutar las varias instrucciones simult\'aneas: 
\begin{itemize}
  \item Emisi\'on de instrucciones en orden con terminaci\'on en orden: seg\'un llegan
 las instrucciones se ejecutan, hay que parar el proceso en cada dependencia
 de una instrucci\'on con la anterior.
 Tambi\'en las instrucciones m\'as r\'apidas tienen que esperar a que terminen
 las instrucciones m\'as lentas para mantenerse el orden de partida.

  \item Emisi\'on en orden con terminaci\'on en desorden: las instrucciones pueden acabar
 en cualquier orden, evitando tener que esperar a las instrucciones lentas.

  \item Emisi\'on en desorden con terminaci\'on en desorden: las instrucciones se emiten
 y acaban en cualquier orden, este es el m\'etodo que consigue mayor rendimiento.
 Se necesita tomar decisiones sobre qu\'e instrucci\'on ejecutar primero de entre
 un buffer de instrucciones.
 Esta t\'ecnica hace tiempo que la vienen usando los compiladores para conseguir
 mayores optimizaciones de los programas.
\end{itemize}

El problema de los ordenadores superescalares est\'a en los llamados
 {\em parones de instrucciones}.
 Los parones de instrucciones se producen por la dependencia que puede existir
 en instrucciones secuenciales.
 Estos parones son retardos, vienen dados cuando existe una dependencia
 entre dos instrucciones que est\'an intentando usar un mismo registro para
 escribir datos o una instrucci\'on que debe esperar a que otra actualiza
 un dato para leer de \'el.
 Para eliminar los parones\footnote{Interesante objectivo puesto que
 evitan la posibilidad de paralelismo, es decir, se tiene que esperar a otras
 instrucciones. Si se dieran cont\'\i nuamente el ordenador no tendr\'\i a ninguna
 mejora por el hecho de ser superescalar.} podremos recurrir a: 
\begin{itemize}
  \item {\bf Renombramiento de registros}, esta t\'ecnica tambi\'en viene siendo usada por
 compiladores hace tiempo y varias arquitecturas la soportan, por ejemplo
 los R10000 tienen 64 registros para renombramiento.
  \item {\bf Adelantamiento de datos} a nivel de unidades funcionales, hacemos que no
 tenga que esperarse que el dato se almacene en un lugar determinado para
 que est\'e disponible.
  \item {\bf Predicci\'on de saltos}, desde las las sencillas hasta las de nivel 2 o las
 de predicci\'on por trazas del Pentium4.
\end{itemize}
Usando estas t\'ecnicas eficientemente, en ausencia de dependencia de datos
 y otro tipo de parones, es cuando se consigue el aut\'entico paralelismo
 en computadores superescalares. Estas t\'ecnicas se suelen dar a dos niveles:
\begin{itemize}
  \item Nivel de procesador.

Son los procesadores con planificaci\'on din\'amica. El procesador lee las
 instrucciones secuencialmente, decide qu\'e instrucciones pueden ser
 ejecutadas en un mismo ciclo de reloj y las env\'\i a a las unidades
 funcionales correspondientes para ejecutarse. El problema de esta
 soluci\'on es que carga al procesador de trabajo y el circuito
 electr\'onico para implementarlo requiere muchos transistores.
 Por ejemplo el procesador AMD K7 tiene un planificador con 36 entradas
 para las operaciones de punto flotante, con esas 36 entradas toma las
 decisiones oportunas para rellenar un registro de 88 entradas que son
 la cola que espera para que las 3 unidades de ejecuci\'on totalmente independientes del
 K7 las ejecuten. En este ejemplo se ve la complejidad del hardware necesario.

  \item Nivel de compilador. La m\'aquina VLIW.

Un procesador VLIW deja al compilador hacer todo el trabajo de decisi\'on.
 El compilador VLIW agrupa las instrucciones en paquetes de una cierta longitud
 y los env\'\i a al procesador. Las decisiones se toman una sola vez en tiempo de compilaci\'on y no cada
 vez en tiempo de ejecuci\'on. El problema es que al tener que enviar paquetes de instrucciones fijos
 (y por otros trucos) se necesita enviar instrucciones
 NOPS\footnote{Instrucci\'on del lenguaje ensamblador para dejar un
 ciclo de instrucci\'on sin realizar nada.} para rellenar los huecos vac\'\i os.
 Como ejemplo la arquitectura MAJC de Sun dispone de cuatro
 unidades de ejecuci\'on totalmente independientes que no saben nada de los
 tipos de datos, esto quiere decir que pueden realizar cualquier tipo
 de operaciones por lo que cuando se ejecuta un programa de un
 solo tipo de operaci\'on (por ejemplo coma flotante) no se est\'an desaprovechando las unidades que
 no se encargan de ese tipo (enteros).
 Eso permitir\'a a este procesador un m\'aximo uso de su cauce.
\end{itemize} 
Un {\bf procesador} se dice {\bf segmentado} cuando tiene sus funciones divididas en
 varias subfunciones que se realizan de forma independiente y que pueden
 ejecutarse simult\'aneamente\footnote{Dentro del procesador, el hardware utilizado para cada etapa es diferente.}.
 Si todo el procesador actuara como un bloque, solo se podr\'\i a ejecutar una
 sola instrucci\'on a la vez que tardase {\tt M} ciclos, {\tt N} instrucciones tardar\'\i an
 {\tt N}*{\tt M} ciclos.
 En un ciclo sin parones pueden ejecutarse {\tt N} instrucciones en {\tt M +(N-1)M/k}
 ciclos siendo {\tt k} el n\'umero de etapas. Por tanto cuantas m\'as etapas m\'as trabajo
 paralelo se realiza y m\'as velocidad se obtiene.
 
As\'\i\ por ejemplo un procesador dividido en 4 etapas sin dependencias de datos y empezando
 por la primera instrucci\'on se ve como en el cuadro \ref{arq_4etapas_cpu}.
\begin{table}
\begin{center}
\begin{tabular}{ | c | c | c | c | c | c | c | } \hline
 & Tiempo 1 & Tiempo 2 &  Tiempo 3 &  Tiempo 4 &  Tiempo 5 &  Tiempo 6  \\ \hline \hline
Subfunci\'on 1 & Instr 1 & Instr 2 & Instr 3 & Instr 4 & Instr 5 & Instr 6  \\ \hline
Subfunci\'on 2 &         & Instr 1 & Instr 2 & Instr 3 & Instr 4 & Instr 5  \\ \hline 
Subfunci\'on 3 &         &         & Instr 1 & Instr 2 & Instr 3 & Instr 4  \\ \hline  
Subfunci\'on 4 &         &         &         & Instr 1 & Instr 2 & Instr 3  \\ \hline 
Resultados     &         &         &         &         & Result 1& Result 2 \\ \hline  
\end{tabular}
\caption{Arquitecturas. Procesador dividido en 4 etapas y sin dependencias}
\label{arq_4etapas_cpu}
\end{center}
\end{table}
Si se ejecutaran a la vez tantas instrucciones como subfunciones
 tuviera podr\'\i a realizarse una instrucci\'on por ciclo.
 Como ejemplo, las 3 unidades de punto flotantes que se han visto en un ejemplo
 anterior del K7 est\'an divididas en 15 etapas.

Seguidamente se muestran unos datos sobre como esta divisi\'on en tantas etapas permiten
 a los K7 hacer las operaciones en menos ciclos por instrucci\'on que
 a los Pentium4, que est\'an divididos en menos etapas (ver
 cuadro \ref{arq_p4vsk7}).
\begin{table}
\begin{center}
\begin{tabular}{ | c | c | c | } \hline
Instrucci\'on & ciclos en P4 & ciclos en K7 \\ \hline \hline
FADD & 1 & 1 \\ \hline  
FMUL & 2 & 1 \\ \hline  
FDIV float & 23 & 13 \\ \hline  
FDIV double & 38 & 17 \\ \hline  
FSQRT float & 23 & 16 \\ \hline  
FSQRT double & 38 & 24 \\ \hline  
\end{tabular}
\caption{Arquitecturas. Inferioridad del procesador P4 frente a K7}
\label{arq_p4vsk7}
\end{center}
\end{table}
Para los amantes de las mejores arquitecturas aqu\'\i\ se da una
buena raz\'on para inclinar la balanza hacia los de Advanced Micro
Devices. La diferencia entre ciclos por instrucci\'on ser\'\i a mucho
mayor al incluir en el cuadro una arquitectura PPC de Apple: \'estas llegan a ser
competitivas a\'un funcionando a 800MHz en unos tiempos donde los P4
llegan ya los 3GHz.

La soluci\'on mixta de los dos anteriores es un {\bf procesador segmentado superescalar}.
 En este procesador entran varias instrucciones a la vez que se ejecutan
 en cada una de las subfunciones, as\'\i\ se est\'an ejecutando a la
 vez {\tt M*N} instrucciones siendo {\tt M} y {\tt N} los grados de segmentaci\'on y escalabilidad del procesador.
 Si en el cuadro \ref{arq_4etapas_cpu} se a\~{n}adiese la capacidad de ser superescalar en
 cada casilla cabr\'\i an {\tt N} instrucciones, as\'\i\ donde antes hab\'\i a {\tt instrucci\'on 1}
 ahora habr\'\i a {\tt instrucci\'on 1 ... instrucci\'on N}. Asimismo en {\tt
 instrucci\'on 2} habr\'\i a {\tt instrucci\'on N+1 ... instrucci\'on
 2N}, {\em etc.}
 
\subsubsection*{SIMD}
Los computadores SIMD son bastante menos frecuentes que los SISD eso se
 debe a que su programaci\'on es bastante m\'as compleja.
 Un computador SIMD est\'a compuesto de una serie de unidades de ejecuci\'on,
 unos m\'odulos de memoria y una red de interconexi\'on.
 Gracias a las conexiones cualquier elemento de proceso puede acceder
 a cualquier m\'odulo de memoria.
 El paralelismo se consigue ejecutando la misma instrucci\'on sobre varios
 datos a la vez (cada elemento de proceso un dato).
 Aqu\'\i\ es donde se complica la programaci\'on pues antes de ejecutar la instrucci\'on
 debe especificar d\'onde se encuentra los datos en memoria y la configuraci\'on
 de la red de conexi\'on en cada instante.

Aunque se han intentado construir m\'aquinas SIMD puras no han tenido demasiado
 \'exito, no eran suficientemente flexibles y poco eficientes para algunas
 operaciones (por ejemplo operaciones condicionales).
 Hoy en d\'\i a lo que se desarrollan son extensiones vectoriales de procesadores
 SISD, como ejemplos tenemos AltiVec de Motorola, MMX de Intel o 3DNow de
 AMD.

Motorola ha apostado por a\~{n}adir m\'as hardware: 32 registros de
 128 bits para operaciones vectoriales. Dos unidades de ejecuci\'on
 independientes (una ALU vectorial y otra para permutaciones), puede
 mantener una operaci\'on por ciclo siempre que encuentre los datos en la
 cache de nivel 1.

 El MMX de Intel en cambio usa registros de 64 bits que ya exist\'\i an (registros del coprocesador para punto flotante) y a\~{n}adi\'o 57 instrucciones.
 Con SSE anteriormente conocida como MMX2 se a\~{n}adieron 8 registros nuevos
 de 128 bits y una unidad independiente de suma vectorial.
 Tambi\'en hay una unidad de coma flotante vectorial pero comparte parte de
 su {\em pipeline} con la unidad de ejecuci\'on de coma flotante corriente.
 Cuando se quiere hacer una operaci\'on de 128 bits, se dividen en dos y se
 env\'\i an a las unidades de suma y multiplicaci\'on por lo que no siempre se
 obtiene una instrucci\'on por ciclo, pero permite a Intel mantener sus buses
 internos del procesador a 64 bits.

 3DNow implementa MMX, SSE y SSE2 de la misma manera que Intel y a\~{n}ade sus
 propias instrucciones para las que no pone registros nuevos (pero tiene
 registros ocultos al programador, para hacer renombramiento), no permite
 realizar operaciones de 128 bits pero a cambio las unidades de coma flotante
 son totalmente independientes.
 
El problema de estas nuevas instrucciones es el mismo que tuvieron las m\'aquinas
 SIMD: s\'on m\'as dif\'\i ciles de programar. Hoy en d\'\i a se usan mucho los
 lenguajes de alto nivel por lo tanto se deja la optimizaci\'on y vectorizaci\'on
 de los datos a los compiladores.

 Como curiosidad, notar que el compilador de Intel, que es capaz de vectorizar
 aproximadamente la mitad de los bucles de un programa y que usa SSE2 para
 tantas operaciones en punto flotante como puede, aumenta los rendimientos
 por encima de los del procesador AMD K7 en instrucciones como FADD, FMUL
 y FSQRT. Tras ver el cuadro 2 se comprueba como realmente el AMD K7 es
 m\'as potente en lo que se refiere a operaciones en coma flotante, no
 obstante por el momento no dispone de compilador espec\'\i fico en el
 mercado, esto lo pone en posici\'on desfavorable frente al P4 dentro del uso del paralismo.

\subsubsection*{MIMD}
Pueden distinguirse multiprocesadores y multicomputadores.

 Un sistema multiprocesador consta de muchos procesadores independientes
 unidos por alg\'un mecanismo entre ellos y la memoria.
 La primera consideraci\'on que se hace al desarrollar un sistema multiprocesador
 es c\'omo conectar los procesadores entre s\'\i\ y \'estos a la memoria.
 Los procesadores deben estar conectados o al menos tener una zona de memoria
 com\'un, pues est\'an haciendo un trabajo cooperativo.

Seg\'un el tipo de acceso a memoria de todos los procesadores de la m\'aquina
 MIMD se caracterizan en dos tipos, que son: UMA (acceso uniforme a memoria) y NUMA (accesso no
 uniforme a memoria).
\begin{itemize}
  \item UMA significa que todos los procesadores acceden a la misma memoria a la
 misma velocidad, por ejemplo un sistema SMP es UMA, Por ejemplo los compatibles
 con x86 tienen una arquitectura con la memoria compartida, global para
 todos los procesadores, con un solo bus para acceder a memoria que comparten
 todos los procesadores.
  \item NUMA significa que no los procesadores no pueden acceder a toda la memoria
 con la misma velocidad.
 Habr\'a zonas de memoria a la que se accede m\'as r\'apido y zonas a las que
 se accede a menos velocidad.
 Los procesadores tienen conexi\'on directa con una memoria local, pero tambi\'en
 tienen una conexi\'on m\'as lenta a memorias locales de otros procesadores
 (la memoria local no es memoria cache).
\end{itemize}
Los sistema UMA a su vez pueden ser de dos tipos dependiendo de la red
de interconexi\'on. As\'\i\ en los {\bf multiprocesadores en bus} (los
procesadores comparten buses de direcci\'on, datos y control) existe
un \'arbitro en este bus que es quien decide quien puede en cada momento acceder a los buses.
 El problema de esta arquitectura es que los buses se saturan con facilidad,
 a partir de 64 CPUs el bus es el cuello de botella y aumentar el n\'umero
 de CPUs no aumenta el rendimiento.
\begin{latexonly}
\ifpdf
        \begin{figure}[htbp]
        \includegraphics[bb = -100 100 0 0, scale=1]{./pics/bus_local.png}
        \vspace{1.5in}
        \caption{Arquitecturas. Multiprocesadores en bus}
        \end{figure}
\else
\end{latexonly}
        \begin{figure}
        \centering
        \includegraphics{pics/bus_local}
        \caption{Arquitecturas. Multiprocesadores en bus}
        \end{figure}
\begin{latexonly}
\fi
\end{latexonly}

Los {\bf multiprocesadores con conmutador} s\'on la soluci\'on para los problemas que
 conlleva el bus y, evidentemente, es una arquitectura m\'as cara.
 La memoria se divide en m\'odulos, varios procesadores pueden acceder a memoria
 de forma simult\'anea.
 Las CPUs y las memorias est\'an conectadas a trav\'es de puntos de cruce.
 Si dos procesadores quieren acceder simult\'aneamente a la misma memoria, tendr\'an
 que esperar, en cualquier otro caso el acceso se hace de forma simult\'anea.
 El problema que conlleva esta arquitectura es que el n\'umero de conmutadores
 es alto {\tt N}x{\tt M} (si {\tt N} es el n\'umero de procesadores y {\tt M} el n\'umero de m\'odulos
 de memoria) adem\'as estos conmutadores son caros pues tienen que ser de
 alta tecnolog\'\i a para que no se haga m\'as lento el acceso a la memoria.
\begin{latexonly}
\ifpdf
        \begin{figure}[htbp]
        \includegraphics[bb = -150 200 0 0, scale=1]{./pics/mimd.png}
        \vspace{3in}
        \caption{Arquitecturas. Multiprocesadores en conmutador}
        \end{figure}
\else
\end{latexonly}
        \begin{figure}
        \centering
        \includegraphics{pics/mimd}
        \caption{Arquitecturas. Multiprocesadores en conmutador}
        \end{figure}
\begin{latexonly}
\fi
\end{latexonly}

Para solucionar estos problemas se cre\'o la red Omega que tiene un n\'umero
 menor de conmutadores. Pero en esta red, y por tener menos elementos de conmutaci\'on, ocurren m\'as esperas
 que en una red de barras como la anterior, aunque menos que en una estructura en bus.
 Para solucionar todos estos es para lo que se cre\'o la estructura NUMA,
 pero esta estructura necesita algoritmos complejos.
\begin{latexonly}
\ifpdf
        \begin{figure}[htbp]
        \includegraphics[bb = -100 200 0 0, scale=1]{./pics/red_omega.png}
        \vspace{3in}
        \caption{Arquitecturas. Red Omega}
        \end{figure}
\else
\end{latexonly}
        \begin{figure}
        \centering
        \includegraphics{pics/red_omega}
        \caption{Arquitecturas. Red Omega}
        \end{figure}
\begin{latexonly}
\fi
\end{latexonly}

Un problema que estos sistemas MIMD tienen que solventar es la coherencia
 de las memorias cache.
 Las memorias cache de cada procesador guardan los datos que este est\'a
 accediendo m\'as frecuentemente.
 En la ejecuci\'on normal de un procesador se leen los datos de memoria y
 se dejan en memoria cache, las pr\'oximas lecturas o escrituras se realizan
 en ella ahorrando tener que ir a memoria principal.
 El problema ocurre con las escritura en estos datos.
 Pongamos un ejemplo:
\begin{enumerate}
 \item la CPU 1 lee de memoria la variable {\tt varGlobal}.
 \item la CPU 2 lee de memoria la misma variable.
 \item la CPU 1 y la CPU 2 escriben en su copia local de cache un valor.
 \item Cuando esos valores se actualizen en memoria (depende de si la cache es
 de postescritura o escritura directa) estaremos ante una {\em condici\'on de carrera}.
 Adem\'as el valor escrito por un procesador no se ha propagado a los dem\'as
 procesadores que est\'an trabajando con un valor antiguo.
\end{enumerate} 
Se podr\'\i a pensar que la soluci\'on ser\'\i a {\bf no permitir memoria compartida} o
 tener un mecanismo para controlar la situaci\'on en ese tipo de memoria,
 no obstante hay que tener en cuenta que tambi\'en puede ocurrir el caso de que un
 proceso corriendo en el procesador 1 lea las variables propias (que pueden
 ser privadas), entonces se mueve al procesador 2 donde escribe en esas
 variables.
 Cuando vuelve a migrar al procesador 1 las variables que el proceso piensa
 que escribi\'o (la migraci\'on es transparente al proceso) no est\'an escritas.

Para evitar estas situaciones se han desarrollado una serie de t\'ecnicas.
 La primera de ellas es dividir la memoria principal y dejar parte de esa
 memoria como memoria local. S\'olo se pasar\'\i an a cache los datos de esa memoria
 local y esto plantea bastantes problemas (copiar datos de la memoria global a
 la local, perdemos eficiencia en accesos a memoria compartida) y
 seguramente por esta raz\'on no es una soluci\'on amplamente usada.

Tener la {\bf cache compartida} por los procesadores implicar\'\i a una velocidad
 de bus muy elevada, no se implementa en la pr\'actica por sus costes econ\'omicos.

Existen las {\bf cache privadas} con directorio compartido donde se guarda el
 estado de cada uno de los datos, gracias a esta informaci\'on se
 implementa un algoritmo que mantiene la cache coherentes.
 Se implementa en sistemas con multiprocesadores con redes de conexi\'on (no
 en bus).
 Existen varios protocolos que se diferencian en la informaci\'on que guardan
 referida a los bloques de memoria y como se mantiene esa informaci\'on.
\begin{itemize}
  \item Directorios completos.

El directorio tiene un bit de procesador que indica
 si el bloque est\'a o no en la cache del procesador y un bit de modificado
 si ha sido modificado, si ha sido modificado, s\'olo est\'a en ese procesador
 y s\'olo \'el tiene permiso para modificarlo.
 Tambi\'en hay 2 bits por cada bloque de la memoria cache, uno indica si ese
 bloque es v\'alido o no y el otro indica si se puede escribir en \'el.

  \item Directorios limitados.

Cuando el n\'umero de procesadores es muy alto, la
 soluci\'on anterior requiere mucha memoria, para disminuir esta necesidad
 se usan directorios limitados que siguiendo el mismo esquema, imponen un
 l\'\i mite al n\'umero de procesadores.
 Si se solicita el bloque en m\'as cache de las que entradas tiene el directorio
 se aplica un algoritmo de reemplazo.
 El directorio debe especificar el procesador porque ahora no sabemos como
 antes que bits indican que procesadores.

  \item Directorios encadenados.

El directorio s\'olo indica el primer procesador
 que tiene el bloque, este procesador indica el siguiente creando una cadena,
 hasta encontrar el procesador que tiene el bit que indica final de cadena
 activado.
\end{itemize} 
Entre las soluciones m\'as implantadas en multiprocesadores con sistema de memoria
 basado en bus se encuentran los protocolos de {\em snoopy}:
 los procesadores se mantienen a la escucha de lo que ocurre en los distintos bloques
 de cache. Ya que el problema ocurre cuando se escribe un dato en la cache, cuando
 se escribe uno hay dos posibilidades:
\begin{itemize} 
\item Invalidar el dato de todas las dem\'as cache cuando uno de los procesadores escribe en su cache
 ese mismo dato. El problema de esta soluci\'on
 es que puede ocurrir que un dato sea muy utilizado y se tenga que estar
 cogiendo de memoria constantemente pues fue invalidado.

\item Actualizar los datos de los dem\'as procesadores cuando
 modificamos un bloque, el problema de esta aproximaci\'on es que se
 puede generar mucho tr\'afico y el bus tiene que ser r\'apido.
\end{itemize} 
A nivel de m\'aquina se han desarrollado otros sistemas para conseguir paralelizar todo tipo
 de tareas a todos los niveles. El primero de ellos son las IRQs lo
 que permiten al procesador estar ejecutando programas a la vez que los perif\'ericos est\'an ejecutando sus funciones. Este es un paralelismo a nivel de m\'aquina que permite que se lleve la cuenta
 del reloj a la vez que se acepta una pulsaci\'on de una tecla a la vez que el procesador est\'a haciendo alg\'un proceso pesado.
 
Tras las IRQs se implant\'o el DMA, esto es un chip especial dentro de la
 placa del ordenador que permite ser programado para que un perif\'erico haga
 accesos a un bloque de memoria sin intervenci\'on de la CPU, esto permite
 mayor autonom\'\i a de los perif\'ericos que ejecutan m\'as funciones sin interrumpir
 a la CPU, con un bus compartido no podr\'an acceder m\'as de un perif\'erico
 a la vez a memoria.

Los sistemas multiprocesadores actuales est\'an consiguiendo mejores pol\'\i ticas de gesti\'on por parte del sistema operativo.

 Para poner un ejemplo de hacia donde se dirigen las nuevas capacidades
 de los procesadores para trabajar en paralelo se presenta el caso del
 procesador MAJC de Sun.

MAJC es capaz de, cuando est\'a en una configuraci\'on multiprocesador
 y detecta que en el {\em thread} actual se ha llegado a un bucle del
 que se tardar\'a en salir, producir de manera especulativa (al igual
 que se realizan instrucciones) un nuevo {\em thread} que
 se ejecuta en otro procesador ejecutando las posibles instrucciones futuras
 en tiempo presente, a esto lo llaman STC (computaci\'on espacio temporal).

MAJC implementa tambi\'en {\em vertical multithreading}. As\'\i\
 cuando un {\em thread} tiene un fallo de cache
 y tiene que ir a memoria a por el dato se hace un cambio de contexto y
 se ejecuta otro {\em thread} hasta que el dato ha llegado.
 Esto es posible porque mucho espacio del procesador MAJC se gasta en registros
 y es capaz de mantener la informaci\'on de estado necesaria para hacer el
 cambio de contexto entre dos {\em threads} en ellos, con lo que el cambio de contexto
 es el m\'as r\'apido que puede implementarse actualmente y da tiempo a ejecutar instrucciones antes de que el dato de memoria haya llegado.

\vspace{.2in}
{\bf Multicomputadores:}

Ya se vi\'o en el tema de introducci\'on c\'omo aparecieron y evolucionaron estos
 sistemas.
 Es una arquitectura MIMD donde cada uno de los procesadores puede tener
 a su vez varios procesadores.
 En este apartado lo que antes era un procesador ahora ser\'a un
 ordenador entero.
 Cada uno tiene acceso directo a su memoria local.
 Entonces es la misma arquitectura que en multiprocesadores pero escalada.
 Tambi\'en aqu\'\i\ se puede tener una arquitectura en bus u otro tipo, podr\'an aplicarse exactamente las mismas explicaciones sobre arquitecturas de las que hablamos en multiprocesadores, solo se var\'\i a el esquema levemente:
\begin{itemize}
  \item Bus: por ejemplo una red de ordenadores conectados a trav\'es de una LAN se
 adapta a este esquema.
 Las velocidades de transmisi\'on son menores que en un sistema multiprocesador
 en base a buses y las latencias son mayores.
 B\'asicamente igual que su hom\'ologo en multiprocesadores, excepto que esta
 arquitectura es NUMA (pues un ordenador accede a su memoria local mucho
 m\'as r\'apidamente que a la memoria de otro ordenador), se usan algoritmos
 complejos para las operaciones con memoria.
 
  \item Conmutadores: ahora los conmutadores son los propios nodos, pues tienen
 la capacidad de conectarse a sus nodos vecinos (tienen tarjetas de red)
 se pueden dar en dos arquitecturas diferentes:
  \begin{enumerate}
    \item Transputers: los ordenadores forman una ret\'\i cula 2D.
 Cada nodo solo tiene que conectar con (como mucho) 4 nodos m\'as en cualquier
 caso, esto es perfecto para escalar el cluster sin tener que a\~{n}adir tarjetas
 de red a los nodos ya existentes.
 N\'otese que no hay simetr\'\i a pues los nodos en los bordes se conectan con
 menos nodos.
    \item Hipercubos: los ordenadores forman una ret\'\i cula 3D.
 Es dif\'\i cil de visualizar a partir del orden 4.
 Cada nodo se conecta a N nodos siendo N el grado del hipercubo.
 Es una configuraci\'on sim\'etrica.
  \end{enumerate}
\end{itemize}
%=========================================================================== 
\subsection{Soluciones software}
Una vez que el hardware obtuvo la potencia suficiente se empez\'o a explotar
 el paralelismo a nivel de software, en el cap\'\i tulo sobre sistemas
 operativos se ver\'a c\'omo se comenz\'o dividiendo el tiempo de CPU entre varios procesos,
 esto permit\'\i a una mayor eficiencia e interacci\'on entre usuarios y m\'aquinas.
 Con la llegada de las IRQ el sistema operativo tuvo que gestionarlas, teniendo
 as\'\i\ la responsabilidad de mantener todos los perif\'ericos trabajando a la
 vez el m\'aximo tiempo posible.

Los sistemas operativos tienen la responsabilidad de utilizar eficientemente
 los mecanismos que le da el hardware para hacer trabajos paralelamente.
 As\'\i\  por ejemplo en multiprocesadores es el sistema operativo quien tiene
 que distribuir los procesos por los procesadores de una forma que todos
 los procesadores est\'en ocupados.
 El kernel tiene que tener en cuenta que un proceso se ejecuta m\'as
 eficientemente en un procesador donde se ejecut\'o recientemente,
 pues el procesador tiene en su cache datos de ese procesador, tambi\'en tiene que tener en cuenta
 si todos los procesadores tienen acceso a entrada/salida o no.
 Por ejemplo Windows NT ha elegido una forma muy sencilla que es usar {\tt N-1}
 procesadores para los {\tt N-1} procesos m\'as prioritarios y el \'ultimo para el
 resto, el rendimiento de este esquema es m\'as que discutible, pero desde
 luego es simple.

La evoluci\'on de las soluciones software ha seguido la misma que las soluciones
 hardware: hasta que el hardware no las soportaba las soluciones software
 no se hac\'\i an realmente eficientes.
 As\'\i\ tradicionalmente todos los recursos de una m\'aquina los ha gestionado
 el n\'ucleo y \'este se ha ido adaptando a las nuevas posibilidades, aunque
 siempre ha habido hardware de un nivel m\'as alto que controlaba su propio
 funcionamiento, como grandes sistemas gestores de bases de datos.

Con la llegada de los multicomputadores, se plantearon nuevos retos, pero
 esta vez en vez de ser superados por los dise\~{n}adores de sistemas operativos,
 se vi\'o como un problema de las aplicaciones.
 Quiz\'as esto fue as\'\i\  porque cuando aparecieron las primeras redes no eran
 algo barato ni tan com\'un, por eso se consideraba tan puntero el sistema
 operativo Unix al soportarlas.
 Tampoco se dispon\'\i a de hardware barato y potente.
 Por lo tanto aunque el n\'ucleo daba funcionalidades b\'asicas (creaci\'on de
 procesos, comunicaci\'on, manejo de red) todo el problema se dejaba al espacio
 de usuario.
 Afortunadamente esta aproximaci\'on est\'a cambiando y ya existen varios sistemas
 de ficheros enfocados a tener un sistema de ficheros \'unico en todo el cluster,
 pero a\'un queda mucho por hacer.

Hoy en d\'\i a una red es muy barata y est\'an muy extendidas por lo tanto las
 aplicaciones que funcionan sobre ellas se han extendido en todas las \'areas
 del software.
 Al principio estas aplicaciones eran muy b\'asicas, eran poco m\'as que los
 protocolos que implementaban (que para la \'epoca implementar el protocolo
 era un logro suficiente), poco a poco se fueron haciendo m\'as complejas.
 Por aquel entonces Sun hac\'\i a mucho desarrollo e implement\'o algo que hasta
 la fecha est\'a siendo muy utilizado: RPC. Este sistema permite una
 comunicaci\'on de dos m\'aquinas, una de las m\'aquinas ejecuta unos procedimientos de la otra m\'aquina.

