Logo Studenta

Silberschatz 10a castellano cap3d (1)

¡Este material tiene más páginas!

Vista previa del material en texto

Segunda parte
Administración de Procesos
Un proceso es un programa en ejecución. Un proceso necesitará ciertos recursos, como tiempo de CPU, memoria, archivos y dispositivos de E/S, para cumplir su tarea Estos recursos generalmente se asignan a proceso mientras se está ejecutando.
Un proceso es la unidad de trabajo en la mayoría de los sistemas. Los sistemas consisten de una colección de procesos: los procesos del sistema operativo ejecutan el código del sistema, y los procesos de usuario ejecutan código de usuario. Todos estos procesos pueden ejecutar simultáneamente
Los sistemas operativos modernos soportan procesos que tienen múltiples hilos de control. En sistemas con múltiples núcleos de procesamiento de hardware, estos hilos pueden ejecutarse en paralelo.
Uno de los aspectos más importantes de un sistema operativo es cómo funciona la planificación de los hilos en los núcleos de procesamiento disponibles. Varias opciones para el diseño de planificadores de CPU están disponibles para los programadores.
			
Procesos
Las primeras computadoras permitían ejecutar solo un programa a la vez. Este programa tenía control completo del sistema y tenía acceso a todos los recursos del sistema. En contraste, los sistemas informáticos contemporáneos permiten múltiples programas para cargarse en la memoria y ejecutarse simultáneamente. Esta evolución requiere un control más firme y mejor compartimento de los diversos programas; y estas necesidades dieron como resultado la noción de un proceso, que es un programa en ejecución. Un proceso es la unidad de trabajo en un sistema informático moderno.
Cuanto más complejo es el sistema operativo, más se espera que haga en nombre de sus usuarios. Aunque su principal preocupación es la ejecución de programas de usuario, también necesita ocuparse de varias tareas del sistema que se realizan mejor en el espacio del usuario, en lugar de dentro del kernel. Por lo tanto, un sistema consiste en una colección de procesos, algunos ejecutan código de usuario, otros ejecutan código de sistema operativo.
Potencialmente, todos estos procesos pueden ejecutarse simultáneamente, con la CPU (o CPUs) multiplexando entre ellos. En este capítulo, leerá sobre qué procesos son, cómo están representados en un sistema operativo y cómo funcionan.
OBJETIVOS DEL CAPÍTULO
• Identificar los componentes separados de un proceso e ilustrar cómo son representados y planificados en un sistema operativo.
• Describir cómo se crean y finalizan los procesos en un sistema operativo, incluyendo el desarrollo de programas utilizando las llamadas al sistema apropiadas que realizan estas operaciones.
• Describir y contrastar la comunicación entre procesos utilizando la memoria compartida y paso de mensajes.
• Diseñar programas que usen tuberías (pipes) y memoria compartida POSIX para realizar comunicación entre procesos.
• Describir la comunicación cliente-servidor usando sockets y llamadas a procedimientos remotos.
• Diseñar módulos de kernel que interactúen con el sistema operativo Linux.
3.1 Concepto de proceso
Una pregunta que surge al discutir los sistemas operativos es cómo llamar todas las actividades de la CPU. Las primeras computadoras eran sistemas por lotes que ejecutaban trabajos, seguido por la aparición de sistemas de tiempo compartido que ejecutaban programas de usuario o Tareas. Incluso en un sistema de usuario único, un usuario puede ejecutar varios programas a la vez: un procesador de palabras, un navegador web y un paquete de correo electrónico. E incluso, si una computadora puede ejecutar solo un programa por vez, como en un dispositivo embebido, que no es compatible con la multitarea, el sistema operativo puede necesitar admitir sus propias actividades internas programadas, como la administración de memoria. En muchos aspectos, todas estas actividades son similares, por lo que llamamos a todas ellas procesos.
Aunque preferimos el proceso como término más contemporáneo, el trabajo es un término que tiene un significado histórico, como en gran parte de la teoría y la terminología de sistemas operativos se desarrolló durante un tiempo cuando la principal actividad de los sistemas operativos era el procesamiento de trabajos. Por lo tanto, en algunos casos apropiados usamos el término trabajo al describir la función del sistema operativo. Pero por ejemplo, sería engañoso decirle trabajo a todo lo que se ejecuta (como el planificador de trabajos) simplemente porque el proceso ha reemplazado el trabajo.
3.1.1 El proceso
Informalmente, como se mencionó anteriormente, un proceso es un programa en ejecución. El estado de la actividad actual de un proceso está representado por el valor del contador de programa (PC) y el contenido de los registros del procesador. El diseño de memoria de un proceso generalmente se divide en varias secciones, como se muestra en la Figura 3.1. Estas secciones incluyen:
• Sección de texto: el código ejecutable
• Sección de datos: variables globales
• Sección de almacenamiento dinámico: memoria que se asigna dinámicamente en la ejecución del programa
• Sección de pila: almacenamiento temporal de datos al invocar funciones (como parámetros de función, direcciones de retorno y variables locales) 
Figura 3.1 Disposición de un proceso en memoria.
Observe que los tamaños de las secciones de texto y datos son fijos, ya que no cambian durante el tiempo de ejecución del programa. Sin embargo, las secciones de pila y heap pueden reducir y crecer dinámicamente durante la ejecución del programa. Cada vez que una función es llamada, un registro de activación que contiene parámetros de función, variables locales, y la dirección de retorno se inserta en la pila; cuando se devuelve el control de la función, el registro de activación se saca de la pila. Del mismo modo, el heap crecerá a medida que la memoria se asigne dinámicamente y se reducirá cuando se devuelve memoria al sistema. Aunque las secciones de pila y heap crecen en sentido contrario (avanzando una sobre otra), el sistema operativo debe asegurar de que no se superpongan entre sí.
			
