Sistemas operativos: tres piezas sencillas. Parte 3: API de proceso (traducción)

Introducción a los Sistemas Operativos

¡Hola, Habr! Me gustaría llamar su atención sobre una serie de artículos, traducciones de una literatura interesante en mi opinión: OSTEP. Este material analiza con bastante profundidad el trabajo de los sistemas operativos similares a Unix, es decir, el trabajo con procesos, varios programadores, memoria y otros componentes similares que conforman un sistema operativo moderno. Puedes ver el original de todos los materiales aquí aquí. Tenga en cuenta que la traducción se hizo de manera poco profesional (con bastante libertad), pero espero haber conservado el significado general.

El trabajo de laboratorio sobre este tema se puede encontrar aquí:

Otras partes:

También puedes ver mi canal en telegrama =)

¡Alarma! ¡Hay un laboratorio para esta conferencia! Mirar github

API de proceso

Veamos un ejemplo de creación de un proceso en un sistema UNIX. Sucede a través de dos llamadas al sistema. tenedor() и exec ().

Llamar a la bifurcación()

Sistemas operativos: tres piezas sencillas. Parte 3: API de proceso (traducción)

Considere un programa que realiza una llamada fork(). El resultado de su ejecución será el siguiente.

Sistemas operativos: tres piezas sencillas. Parte 3: API de proceso (traducción)

En primer lugar, ingresamos a la función main() e imprimimos la cadena en la pantalla. La línea contiene el identificador del proceso que en el original se llama PID o identificador de proceso. Este identificador se utiliza en UNIX para referirse a un proceso. El siguiente comando llamará fork(). En este punto, se crea una copia casi exacta del proceso. Para el sistema operativo, parece que hay 2 copias del mismo programa ejecutándose en el sistema, lo que a su vez saldrá de la función fork(). El proceso hijo recién creado (en relación con el proceso padre que lo creó) ya no se ejecutará, a partir de la función main(). Debe recordarse que un proceso hijo no es una copia exacta del proceso padre; en particular, tiene su propio espacio de direcciones, sus propios registros, su propio puntero a instrucciones ejecutables y similares. Por lo tanto, el valor devuelto al llamador de la función fork() será diferente. En particular, el proceso padre recibirá el valor PID del proceso hijo como retorno, y el hijo recibirá un valor igual a 0. Con estos códigos de retorno, puede separar procesos y forzar a cada uno de ellos a hacer su propio trabajo. . Sin embargo, la ejecución de este programa no está estrictamente definida. Después de dividirse en 2 procesos, el sistema operativo comienza a monitorearlos, así como a planificar su trabajo. Si se ejecuta en un procesador de un solo núcleo, uno de los procesos, en este caso el padre, continuará funcionando y luego el proceso hijo recibirá el control. Al reiniciar, la situación puede ser diferente.

Llamada en espera()

Sistemas operativos: tres piezas sencillas. Parte 3: API de proceso (traducción)

Considere el siguiente programa. En este programa, debido a la presencia de una llamada Espere() El proceso padre siempre esperará a que se complete el proceso hijo. En este caso, obtendremos una salida de texto estrictamente definida en la pantalla.

Sistemas operativos: tres piezas sencillas. Parte 3: API de proceso (traducción)

llamada ejecutiva()

Sistemas operativos: tres piezas sencillas. Parte 3: API de proceso (traducción)

Considere el desafío exec (). Esta llamada al sistema es útil cuando queremos ejecutar un programa completamente diferente. aquí llamaremos execvp () para ejecutar el programa wc, que es un programa de conteo de palabras. ¿Qué sucede cuando se llama a exec()? A esta llamada se le pasa el nombre del archivo ejecutable y algunos parámetros como argumentos. Después de lo cual se cargan el código y los datos estáticos de este archivo ejecutable y se sobrescribe su propio segmento con el código. Las áreas de memoria restantes, como la pila y el montón, se reinicializan. Después de lo cual el sistema operativo simplemente ejecuta el programa, pasándole un conjunto de argumentos. Así que no creamos un nuevo proceso, simplemente transformamos el programa que se está ejecutando actualmente en otro programa en ejecución. Después de ejecutar la llamada exec() en el descendiente, parece como si el programa original no se ejecutara en absoluto.

Esta complicación de inicio es completamente normal para un shell Unix y permite que ese shell ejecute código después de llamar tenedor(), pero antes de la llamada exec (). Un ejemplo de dicho código sería ajustar el entorno del shell a las necesidades del programa que se está ejecutando, antes de iniciarlo.

Cáscara - sólo un programa de usuario. Ella te muestra la línea de invitación y espera a que escribas algo en ella. En la mayoría de los casos, si escribe el nombre de un programa allí, el shell encontrará su ubicación, llamará al método fork() y luego llamará a algún tipo de exec() para crear un nuevo proceso y esperará a que se complete usando un espera() llamada. Cuando el proceso hijo sale, el shell regresará de la llamada de espera () e imprimirá el mensaje nuevamente y esperará a que se ingrese el siguiente comando.

La división fork() & exec() permite que el shell haga las siguientes cosas, por ejemplo:
archivo wc > nuevo_archivo.

En este ejemplo, la salida del programa wc se redirige a un archivo. La forma en que el shell logra esto es bastante simple: creando un proceso hijo antes de llamar exec (), el shell cierra la salida estándar y abre el archivo archivo nuevo, por lo tanto, toda la salida del programa en ejecución posterior wc será redirigido a un archivo en lugar de a una pantalla.

tubería Unix se implementan de manera similar, con la diferencia de que utilizan una llamada pipe(). En este caso, el flujo de salida del proceso se conectará a una cola de tubería ubicada en el kernel, a la que se conectará el flujo de entrada de otro proceso.

Fuente: habr.com

Añadir un comentario