Final Flashcards
¿Qué es una system call? ¿Para qué se usan? ¿Cómo funcionan? Explicar en detalle el funcionamiento de una system call en particular.
Syscall es un tipo de instrucción, son llamadas al sistema.Sirven para proveer funcionalidad, que solo puede ser accedida por el kernel, a las aplicaciones de usuario.A diferencia de una llamada a subrutina común y corriente, las llamadas al sistema requieren cambiar el nivel de privilegio, un cambio de contexto, a veces una interrupción, etc. Los pasos son los mismos que para las interrupciones:
- El hardware mete el PC, PSW, registros, etc. a la pila.
- El hardware carga el nuevo PC del vector de interrupciones.
- Procedimiento en lenguaje ensamblador guarda los registros.
- Procedimiento en lenguaje ensamblador establece la nueva pila.
- El servicio de interrupciones de C se ejecuta.
- El scheduler decide que proceso se va a ejecutar a continuación.
- Procedimiento en C regresa al código en ensamblador.
- Procedimiento en lenguaje ensamblador inicia el nuevo proceso actual.
Las llamadas más típicas de un sistema son: *Open *Read *Write *Close *Wait *Exec *Fork *Exit *Kill
Llamada al sistema fork()
En un sistema operativo, hace referencia a la creación de una copia de sí mismo por parte de un programa, que entonces actúa como un “proceso hijo” del proceso originario, ahora llamado “padre”. Los procesos resultantes son idénticos, salvo que tienen distinto número de procesos.
¿Las system calls son universales a todos los sistemas operativos o son dependientes de cada sistema?
No son universales, cada sistema operativo provee la interfaz para interactuar con su núcleo.
¿Para qué sirve la system call fork? ¿Qué debilidades tiene? Comparar con vfork y la creación de threads.
La system call fork se utiliza en sistemas operativos Unix y Linux para crear un nuevo proceso, llamado proceso hijo, que es una copia del proceso padre. El proceso hijo recibe una copia del espacio de memoria del proceso padre, incluidas las variables, el estado del programa y los descriptores de archivos.
Funcionamiento de fork
Creación del Proceso Hijo:
fork crea un nuevo proceso duplicando el proceso que lo llama.
Ambos procesos, el padre y el hijo, continúan ejecutándose desde el punto donde se realizó la llamada a fork.
Espacio de Memoria:
El proceso hijo tiene una copia exacta del espacio de memoria del proceso padre en el momento de la llamada a fork.
Aunque inicialmente comparten el mismo espacio de memoria, los cambios realizados por uno no afectan al otro debido a la técnica de “Copy on Write” (COW).
Retorno de Valores:
fork retorna dos veces: una vez en el proceso padre y otra en el proceso hijo.
En el proceso padre, fork retorna el PID del hijo.
En el proceso hijo, fork retorna 0.
Debilidades de fork
Uso de Recursos:
fork puede consumir muchos recursos del sistema, especialmente memoria, ya que inicialmente crea una copia completa del espacio de memoria del proceso padre.
Eficiencia:
Aunque la técnica de Copy on Write ayuda a mitigar el uso intensivo de memoria, la duplicación inicial del espacio de memoria puede ser costosa en términos de tiempo y recursos.
Complejidad:
Manejar la sincronización y comunicación entre el proceso padre e hijo puede ser complejo y propenso a errores.
Comparación con vfork y la Creación de Threads
vfork
Propósito:
vfork es una variante de fork diseñada para casos donde el proceso hijo ejecuta una llamada a exec inmediatamente después de ser creado.
Funcionamiento:
vfork no duplica el espacio de memoria del proceso padre. En cambio, el proceso hijo comparte el mismo espacio de memoria que el padre hasta que se llama a exec o exit.
El proceso padre se bloquea hasta que el proceso hijo llame a exec o exit.
Ventajas:
Más eficiente en términos de memoria y tiempo en comparación con fork, ya que no se realiza una copia completa del espacio de memoria.
Desventajas:
Compartir el mismo espacio de memoria entre padre e hijo puede ser riesgoso y puede llevar a comportamientos no deseados si el proceso hijo modifica el espacio de memoria antes de llamar a exec o exit.
Creación de Threads
Propósito:
Los threads (hilos) son una forma de concurrencia dentro del mismo proceso, compartiendo el mismo espacio de memoria.
Funcionamiento:
Los threads se crean dentro de un proceso y comparten el mismo espacio de direcciones, variables globales y descriptores de archivos.
Ventajas:
Más ligeros y rápidos de crear y destruir en comparación con procesos.
Menor consumo de memoria y recursos del sistema, ya que no requieren duplicar el espacio de memoria.
Facilita la comunicación y el intercambio de datos entre hilos debido a que comparten el mismo espacio de memoria.
Desventajas:
Mayor riesgo de problemas de concurrencia como condiciones de carrera y problemas de sincronización.
Un fallo en un thread puede afectar a todo el proceso, ya que todos los threads comparten el mismo espacio de memoria.
Diferencias entre system calls para crear procesos entre Linux y Windows.
Linux y Windows tienen arquitecturas de núcleo diferentes, lo que afecta cómo se gestionan los procesos. En Linux, el núcleo sigue una arquitectura monolítica, donde todo el sistema operativo está contenido en el núcleo. Esto significa que el núcleo maneja la gestión de procesos, la gestión de memoria, los controladores de dispositivos y las llamadas al sistema dentro del espacio del núcleo. Por otro lado, Windows utiliza una arquitectura de núcleo híbrido, que combina elementos de arquitecturas monolíticas y microkernel. El núcleo de Windows proporciona servicios esenciales y gestiona recursos básicos del sistema, mientras que los controladores de dispositivos y algunos servicios del sistema se ejecutan en modo usuario. En cuanto a la gestión de procesos, tanto Linux como Windows utilizan conceptos similares como la planificación de procesos, la gestión de memoria y la comunicación entre procesos, pero los detalles de implementación difieren debido a sus arquitecturas de núcleo. En general, las diferencias en la arquitectura del núcleo entre Linux y Windows resultan en enfoques variados para la gestión de procesos y el manejo de recursos del sistema.
¿Cómo funcionan los estados de un proceso? Ready, bloqueado, running. Explicar las transiciones de cada estado a cada estado (en particular, de waiting a ready).
En un sistema operativo, un proceso puede encontrarse en diferentes estados durante su ciclo de vida. Los estados principales son:
Listo (Ready): El proceso está preparado para ejecutarse pero no puede hacerlo porque la CPU está ocupada con otros procesos.
Corriendo (Running): El proceso está actualmente en ejecución, utilizando la CPU.
Bloqueado (Blocked/Waiting): El proceso no puede continuar su ejecución hasta que ocurra un evento externo, como la finalización de una operación de entrada/salida (E/S).
Terminado (Terminated): El proceso ha completado su ejecución.
Transiciones entre estados
De Listo a Corriendo: Cuando la CPU se libera y el planificador de procesos selecciona un proceso de la cola de listos para su ejecución.
De Corriendo a Bloqueado: Cuando un proceso en ejecución necesita esperar por algún evento externo (como una operación de E/S), pasa al estado bloqueado.
De Bloqueado a Listo: Una vez que el evento externo que el proceso estaba esperando ocurre (por ejemplo, una operación de E/S se completa), el proceso pasa de bloqueado a listo.
De Corriendo a Listo: Si el proceso en ejecución es interrumpido por el planificador de procesos (por ejemplo, debido a la política de tiempo compartido), pasa al estado listo, esperando su turno para ejecutarse nuevamente.
De Corriendo a Terminado: Cuando el proceso completa su ejecución.
De Listo a Terminado: Esta transición generalmente no ocurre directamente en la práctica, ya que un proceso debe estar en ejecución (Corriendo) para completar su ejecución y pasar a Terminado.
¿Qué estructura se debe mantener en memoria para poder tener procesos? Hablar de la tabla de procesos.
El sistema operativo mantiene una tabla (un arreglo de estructuras), llamada tabla de procesos, con una entrada por cada proceso. Esta entrada contiene información importante sobre el estado del proceso, incluyendo su contador de programa, puntero de pila, asignación de memoria, el estado de sus archivos abiertos, su información de contabilidad y planificación, y todo lo demás sobre el proceso que debe guardarse cuando el proceso se cambia de estado de ejecución a estado de listo o bloqueado, para que pueda reiniciarse más tarde como si nunca hubiera sido detenido.
¿Qué es un proceso, un thread y en qué se diferencian?
Un proceso es una unidad independiente de ejecución que contiene su propio espacio de memoria. Es un programa en ejecución que tiene recursos asignados, como memoria y tiempo de CPU, permitiéndole operar de forma independiente. Los procesos se comunican a través de mecanismos de comunicación interprocesos (IPC) y están aislados entre sí, lo que significa que el fallo en un proceso no afecta directamente a otros.
Un thread o hilo es una unidad de ejecución más ligera dentro de un proceso. A diferencia de los procesos, los threads de un mismo proceso comparten el mismo espacio de memoria y recursos, como archivos y variables de datos. Esto permite una comunicación más directa y eficiente a través de la memoria compartida, pero también implica que un fallo en un thread puede afectar a todos los threads del mismo proceso.
Diferencias principales entre un proceso y un thread
Espacio de memoria: Los procesos tienen su propio espacio de memoria separado, mientras que los threads comparten el espacio de memoria dentro del mismo proceso.
Sobrecarga: Los procesos tienen una sobrecarga mayor debido a la gestión de memoria y recursos separados, mientras que los threads son más eficientes en el uso de recursos debido a la compartición de memoria y recursos.
Ejecución: Los procesos operan de manera independiente, mientras que la ejecución de los threads depende del proceso al que pertenecen y pueden correr en paralelo dentro de este.
Control: Los procesos pueden ser iniciados, detenidos y controlados de manera independiente, mientras que los threads son controlados dentro del contexto de un proceso.
Aislamiento: Los procesos están aislados entre sí, lo que proporciona seguridad y estabilidad al sistema, en contraste con los threads que están expuestos a fallos relacionados entre sí debido a la compartición de recursos.
Impacto de fallos: Un fallo en un proceso no afecta a otros procesos, mientras que un fallo en un thread puede impactar a todos los threads del mismo proceso.
Tiempo de creación: La creación de procesos toma más tiempo debido a la necesidad de asignar recursos exclusivos, en comparación con los threads que se pueden crear más rápidamente.
Casos de uso
Procesos: Son adecuados para aplicaciones que requieren aislamiento y seguridad en la ejecución, como aplicaciones que manejan datos sensibles o críticos.
Threads: Ideales para tareas que requieren una comunicación frecuente y acceso a recursos compartidos, como las aplicaciones que realizan muchas tareas de fondo que son menos intensivas en CPU pero que necesitan interactuar entre sí de manera eficiente.
En resumen, elegir entre procesos y threads depende de los requerimientos específicos de la aplicación en términos de aislamiento, seguridad, rendimiento y eficiencia en la comunicación.
¿Qué debería agregar a la PCB para manejar los threads?
Deberíamos agregar una lista de threads con los valores de los registros y el estado del stack para poder retomar la ejecución de estos.
En la PCB del proceso que tiene varios threads incluye una tabla con los threads id
¿Qué pasaría si los threads compartieran el stack?
Si los threads compartieran stack, se molestan mutuamente a la hora de ejecutarse porque uno podría cambiar el valor de una variable o cambiar el contexto y cuando pase a ejecutarse el otro thread el contexto sería erróneo o el valor de la variable no sería el esperado.
Qué tendría que ver en un sistema para que piense que va a andar mejor agregando:
más procesadores.
más memoria.
Más procesador se necesita cuando la carga del sistema es alta, es decir hay procesos listos que no se están ejecutando.
Más memoria se necesita cuando tenemos muchos procesos que necesitan memoria y esta no alcanza.
Hablar de strace y ptrace.
Ambas son syscalls.
El programa strace, que está disponible en los sistemas Linux, lista cada llamada al sistema a medida que se ejecuta. strace—rastrea las llamadas al sistema invocadas por un proceso.
ptrace permite que un proceso controle a otro, permitiendo al controlador inspeccionar y manipular el estado interno de su objetivo. ptrace es utilizado por depuradores y otras herramientas de análisis de código, principalmente como ayudas para el desarrollo de software.
Describir los objetivos que pueden tener las políticas de scheduling (fairness, carga del sistema, etc.).
Ecuanimidad (fairness): que cada proceso reciba una dosis “justa” de CPU (para alguna definición de justicia).
Eficiencia: tratar de que la CPU este ocupada todo el tiempo.
Carga del sistema: minimizar la cant. de procesos listos que están esperando CPU.
Tiempo de respuesta: minimizar el tiempo de respuesta percibido por los usuarios interactivos.
Latencia: minimizar el tiempo requerido para que un proceso empiece a dar resultados.
Tiempo de ejecución: minimizar el tiempo total que le toma a un proceso ejecutar completamente.
Rendimiento (throughput): maximizar el número de procesos terminados por unidad de tiempo.
Liberación de recursos: hacer que terminen cuanto antes los procesos que tiene reservados más recursos.
¿Qué objetivo prioriza SJF y por qué no se usa en la práctica?
Prioriza el trabajo más corto primero. Está ideada para sistemas donde predominan los trabajos batch. Está orientada a maximizar el throughput.En esos casos, muchas veces se puede predecir la duración del trabajo o al menos clasificarlo.Si conozco las duraciones de antemano, es óptimo.
Otra alternativa es no pensar en la duración total, sino más bien en cuanto tiempo necesita hasta hacer E/S de nuevo.
No se usa porque el problema real es como saber cuanta CPU va a necesitar un proceso.Una alternativa es usar la info del pasado para predecir. Puede salir mal si los procesos tienen comportamiento irregular.
¿Cómo funciona el scheduling con múltiples colas?
Colas con 1, 2, 4, 8 quanta c/u. A la hora de elegir un proceso la prioridad la tiene siempre la cola con menos quanta.
Cuando a un proceso no le alcanza su cuota de CPU es pasado a la cola siguiente, lo que disminuye su prioridad, pero le asigna más tiempo de CPU en el próximo turno.
Los procesos de máxima prioridad, los interactivos en gral, van a la cola de máxima prioridad. Se puede hacer que cuando un proceso termina de hacer E/S vuelva a la cola de máxima prioridad, porque se supone que va a volver a hacerse interactivo.
La idea general es minimizar el tiempo de respuesta para los procesos interactivos, suponiendo que los computos largos son menos sensibles a demoras.
¿Hay algún problema con que las prioridades fueran fijas?
Un problema importante con los algoritmos de planificación por prioridad es el bloqueo indefinido, o inanición. Un proceso que está listo para ejecutarse pero esperando por la CPU puede considerarse bloqueado. Un algoritmo de planificación por prioridad puede dejar algunos procesos de baja prioridad esperando indefinidamente. En un sistema informático muy cargado, un flujo constante de procesos de mayor prioridad puede evitar que un proceso de baja prioridad obtenga la CPU. Generalmente, sucederá una de dos cosas. O el proceso eventualmente se ejecutará (a las 2 A.M. del domingo, cuando el sistema finalmente esté ligeramente cargado), o el sistema informático eventualmente fallará y perderá todos los procesos de baja prioridad no terminados. Una solución al problema del bloqueo indefinido de los procesos de baja prioridad es el envejecimiento. El envejecimiento implica aumentar gradualmente la prioridad de los procesos que esperan en el sistema durante mucho tiempo.
Hablar sobre la afinidad de un procesador. ¿Qué información extra tenemos que tener en la PCB para mantener afinidad en un sistema multicore?
Uno de los problemas que enfrentan los algoritmos de scheduling al haber varios procesadores, es que cada uno de los procesadores tiene su memoria caché, y si uno de los procesos es ejecutado en un cpu para luego ser ejecutado en otro, no hay cache hits y se hace más lento (siempre teniendo en cuenta scheduling con desalojo). Una mitigación para esto es utilizar el concepto de afinidad al procesador, es decir, tratar de que un proceso siempre utilice el mismo procesador, aunque se tarde un poco más de tiempo en obtenerlo. Dentro de afinidad al procesador, esta puede ser afinidad dura, es decir que un proceso siempre se va a ejecutar en el mismo procesador, o afinidad blanda, donde un proceso trata de ejecutarse en el mismo procesador pero podría llegar a cambiar y ejecutarse en otro.
Otro desafío a tener en cuenta es el balance de carga entre los procesadores, para esto se usa push y pull migration. En push migration, una tarea se encarga de revisar las cargas de cada uno de los procesadores y mueve procesos de una cola a otro para balancearlos. Pull migration es cuando un procesador en estado idle le saca un proceso de la cola a otro procesador para ejecutarlo.
En el PCB deberíamos tener en qué procesador se estuvo ejecutando el proceso.
Explicar el problema de inversión de prioridades.
Un desafío de planificación surge cuando un proceso de mayor prioridad necesita leer o modificar datos del núcleo que actualmente están siendo accedidos por un proceso de menor prioridad, o una cadena de procesos de menor prioridad. Dado que los datos del núcleo generalmente están protegidos con un bloqueo, el proceso de mayor prioridad tendrá que esperar a que el de menor prioridad termine con el recurso. La situación se complica si el proceso de menor prioridad es preemptado a favor de otro proceso con una prioridad más alta.
Como ejemplo, supongamos que tenemos tres procesos—L, M y H—cuyas prioridades siguen el orden L < M < H. Supongamos que el proceso H requiere un semáforo S, que actualmente está siendo accedido por el proceso L. Normalmente, el proceso H esperaría a que L termine de usar el recurso S. Sin embargo, ahora supongamos que el proceso M se vuelve ejecutable, preemptando así al proceso L. Indirectamente, un proceso con una prioridad más baja—el proceso M—ha afectado el tiempo que el proceso H debe esperar para que L libere el recurso S.
Este problema de vivacidad se conoce como inversión de prioridad, y solo puede ocurrir en sistemas con más de dos prioridades. Típicamente, la inversión de prioridad se evita implementando un protocolo de herencia de prioridad. Según este protocolo, todos los procesos que están accediendo a recursos necesarios para un proceso de mayor prioridad heredan la prioridad más alta hasta que terminan con los recursos en cuestión. Cuando terminan, sus prioridades vuelven a sus valores originales.
En el ejemplo anterior, un protocolo de herencia de prioridad permitiría que el proceso L herede temporalmente la prioridad del proceso H, evitando así que el proceso M preempte su ejecución. Cuando el proceso L haya terminado de usar el recurso S, renunciaría a su prioridad heredada de H y asumiría su prioridad original. Debido a que el recurso S ahora estaría disponible, el proceso H—no M—se ejecutaría a continuación.
¿Para qué necesitamos sincronización entre procesos? ¿Qué soluciones nos ofrece el HW? Explicar el caso para monoprocesador y para multiprocesador. (instrucciones atómicas y deshabilitar interrupciones)
Se generan condiciones de carrera. Toda ejecución debería dar un resultado equivalente a alguna ejecución secuencial de los mismos procesos.
HW:
Barrera de memoria: cuando se ejecuta esta instrucción, el sistema asegura que todas las operaciones de carga y almacenamiento se completen antes de que cualquier operación de carga o almacenamiento posterior sea ejecutada. Por lo tanto, incluso si las instrucciones fueron reordenadas, la barrera de memoria asegura que las operaciones de almacenamiento se completen en memoria y sean visibles para otros procesadores antes de que se realicen futuras operaciones de carga o almacenamiento.
Una alternativa podría ser la de suspender todo tipo de interrupciones dentro de la sección crítica. Esto elimina temporalmente la multiprogramación. Aunque garantiza la correcta actualización de los datos compartidos, trae todo tipo de problemas.
TestAndSet: La idea es que pone 1 y devuelve el valor anterior, pero de manera atómica. Asumo que se posee un registro atómico SWMR (Single-Writer/Multiple-Reader).
Explique ventajas y desventajas de cada una. No hay mejor o peor, simplemente tienen usos distintos. Ejemplos:
TAS sirve para hacer TASLock y TTASLock.
CAS tiene número de consenso infinito, a diferencia de TAS que sólo es 2.
Variables atómicas: proporcionan operaciones atómicas en tipos de datos básicos como enteros y booleanos.
¿Cómo nos afecta si el scheduler es preemptive o non-preemptive en la implementación de un semáforo?
Si el scheduler es preemptive, al momento de despertar el proceso bloqueado, se revisa si su prioridad es mayor al que actualmente está ejecutando. Si lo es, se lo desaloja y se le asigna la CPU al proceso recién despertado— en el contexto de Linux, esto se conoce como wake-up preemption.
Evaluar si están bien o mal utilizadas en los siguientes ejemplos las primitivas de sincronización:
Usar TASLock (spinlock) para acceder a disco.
Usar semáforos para incrementar un contador.
Usar un contador atómico para un recurso que tienen que poder acceder 3 procesos a la vez.
usar spinlock para un recurso que tienen que poder acceder 3 procesos a la vez.
Acceso exclusivo a disco utilizando TAS Lock.
No es ideal. TAS Lock hace busy waiting, lo cual implica que el proceso se mantiene ciclando hasta que logra obtener el lock. Hacer esto significa un desperdicio de tiempo de CPU dado que mientras cicla el procesador no hace ningún trabajo real. Debido a esto, no es conveniente usar un TAS Lock cuando el lock se mantiene por largos períodos de tiempo, como en el caso de hace E/S a disco. El disco es un dispositivo muy lento en términos del CPU, por lo cual los procesos que se encuentren haciendo busy waiting a la espera de que se libere el TAS Lock desperdiciarán mucho tiempo de procesamiento.
En este caso es mejor usar un mutex implementado mediante un semáforo, ya que el SO bloqueará a los procesos que fallen en obtener el acceso a este y los desbloqueará a medida que se vaya liberando. Esto permite que el SO ponga a ejecutar otras tareas mientras el proceso que tiene el lock realiza el acceso a disco. Los procesos esperando por el lock no hacen busy waiting sino que permanecen bloqueados hasta que se libera el lock, momento en el cual el SO despierta a alguno de ellos.
Acceso a un contador que se desea incrementar mediante un semáforo binario.
No es correcta. Las operaciones de los semáforos se implementan mediante system calls, lo cual implica que se debe realizar un cambio de contexto cada vez que son realizadas. Esto tiene un overhead que no se justifica para un acceso exclusivo de tan corto tiempo como lo es incrementar un contador.
Para esta situación es mejor usar una variable numérica atómica que puede ser incrementada con una única instrucción atómica del procesador. Esto es muy eficiente, pero requiere soporte del hardware. A falta del mismo, la alternativa es usar un TAS Lock ya que no requiere cambiar el contexto y el hecho de que el lock se mantenga por un tiempo corto implica que no se perderán tantos ciclos haciendo busy waiting.
Acceso a una estructura que permite hasta 3 accesos simultáneos con semáforos.
Es correcta. Los semáforos tienen un valor que puede ser decrementado e incrementado con las operaciones wait y signal. Si el valor del semáforo es menor o igual a 0, un proceso que hace wait se bloquea y queda en espera. Cuando otro proceso hace signal, el SO incrementa el valor del semáforo y despierta a uno de los procesos en espera. Este último continúa su ejecución decrementando el valor del semáforo antes de hacerlo.
Con esto, se puede proteger el acceso a la estructura con el uso de un semáforo cuyo valor inicial es 3. Los procesos hacen wait antes de acceder a ella y hacen signal una vez que terminaron. De esta forma, sólo 3 procesos podrán obtener el acceso mientras el resto queda en espera. A medida que estos van haciendo signal, se van despertando los procesos en espera uno por uno.
Diferencia entre spin lock y semáforos (hablar de TTAS). ¿En qué contexto preferimos uno sobre el otro y por qué?.
Spinlock hace busy waiting mientras que los semáforos no.Las operaciones de los semáforos se implementan mediante system calls, lo cual implica que se debe realizar un cambio de contexto cada vez que son realizadas.
Entonces preferimos spinlock para situaciones donde se cambie constantemente de proceso, para no tener tanto busy waiting, mientras que el semáforo es preferible para situaciones donde los procesos se ejecuten por más tiempo justificando así el cambio de contexto.
¿Cómo implementamos una sección crítica con spin locks?
Un spinlock es un mecanismo de sincronización utilizado para controlar el acceso a una sección crítica en programación concurrente. En lugar de poner en espera a los hilos que no pueden acceder a la sección crítica, un spinlock hace que estos hilos entren en un bucle de espera activo hasta que el recurso esté disponible.
- Inicialización: El spinlock se inicia en un estado desbloqueado, indicando que el recurso está disponible.
- Adquisición: Un hilo intenta adquirir el spinlock mediante una operación atómica que establece el estado del bloqueo a ocupado. Si el bloqueo ya está ocupado, el hilo entra en un bucle de espera activa.
- Liberación: Una vez que el hilo termina de usar la sección crítica, libera el spinlock, volviendo a ponerlo en un estado desbloqueado.
- Consumo de CPU: Los spinlocks pueden consumir mucha CPU debido a la espera activa, por lo que son más adecuados para secciones críticas de corta duración.
- Evitar Deadlocks: Es crucial diseñar el uso de spinlocks para evitar deadlocks, donde varios hilos podrían quedarse esperando indefinidamente.
En resumen, los spinlocks son útiles para sincronizar el acceso a recursos en situaciones donde el tiempo de espera es breve, gracias a su implementación basada en operaciones atómicas para asegurar que solo un hilo acceda a la vez a la sección crítica.
Explicar el problema clásico de lectores y escritores. Explicar cómo podemos evitar inanición del escritor.
Hay una variable compartida.Los escritores necesitan acceso exclusivo.Pero los lectores pueden leer simultáneamente.Propiedad SWMR (Single-Writer/Multiple-Readers):
Puede haber inanición de escritores.¿Por qué? Puede ser que haya siempre (al menos) un lector.Se viola la propiedad de progreso global dependiente(STARVATION-FREEDOM).Esto es, si todo proceso sale de CRIT entonces todo proceso que está en TRY entra inevitablemente a CRIT .
Supongamos que tenemos una base de datos compartida entre varios procesos concurrentes, a la que
podemos acceder usando las funciones dadas write y read. Algunos de estos procesos podrían querer
solo leer la base de datos, mientras que otros podrían querer actualizarla. Distinguimos entre estos dos
tipos de procesos a partir de referirnos a los primeros como lectores y a los últimos como escritores.
Queremos que los lectores puedan leer en simultáneo con otros lectores, pero que los escritores necesiten
acceso exclusivo sobre la base para poder escribir. Esta propiedad se conoce como Single-Writer/Multiple
Readers (SWMR). Concretamente, queremos que se cumplan las siguientes condiciones:
* Cualquier número de lectores puede estar en la sección crítica de manera simultánea.
* Los escritores deben tener acceso exclusivo a la sección crítica.
En otras palabras, un escritor no puede entrar a la sección crítica mientras que cualquier otro proceso
(escritor o lector) esté allí. Mientras haya algún escritor en la sección critica, no puede entrar ningún
otro proceso. En primera instancia, buscamos una solución que nos garantice el cumplimiento de estas
restricciones, que permita el acceso a la base de datos a escritores y lectores, y que sea libre de deadlocks.
Vamos a necesitar las siguientes variables compartidas para resolver el problema.
1 int readers = 0
2 semaphore mutex = Semaphore(1)
3 semaphore roomEmpty = Semaphore(1)
El contador readers mantiene un registro de cuántos lectores hay actualmente en la sección crítica—mutex protege el contador compartido. roomEmpty vale 1 si no hay ningún proceso (lector o escritor) en la sección crítica, y 0 en el caso contrario. El código para escritores es simple. Si la sección crítica está vacía, un escritor puede entrar, pero entrar tiene el efecto de excluir a todos los demás procesos.
1 roomEmpty.wait()
2 write()
3 roomEmpty.signal()
Notemos que cuando el escritor sale de la sección crítica, ésta queda vacía. Esto se debe a que ningún otro proceso pudo haber entrado mientras el escritor estaba en la sección crítica.
Para el caso de los lectores, necesitamos mantener un registro de la cantidad de lectores que actualmente están en la sección crítica. De este modo, vamos a poder distinguir el caso en el que entra el primer lector y el caso en el que sale el último lector.
El primer lector que llega tiene que esperar por roomEmpty.
* Si la sección crítica está vacía, entonces el lector procede y, al mismo tiempo, restringe la entrada de nuevos escritores. Los lectores subsiguiente pueden entrar, porque ninguno de ellos intenta esperar por roomEmpty.
* Si ya había un escritor en la sección crítica, entonces el primer lector espera por roomEmpty. Como el lector mantiene el mutex, cualquier lector subsiguiente se quedará esperando por el mutex.
El código luego de la sección crítica es similar. El último lector en salir de la sección crítica apaga las luces—esto es, hace un signal en roomEmpty, permitiendo que si había algún escritor esperando, éste pueda entrar.
1 mutex.wait()
2 readers += 1
3 if readers == 1: # el primer lector bloquea CRIT
4 roomEmpty.wait() # para uso exclusivo de lectores
5 mutex.signal()
6
7 read()
8
9 mutex.wait()
10 readers -= 1
11 if readers == 0:
12 roomEmpty.signal() # el último lector desbloquea CRIT
13 mutex.signal()
Se nos muestra un árbol de procesos donde cada proceso tiene una serie de page frames asignados. Explicar las siguientes situaciones:
¿Por qué todos los procesos de un sistema compartirían una página? (páginas del kernel o bibliotecas compartidas)
¿Por qué dos procesos específicos podrían compartir una página? (hablar de fork y copy-on-write)
La estrategia más común consiste en hacer copy-on-write: Al crear un nuevo proceso se utilizan las mismas páginas.Hasta que alguno de los dos escribe en una de ellas. Ahí se duplican y cada uno queda con su copia independiente.
¿Para qué sirve la paginación de la memoria? ¿Qué ventajas tiene sobre utilizar direcciones físicas? Hablar sobre el tamaño de las páginas y cómo se relaciona con el tamaño de los bloques en disco. (hablar de fragmentación interna y fragmentación externa)
La paginación evita la fragmentación externa y la necesidad asociada de compactación, dos problemas que afectan la asignación de memoria contigua. Debido a que ofrece numerosas ventajas, la paginación en sus diversas formas se utiliza en la mayoría de los sistemas operativos, desde aquellos para grandes servidores hasta los para dispositivos móviles. La paginación se implementa mediante la cooperación entre el sistema operativo y el hardware de la computadora.
El método básico para implementar la paginación implica dividir la memoria física en bloques de tamaño fijo llamados marcos y dividir la memoria lógica en bloques del mismo tamaño llamados páginas. Cuando se va a ejecutar un proceso, sus páginas se cargan en cualquier marco de memoria disponible desde su fuente (un sistema de archivos o el almacén de respaldo). El almacén de respaldo se divide en bloques de tamaño fijo que son del mismo tamaño que los marcos de memoria o en grupos de múltiples marcos.
Cada dirección generada por la CPU se divide en dos partes: un número de página (p) y un desplazamiento de página (d):
El número de página se utiliza como un índice en la tabla de páginas por proceso. A continuación, se describen los pasos que sigue la MMU para traducir una dirección lógica generada por la CPU a una dirección física:
Extraer el número de página p y usarlo como un índice en la tabla de páginas.
Extraer el número de marco correspondiente f de la tabla de páginas.
Reemplazar el número de página p en la dirección lógica con el número de marco f.
Cuando utilizamos un esquema de paginación, no tenemos fragmentación externa: cualquier marco libre puede ser asignado a un proceso que lo necesite. Sin embargo, podemos tener algo de fragmentación interna. Nótese que los marcos se asignan como unidades. Si los requisitos de memoria de un proceso no coinciden con los límites de la página, es posible que el último marco asignado no esté completamente lleno.
Si el tamaño del proceso es independiente del tamaño de la página, esperamos que la fragmentación interna promedio sea de media página por proceso. Esta consideración sugiere que los tamaños de página pequeños son deseables. Sin embargo, cada entrada de la tabla de páginas conlleva un costo adicional, y este costo se reduce a medida que aumenta el tamaño de las páginas. Además, la E/S de disco es más eficiente cuando la cantidad de datos transferidos es mayor.
Un aspecto importante de la paginación es la clara separación entre la visión del programador de la memoria y la memoria física real. El programador ve la memoria como un único espacio, que contiene solo su programa. En realidad, el programa del usuario está disperso por toda la memoria física, que también contiene otros programas. La diferencia entre la visión del programador de la memoria y la memoria física real se reconcilia mediante el hardware de traducción de direcciones.
¿Qué es un page fault y cómo se resuelve?
¿qué sucede si el proceso intenta acceder a una página que no se ha traído a la memoria? El acceso a una página marcada como inválida provoca una falla de página. El hardware de paginación, al traducir la dirección a través de la tabla de páginas, notará que el bit de inválido está establecido, provocando una interrupción al sistema operativo. Esta interrupción es el resultado de la falta del sistema operativo para traer la página deseada a la memoria. El procedimiento para manejar esta falla de página es sencillo:
Verificamos una tabla interna (generalmente mantenida con el bloque de control de proceso) para este proceso para determinar si la referencia fue un acceso a memoria válido o inválido.
Si la referencia fue inválida, terminamos el proceso. Si fue válida pero aún no hemos traído esa página, la traemos ahora.
Encontramos un marco libre (tomando uno de la lista de marcos libres, por ejemplo).
Programamos una operación de almacenamiento secundario para leer la página deseada en el marco recién asignado.
Cuando la lectura del almacenamiento se completa, modificamos la tabla interna mantenida con el proceso y la tabla de páginas para indicar que la página ahora está en memoria.
Reiniciamos la instrucción que fue interrumpida por la interrupción. El proceso ahora puede acceder a la página como si siempre hubiera estado en memoria.
¿Por qué puede pasar que tengamos muchos procesos en waiting, y cómo podría tratar de arreglarlo si no pudiese agregar memoria?
Matar algunos procesos para poder tener más espacio de memoria.
Hablar de RAID (para qué sirve). Explicar la diferencia entre RAID 4 y RAID 5. ¿Cuál es mejor y por qué?
RAID (Redundant Array of Independent Disks) es una tecnología de almacenamiento que combina múltiples discos duros para mejorar el rendimiento y/o la redundancia de datos. RAID 4 y RAID 5 son dos tipos específicos de configuraciones RAID, cada una con sus propias características y beneficios.
RAID 4
RAID 4 utiliza el striping de datos (división de datos en tiras) similar a RAID 0, pero con la adición de un disco dedicado para la paridad. La paridad es información de corrección de errores que permite la reconstrucción de datos en caso de fallo de un disco. En RAID 4:
Datos y paridad: Los datos se dividen en bloques y se distribuyen entre los discos, mientras que un disco específico se dedica exclusivamente a almacenar la información de paridad.
Rendimiento de lectura: La lectura puede ser rápida ya que los datos se pueden leer de múltiples discos simultáneamente.
Rendimiento de escritura: Las escrituras son más lentas que en RAID 0 porque cada vez que se escribe un bloque de datos, se debe actualizar el disco de paridad.
Capacidad de recuperación: Si un disco de datos falla, los datos pueden ser reconstruidos utilizando la información de paridad almacenada en el disco dedicado.
RAID 5
RAID 5 también utiliza el striping de datos, pero en lugar de tener un disco dedicado a la paridad, distribuye la paridad entre todos los discos del arreglo. En RAID 5:
Datos y paridad distribuida: Los datos y la paridad se distribuyen entre todos los discos, lo que evita el cuello de botella de tener un solo disco de paridad.
Rendimiento de lectura: Es alto, similar a RAID 4, ya que los datos se pueden leer de múltiples discos simultáneamente.
Rendimiento de escritura: Mejor que RAID 4 en la mayoría de los casos, ya que la paridad no se encuentra en un solo disco, aunque aún es más lento que RAID 0 debido a la necesidad de calcular y escribir la paridad.
Capacidad de recuperación: Si un disco falla, los datos pueden ser reconstruidos utilizando la paridad distribuida en los otros discos.
Comparación
Rendimiento: RAID 5 generalmente ofrece un mejor rendimiento de escritura que RAID 4 debido a la distribución de la paridad.
Redundancia: Ambos proporcionan redundancia para fallos de un solo disco, pero RAID 5 es más eficiente en términos de utilización del espacio de almacenamiento, ya que no dedica un disco completo a la paridad.
Coste: RAID 5 puede ser más rentable porque no requiere un disco adicional dedicado exclusivamente a la paridad.
En resumen, RAID 4 y RAID 5 ofrecen formas de mejorar la redundancia y el rendimiento del almacenamiento, pero RAID 5 es más popular debido a su mejor equilibrio entre rendimiento y uso del espacio de almacenamiento.