Hacemos hincapié en que un programa en sí mismo no es un proceso. Un programa es una entidad pasiva, como un archivo que contiene una lista de instrucciones almacenadas en el disco (a menudo llamado un archivo ejecutable). Por el contrario, un proceso es una entidad activa, con un contador de programa que especifica las siguientes instrucciones para ejecutar y un conjunto de recursos asociados. Un programa se convierte en un proceso cuando un archivo ejecutable se carga en la memoria. Se usan dos técnicas para cargar archivos ejecutables: haciendo doble clic en un icono que representa un archivo ejecutable o ingresando el nombre del archivo ejecutable en la línea de comandos (como en prog.exe o a.out).
Aunque dos procesos pueden estar asociados con el mismo programa, ellos sin embargo, se consideran dos secuencias de ejecución separadas. Por ejemplo, varios usuarios pueden ejecutar diferentes copias del programa de correo, o el mismo usuario puede invocar muchas copias del programa del navegador web. Cada uno de estos es un
proceso separado; y aunque las secciones de texto son equivalentes, los datos, las secciones heap y pila varían. También es común tener un proceso que genera muchos procesos mientras se ejecuta. Discutimos estos asuntos en la Sección 3.4.
Tenga en cuenta que un proceso en sí mismo puede ser un entorno de ejecución para otro código. El entorno de programación Java proporciona un buen ejemplo. En la mayoría de las circunstancias, un programa ejecutable de Java se ejecuta dentro de la máquina virtual Java (JVM). La JVM se ejecuta como un proceso que interpreta el código Java cargado y realiza acciones (a través de instrucciones de máquina nativas) en nombre de ese código.
Por ejemplo, para ejecutar el programa Java compilado 
Program.class, deberíamos ingresar
java Program
El comando java ejecuta la JVM como un proceso ordinario, que a su vez ejecuta Program en la máquina virtual. El conceptoes igual que la simulación, excepto que el código, en lugar de escribirse con un diferente conjunto de instrucciones, está escrito en lenguaje Java.
3.1.2 Estado del proceso
A medida que se ejecuta un proceso, cambia de estado. El estado de un proceso se define en partes, por la actividad actual de ese proceso. Un proceso puede estar en uno de los siguientes estados:
• Nuevo. El proceso se está creando.
• Ejecutando. Las instrucciones se están ejecutando.
• Esperando. El proceso está esperando que ocurra algún evento (como una finalización de E/S o recepción de una señal).
• Listo. El proceso está esperando ser asignado a un procesador.
• Terminado. El proceso ha finalizado la ejecución.
Figura 3.2 Diagrama de estados del proceso
Estos nombres son arbitrarios y varían según los sistemas operativos. Los Estados que representan se encuentran en todos los sistemas, sin embargo. Ciertos sistemas operativos distinguen más estados de un proceso. Es importante darse cuenta de que sólo un proceso puede ejecutarse en cualquier core de procesador en cualquier instante. Sin embargo, muchos procesos pueden estar listos y en espera. El diagrama de estado correspondiente a estos estados se presenta en la Figura 3.2.
• Estado del proceso. Estado puede ser nuevo, listo, en ejecución, en espera, detenido, etc.
• Contador de programa. El contador indica la dirección de la próxima instrucción a ser ejecutada para este proceso.
• Registros de CPU. Los registros varían en número y tipo, dependiendo de la arquitectura del Computador. Incluyen acumuladores, registros de índice, punteros de pila y registros de uso general, más cualquier información de código de condición. Junto con el contador del programa (PC), esta información de estado debe guardarse cuando ocurre una interrupción, para permitir que el proceso continúe luego correctamente, cuando se planifica para ejecutarse.
• Información de planificación de la CPU. Esta información incluye la prioridad de proceso,
punteros a las colas de planificación y cualquier otro parámetro de planificación. (El Capítulo 5 describe la planificación del proceso).
• Información de gestión de memoria. Esta información puede incluir elementos como el valor de los registros base y límite y las tablas de páginas, dependiendo del sistema de memoria utilizado por el sistema operativo (Capítulo 9).
• Información contable. Esta información incluye la cantidad de CPU y tiempo real utilizado, límites de tiempo, números de cuenta, números de trabajo o proceso, etc.
• Información de estado de E/S. Esta información incluye la lista de dispositivos de E/S asignado al proceso, una lista de archivos abiertos, etc.
3.1.3 Bloque de control de proceso
Cada proceso está representado en el sistema operativo por un bloque control de proceso (PCB): también denominado bloque de control de tareas. Una PCB se muestra en la Figura 3.3. Contiene muchos datos asociados con un proceso específico, incluyendo estos:
Figura 3.3 Bloque de 
control de proceso (PCB).
En resumen, el PCB simplemente sirve como repositorio de todos los datos necesarios para comenzar o reiniciar, un proceso, junto con algunos datos contables.
DISPOSICIÓN DE MEMORIA DE UN PROGRAMA C
La figura que se muestra a continuación ilustra el diseño de un programa C en memoria, destacando cómo las diferentes secciones de un proceso se relacionan con un programa C actual. Esta figura es similar al concepto general de un proceso en memoria. como se muestra en la Figura 3.1, con algunas diferencias:
• La sección de datos globales se divide en diferentes secciones para (a) datos inicializadas y (b) datos no inicializados.
• Se proporciona una sección separada para los parámetros argc y argv pasados a la función main ().
El comando tamaño GNU se puede utilizar para determinar el tamaño (en bytes) de algunas de estas secciones. Asumiendo el nombre del archivo ejecutable de lo anterior El programa C es memoria, el siguiente es el resultado generado al ingresar el comando size memory (Tamaño de la memoria):
texto 	data	bss	dec	hex	nombre de archivo
1158	284 	8 	1450 	5aa 	memory
El campo de datos se refiere a datos no inicializados, y bss se refiere a datos inicializados. (BSS es un término histórico en referencia al bloque iniciado por símbolo). dec y los valores hexadecimales son la suma e las tres secciones representadas en decimal y hexadecimal, respectivamente.
REPRESENTACIÓN DE PROCESOS EN LINUX
El bloque de control de proceso en el sistema operativo Linux está representado por la estructura en C: task_struct, que se encuentra en el <include /linux /sched.h> archivo incluido en el directorio del código fuente del kernel. Esta estructura contiene toda la información necesaria para representar un proceso, incluido el estado del proceso, la planificación e información de gestión de memoria, lista de archivos abiertos e indicadores para proceso padre y una lista de sus hijos y hermanos. (El padre de un proceso es el proceso que lo creó; sus hijos son cualquier proceso que crea. Sus hermanos son hijos con el mismo proceso padre.) Algunos de estos campos incluyen:
long state;			 /* state of the process */
struct sched entity se; 	/* scheduling information */
struct task struct *parent; 	/* this process’s parent */
struct list head children; 	/* this process’s children */
struct files struct *files; 	/* list of open files */
struct mm struct *mm; 	/* address space */
Por ejemplo, el estado de un proceso está representado por el campo long state en esta estructura. Dentro del kernel de Linux, todos los procesos activos están representados utilizando una lista doblemente enlazada en la task_struct. El kernel mantiene un puntero actual: al proceso que se ejecuta actualmente en el sistema, como se muestra a continuación:
Aquí se muestra cómo el kernel podría manipular uno de los campos en la task_struct para un proceso específico. Supongamos que al sistema le gustaría cambiar el estado del proceso que se ejecuta actualmente al valor de estado nuevo. Si el actual (current) es un puntero al proceso que se está ejecutando actualmente, su estado cambia con lo siguiente:
current->state = new state;
3.2.3 Cambio de contexto
Figura 3.6 Diagrama que muestra el cambio de contexto de un proceso a otro.
		
Como se mencionó en la Sección 1.2.1, las interrupciones provocan que el sistema operativo cambie un core de CPU de su tarea actual y ejecute una rutina de kernel. Tales operaciones suceden con frecuencia en sistemas de uso general. Cuando ocurre una interrupción, el sistema necesita guardar el contexto actual del proceso que se ejecuta en el core de CPU para que pueda restaurar ese contexto cuando se realiza su procesamiento, esencialmente
suspender el proceso y luego reanudarlo. El contexto está representado en el PCB del proceso. Incluye el valor de los registros de la CPU, el estado del proceso (ver Figura 3.2) e información de administración de memoria. Genéricamente, se realiza un “salvado” (guardado) del estado actual del core de la CPU, ya sea en modo kernel o en el usuario, y luego una restauración de estado para reanudar las operaciones.
Cambiar el core de la CPU a otro proceso requiere guardar el estado del proceso actual y restaurar el estado de un proceso diferente. Esta tarea se conoce como cambio de contexto y se ilustra en la Figura 3.6. Cuando se produce un cambio de contexto, el kernel guarda el contexto del proceso anterior en su PCB y carga el contexto guardado del nuevo proceso planificado para ejecutarse. El tiempo de Cambio de contexto es pura sobrecarga (overhead), porque el sistema no hace trabajo útil mientras hace el intercambio. La velocidad de cambio varía de máquina a máquina, dependiendo de la velocidad de memoria, el número de registros que deben copiarse y la existencia de instrucciones especiales (como una sola instrucción para cargar o almacenar todos los registros). La velocidad típica es de varios microsegundos. Los tiempos decambio de contexto dependen en gran medida del soporte de hardware. Por ejemplo, algunos procesadores proporcionan múltiples conjuntos de registros. Un cambio de contexto aquí simplemente requiere cambiar el puntero al conjunto de registros actual. Por supuesto, Si hay más procesos activos que conjuntos de registros, el sistema requiere copiar datos de registros hacia y desde la memoria, como antes. Además, cuanto más complejo es el sistema operativo, mayor es la cantidad de trabajo que se debe hacer durante un cambio de contexto. Como veremos en el Capítulo 9, la gestión avanzada de memoria, las técnicas pueden requerir que los datos adicionales se cambien con cada contexto. Por ejemplo, el espacio de direcciones del proceso actual debe conservarse del espacio de la siguiente tarea que está preparado para su uso. Cómo se conserva el espacio de direcciones, y qué cantidad de trabajo se necesita para preservarlo, depende del método de la administración de la memoria del sistema operativo.
3.1.4 Hilos
El modelo de proceso discutido hasta ahora ha implicado que un proceso es un programa que realiza un solo hilo de ejecución. Por ejemplo, cuando se está ejecutando un proceso como un programa de procesador de textos, se está ejecutando un solo hilo de instrucciones. Este único hilo de control permite que el proceso realice solo una tarea a la vez. Por lo tanto, el usuario no puede escribir caracteres simultáneamente y ejecutar el corrector ortográfico. La mayoría de los sistemas operativos modernos han ampliado el concepto de proceso para permitir que un proceso tenga múltiples hilos de ejecución y así realizar más de una tarea a la vez. Esta característica es especialmente beneficiosa en sistemas multinúcleo, donde múltiples hilos pueden ejecutarse en paralelo. Un procesador de texto podría, por ejemplo, asignar un hilo para administrar la entrada de teclado del usuario mientras otro hilo ejecuta el corrector ortográfico. En sistemas que admiten hilos, la PCB se expande para incluir información para cada hilo. El sistema operativo también necesita otros cambios para admitir hilos. El Capítulo 4 explora hilos en detalle.
			
3.2 Planificación de procesos
El objetivo de la multiprogramación es tener algún proceso en ejecución en todo momento para maximizar la utilización de la CPU. El objetivo del tiempo compartido es cambiar un core de CPU entre procesos con tanta frecuencia que los usuarios pueden interactuar con cada programa mientras se está ejecutando. Para cumplir con estos objetivos, el planificador de procesos selecciona un proceso disponible (posiblemente de un conjunto de varios procesos disponibles) para la ejecución del programa en un núcleo. Cada núcleo de CPU puede ejecutar un proceso a la vez. Para un sistema con un solo núcleo de CPU, nunca habrá más de un proceso ejecutándose a la vez, mientras que un sistema multinúcleo puede ejecutar múltiples procesos a la vez. Si hay más procesos que núcleos, el exceso de procesos tendrá esperar hasta que un núcleo esté libre y pueda planificarse. El número de procesos actualmente en memoria se conoce como el grado de multiprogramación. Balanceando los objetivos de multiprogramación y tiempo compartido también se requiere tener en cuenta el comportamiento general de un proceso. En general, la mayoría de los procesos se pueden describir como “limitados” por E/S o “limitados” por CPU. Uno “limitado” por E/S, el proceso es uno que pasa más tiempo haciendo E/S de lo que gasta haciendo cálculos. Un proceso “limitado” por la CPU, por el contrario, genera solicitudes de E/S con poca frecuencia, usa más tiempo haciendo cálculos.
3.2.1 Planificación de colas
A medida que los procesos ingresan al sistema, se colocan en una cola de procesos listos, donde están en condiciones y esperando para ejecutarse en el núcleo de una CPU Esta cola generalmente se almacena como
una lista enlazada; un encabezado de cola ready (listos) contiene punteros al primer PCB de la lista, y cada PCB incluye un campo de puntero que apunta al siguiente PCB en la cola lista.
El sistema también incluye otras colas. Cuando a un proceso se le asigna un núcleo de CPU, se ejecuta por un tiempo y finalmente termina, se interrumpe o espera para la ocurrencia de un evento particular, como la finalización de una solicitud de E/S. Supongamos que el proceso realiza una solicitud de E/S a un dispositivo como un disco. Dado que los dispositivos funcionan significativamente más lentos que los procesadores, el proceso tendrá que esperar a que la E/S esté disponible. Los Procesos que esperan un cierto evento que se produzca, como la finalización de E/S, se colocan en una cola de espera (Figura 3.4).
Figura 3.4 La cola de procesos listos y la cola de procesos en espera
			
