Tema II (Procesos)
Tema II (Procesos)
Tema II (Procesos)
Tema II
GESTIÓN DE PROCESOS
2.1. PROCESO.
En todos los computadores actuales, se pueden hacer varias cosas a la vez, mientras está
ejecutando un programa de usuario, puede perfectamente también estar leyendo de un disco e
imprimiendo texto en una pantalla o una impresora. En un sistema multiprogramado la CPU
(Unidad Central de Proceso) también conmuta de unos programas a otros, ejecutando cada uno
de ellos durante decenas o cientos de milisegundos. Aunque, estrictamente hablando, en cualquier
instante de tiempo la CPU sólo está ejecutando un programa, en el transcurso de un segundo ha
podido estar trabajando sobre varios programas, dando entonces a los usuarios la impresión de
un cierto paralelismo (pseudoparalelismo), en contraste con el auténtico paralelismo del hardware
de los sistemas multiprocesador (que tienen dos o más CPUs compartiendo la misma memoria
física). Seguir la pista de múltiples actividades paralelas resulta muy complicado, por ese motivo
los diseñadores de los sistemas operativos han desarrollado un modelo conceptual evolucionado
(el de los procesos secuenciales) que permite tratar el paralelismo de una forma más fácil.
Debe hacerse una distinción entre un programa y un proceso. Un programa es una entidad estatica
constituida por sentencias de programa que definen la conducta del proceso cuando se ejecutan
utilizando un conjunto de datos. El proceso se puede definir como un programa puesto en
ejecución por el sistema operativo y de una forma más precisa, como la unidad de procesamiento
gestionada por el sistema operativo. Dos o mas procesos podrían estar ejecutando el mismo
programa, empleando sus propios datos y recursos, por ejemplo, si dos o más usuarios están
usando simultáneamente el mismo editor de texto. El programa es el mismo, pero cada usuario
tiene un proceso distinto (y con distintos datos).
Por analogía al preparar una receta de una torta. El programa es la receta, el proceso es la
actividad que consiste en leer la receta, mezclar los ingredientes y hornear la torta.
Cuando un sistema operativo arranca, se crean típicamente varios procesos. Algunos de esos
procesos son procesos foreground (en primer plano), esto es, procesos que interactúan con los
usuarios y realizan trabajo para ellos. Otros son procesos background (en segundo plano), que no
están asociados con usuarios particulares, sino que tienen alguna función específica. Los procesos
que se ejecutan como procesos background para llevar a cabo alguna actividad tal como el correo
electrónico, las páginas web, la impresión de archivos de salida, etc, se denominan demonios en
Unix o servicios en Windows. Los sistemas grandes tienen comúnmente docenas de ellos. En
UNIX, el programa ps puede utilizarse para listar los procesos que están en marcha. En Windows
se utiliza el administrador de tareas.
Adicionalmente a los procesos creados en el momento del arranque, también pueden crearse
nuevos procesos después. A menudo un proceso en ejecución puede hacer llamadas al sistema
para crear uno o más procesos nuevos para que le ayuden en su trabajo.
En sistemas interactivos los usuarios pueden arrancar un programa tecleando un comando o
haciendo clic (dos veces) con el ratón sobre un icono. Realizando cualquiera de esas acciones se
consigue que comience un nuevo proceso y se ejecute el programa correspondiente. En sistemas
UNIX basados en comandos y ejecutando X-Windows, el nuevo proceso creado se ejecuta sobre
la ventana en la cual se le activó. En Windows, cuando un proceso comienza no tiene ninguna
ventana asignada, aunque puede crear una o más. En ambos sistemas, los usuarios pueden tener
múltiples ventanas abiertas a la vez, cada una de ellas ejecutando algún proceso. Utilizando el
ratón, el usuario puede seleccionar una ventana e interactuar con el proceso, por ejemplo,
proporcionando datos de entrada cuando sean necesarios.
La última situación que provoca la creación de procesos se aplica sólo a los sistemas en batch
(por lotes) que podemos encontrar en los grandes mainframes. En esos sistemas los usuarios
pueden lanzar (submit) al sistema trabajos en batch (posiblemente de forma remota). Cuando el
sistema operativo detecta que dispone de todos los recursos necesarios para poder ejecutar otro
trabajo, crea un nuevo proceso y ejecuta sobre él el siguiente trabajo que haya en la cola de
entrada.
En UNIX sólo existe una llamada al sistema para crear un nuevo proceso: fork. Esta llamada crea
un clon (una copia exacta) del proceso que hizo la llamada. Después del fork, los dos procesos, el
padre y el hijo, tienen la misma imagen de memoria, las mismas variables de entorno y los mismos
archivos abiertos. Eso es todo lo que hay. Usualmente, a continuación el proceso hijo ejecuta
execve o una llamada al sistema similar para cambiar su imagen de memoria y pasar a ejecutar
un nuevo programa. En Windows, una única llamada al sistema de Win32, CreateProcess, realiza
tanto la creación del proceso como la carga del programa correcto dentro del nuevo proceso. Tanto
en UNIX como en Windows, después de crear un proceso, tanto el padre como el hijo cuentan con
sus propios espacios de direcciones disjuntos.
El planificador (scheduler) forma parte del núcleo del sistema operativo. Entra en ejecución cada
vez que se activa el sistema operativo y su misión es seleccionar el proceso que se ha de ejecutar
a continuación. El activador (dispatcher) también forma parte del sistema operativo y su función
es poner en ejecución el proceso seleccionado por el planificador. Debe ser muy rápido pues una
de sus funciones es encargarse del cambio de contexto (context switch). Al tiempo entre detener
un proceso y comenzar a correr otro se le llama dispatch latency.
El dispatcher es también el que se encarga de pasar a modo usuario el proceso que esta activando
y “saltar” a la dirección de la instrucción que comienza la ejecución del programa.
Cuando el Sistema Operativo cambia la atención de la CPU entre los procesos, utiliza las áreas
de preservación del PCB para mantener la información que necesita para reiniciar el proceso
cuando consiga de nuevo la CPU.
procesador, es puro overhead o sobrecosto, puesto que entretanto la CPU no hace trabajo útil
(ningún proceso avanza). Considerando que la CPU hace varios cambios de contexto en un
segundo, su costo es relativamente alto.
Algunos procesadores tienen instrucciones especiales para guardar todos los registros de una vez.
Otros tienen varios conjuntos de registros, de manera que un cambio de contexto se hace
simplemente cambiando el puntero al conjunto actual de registros. El problema es que si hay más
procesos que conjuntos de registros, igual hay que apoyarse en la memoria. Por ser un cuello de
botella tan importante para el desempeño del sistema operativo se emplean estructuras nuevas
(hilos) para evitarla hasta donde sea posible.
Por ejemplo, en Unix, cuando se carga el sistema operativo, se inicializa un proceso init. Este
proceso lee un archivo que dice cuántos terminales hay, y crea un proceso login para cada
terminal, que se encarga de solicitar nombre y contraseña. Cuando un usuario entra, login
determina qué shell le corresponde al usuario, y crea otro proceso para ejecutar esa shell. A su
vez, la shell crea más procesos según los comandos que ejecute el usuario, generándose así todo
un árbol de procesos: cada proceso tiene cero o más hijos, y exactamente un padre (salvo init,
que no tiene padre). Haciendo una analogia en Windows, aun cuando este sistema operativo no
mantiene esta jerarquia y todos son iguales, unos de los primeros procesos seria System, luego
Winlogon para crear una sesion y sobre ella crea su shell que es el Explorer, en el cual se
ejecutaran todos los demas programas como hijos de este proceso.
Una hebra (thread, hilo o proceso liviano) es una linea de control dentro de un proceso. Un proceso
tradicional tiene sólo una linea de control. Si usamos threads, entonces podemos tener varias
lineas dentro de un proceso. Cada linea representa una actividad o unidad de computación dentro
del proceso, es decir, tiene su propio PC, conjunto de registros y stack, pero comparte con las
demás lineas el espacio de direccionamiento y los recursos asignados, como archivos abiertos y
otros.
En muchos aspectos los procesos livianos son similares a los procesos pesados, comparten el
tiempo de CPU, y a lo más un thread está activo (ejecutando) a la vez, en un monoprocesador.
Los otros pueden estar listos o bloqueados. Pero los procesos pesados son independientes, y el
sistema operativo debe proteger a unos de otros, lo que acarrea algunos costos. Los procesos
livianos dentro de un mismo proceso pesado no son independientes, cualquiera puede acceder a
toda la memoria correspondiente al proceso pesado. En ese sentido, no hay protección entre
threads, nada impide que un thread pueda escribir, por ejemplo, sobre el stack de otro.
POSIX (Linux) especifica una serie de políticas de planificación, aplicables a procesos pesados y
procesos ligeros, que debe implementar cualquier sistema operativo que ofrezca esta interfaz. En
Windows la unidad básica de ejecución es el proceso ligero y, por tanto, la planificación se realiza
sobre este tipo de procesos.
• La equidad (Justicia), que todos los procesos tienen que ser tratados de igual forma y a todos
se les debe dar la oportunidad de ejecutarse.
• Recursos equilibrados, que la política de planificación que se elija debe mantener ocupados
los recursos del sistema, favoreciendo a aquellos procesos que no abusen de los recursos
asignados, sobrecargando el sistema y bajando la performance general.
Un proceso retiene el control de la CPU hasta que ocurra alguna de las siguientes situaciones:
• La libera voluntariamente.
• El reloj la interrumpe.
• Alguna otra interrupción atrae la atención de la CPU.
• Planificación de alto nivel (largo plazo o long term). Determina a qué trabajos se les va a
permitir competir activamente por los recursos del sistema, lo cual se denomina Planificación
de admisión.
• Planificación de nivel intermedio (mediano plazo o medium term). Determina a qué procesos
se les puede permitir competir por la CPU. Responde a fluctuaciones a corto plazo en la carga
del sistema y efectúa “suspensiones” y “activaciones” (reanudaciones) de procesos. Debe
ayudar a alcanzar ciertas metas en el rendimiento total del sistema.
• Planificación de bajo nivel (corto plazo o short term). Determina a qué proceso listo se le asigna
la CPU cuando esta queda disponible y asigna la CPU al mismo, es decir que “despacha” la
CPU al proceso. La efectúa el Despachador del Sistema Operativo.
Los distintos Sistemas Operativos utilizan varias Políticas de Planificación, que se instrumentan
mediante Mecanismos de Planificación.
• Batch (lotes)
• Interactivo
• Tiempo Real.
En los sistemas en batch, no existen usuarios que estén esperando impacientemente por una
rápida respuesta ante sus terminales. En consecuencia, son aceptables los algoritmos no
expulsores, o los algoritmos expulsores con largos periodos de tiempo para cada proceso. Con
este enfoque se reduce el número de cambios de proceso, mejorando por tanto el rendimiento.
En un entorno con usuarios interactivos es indispensable que haya expulsiones para impedir que
un proceso acapare la CPU, negando cualquier servicio de la CPU a los demás. Incluso aunque
ningún proceso tenga intención de ejecutarse eternamente, es posible que debido a un error en el
programa un proceso mantenga parados a todos los demás indefinidamente. La expulsión es
necesaria para impedir ese comportamiento.
En los sistemas con restricciones de tiempo real, por extraño que parezca, la expulsión es algunas
veces innecesaria debido a que los procesos saben que no pueden ejecutarse durante largos
periodos de tiempo y usualmente hacen su trabajo y rápidamente se bloquean. La diferencia con
los sistemas interactivos es que los sistemas en tiempo real sólo ejecutan programas pensados
como parte de una misma aplicación. Los sistemas interactivos por el contrario son sistemas de
propósito general y pueden ejecutar programas arbitrarios no cooperantes o incluso maliciosos.
Es un algoritmo que no usa expropiación, y atiende a los procesos por estricto orden de llegada a
la cola de listos (READY). Cada proceso se ejecuta hasta que termina, o hasta que hace una
llamada bloqueante (de E/S), o sea, ejecuta su fase de CPU completa. El código para implementar
este algoritmo es simple y comprensible. Pero el tiempo promedio de espera puede ser largo.
Considerando que los procesos P1, P2 y P3 están LISTOS para ejecutar su siguiente fase de
CPU, cuya duración será de 24, 3 y 3 milisegundos, respectivamente. Si ejecutan en el orden P1,
P2, P3, entonces los tiempos de espera son: 0 para P1, 24 para P2 y 27 para P3, o sea, en
promedio, 17 ms. Pero si se ejecutan en orden P2, P3, P1, entonces el promedio es sólo 3 ms. En
consecuencia, FCFS no asegura para nada que los tiempos de espera sean los mínimos posibles;
peor aún, con un poco de mala suerte pueden llegar a ser los máximos posibles.
Suponiendo que se tiene un proceso intensivo en CPU (CPU bound) y varios procesos intensivos
en E/S (I/O bound). Entonces podría pasar lo siguiente: El proceso intensivo en CPU toma la CPU
por un período largo, suficiente como para que todas las operaciones de E/S pendientes se
completen. En esa situación, todos los procesos están LISTOS, y los dispositivos desocupados.
En algún momento, el proceso intensivo en CPU va a solicitar E/S y va a liberar la CPU. Entonces
van a ejecutar los otros procesos, pero como son intensivos en E/S, van a liberar la CPU muy
rápidamente y se va a invertir la situación: todos los procesos van a estar BLOQUEADOS, y la
CPU desocupada. Este fenómeno se conoce como efecto convoy, y se traduce en una baja
utilización tanto de la CPU como de los dispositivos de E/S. Obviamente, el rendimiento mejora si
se mantienen ocupados la CPU y los dispositivos (o sea, conviene que no haya colas vacías).
de espera promedio sea bajo. Se puede utilizar en planificadores de largo plazo, pero no en los de
corto plazo pues no hay manera de conocer la medida de la próxima ráfaga. Se podría aproximar
considerando el valor de la previa.
Suponiendo que se tiene tres procesos cuyas próximas fases de CPU son de a, b y c milisegundos
de duración. Si ejecutan en ese orden, el tiempo medio de espera es:
(0 + a + (a + b))/3 = (2a+b)/3
El primer proceso que se ejecute es el que tiene mayor incidencia en el tiempo medio, y el último,
tiene incidencia nula. En conclusión, el tiempo medio se minimiza si se ejecuta siempre el proceso
con la menor próxima fase de CPU que esté LISTO. Además, es una buena manera de prevenir
el efecto convoy. Lo malo es que para que esto funcione, hay que adivinar el futuro, pues se
requiere conocer la duración de la próxima fase de CPU de cada proceso.
Por ejemplo, si un cambio de contexto toma 5 ms, y se fija el quantum en 20 ms, entonces 20%
del tiempo de la CPU se perderá en sobrecosto. Un valor típico es 100 ms. Una regla que suele
usarse es que el 80% de las fases de CPU deben ser de menor duración que un quantum. Con
respecto a FCFS, se mejora el tiempo de respuesta y la utilización de la CPU, ya que se mantienen
más balanceadas las colas listos (READY) y bloqueadas (BLOCKED). Pero RR tampoco asegura
que los tiempos de espera sean los mínimos posibles. Usando el mismo ejemplo anterior, y
considerando un quantum de 4ms, pero sin considerar costos de cambio de contexto, si el orden
es P1, P2, P3 entonces el tiempo medio de espera es 5.66ms (P1 espera 6ms, P2 espera 4ms. y
P3 espera 7ms.)
tal que haya factores, por ejemplo, el tiempo que lleva esperando en colas, que puedan ayudar a
que haya un mejor nivel de competencia elevando la prioridad de procesos postergados y evitar
una situación indeseable llamada starvation (inanición). A la técnica de elevar la prioridad a un
proceso de acuerdo al tiempo que hace que esta en el sistema se le llama envejecimiento (aging).
Si la prioridad es interna, es determinada en función del uso de los recursos (memoria, archivos
abiertos, tiempos). Si es externa, puede decidirse darle alta prioridad a un proceso de importancia,
a consideración del operador. POSIX (Linux) y Win32 (Windows) proporcionan planificación
basada en prioridades.
Existen algoritmos que contemplan esta situación dividiendo la cola de listos (ready) en distintas
colas según el tipo de proceso, estableciendo una competencia entre las colas y entre los procesos
del mismo tipo entre si. Por ejemplo, se puede tener una cola para
• Procesos de sistema.
• Procesos interactivos.
• Procesos de los alumnos.
• Procesos por lotes.
Cada cola usa su propio algoritmo de planificación, pero se necesita un algoritmo de planificación
entre las colas. Una posibilidad es prioridad absoluta con expropiación. Otra posibilidad: asignar
tajadas de CPU a las colas. Por ejemplo, a la cola del sistema se le puede dar el 60% de la CPU
para que haga RR, a la de procesos por lotes el 5% para que asigne a sus procesos según FCFS,
y a las otras el resto.
Por otra parte, se podría hacer que los procesos migren de una cola a otra. Por ejemplo: varias
colas planificadas con RR, de prioridad decreciente y quantum creciente. La última se planifica con
FCFS. Un proceso en la cola i que no termina su fase de CPU dentro del quantum asignado, se
pasa al final de la siguiente cola de menor prioridad, pero con mayor quantum. Un proceso en la
cola i que sí termina su fase de CPU dentro del quantum asignado, se pasa al final de la siguiente
cola de mayor prioridad, pero con menor quantum. Ejemplo:
En este modelo con retroalimentación, un proceso puede “moverse” entre colas, es decir, según
convenga puede llegar a asignarse a otra cola. Los procesos se separan de acuerdo a la ráfaga
de CPU que usan. Si esta usando mucha CPU se lo baja a una cola de menor prioridad. Se trata
de mantener los procesos interactivos y de mucha E/S en las colas de mayor prioridad.
Si además un proceso estuvo demasiado tiempo en una cola de baja prioridad puede moverse a
una cola de mayor prioridad para prevenir inanición (starvation).
• SJF puede ser apropiativo o no. Si mientras se esta ejecutando un proceso llega uno cuya
próxima ráfaga de CPU es mas corta que lo que queda de ejecutar del activo, puede existir
la decisión de interrumpir el que se esta ejecutando o no. Al SJF apropiativo, se le llama
shortest-remaining-time-first. (primero el de tiempo restante más corto).
• Prioridades puede ser apropiativo o no. Si mientras se esta ejecutando un proceso llega a
la cola de ready un proceso de mayor prioridad que el que se esta ejecutando puede existir
la decisión de interrumpir el que se esta ejecutando o no. Si es no apropiativo, en lugar de
darle la CPU al nuevo proceso, lo pondrá primero en la cola de ready.
• RR es apropiativo pues si el proceso excede el tiempo asignado, hay una interrupción para
asignarla la CPU a otro proceso.
Windows utiliza una planificación basada en colas múltiples de prioridades. Posee 32 niveles de
colas, clasificadas en clase de Tiempo Real fija (16-31) y prioridad dinamica (0-15). Las colas se
recorren de mayor a menor ejecutando los hilos asociados. Cada cola es manejada por medio de
un algoritmo de Round Robin, aun así, si un hilo de mayor prioridad llega, el procesador le es
asignado a éste. La prioridades más altas son favorecidas. La prioridades de un thread no pueden
ser reducidas.
Los procesos en Linux pueden ser divididos en tres categorías, relacionadas con la prioridad:
interactivos, por lotes y de tiempo real. Los procesos en Tiempo Real son manejados bien por un
algoritmo FIFO o Round Robin. Los demás procesos son despachados utilizando planificación
Round Robin con un sistema de envejecimiento basado en créditos, donde el siguiente proceso a
ejecutar es aquel que más créditos posea. Los procesos en Tiempo Real son considerados
prioritarios sobre cualquier otro proceso en el sistema, por lo que serán ejecutados antes que los
demás. Algunos aspectos de la estructura interna del kernel que caben destacarse son:
• La PCB está representada por la estructura task_struct. Ésta indica el tipo de planificación
(FIFO,RR) por medio del campo policy, la prioridad (priority), el contador del programa
(counter), entre otros.
• La función goodness otorga una “calificación” al proceso pasado como parámetro. Dicha
puntuación oscila entre -1000 (no elegible) y +1000 (TR). Los procesos que comparten una
zona de memoria ganan una puntuación equivalente a su prioridad.
• El quantum varía según el proceso y su prioridad. La duración base es de aprox. 200ms.
• La función switch_to es la encargada de salvar la información de un proceso y cargar el
siguiente.
• Las funciones sched_{get/set}scheduler se refieren al mecanismo de planificación asociado
a ese proceso.
• Una nueva copia del proceso actual es creada mediante la llamada al sistema fork. Para
ejecutar un nuevo programa se utiliza la función execve.
• Las prioridades bajas son favorecidas. La mayoría de los procesos usan políticas de prioridad
dinámica. El valor “nice” establece la prioridad base de un proceso. Los valores de nice van
desde -20 a +20 (más grande = menor prioridad). Los usuarios no privilegiados sólo pueden
especificar valores de nice positivos. Los procesos normales se ejecutan sólo cuando no
quedan procesos de tiempo real (de prioridad fija) en la cola de listos.