Figura 3.5 Representación del diagrama de colas de la planificación de procesos
Una representación común de la planificación de procesos es un diagrama de cola, como el de la figura 3.5. Hay dos tipos de colas presentes: la cola ready (lista) y un conjunto de colas de espera. Los círculos representan los recursos que sirven las colas, y las flechas indican el flujo de procesos en el sistema.
Un nuevo proceso se coloca inicialmente en la cola lista. Espera allí hasta que sea seleccionado para su ejecución o despacho. Una vez que el proceso se le asigna un núcleo de CPU y se está ejecuta, podría ocurrir uno de varios eventos:
• El proceso podría emitir una solicitud de E/S y luego colocarse en una cola espera de E/S.
• El proceso podría crear un nuevo proceso hijo y luego ser puesto en espera y hacer cola mientras espera la terminación del hijo.
• El proceso podría ser expulsado de la CPU por el kernel, como resultado de una interrupción o que su intervalo de tiempo caduque, y volver a colocarse en la cola ready.
En los primeros dos casos, el proceso finalmente cambia del estado de espera al estado listo y luego se vuelve a poner en la cola lista. Un proceso continúa este ciclo hasta que finaliza, momento en el que se elimina de todas las colas y tiene desasignado su PCB y recursos.
			
3.2.2 Planificación de CPU
Un proceso migra entre la cola ready y varias colas de espera en toda su vida. La función del planificador de la CPU es seleccionar entre los procesos que están en la cola ready y asignar un core de CPU a uno de ellos. El planificador de la CPU debe seleccionar un nuevo proceso para la CPU con frecuencia. Un proceso limitado por E/S puede ejecutarse sólo unos pocos milisegundos antes de esperar por una solicitud de E/S. Aunque un proceso limitado por CPU requerirá un core de CPU para duraciones más largas, es poco probable que el planificador otorgue el core a un proceso para un período extendido. En cambio, es probable que esté diseñado para expulsar por la fuerza a un proceso de la CPU y planificar otro proceso para ejecutar. Por lo tanto, el planificador de la CPU se ejecuta en al menos una vez cada 100 milisegundos, aunque generalmente es mucho más frecuente.
		
Algunos sistemas operativos tienen una forma intermedia de planificación, conocida como intercambio (swapping), cuya idea clave es que a veces puede ser ventajoso expulsar un proceso de la memoria (y de la contención activa por la CPU) y así reducir el grado de multiprogramación. Más tarde, el proceso puede ser reintroducido en la memoria, y su ejecución puede continuar donde lo dejó. Este esquema se conoce como intercambio, porque un proceso puede ser "intercambiado" de la memoria al disco, donde se guarda su estado actual y luego se "intercambia" desde el disco a la memoria, donde se restaura su estado. El intercambio es sólo necesario cuando la memoria se ha comprometido en exceso y debe liberarse. El intercambio se analiza en el Capítulo 9.
		
MULTITASKING EN SISTEMAS MÓVILES
Debido a las restricciones impuestas por los dispositivos móviles, las primeras versiones de iOS no proporcionaron multitarea para las aplicaciones de usuario; solo se ejecutaba una aplicación en primer plano, mientras que todas las demás aplicaciones de usuario eran suspendidas.Las tareas del Sistema operativo escritas por Apple, eran multitarea . Luego con iOS 4, Apple proporcionó una forma limitada de multitarea para aplicaciones de usuario, lo que permite una sola aplicación en primer plano para ejecutarse simultáneamente con múltiples aplicaciones en segundo plano. (En un dispositivo móvil, la aplicación en primer plano es la aplicación actualmente abierta y que aparece en la pantalla. La aplicación de fondo permanece en la memoria, pero no ocupa la pantalla de visualización). La API de programación de iOS 4 proporcionó soporte para la multitarea, permitiendo así que un proceso se ejecute en segundo plano sin ser suspendido Sin embargo, era limitado y solo estaba disponible para algunos tipos de aplicaciones. A medida que el hardware para dispositivos móviles comenzó a ofrecer mayores capacidades de memoria, múltiples cores de procesamiento y mayor duración de la batería, Las versiones posteriores de iOS comenzaron a admitir una funcionalidad más rica para la multitarea, con menos restricciones Por ejemplo, la pantalla más grande en tabletas iPad permitió ejecutar dos aplicaciones en primer plano al mismo tiempo, una técnica conocida como pantalla dividida.
Desde sus orígenes, Android ha admitido la multitarea y no pone restricciones sobre los tipos de aplicaciones que pueden ejecutarse en segundo plano. Si una aplicación requiere procesamiento mientras que en el fondo, la aplicación debe usar un servicio, un componente de aplicación separado que se ejecuta en nombre del proceso de fondo. Considere una aplicación de transmisión de audio: si la aplicación pasa a segundo plano, el servicio continúa enviando datos de audio al manejador del dispositivo de audio en nombre de la aplicación en segundo plano. De hecho, el servicio continuará ejecutándose incluso si la aplicación en segundo plano es suspendida. Los servicios no tienen una interfaz de usuario y usan poca memoria, proporcionando así una técnica eficiente para la multitarea en un ambiente móvil.
		
3.3 Operaciones en procesos
Los procesos en la mayoría de los sistemas pueden ejecutarse simultáneamente, y pueden crearse y eliminarse dinámicamente. Por lo tanto, estos sistemas deben proporcionar un mecanismo para la creación y terminación de procesos. En esta sección, exploramos los mecanismos involucrados en la creación de procesos e ilustramos la creación de procesos en UNIX y sistemas Windows.
3.3.1 Creación de procesos
			
Durante el curso de la ejecución, un proceso puede crear varios procesos nuevos. Como mencionamos anteriormente, el proceso que crea se llama proceso padre, los procesos nuevos se denominan hijos de ese proceso. Cada uno de estos nuevos procesos a su vez puede crear otros procesos, formando así un árbol de procesos.
La mayoría de los sistemas operativos (incluidos UNIX, Linux y Windows) identifican Procesos de acuerdo con una identificación de proceso única (o pid), que normalmente es un número entero. El pid proporciona un valor único para cada proceso en el sistema, y ​​se puede usar como índice para acceder a varios atributos de un proceso
dentro del kernel. 
La Figura 3.7 ilustra un árbol de procesos típico para el sistema operativo Linux, mostrando el nombre de cada proceso y su pid. (Usamos el término proceso más, a pesar que Linux prefiere el término tarea en su lugar).
El proceso (que siempre tiene un pid = 1) sirve como el proceso padre raíz para todos los procesos de usuario, y es el primer proceso de usuario creado cuando se inicia el sistema. Una vez que el sistema se ha iniciado, el proceso systemd crea procesos que proporciona servicios adicionales como un servidor web o de impresión, un servidor ssh y otros. En la Figura 3.7, vemos dos hijos de systemd: logind y sshd. El proceso de inicio de sesión es responsable de administrar los clientes que inician sesión directamente al sistema. En este ejemplo, un cliente ha iniciado sesión y está utilizando el shell bash, que se le ha asignado un pid 8416. Usando la interfaz de línea de comandos bash,
éste usuario ha creado el proceso ps, así como el editor vim. El proceso sshd es responsable de administrar clientes que se conectan al sistema mediante ssh (que es la abreviatura de shell seguro).
Figura 3.7 Un árbol de procesos en un sistema Linux típico
Los procesos init y systemd
Los sistemas UNIX tradicionales identifican el proceso init como la raíz de todos los procesos hijos. init (también conocido como System V init) se le asigna un pid de 1, y es el primer proceso creado cuando se inicia el sistema. Un árbol de procesos similar se muestra en la Figura 3.7, init está en la raíz.
Los sistemas Linux inicialmente adoptaron el enfoque de System V init, pero recientemente las distribuciones lo han reemplazado con systemd. Como se describe en la Sección 3.3.1, systemd sirve como el proceso inicial del sistema, muy similar al Sistema V init; Sin embargo, es mucho más flexible y puede proporcionar más servicios que Init.
En sistemas UNIX y Linux, podemos obtener una lista de procesos mediante el uso del comando ps. Por ejemplo, el comando:
ps –el
Enumerará la información completa para todos los procesos actualmente activos en el sistema. Un árbol de procesos similar al que se muestra en la Figura 3.7 puede ser construido siguiendo los procesos padres hasta el proceso systemd. (Además, los sistemas Linux proporcionan el comando pstree, que muestra un árbol de todos los procesos en el sistema).
En general, cuando un proceso crea un proceso hijo, ese proceso hijo necesita ciertos recursos (tiempo de CPU, memoria, archivos, dispositivos de E/S) para lograr su tarea. Un proceso hijo puede obtener sus recursos directamente del sistema operativo, o puede estar limitado a un subconjunto de los recursos del proceso padre. El padre puede tener que dividir sus recursos entre sus hijos, o puede compartir algunos recursos (como memoria o
archivos) entre varios de sus hijos. Restringir un proceso hijo a un subconjunto de los recursos de los padres evita que cualquier proceso sobrecargue el sistema creando demasiados procesos hijos.
Además de suministrar varios recursos físicos y lógicos, el proceso padre puede pasar datos de inicialización (entrada) al proceso hijo. Por ejemplo, considere un proceso cuya función es mostrar el contenido de un archivo:
Por ejemplo: hw1.c en la pantalla de una terminal. Cuando se cree el proceso, se obtendrá, como entrada de su proceso padre, el nombre del archivo hw1.c. Usando ese nombre de archivo, abrirá el archivo y mostrará su contenido. También puede obtener el nombre del dispositivo de salida. Alternativamente, algunos sistemas operativos transfieren recursos a los procesos hijos. En dicho sistema, el nuevo proceso puede obtener dos archivos abiertos, hw1.c y el dispositivo terminal, y puede simplemente transferir el dato entre los dos.
Cuando un proceso crea un nuevo proceso, existen dos posibilidades de ejecución:
1. El padre continúa ejecutándose simultáneamente con sus hijos.
2. El padre espera hasta que algunos o todos sus hijos hayan terminado.
También hay dos posibilidades de espacio de direcciones para el nuevo proceso:
1. El proceso hijo es un duplicado del proceso padre (tiene el mismo programa y datos como padre).
2. El proceso hijo tiene un nuevo programa cargado.
Para ilustrar estas diferencias, consideremos primero el sistema operativo UNIX. En UNIX, como hemos visto, cada proceso se identifica por su identificador de proceso (PID), un entero único. La llamada al sistema fork () crea un nuevo proceso. El nuevo proceso consiste en una copia del espacio de direcciones del proceso original.
Este mecanismo permite que el proceso padre se comunique fácilmente con su proceso hijo. Ambos procesos (el padre y el hijo) continúan la ejecución en la instrucción después de fork (), con una diferencia: el código de retorno para fork () es cero para el nuevo proceso (hijo), mientras que el número de identificador del proceso del hijo (distinto de cero) es devueltoal padre.
Después de una llamada al sistema fork (), uno de los dos procesos generalmente usa el llamada al sistema exec () para reemplazar el espacio de memoria del proceso con un nuevo programa. La llamada al sistema exec () carga un archivo binario en la memoria (destruyendo la imagen de memoria del programa que contiene la llamada al sistema exec ()) y comienza su ejecución. De esta manera, los dos procesos pueden comunicarse y luego se van por caminos separados. El padre puede entonces crear más hijos; o si no tiene nada más que hacer mientras el hijo se ejecuta, puede emitir una llamada al sistema wait () para salir de la cola de espera para cuando termine el hijo. Porque
la llamada a exec () superpone el espacio de direcciones del proceso con un nuevo programa, exec () no devuelve el control a menos que ocurra un error.
Figura 3.8 Creación de un proceso separado utilizando la llamada al sistema fork () de UNIX
El programa C que se muestra en la Figura 3.8 ilustra las llamadas del sistema UNIX previamente descritas. Ahora tenemos dos procesos diferentes que ejecutan copias del mismo programa. La única diferencia es que el valor de la variable pid para el proceso hijo es cero, mientras que para el padre es un valor entero mayor que cero (de hecho, es el número pid real del proceso hijo). El proceso hijo hereda privilegios y atributos de planificación del padre, así como ciertos recursos, como archivos abiertos. El proceso hijo superpone su espacio de direcciones con el Comando UNIX/bin/ls (usado para obtener un listado de directorio) usando la llamada al sistema execlp (). (execlp () es una versión de la llamada al sistema exec ()). El padre espera para que el proceso secundario se complete con la llamada al sistema wait (). Cuando el proceso hijo se completa (ya sea invocando implícita o explícitamente exit ()), el proceso padre se reanuda desde la llamada a wait (), donde se completa usando la llamada al sistema exit (). Esto también se ilustra en la Figura 3.9.
Figura 3.9 Creación de procesos utilizando la llamada al sistema fork ().
Por supuesto, no hay nada que evite que el hijo no invoque a exec () y en lugar de continuar ejecutándose como una copia del proceso padre. En este escenario, el padre y el hijo son procesos concurrentes que ejecutan el mismo código instrucciones. Como el hijo es una copia del padre, cada proceso tiene su propia copia de cualquier dato.
Figura 3.10 Creación de un proceso separado utilizando la API de Windows.
Como ejemplo alternativo, consideramos la creación de procesos en Windows. Los procesos se crean en la API de Windows utilizando la función CreateProcess (), que es similar a fork () en que un padre crea un nuevo proceso hijo.
Sin embargo, mientras que fork () tiene el proceso hijo heredando el espacio de direcciones de su padre, CreateProcess () requiere cargar un programa específico en el espacio de direcciones del proceso hijo en la creación del proceso. Además, mientras que fork () no pasa parámetros, CreateProcess () espera no menos de 10 parámetros
El programa C que se muestra en la Figura 3.10 ilustra el CreateProcess (), que crea un proceso hijo que carga la aplicación mspaint.exe. Optamos por muchos de los valores predeterminados de los diez parámetros pasados ​​a CreateProcess (). Lectores interesados ​​en buscar los detalles de la creación de procesos y la administración en la API de Windows se recomienda consultar las notas bibliográficas al final de este capítulo.
Los dos parámetros pasados ​​a la función CreateProcess () son instancias de las estructuras STARTUPINFO e PROCESS_INFORMATION. STARTUPINFO especifica muchas propiedades del nuevo proceso, como el tamaño y apariencia de la ventana y maneja los archivos de entrada y salida estándar. La estructura de INFORMACIÓN DE PROCESO (PROCESS_INFORMATION) contiene un identificador y los identificadores para el proceso recién creado y su hilo. Invocamos la función ZeroMemory () para asignar memoria para cada una de estas estructuras antes de continuar con CreateProcess ().
Los dos primeros parámetros pasados a CreateProcess () son: el nombre de la aplicación y los parámetros de la línea de comandos. Si el nombre de la aplicación es NULL (como en este caso), el parámetro de línea de comandos especifica la aplicación a cargar. En este caso, estamos cargando la aplicación mspaint.exe de Microsoft Windows.
Más allá de estos dos parámetros iniciales, utilizamos los parámetros predeterminados para el proceso de herencia y los hilos del hijo también especifican que no habrá banderas de creación. También usamos el bloque de entorno existente y directorio de inicio del padre. Por último, proporcionamos dos punteros a STARTUPINFO y PROCESS_INFORMATION creadas al inicio del programa. En la figura 3.8, el proceso padre espera a que el hijo se complete invocando la llamada al sistema wait (). El equivalente de esto en Windows es WaitForSingleObject (),
Dónde se pasa un identificador del proceso hijo: pi.hProcess, y espera a este proceso para completar. Una vez que el proceso hijo termina, el control regresa de la Función WaitForSingleObject () en el proceso padre.
			
3.3.2 Terminación de proceso
Un proceso finaliza cuando termina de ejecutar su última instrucción y pregunta al sistema operativo para eliminarlo utilizando la llamada al sistema exit (). En ese punto, el proceso puede devolver un valor de estado (generalmente un entero) a su proceso padre que espera (a través de la llamada al sistema wait ()). Todos los recursos del proceso.
—Incluyendo memoria física y virtual, archivos abiertos y buffers de E / S — son desasignados (quitados) y reclamados por el sistema operativo.
La terminación también puede ocurrir en otras circunstancias. Un proceso puede causar la terminación de otro proceso a través de una llamada al sistema apropiada (por ejemplo, TerminateProcess () en Windows). Por lo general, sólo puede invocar dicha llamada al sistema el padre del proceso que se va a terminar. De lo contrario, un usuario o una aplicación que “se porta mal”: podría eliminar arbitrariamente los procesos de otro usuario. Tenga en cuenta que un padre necesita saber las identidades de los hijos que va a terminar. Por lo tanto, cuando un proceso crea un nuevo proceso, la identidad del nuevo proceso creado se pasa al padre.
Un padre puede terminar la ejecución de uno de sus hijos por una variedad de razones como estas:
• El hijo ha excedido el uso de algunos de los recursos que le han asignado. (Para determinar si esto ha ocurrido, el padre debe tener un mecanismo para inspeccionar el estado de sus hijos).
• La tarea asignada al hijo ya no es necesaria.
• El padre está terminando y el sistema operativo no permite que un hijo continúe si su padre termina.
Algunos sistemas no permiten que exista un hijo si su padre ha terminado. En tales sistemas, si un proceso termina (ya sea normal o anormalmente), entonces todos sus hijos también deben ser terminados. Este fenómeno, denominado terminación en cascada, normalmente es iniciada por el sistema operativo.
Para ilustrar la ejecución y terminación del proceso, tenga en cuenta que, en Linux y Sistemas UNIX, se puede terminar un proceso utilizando la llamada al sistema exit (), proporcionando un estado de salida como parámetro:
/* exit with status 1 */
exit(1);
De hecho, cuando terminación es normal, se llamará a exit () directamente (como se muestra arriba) o indirectamente, como la biblioteca de tiempo de ejecución C (que se agrega en UNIX a los archivos ejecutables) incluirá una llamada a exit () por defecto. Un proceso padre puede esperar la finalización de un proceso hijo usando
la llamada al sistema wait (). La llamada al sistema wait () pasa un parámetro que permite al padre obtener el estado de salida del hijo. Esta llamada al sistema también devuelve el identificador de proceso del hijo terminado para que el padre pueda saber cuál de sus hijos ha terminado:
pid t pid;
int status;
pid = wait(&status);
			
Cuando un proceso finaliza, sus recursos son desasignadospor el sistema operativo. Sin embargo, su entrada en la tabla de proceso debe permanecer allí hasta que el padre complete su llamada wait (), porque la tabla de proceso contiene el estado de finalizado del proceso. Un proceso que ha finalizado, pero cuyo padre aún no ha llamado a wait (), es conocido como un proceso zombie. Todos los procesos pasan a este estado cuando terminan, pero generalmente existen como zombis solo brevemente. Una vez que el padre llama a wait (), el identificador de proceso del proceso zombie y su entrada en la tabla de proceso se liberan.
Ahora considere lo que sucedería si un padre no invocara wait () y en cambio termine, dejando sus procesos hijos como huérfanos. Los sistemas tradicionales UNIX abordaron este escenario asignando el proceso init como el nuevo
padre de procesos huérfanos. (Recuerde de la Sección 3.3.1 que init sirve como la raíz de la jerarquía de procesos en sistemas UNIX.) El proceso init periódicamente invoca wait (), lo que permite que el estado de salida de cualquier proceso huérfano sea recopilado y se libera el identificador de proceso del huérfano y la entrada de la tabla de proceso.
Aunque la mayoría de los sistemas Linux han reemplazado init con systemd, este proceso systemd puede cumplir el mismo rol, aunque Linux también permite a otros procesos (aparte de systemd) que hereden procesos huérfanos y gestionar su terminación.
3.3.2.1 Jerarquía de procesos de Android
Debido a limitaciones de recursos como la poca memoria, el funcionamiento de los sistemas móviles puede tener que terminar los procesos existentes para reclamar los recursos En lugar de terminar un proceso arbitrario, Android ha identificado una jerarquía de importancia de procesos, y cuando el sistema debe terminar un proceso para hacer que los recursos estén disponibles para un proceso nuevo o uno más importante, termina los procesos en orden de importancia creciente. De mayor a menor importancia, la jerarquía de clasificaciones de los procesos es la siguiente:
• Proceso en primer plano: el proceso actual visible en la pantalla, que representa la aplicación con la que el usuario está interactuando actualmente
• Proceso visible: un proceso que no es directamente visible en primer plano pero realiza una actividad que necesita el proceso de primer plano (es decir, un proceso que realiza una actividad cuyo estado se muestra en el proceso de primer plano)
• Proceso de servicio: un proceso que es similar a un proceso en segundo plano pero está realizando una actividad para el usuario (como la transmisión música)
• Proceso de fondo (background): un proceso que puede estar realizando una actividad pero en aparencia no es para el usuario.
• Proceso vacío: un proceso que no contiene componentes activos asociados con ninguna aplicación
Si los recursos del sistema deben ser recuperados, Android terminará primero procesos vacíos, seguidos de procesos en segundo plano, etc. Los procesos tienen asignado una clasificación de importancia, y Android intenta asignar un alto ranking a un proceso tanto como sea posible. Por ejemplo, si un proceso proporciona un servicio y también es visible, se le asignará la clasificación visible más importante.
Además, las prácticas de desarrollo de Android sugieren seguir las pautas del ciclo de vida del proceso. Cuando se siguen estas pautas, el estado de un proceso se guardará antes de la finalización y se reanudará en su estado guardado si el usuario regresa a la aplicación.
			
ARQUITECTURA MULTIPROCESO - NAVEGADOR Chrome
Muchos sitios web tienen contenido activo, como JavaScript, Flash y HTML5 para proporcionar una navegación web rica y dinámica. Desafortunadamente, estas aplicaciones web también pueden contener errores de software, lo que puede provocar tiempos de respuesta lentos e incluso pueden hacer que el navegador web se bloquee. Esta no es un gran problema en un navegador web que muestra contenido de un solo sitio web.
Pero la mayoría de los navegadores web contemporáneos ofrecen navegación por pestañas, que permite que una sola instancia de la aplicación del navegador web abra varios sitios web al mismo tiempo, con cada sitio en una pestaña separada. Para cambiar entre los diferentes sitios, un usuario solo necesita hacer clic en la pestaña correspondiente. Este arreglo se ilustra a continuación:
Un problema con este enfoque es que si una aplicación web en alguna pestaña falla, todo el proceso, incluidas todas las demás pestañas que muestran sitios web adicionales, se bloquean también.
El navegador web Chrome de Google fue diseñado para abordar este problema utilizando una arquitectura multiproceso. Chrome identifica tres tipos diferentes de procesos: navegador, “renderizadores” y complementos.
• El proceso navegador es responsable de administrar la interfaz de usuario, así como las E/S de disco y de red. Se crea un nuevo proceso de navegador cuando se inicia Chrome. Sólo se crea un proceso de navegador.
• Los procesos de representación contienen lógica para representar páginas web. Por lo tanto, ellos contienen la lógica para manejar HTML, Javascript, imágenes, etc. Como regla general, se crea un nuevo proceso de representación para cada sitio web abierto en una nueva pestaña, por lo que varios procesos de representación pueden estar activos al mismo tiempo.
• Se crea un proceso de complemento para cada tipo de complemento (como Flash o QuickTime) en uso. Los procesos de complemento contienen el código para el complemento, así como código adicional que permite que el complemento se comunique con procesos de renderización asociados y el proceso del navegador. 
La ventaja del enfoque multiproceso es que los sitios web se ejecutan de forma aislada el uno del otro. Si un sitio web falla, solo su proceso de representación es afectado; todos los demás procesos permanecen ilesos. Además, los procesos de representación se ejecutan en un entorno limitado, lo que significa que el acceso al disco y la red de E/S es restringido, minimizando los efectos de cualquier vulnerabilidad de seguridad
3.4 Comunicación entre procesos
Los procesos que se ejecutan simultáneamente en el sistema operativo pueden ser procesos independientes o procesos cooperativos. Un proceso es independiente si lo hace sin compartir datos con ningún otro proceso que se ejecute en el sistema. Un proceso es cooperativo si puede afectar o verse afectado por otros procesos activos en el sistema. Claramente, cualquier proceso que comparta datos con otros procesos es un proceso cooperativo
Hay varias razones para proporcionar un entorno que permita la cooperación de procesos:
• El intercambio de información. Dado que varias aplicaciones pueden estar interesadas en la misma información (por ejemplo, copiar y pegar), debemos Proporcionar un entorno para permitir el acceso concurrente a dicha información.
• Aceleración de la computación. Si queremos que una tarea en particular se ejecute más rápido, debemos dividirla en sub-tareas, cada una de las cuales se ejecutará en paralelo con las otras. Tenga en cuenta que dicha aceleración solo se puede lograr si la computadora tiene múltiples cores de procesamiento.
• Modularidad. Es posible que queramos construir el sistema de manera modular, dividiendo las funciones del sistema en procesos o hilos separados, como se ha discutido en el Capítulo 2.
La cooperación de procesos requiere un mecanismo de comunicación entre procesos (IPC) que les permitirá intercambiar datos, es decir, enviar datos y recibir datos el uno del otro. Hay dos modelos fundamentales de
comunicación entre procesos: 
· memoria compartida y 
· transmisión de mensajes. 
En el modelo de memoria compartida, se establece una región de memoria compartida para los procesos cooperantes. Los procesos pueden intercambiar información leyendo y escribiendo datos en la región compartida. 
En el modelo de paso de mensajes, la comunicación se realiza mediante intercambio de mensajes entre procesos cooperativos. 
Los dos modelos de comunicación secontrastan en la Figura 3.11.
Figura 3.11 Modelos de comunicaciones. (a) Memoria compartida. (b) Mensaje que pasa.
Los dos modelos mencionados son comunes en los sistemas operativos, y muchos sistemas implementan ambos. El paso de mensajes es útil para intercambiar pequeñas cantidades de datos, porque no es necesario evitar conflictos. El Paso de mensajes también es más fácil de implementar en un sistema distribuido que la memoria compartida.
(Aunque existen sistemas que proporcionan memoria compartida distribuida, nosotros no los consideramos en este texto). La memoria compartida puede ser más rápida que la transmisión de mensajes, dado que los sistemas de paso de mensajes generalmente se implementan utilizando llamadas al sistema y por lo tanto, requieren una mayor intervención del kernel. En los sistemas de memoria compartida, las llamadas al sistema solo son necesarias para establecer regiones de memoria compartida. Una vez que se establece la memoria compartida, se tratan todos los accesos como accesos a memoria normal (en espacio de usuario), y no se requiere asistencia del kernel. 
En la Sección 3.5 y la Sección 3.6 exploramos los sistemas de memoria compartida y el paso de mensajes con más detalle.
		
3.5 IPC en sistemas de memoria compartida
La comunicación entre procesos usando memoria compartida requiere que los procesos se comuniquen para establecer una región de memoria compartida. Por lo general, una memoria compartida es una región que reside en el espacio de direcciones del proceso que crea el segmento de memoria compartida. Otros procesos que desean comunicarse utilizando ese segmento de memoria compartida, debe adjuntarlo a su espacio de direcciones. Recordemos que, normalmente, el sistema intenta evitar que un proceso acceda a la memoria de otro. La memoria compartida requiere que dos o más procesos acuerden eliminar esta restricción. Luego pueden intercambiar información leyendo y escribiendo datos en las áreas compartidas. Los procesos determinan la forma de los datos y la ubicación y no están bajo el control del sistema operativo. Los procesos también son responsables de asegurarse de que no están escribiendo a la misma ubicación al mismo tiempo (o sea es una responsabilidad que deben asumir).
Para ilustrar el concepto de procesos cooperativos, consideremos al problema del productor/consumidor, que es un paradigma común para los procesos cooperativos.
Un proceso productor produce información que es consumida por un proceso consumidor. Por ejemplo, un compilador puede producir código ensamblador que es consumido por un programa ensamblador. El ensamblador, a su vez, puede producir módulos de objetos que son consumidos por el cargador. El problema productor-consumidor también proporciona un modelo útil para el paradigma cliente-servidor. Generalmente pensamos un servidor como productor y un cliente como consumidor. Por ejemplo, un servidor web produce (es decir, proporciona) contenido web, como archivos e imágenes HTML, que son consumidos (es decir, leídos) por el navegador web del cliente que solicita el recurso.
Una solución al problema productor-consumidor utiliza la memoria compartida. Para permitir que los procesos productor y consumidor se ejecuten simultáneamente, debemos tener disponible un búfer de elementos que el productor puede llenar y ser vaciado por el consumidor. Este búfer residirá en una región de memoria compartida por los procesos productor y consumidor. Un productor puede producir un ítem mientras el consumidor está consumiendo otro ítem. El productor y el consumidor deben ser sincronizados, para que el consumidor no intente consumir un artículo que todavía no se ha producido.
Se pueden usar dos tipos de búferes:
El búfer infinito no tiene límites prácticos en el tamaño del búfer. El consumidor puede tener que esperar por nuevos ítems, pero el productor siempre puede producir nuevos artículos. 
		
El búfer acotado o limitado asume un tamaño de búfer fijo. En este caso, el consumidor debe esperar si el búfer
está vacío y el productor debe esperar si el búfer está lleno.
Veamos más de cerca cómo el búfer acotado ilustra la comunicación interproceso usando la memoria compartida. Las siguientes variables residen en una región de memoria compartida por los procesos productor y consumidor:
#define BUFFER SIZE 10
typedef struct {
. . .
} item;
item buffer[BUFFER SIZE];
int in = 0;
int out = 0;
El búfer compartido se implementa como un vector circular con dos punteros lógicos: in y out. La variable in apunta a la siguiente posición libre en el búfer; out apunta a la primera posición completa en el búfer. 
El búfer está vacío cuando in ==out
El búfer está lleno cuando ((in + 1)% BUFFER SIZE) == out.
El código para el proceso del productor se muestra en la Figura 3.12, y 
El código para el proceso del consumidor se muestra en la Figura 3.13. 
El proceso del productor tiene una variable local next_produced en la que se almacena el nuevo elemento a producir. 
El proceso consumidor tiene una variable local next_consumed que en la que se almacena el elemento consumido.
Este esquema permite al menos que haya BUFFER_SIZE - 1 items en el buffer al mismo tiempo. Lo dejamos como un ejercicio para que proporcione una solución en la que los elementos de BUFFER_SIZE pueden estar en el buffer al mismo tiempo. En la Sección 3.7.1, ilustramos la API POSIX para memoria compartida
Figura 3.12 El proceso productor utilizando memoria compartida.
Figura 3.13 El proceso consumidor usando memoria compartida
Un problema que esta ilustración no aborda se refiere a la situación en la que tanto el proceso productor como el proceso consumidor intentan acceder al búfer compartido al mismo tiempo. En el Capítulo 6 y el Capítulo 7, discutimos cómo la sincronización entre los procesos cooperativos se puede implementar de manera efectiva en un entorno de memoria compartida.
			
3.7.4 Tuberías
Una tubería actúa como un conducto que permite que dos procesos se comuniquen. Las tuberías eran uno de los primeros mecanismos de IPC en los primeros sistemas UNIX. Por lo general, proporcionan una de las formas más simples para que los procesos se comuniquen entre sí, aunque también tienen algunas limitaciones. Al implementar una tubería, hay cuatro problemas que deben ser considerados:
1. ¿La tubería permite la comunicación bidireccional, o es comunicación unidireccional?
2. Si se permite la comunicación bidireccional, ¿es half duplex (los datos pueden viajar solo en un sentido a la vez) o dúplex completo (los datos pueden viajar en ambas direcciones al mismo tiempo)?
3. ¿Debe existir una relación (como padre-hijo) entre los procesos que se comunican?
4. ¿Pueden las tuberías comunicarse a través de una red o la comunicación debe hacerse con procesos que residen en la misma máquina?
En las siguientes secciones, exploramos dos tipos comunes de tuberías utilizadas en ambos Sistemas: UNIX y Windows: tuberías ordinarias y tuberías con nombre.
			
3.7.4.1 Tuberías ordinarias
Las tuberías ordinarias permiten que dos procesos se comuniquen en el problema clásico de productor/consumidor: el productor escribe en un extremo de la tubería (el final de la escritura) y el consumidor lee desde el otro extremo (el final de lectura). Por lo general Las tuberías son unidireccionales, lo que permite una comunicación en una sola. Si se requiere una comunicación bidireccional, se deben usar dos tuberías, cada una con direcciones diferentes. A continuación ilustramos la construcción de tuberías ordinarias en sistemas UNIX y Windows. En ambos ejemplos de programas, un proceso escribe el mensaje de Saludos a la tubería, mientras que el otro proceso lee este mensaje de la tubería. En sistemas UNIX, las tuberías ordinarias se construyen utilizando la función 
pipe(int fd [])
Esta función crea una tubería a la que se accede a través de los descriptores de archivo int fd []:
fd [0] es el extremo de lectura de la tubería, y fd [1] es el extremo de escritura. UNIX trata una tubería como un tipo especial de archivo. Porlo tanto, se puede acceder a las tuberías utilizando llamadas al sistema read() ordinario y write().
A una tubería ordinaria no se puede acceder sino desde el proceso que la creó. Típicamente, un proceso padre crea una tubería y la usa para comunicarse con un proceso hijo que crea a través de fork (). Recordemos de la Sección 3.3.1 que un proceso hijo hereda los archivos abiertos de su padre. Dado que una tubería es un tipo especial de
archivo, el hijo hereda la tubería de su proceso padre. La figura 3.20 ilustra la relación de los descriptores de archivo en la matriz fd con procesos padre e hijo. Como esto ilustra, cualquier escritura del padre lo hace al final de la
tubería — fd [1] — y puede ser leído por el hijo desde el final de lectura —fd [0] —de la tubería.
Figura 3.20 Descriptores de archivo para una tubería ordinaria.
Figura 3.21 Tubería ordinaria en UNIX.
 Figura 3.22 continuación de la Figura 3.21
En el programa UNIX que se muestra en la Figura 3.21, el proceso padre crea un pipe y luego envía una llamada fork() creando el proceso hijo. Lo que ocurra después de la llamada fork() depende de cómo deben fluir los datos a través de la tubería. En este caso, el padre escribe en la tubería y el hijo lee de ella. Es importante notar que tanto el proceso padre como el proceso hijo inicialmente cierran sus extremos no utilizados de la tubería. Aunque el programa que se muestra en la Figura 3.21 no requiere esta acción, es un paso importante para garantizar que la lectura de un proceso desde la tubería puede detectar el final del archivo (read () devuelve 0) cuando el escritor
ha cerrado su final de la tubería.
Las tuberías ordinarias en sistemas Windows se denominan tuberías anónimas y se comportan de manera similar a sus homólogos de UNIX: son unidireccionales y emplean relaciones padre-hijo entre los procesos de comunicación. Además, leer y escribir en la tubería se puede lograr con Funciones ordinarias ReadFile () y WriteFile (). La API de Windows para crear tuberías es la función CreatePipe (), a la que se le pasan cuatro parámetros. Los parámetros proporcionan manejadores separados para (1) lectura y (2) escritura en la tubería, así como (3) una instancia de la estructura STARTUPINFO, que se utiliza para especificar que el proceso hijo heredar los controladores de la tubería. Además, (4) Se puede especificar el tamaño de la tubería (en bytes).
La figura 3.23 ilustra un proceso padre que crea una tubería anónima para comunicarse con su hijo. A diferencia de los sistemas UNIX, en los que un proceso hijo hereda automáticamente una tubería creada por su padre, Windows requiere que el programador especifique qué atributos heredará el proceso hijo. Esto es logrado mediante la inicialización de la estructura de SECURITY_ATRIBUTES para permitir heredar manejadores y luego redirigir los manejadores a los hijos del proceso para entrada estándar o salida estándar, para el manejador de lectura o escritura de la tubería. Ya que el hijo leerá desde la tubería, el padre debe redirigir al hijo la Entrada estándar al manejador de lectura de la tubería. Además, como las tuberías son medio dúplex, es necesario prohibir que el hijo herede el final de escritura de la tubería. El programa para crear el proceso hijo es similar al programa de la Figura 3.10, excepto que el quinto parámetro se establece en VERDADERO, lo que indica que el proceso hijo heredará los identificadores designados de su padre. Antes de escribir en la tubería, el padre primero cierra su extremo de lectura no utilizado de la tubería. El proceso hijo que lee de la tubería se muestra en la Figura 3.25. Antes de leer desde la tubería, este programa obtiene el identificador de lectura para la tubería invocando GetStdHandle ().
Tenga en cuenta que las tuberías comunes requieren una relación padre-hijo entre los procesos que se comunican en sistemas UNIX y Windows. Esto significa que estas tuberías sólo se pueden utilizar para la comunicación entre procesos en el misma máquina
TUBERIA (pipes) EN LA PRÁCTICA
Las tuberías se utilizan con bastante frecuencia en el entorno de línea de comandos de UNIX para situaciones en el que la salida de un comando sirve como entrada a otro. Por ejemplo, el comando ls de UNIX produce una lista de directorios. Para listados largos de directorio, la salida puede desplazarse a través de varias pantallas. El comando less administra la salida al mostrar sólo una pantalla de salida en un momento en el que el usuario puede usar ciertas teclas para avanzar o retroceder en el archivo. Configurar un pipe entre los comandos ls y less (que se ejecutan como procesos individuales) permite que se entregue la salida de ls como entrada a less, lo que permite al usuario mostrar un directorio grande con un listado de pantalla a la vez. Se puede construir una tubería en la línea de comando utilizando el carácter |. El comando completo es 
ls | less
En este escenario, el comando ls sirve como productor y su salida es el consumido (el comando less).
Los sistemas Windows proporcionan un comando more para el shell de DOS con funcionalidad similar a la de su homólogo de UNIX less. (Los sistemas UNIX también proporcionan un comando more, pero en el estilo irónico en UNIX, el comando less de hecho proporciona más funcionalidad que more!) El shell de DOS también usa el Carácter | para establecer una tubería. La unica diferencia es que para obtener una lista de directorio, DOS usa el comando dir en lugar de ls, como mostrado a continuación:
dir | more
Figura 3.23 Canal anónimo de Windows: proceso padre
Figure 3.24 Continuación de la Figure 3.23
Figura 3.25 Canalizaciones anónimas de Windows: proceso hijo.
3.7.4.2 Tuberías con nombre
Las tuberías ordinarias proporcionan un mecanismo simple para permitir comunicar un par de procesos. Sin embargo, las tuberías ordinarias existen solo mientras los procesos están comunicándose unos con otros. En los sistemas UNIX y Windows, una vez los procesos han terminado de comunicarse y han terminado, lo normal es que
la tubería deje de existir.
Las tuberías con nombre proporcionan una herramienta de comunicación mucho más poderosa. La Comunicación puede ser bidireccional y no se requiere relación padre-hijo. Una vez que se establece una tubería con nombre, varios procesos pueden usarla para la comunicación. De hecho, en un escenario típico, una tubería con nombre tiene varios escritores. Adicionalmente, las tuberías con nombre continúan existiendo después de que la comunicación entre los procesos terminan. Tanto los sistemas UNIX como Windows admiten tuberías con nombre, aunque los detalles de implementación difieren mucho. A continuación, exploramos tuberías con nombre en cada uno de estos sistemas.
Las tuberías con nombre se denominan FIFO en los sistemas UNIX. Una vez creados, ellos aparecen como archivos típicos en el sistema de archivos. Se crea un FIFO con la llamada al sistema mkfifo() y manipulada con las clásicas llamadas al sistema open (), read (), write (), y close (). Continuará existiendo hasta que se elimine explícitamente
del sistema de archivos. Aunque los FIFO permiten la comunicación bidireccional, sólo se permite la transmisión semidúplex. Si los datos deben viajar en ambas direcciones, se usan normalmente dos FIFO. Además, los procesos que se comunican deben Residir en la misma máquina. Si se requiere comunicación entre máquinas, se deben usar socket (Sección 3.8.1) debe ser utilizado.
Las tuberías con nombre en los sistemas Windows proporcionan un mecanismo de comunicación más rico que sus homólogos de UNIX. Se permite la comunicación full-duplex, y los procesos de comunicación pueden residir en el mismo o diferentes máquinas. Además, sólo los datos orientados a bytes pueden transmitirse a través de un FIFO UNIX, mientras que los sistemas Windows permiten bytes o datos como mensajes. Las canalizaciones con nombre se crean con la función CreateNamedPipe() y el cliente puede conectarse a una tubería con nombre usando ConnectNamedPipe (). La Comunicación sobrela tubería con nombre se puede lograr usando Funciones ReadFile () y
WriteFile ().
3.6 IPC en sistemas de paso de mensajes
En la Sección 3.5, mostramos cómo los procesos cooperativos pueden comunicarse en un entorno de memoria compartida. El esquema requiere que estos procesos compartan una región de memoria y que el código para acceder y manipular la memoria compartida debe ser escrita explícitamente por el programador de la aplicación. Otra forma de lograr el mismo efecto es que el sistema operativo proporcione los medios para que los procesos cooperativos puedan comunicarse entre sí a través de la facilidad: paso de mensajes.
El paso de mensajes proporciona un mecanismo para permitir que los procesos se comuniquen y sincronicen sus acciones sin compartir el mismo espacio de direcciones. Eso es particularmente útil en un entorno distribuido, donde puede darse la comunicación entre procesos que residen en diferentes computadoras conectadas por una red. Por ejemplo, se podría diseñar un programa de chat por Internet para que los participantes del chat se comuniquen entre sí mediante el intercambio de mensajes.
El servicio de paso de mensajes proporciona al menos dos operaciones:
send(message)
y
receive(message)
Los mensajes enviados por un proceso pueden ser de tamaño fijo o variable. Si sólo se pueden enviar mensajes de tamaño fijo, la implementación a nivel del sistema es sencilla. Sin embargo, esta restricción hace que la tarea de programar sea más difícil. Por el contrario, los mensajes de tamaño variable requieren un nivel de implementación de sistema más complejo, pero la tarea de programación se vuelve más simple. Este es una situación común de compromiso que se ve en todo el diseño de un sistema operativo.
Si los procesos P y Q quieren comunicarse, deben enviar y recibir mensajes unos de otros: debe existir un enlace de comunicación entre ellos. Este enlace se puede implementar de varias maneras. Aquí no nos preocupa la implementación física del enlace (como memoria compartida, hardware bus, o red, que se tratan en el Capítulo 19), sino más bien su implementación lógica. Aquí hay varios métodos para implementar lógicamente un enlace y las operaciones de send()/receive():
• Comunicación directa o indirecta.
A continuación, analizamos los problemas relacionados con cada una de estas características.
3.6.1 Nombramiento
Los procesos que desean comunicarse deben tener una manera de referirse el uno al otro. Pueden usar, ya sea comunicación directa o indirecta.
Bajo comunicación directa, cada proceso que quiera comunicarse debe nombrar explícitamente al destinatario o al remitente de la comunicación. En este esquema, las primitivas send () y reciben () se definen como:
• send(P, message)—Se envía un message al proceso P.
• receive(Q, message)—Se recibe un message desde el proceso Q.
Un enlace de comunicación en este esquema tiene las siguientes propiedades:
• Se establece automáticamente un enlace entre cada par de procesos que se quieren comunicar. Los procesos sólo necesitan conocer la identificación mutuamente para comunicarse.
• Un enlace está asociado con exactamente dos procesos.
• Entre cada par de procesos, existe exactamente un enlace.
Este esquema exhibe simetría en el direccionamiento; es decir, tanto el proceso emisor como el proceso receptor debe nombrar al otro para comunicarse. Una variante de este esquema emplea asimetría en el direccionamiento. Aquí, sólo los emisores nombran al receptor; No se requiere que el receptor nombre al emisor. En este esquema,
Las primitivas send () y receive () se definen de la siguiente manera:
• send(P, message)— Se envía un message al proceso P.
• receive(id, message)-- Se recibe un mensaje de cualquier proceso. La variable id se establece en el nombre del proceso con el que la comunicación tiene lugar.
La desventaja en ambos esquemas (simétrico y asimétrico) es la modularidad limitada de las definiciones de proceso resultantes. Cambiar el identificador de un proceso puede requerir que se modifiquen todas las restantes definiciones de proceso. Deben localizarse todas las referencias al identificador anterior, para sustituirlas por el nuevo identificador. En general, cualquiera de estas técnicas de codificación rígida, donde los identificadores deben declararse explícitamente, son menos deseables que las técnicas que involucran indirección, como se describe a continuación.
Con comunicación indirecta, los mensajes se envían y reciben de buzones de correo o puertos. Un buzón se puede ver de forma abstracta como un objeto en el que los procesos pueden colocar mensajes y desde donde pueden extraer y eliminar mensajes. Cada buzón tiene una identificación única. Por ejemplo, las colas de mensajes en POSIX, usan un valor entero para identificar cada buzón de correo. Un proceso puede comunicarse con otro proceso a través de varios buzones diferentes, pero dos los procesos sólo pueden comunicarse si tienen un buzón compartido. Las primitivas send() y receive()se definen de la siguiente manera:
• send(A, message)—Se envía un message al buzón (mailbox) A.
• receive(A, message)—Se recibe un message desde el buzón (mailbox) A.
En este esquema, un enlace de comunicación tiene las siguientes propiedades: 
• Se establece un enlace entre un par de procesos sólo si ambos miembros de la pareja tiene un buzón compartido.
• Un enlace puede estar asociado con más de dos procesos.
• Entre cada par de procesos de comunicación, pueden existir varios enlaces diferentes, cada enlace corresponde a un buzón.
Ahora suponga que los procesos P1, P2 y P3 comparten el buzón A. El proceso P1 envía un mensaje a A, mientras que P2 y P3 ejecutan una instrucción receive() de A. ¿Qué procesos recibirán el mensaje enviado por P1? La respuesta depende de cuál de los siguientes métodos elegimos:
• Permitir que un enlace se asocie con dos procesos como máximo.
• Permitir como máximo un proceso a la vez para ejecutar una operación de recepción ().
• Permitir que el sistema seleccione arbitrariamente qué proceso recibirá el mensaje (es decir, P2 o P3, pero no ambos, recibirán el mensaje). El sistema puede definir un algoritmo para seleccionar qué proceso recibirá el mensaje (por ejemplo, round robin, donde los procesos se turnan para recibir mensajes). El sistema puede identificar el receptor al emisor.
Un buzón puede ser propiedad de un proceso o del sistema operativo. Si el buzón es propiedad de un proceso (es decir, el buzón es parte del espacio de direcciones del proceso), luego distinguimos entre el propietario (que puede
sólo recibir mensajes a través de este buzón) y el usuario (que sólo puede enviar mensajes al buzón). Como cada buzón tiene un único propietario, no puede haber confusión sobre qué proceso debe recibir un mensaje enviado a este buzón. Cuando finaliza un proceso que posee un buzón, el buzón desaparece. Cualquier proceso que luego envíe un mensaje a este buzón debe ser notificado que el buzón ya no existe.
En contraste, un buzón que es propiedad del sistema operativo tiene existencia propia. Es independiente y no está vinculado a ningún proceso en particular. El sistema operativo debe proporcionar un mecanismo que permita a un proceso hacer lo siguiente:
• Crear un nuevo buzón.
• Enviar y recibir mensajes a través del buzón.
• Eliminar un buzón.
El proceso que crea un nuevo buzón es el propietario de ese buzón de forma predeterminada. Inicialmente, el propietario es el único proceso que puede recibir mensajes a través de este buzón. Sin embargo, la propiedad y el privilegio de recepción pueden pasarse a otros procesos a través de llamadas al sistema apropiadas. Por supuesto, esta disposición podría generar múltiples receptores para cada buzón.
• Comunicación síncrona o asíncrona.
3.6.2 Sincronización
La comunicación entre procesos se realiza a través de llamadas a las primitivas send() y receive(). Existen diferentes opciones de diseño para implementar cada primitiva. Los paso de mensajes pueden ser bloqueantes o no bloqueantes - tambiénconocidos como síncronos y asíncronos. (A lo largo de este texto, usted encontrará los conceptos de comportamiento sincrónico y asincrónico en relación a varios algoritmos del sistema operativo).
• Send Bloqueante. El proceso que envía se bloquea hasta que el mensaje es recibido por el proceso receptor o por el buzón.
• Send sin bloqueo. El proceso envía el mensaje y continúa la operación.
• Receive Bloqueante. El receptor se bloquea hasta que haya un mensaje disponible.
• Receive sin bloqueo. El receptor recupera un mensaje válido o un nulo.
Figura 3.14 El proceso del productor utilizando el paso de mensajes.
Figura 3.15 El proceso del consumidor mediante el paso de mensajes
Son posibles diferentes combinaciones de send() y receive(). Cuando ambos, send() y receive() son bloqueantes, tenemos una cita entre el emisor y el receptor. La solución al problema productor-consumidor se vuelve trivial cuando usamos sentencias de bloqueo send() y receive(). El productor simplemente invoca la llamada de bloqueo send() y espera hasta que el mensaje se entrega al receptor o al buzón de correo. Asimismo, cuando el consumidor
invoca receive(), se bloquea hasta que un mensaje esté disponible. Esto se ilustra en las Figuras 3.14 y 3.15.
		
• Buffering automático o explícito
3.6.3 Almacenamiento intermedio (buffering)
Ya sea que la comunicación sea directa o indirecta, los mensajes intercambiados por los procesos residen en una cola temporal. Básicamente, tales colas pueden ser implementado de tres maneras:
• Capacidad cero. La cola tiene una longitud máxima de cero; así, el enlace no puede tener ningún mensaje esperando en él. En este caso, el emisor debe bloquearse hasta que el destino reciba el mensaje.
• Capacidad limitada. La cola tiene una longitud finita n; por lo tanto, al menos n mensajes pueden residir en ella. Si la cola no está llena cuando se envía un nuevo mensaje, el mensaje se coloca en la cola (el mensaje se copia o se mantiene un puntero al mensaje), y el emisor puede continuar la ejecución sin esperar. Sin embargo, la capacidad del enlace es finita. Si el enlace está lleno, el emisor debe bloquearse hasta que haya espacio disponible en la cola.
• Capacidad ilimitada. La longitud de la cola es potencialmente infinita; por lo tanto, cualquier número de mensajes pueden esperar en él. El emisor nunca se bloquea. 
El caso de capacidad cero a veces se conoce como un sistema de mensajes sin buffer. Los otros casos se denominan sistemas con almacenamiento en búfer automático.
3.7 Ejemplos de sistemas IPC
En esta sección, exploramos cuatro sistemas diferentes de IPC. Primero cubrimos el POSIX API para memoria compartida y luego discutir los paso de mensaje en el funcionamiento del sistema Mach. A continuación, presentamos Windows IPC, que utiliza de manera interesante la memoria como mecanismo para proporcionar ciertos tipos de mensajes pasados. Nosotros Concluimos con tuberías (pipes), uno de los primeros mecanismos de IPC en sistemas UNIX.
3.7.1 POSIX Memoria compartida
Hay varios mecanismos IPC disponibles para los sistemas POSIX, incluidos la memoria compartida y los pasos de mensajes. Aquí, exploramos la API POSIX para memoria compartida.
La memoria compartida POSIX se organiza utilizando archivos mapeados en memoria, que asocian la región de memoria compartida con un archivo. Un proceso primero debe crear un objeto memoria compartida que usando la llamada al sistema shm_open (), de la siguiente manera:
fd = shm open(name, O CREAT | O RDWR, 0666);
El primer parámetro especifica el nombre del objeto de memoria compartida. Los Procesos que deseen acceder a esta memoria compartida deben referirse al objeto con este nombre. Los siguientes parámetros especifican que se creará el objeto de memoria compartida si aún no existe (O CREAT) y que el objeto está abierto para lectura y escritura (O RDWR). El último parámetro establece los permisos de acceso a archivos del objeto de memoria compartida. Una llamada exitosa a shm_open () devuelve un descriptor de archivo (un número entero) para el objeto de memoria compartida.
Una vez que se establece el objeto, la función ftruncate () se usa para configurar el tamaño del objeto en bytes. La llamada
ftruncate(fd, 4096);
establece el tamaño del objeto en 4.096 bytes.
Finalmente, la función mmap () establece un archivo mapeado en memoria que contiene el objeto de memoria compartida. También devuelve un puntero al archivo mapeado en memoria que se usa para acceder al objeto de memoria compartida.
Los programas que se muestran en la Figura 3.16 y la Figura 3.17 utilizan el modelo productor–consumidor en la implementación de memoria compartida. El productor establece un objeto de memoria compartida y escribe en la memoria compartida, y el consumidor lee de la memoria compartida
Figura 3.16 Proceso productor que ilustra la API POSIX de memoria compartida
El productor, que se muestra en la Figura 3.16, crea un objeto de memoria compartida llamado OS y escribe la cadena "¡Hola Mundo!" en la memoria compartida. El programa direcciona (“mapea”) a memoria de un objeto de memoria compartida del tamaño especificado y se le permite escribir en el objeto. La bandera MAP_SHARED especifica que los cambios en el objeto de memoria compartida serán visibles para todos los procesos que compartan el objeto. Note que escribimos en el objeto de memoria compartida llamando a la función sprintf ()
y escribimos la cadena formateada en el puntero ptr. Después de cada escritura, debemos incrementar el puntero por el número de bytes escritos.
Figura 3.17 Proceso del consumidor que ilustra la API de memoria compartida POSIX.
El proceso consumidor, que se muestra en la Figura 3.17, lee y muestra los contenidos de la memoria compartida. El consumidor también invoca la función shm_unlink (), que elimina el segmento de memoria compartida después de que el consumidor accedió a él. Proporcionamos más ejercicios utilizando la API de memoria compartida POSIX en
los ejercicios de programación al final de este capítulo. Además, proporcionamos Cobertura más detallada de la asignación de memoria en la Sección 13.5.
3.7.2 Pasos de mensajes en Mach
Como ejemplo de paso de mensajes, consideramos el sistema operativo Mach. Mach fue especialmente diseñado para sistemas distribuidos, pero mostró ser adecuado también para sistemas de escritorio y móviles, como lo demuestra su inclusión en los sistemas operativos macOS e iOS, como se discutió en el Capítulo 2.
El kernel de Mach admite la creación y destrucción de múltiples tareas, que son similares a los procesos pero tienen múltiples hilos de control y menos recursos asociados. La mayoría de las comunicaciones en Mach, incluidas todas las tareas de comunicación: se lleva a cabo mediante mensajes. Los mensajes son enviados, y recibidos de buzones, que se llaman puertos en Mach. Los puertos son de tamaño finito y unidireccional; para comunicación bidireccional, se envía un mensaje a un puerto, y se envía una respuesta a un puerto de respuesta separado. Cada puerto puede tener múltiples remitentes (emisores), pero solo un receptor. Mach usa puertos para representar recursos tales como tareas, hilos, memoria y procesadores, mientras que el paso de mensajes proporciona un enfoque orientado a objetos para interactuar con estos recursos del sistema y servicios. El paso de mensajes puede ocurrir entre dos puertos en el mismo host o en hosts separados en un sistema distribuido.
Asociado con cada puerto hay un conjunto de derechos de puerto que identifican Las capacidades necesarias para que una tarea interactúe con el puerto. Por ejemplo, para que una tarea reciba un mensaje de un puerto, debe tener la capacidad MACH_PORT_RIGHT_RECEIVE para ese puerto. La tarea que crea un puerto es el propietario del puerto, y el propietario es la única tarea que puede recibir mensajes en ese puerto. El propietario de un puerto también puede manipular las capacidades de un puerto. Esto se hace más comúnmente al establecer un puerto de respuesta.

Continuar navegando