Logo Studenta

Silberschatz 10a castellano cap2

¡Este material tiene más páginas!

Vista previa del material en texto

Cap 2:
Estructuras de los Sistemas Operativos
Un sistema operativo proporciona el entorno dentro del cual se los programas se ejecutan. Internamente, los sistemas operativos varían mucho en su composición, ya que están organizados a lo largo de muchas líneas diferentes. El diseño de un nuevo sistema operativo es una tarea importante. Es importante que los objetivos del sistema estén bien definidos antes de que comience el diseño. Estos objetivos forman la base para elegir entre varias estrategias y algoritmos.
Podemos ver un sistema operativo desde varios puntos de vista. Una vista se centra en los servicios que brinda el sistema; otro, en la interfaz que pone a disposición de los usuarios y programadores; un tercero, en sus componentes y sus interconexiones En este capítulo, exploramos los tres aspectos de los sistemas operativos, que muestran los puntos de vista de los usuarios, programadores y sistemas operativos diseñadores. Consideramos qué servicios proporciona un sistema operativo, cómo se proporcionan, cómo se depuran y cuáles son las diversas metodologías para diseñar tales sistemas. Finalmente, describimos cómo los sistemas operativos se crean y cómo una computadora inicia su sistema operativo.
OBJETIVOS DEL CAPÍTULO
• Identificar los servicios proporcionados por un sistema operativo.
• Ilustrar cómo se utilizan las llamadas al sistema para proporcionar servicios del sistema operativo.
• Comparar y contrastar monolíticos, en capas, microkernel, modulares y estrategias híbridas para diseñar sistemas operativos.
• Ilustrar el proceso para arrancar un sistema operativo.
• Aplicar herramientas para monitorear el rendimiento del sistema operativo.
• Diseñar e implementar módulos de kernel para interactuar con un kernel de Linux.
2.1 Servicios del sistema operativo
Un sistema operativo proporciona un entorno para la ejecución de programas. Pone ciertos servicios a disposición de los programas y de los usuarios de esos programas. Los servicios específicos proporcionados, por supuesto, difieren de un sistema operativo a otro, pero podemos identificar clases comunes. La figura 2.1 muestra una vista de los diversos servicios del sistema operativo y cómo se interrelacionan. Note que estos servicios también facilitan la tarea de programación para el programador.
Un conjunto de servicios del sistema operativo proporciona funciones que son útiles para el usuario.
• Interfaz de usuario. Casi todos los sistemas operativos tienen una interfaz de usuario (IU). Esta interfaz puede tomar varias formas. Más comúnmente se utiliza la 
· interfaz gráfica de usuario (GUI). Aquí, la interfaz es un sistema de ventanas con un mouse que sirve como dispositivo señalador para dirigir E/S, elija de los menús, y hace selecciones y un teclado para ingresar texto.
· Sistemas móviles tales ya que los teléfonos y tabletas proporcionan una interfaz de pantalla táctil, lo que permite a los usuarios deslizar sus dedos por la pantalla o presionar los botones en la pantalla para seleccionar opciones 
· Otra opción es una interfaz de línea de comandos (CLI), que usa comandos en modo texto y un método para ingresarlos (por ejemplo, un teclado para escribir en comandos en un formato específico con opciones específicas). Algunos sistemas proporcione dos o las tres de estas variaciones.
• Ejecución del programa. El sistema debe poder cargar un programa en la memoria y ejecutar ese programa. El programa debe poder finalizar su ejecución, normal o anormalmente (indicando error).
• Operaciones de E/S. El programa de ejecución puede requerir E/S, lo que puede implicar una archivo o un dispositivo de E/S. Para dispositivos específicos, se pueden desear funciones especiales (como leer desde una interfaz de red o escribir en un sistema de archivos). Por eficiencia y protección, los usuarios generalmente no pueden controlar los dispositivos de E/S directamente. Por lo tanto, el sistema operativo debe proporcionar un medio para hacer la E/S.
• Manipulación del sistema de archivos. El sistema de archivos es de particular interés. Obviamente, los programas necesitan leer y escribir archivos y directorios. Ellos también necesitan para crear y eliminar el nombre del equipo, buscar un archivo determinado y mostrar la información del archivo.
Finalmente, algunos sistemas operativos incluyen administración de permisos para permitir o denegar el acceso a archivos o directorios en función de la propiedad del archivo. Muchos sistemas operativos proporcionan una variedad de sistemas de archivos, a veces para permitir elección personal y, a veces, para proporcionar características o rendimiento específicos
• Comunicaciones. Hay muchas circunstancias en las que un proceso necesita intercambiar información con otro proceso. Tal comunicación puede ocurrir entre procesos que se ejecutan en la misma computadora o entre procesos que se ejecutan en diferentes sistemas informáticos unidos por una red. Las comunicaciones pueden implementarse a través de memoria compartida, en la que dos o más procesos leen y escriben en una sección de memoria, o por la transmisión de mensajes, en la que se encuentran paquetes de información en formatos predefinidos que son movidos por el sistema operativo entre los procesos.
• Detección de errores. El sistema operativo debe detectar y corregir errores constantemente Pueden producirse errores en la CPU y el hardware de memoria (como un error de memoria o una falla de energía), en dispositivos de E/S (como un error de paridad en el disco, una falla de conexión en una red o falta de papel en la impresora), y en el programa de usuario (como un desbordamiento aritmético o un intento de acceder a una ubicación de memoria ilegal). Para cada tipo de error, el funcionamiento el sistema debe tomar las medidas adecuadas para garantizar una correcta y coherente computación. A veces, no tiene más remedio que detener el sistema. Otras veces, podría terminar un proceso que causa errores o devolver un código de error a un proceso para que el proceso detecte y posiblemente corrija.
Existe otro conjunto de funciones del sistema operativo no para ayudar al usuario sino más bien para asegurar la operación eficiente del sistema mismo. Sistemas con múltiples procesos puede ganar eficiencia al compartir los recursos de la computadora entre los diferentes procesos.
• Asignación de recursos. Cuando hay múltiples procesos ejecutándose al mismo tiempo, los recursos deben asignarse a cada uno de ellos. El sistema operativo gestiona muchos tipos diferentes de recursos. Algunos (como ciclos de CPU, memoria principal y almacenamiento de archivos) pueden tener un código de asignación especial,
mientras que otros (como los dispositivos de E/S) pueden tener una solicitud mucho más general y liberar código. Por ejemplo, al determinar la mejor manera de usar la CPU, los sistemas operativos tienen rutinas de planificación de CPU que tienen en cuenta la velocidad de la CPU, el proceso que debe ejecutarse, el número de kernels de procesamiento en la CPU y otros factores. También puede haber rutinas para asignar impresoras, unidades de almacenamiento USB y otros dispositivos periféricos.
• Inicio sesión. Queremos hacer un seguimiento de qué programas usan cuánto y qué tipo de recursos de computación. Este mantenimiento de registros puede usarse para contabilidad (para que se pueda facturar a los usuarios) o simplemente para uso Estadístico. Las estadísticas de uso pueden ser una herramienta valiosa para los administradores del sistema, quienes desean reconfigurar el sistema para mejorar los servicios informáticos.
• Protección y seguridad. Los propietarios de la información almacenada en un sistema multiusuario o un sistema de computación en red controlan el uso de esa información. Cuando varios procesos separados se ejecutan simultáneamente, no debería ser posible que un proceso interfiera con los otros o con el funcionamiento del sistema en sí. La protección implica garantizar que el acceso al sistema, a los recursos, estén controlados. La seguridad delsistema se refiere a que personas externas ingresen al sistema. Dicha seguridad comienza con la exigencia de que cada usuario se autentique o ella misma al sistema, generalmente por medio de una contraseña, para obtener acceso a los recursos del sistema. Se extiende a la defensa de dispositivos de E/S externos, incluidos adaptadores de red, desde intentos de acceso no válidos y controlando todas las conexiones para la detección de robos. Si un sistema debe ser protegido y seguro, se deben instituir precauciones en todo el mismo. Una cadena es tan fuerte como su eslabón más débil. 
2.2 Interfaz de usuario y sistema operativo
Mencionamos anteriormente que hay varias formas para que los usuarios interactúen con el sistema operativo. Aquí, discutimos tres enfoques fundamentales. 
· Uno proporciona una interfaz de línea de comandos o intérprete de comandos que permite a los usuarios para ingresar directamente los comandos que debe realizar el sistema operativo. 
· El otro dos permiten a los usuarios interactuar con el sistema operativo a través de un interfaz gráfica de usuario o GUI. 
2.2.1 Intérpretes de comando
La mayoría de los sistemas operativos, incluidos Linux, UNIX y Windows, tratan el intérprete comando como un programa especial que se ejecuta cuando se inicia un proceso o cuando un usuario inicia sesión por primera vez (en sistemas interactivos). En sistemas con múltiples intérpretes de comandos para elegir, los intérpretes se conocen como Shell. Por ejemplo, en sistemas UNIX y Linux, un usuario puede elegir entre varias diferentes shell, incluidas la C shell, la Bourne-Again Shell, la Korn Shell y otras. También hay disponibles shells de terceros y shells gratuitas escritas por el usuario. Muchas shells proporcionan una funcionalidad similar y la elección del usuario de qué shell usar generalmente se basa en la preferencia personal. 
La Figura 2.2 muestra el Bourne-Again (o bash) intérprete de comandos de shell que se utiliza en macOS.
La función principal del intérprete de comandos es obtener y ejecutar el siguiente comando especificado por el usuario. Muchos de los comandos dados en este nivel manipulan archivos: crear, eliminar, enumerar, imprimir, copiar, ejecutar, etc. Las diversas shell disponible en sistemas UNIX funcionan de esta manera. Estos comandos pueden implementarse de dos formas generales.
En un enfoque, el propio intérprete de comandos contiene el código para ejecutar el comando. Por ejemplo, un comando para eliminar un archivo puede causar que el intérprete de comandos salte a una sección de su código que se configura los parámetros y realiza la llamada al sistema adecuada. En este caso, la cantidad de comandos que se pueden dar determina el tamaño del intérprete de comandos, ya que cada el comando requiere su propio código de implementación.
Un enfoque alternativo, utilizado por UNIX, entre otros sistemas operativos: Implementa la mayoría de los comandos a través de los programas del sistema. En este caso, el intérprete de comandos no entiende el comando de ninguna manera; simplemente usa el comando para identificar un archivo que se cargará en la memoria y se ejecutará. Por lo tanto, el comando UNIX para eliminar un archivo
rm file.txt
buscaría un archivo llamado rm, cargaría el archivo en la memoria y ejecutaría file.txt con los parámetros. La lógica asociada con el comando rm sería definida completamente por el código en el archivo rm. De esta manera, los programadores pueden agregar nuevos comandos al sistema fácilmente creando nuevos archivos, generando un archivo-programa que haga lo que se espera. El programa de intérprete de comandos, que puede ser pequeño, no tiene que cambiarse para que se agreguen nuevos comandos.
2.2.2 Interfaz gráfica de usuario
Una segunda estrategia para interactuar con el sistema operativo es a través de una interfaz gráfica amigable de usuario o GUI. Aquí, en lugar de ingresar comandos directamente a través de una interfaz de línea de comandos, los usuarios emplean un Sistema de menú en una ventana basada en mouse caracterizado por un “escritorio” metafórico. El usuario mueve el mouse para colocar su puntero en imágenes o iconos, en la pantalla (el escritorio) que representan programas, archivos, directorios y funciones del sistema. Dependiente de la ubicación del puntero del mouse, hacer clic en un botón del mouse puede invocar un programa, seleccione un archivo o directorio, conocido como carpeta, o desplegar un menú que contiene comandos
Las interfaces gráficas de usuario aparecieron por primera vez debido en parte a la investigación lugar a principios de la década de 1970 en el centro de investigación Xerox PARC. La primera GUI apareció en la computadora Xerox Alto en 1973. Sin embargo, las interfaces gráficas se convirtieron más generalizadas con la llegada de las computadoras Apple Macintosh en la década de 1980. La interfaz de usuario para el sistema operativo Macintosh ha sufrido varios
cambios a lo largo de los años, siendo el más significativo la adopción del interfaz Aqua que apareció con macOS. La primera versión de Windows de Microsoft: la Versión 1.0: se basó en la adición de una interfaz GUI al sistema operativo MS-DOS. Las versiones posteriores de Windows han realizado cambios significativos en la aparición de la GUI junto con varias mejoras en su funcionalidad.
Tradicionalmente, los sistemas UNIX han estado dominados por interfaces de línea de comandos. Sin embargo, hay varias interfaces GUI disponibles, con un desarrollo significativo en diseños de GUI de varios proyectos de código abierto, como Entorno K Desktop (o KDE) y el escritorio GNOME por el proyecto GNU. Ambos los escritorios KDE y GNOME se ejecutan en Linux y varios sistemas UNIX y están disponible bajo licencias de código abierto, lo que significa que su código fuente es fácilmente disponible para leer y modificar bajo términos de licencia específicos.
2.2.3 Interfaz de pantalla táctil
Porque ya sea una interfaz de línea de comandos o un sistema de mouse y teclado es poco práctico para la mayoría de los sistemas móviles, teléfonos inteligentes y tabletas portátiles que normalmente usan una interfaz de pantalla táctil. Aquí, los usuarios interactúan haciendo gestos en la pantalla táctil, por ejemplo, presionar y deslizar los dedos
a través de la pantalla. Aunque los teléfonos inteligentes anteriores incluían un teclado físico, la mayoría de los teléfonos inteligentes y tabletas ahora simulan un teclado en la pantalla táctil. La Figura 2.3 ilustra la pantalla táctil del iPhone de Apple. Tanto el iPad como el iPhone usa la interfaz de pantalla táctil Springboard.
2.2.4 Elección de la interfaz
La elección de usar una línea de comandos o una interfaz GUI es principalmente uno de preferencia personal. Administradores de sistemas que manejan computadoras y los usuarios avanzados que tienen un conocimiento profundo de un sistema con frecuencia usan interfaz de línea de comandos.
Para ellos, es más eficiente, dándoles un acceso más rápido a las actividades que necesitan realizar. De hecho, en algunos sistemas, solo un subconjunto de las funciones del sistema está disponible a través de la GUI, dejando las tareas menos comunes a aquellos que tienen conocimientos de línea de comandos. Además, interfaces de línea de comandos generalmente hacen que las tareas repetitivas sean más fáciles, en parte porque tienen su propia programación. Por ejemplo, si una tarea frecuente requiere un conjunto de pasos de línea de comandos, esos pasos se pueden grabar en un archivo, y ese archivo se puede ejecutar como un programa El programa no está compilado en código ejecutable sino, más bien es interpretado por la interfaz de línea de comandos. Estos scripts de shell son
muy comunes en sistemas orientados a la línea de comandos, como UNIX y Linux
Por el contrario, la mayoría de los usuarios de Windows están felices de usar el entorno GUI de Windows y casi nunca usa la interfaz de shell. Versiones recientes del sistema operativo Windows proporciona una GUI estándar paraescritorio y tradicional computadoras portátiles y una pantalla táctil para tabletas. Los diversos cambios experimentados por los sistemas operativos Macintosh también proporcionan un buen estudio en contraste. Históricamente, Mac OS no ha proporcionado una interfaz de línea de comandos, siempre requiere sus usuarios para interactuar con el sistema operativo utilizando su GUI. Sin embargo, con el lanzamiento de macOS (que se implementa en parte usando un kernel UNIX), el sistema operativo El sistema ahora proporciona una interfaz gráfica de usuario de Aqua y una interfaz de línea de comandos. La Figura 2.4 es una captura de pantalla de la GUI de macOS.
Aunque hay aplicaciones que proporcionan una interfaz de línea de comandos para iOS y sistemas móviles Android, rara vez se usan. En cambio, casi todos los usuarios de los sistemas móviles interactúan con sus dispositivos utilizando la interfaz de pantalla táctil.
La interfaz de usuario puede variar de un sistema a otro e incluso de un usuario a otro al usuario dentro de un sistema; sin embargo, típicamente se elimina sustancialmente de La estructura real del sistema. El diseño de una interfaz de usuario útil e intuitiva.
Por lo tanto, no es una función directa del sistema operativo. En este libro, nosotros nos concentramos en los problemas fundamentales de proporcionar un servicio adecuado a Programas de usuario. Desde el punto de vista del sistema operativo, no Distinguir entre programas de usuario y programas de sistema.
2.3 Llamadas del sistema
Las llamadas al sistema proporcionan una interfaz para los servicios puestos a disposición por un sistema operativo. Estas llamadas están generalmente disponibles como funciones escritas en C y C ++, aunque ciertas tareas de bajo nivel (por ejemplo, tareas donde el hardware debe accederse directamente) puede que tenga que escribirse usando instrucciones de lenguaje ensamblador.
2.3.1 Ejemplo
Antes de analizar cómo un sistema operativo hace que las llamadas al sistema estén disponibles, vamos primero a usar un ejemplo para ilustrar cómo se usan las llamadas al sistema: escribir un simple programa para leer datos de un archivo y copiarlos en otro archivo. La primera entrada que necesitará el programa son los nombres de los dos archivos: el archivo de entrada y el archivo de salida. Estos nombres se pueden especificar de muchas maneras, dependiendo del diseño del sistema operativo. Un enfoque es pasar los nombres de los dos archivos como parte del comando, por ejemplo, el comando cp de UNIX:
cp in.txt out.txt
Este comando copia el archivo de entrada in.txt al archivo de salida out.txt. Un segundo enfoque es que el programa le pida al usuario los nombres. En un sistema interactivo, este enfoque requerirá una secuencia de llamadas al sistema: primero para escribir un mensaje de aviso en la pantalla y luego leer desde el teclado caracteres que definen los dos archivos. En sistemas basados ​​en mouse e iconos, generalmente se muestra un menú de nombres de archivo en una ventana. El usuario puede usar el mouse para seleccionar el nombre de la fuente, y se puede abrir una ventana para nombre de destino a especificar. Esta secuencia requiere muchas llamadas al sistema de E/S.
Una vez que se han obtenido los dos nombres de archivo, el programa debe abrir el archivo de entrada y crear y abrir el archivo de salida. Cada una de estas operaciones requiere otra llamada al sistema. Las posibles condiciones de error para cada llamada al sistema deben ser manejadas. Por ejemplo, cuando el programa intenta abrir el archivo de entrada, puede que encuentre que no hay un archivo con ese nombre o que el archivo está protegido contra el acceso. En estos casos, el programa debería generar un mensaje de error (otra secuencia de llamadas al sistema) y luego terminan de manera anormal (otra llamada al sistema). Si existe el archivo de entrada, entonces debemos crear un nuevo archivo de salida. Podemos encontrar que ya haya un archivo de salida con el mismo nombre. Esta situación puede causar que el programa se aborte (una llamada al sistema), o podemos eliminar el archivo existente (otra llamada al sistema) y cree una nueva (otra llamada al sistema). Otra opción, en un sistema interactivo, es pedirle al usuario (a través de una secuencia de llamadas al sistema, la salida de un mensaje de aviso y leer la respuesta del terminal), para reemplazar el archivo existente o para cancelar el programa.
Cuando ambos archivos están configurados, ingresamos un bucle que se lee desde un archivo de entrada (una llamada al sistema) y escribe en el archivo de salida (otra llamada al sistema). Cada leer y escribir debe devolver información de estado con respecto a varios posibles condiciones de errores En la entrada, el programa puede encontrar que el final del archivo ha sido alcanzado o que hubo una falla de hardware en la lectura (como un error de paridad). La operación de escritura puede encontrar varios errores, dependiendo del dispositivo de salida (por ejemplo, no más espacio disponible en disco).
Finalmente, después de copiar todo el archivo, el programa puede cerrar ambos archivos (dos llamadas al sistema), escribe un mensaje en la consola o ventana (más llamadas al sistema) y finalmente finalizar normalmente (la llamada final del sistema). Esta secuencia de llamadas al sistema se muestra en la Figura 2.5:
2.3.2 Interfaz de programación de aplicaciones
Como puede ver, incluso los programas simples pueden hacer un uso intensivo del sistema operativo. Con frecuencia, los sistemas ejecutan miles de llamadas al sistema por segundo. Sin embargo, la mayoría de los programadores nunca ven este nivel de detalle. Por lo general, los desarrolladores de aplicación diseñan programas de acuerdo con una interfaz de programación de aplicaciones (API). La API especifica un conjunto de funciones que están disponibles para un programador de aplicaciones, incluidos los parámetros que se pasan a cada función
y los valores de retorno que el programador puede esperar. Tres de las más comunes API disponibles para los programadores de aplicaciones son:
· API de Windows para sistemas Windows, 
· API POSIX para sistemas basados ​​en POSIX (que incluyen prácticamente todos versiones de UNIX, Linux y macOS)
· API de Java para programas que se ejecutan en la máquina virtual Java. 
El programador accede a una API a través de una biblioteca de códigos proporcionado por el sistema operativo. En el el caso de UNIX y Linux para programas escrito en el lenguaje C, la biblioteca se llama libc. Tenga en cuenta que, a menos que se los especifique —Los nombres de llamadas al sistema utilizados en este texto son ejemplos genéricos. Cada sistema operativo tiene su propio nombre para cada llamada al sistema. Detrás de lo que se ve, las funciones que componen una API generalmente invocan a la llamada al sistema necesaria en nombre del programador de la aplicación. Por ejemplo, la Función de Windows CreateProcess () (que, como era de esperar, se usa para crear un nuevo proceso) en realidad invoca la llamada al sistema NTCreateProcess () en Kernel de Windows.
EJEMPLO DE API ESTÁNDAR
Como ejemplo de una API estándar, considere la función read () que está disponible en sistemas UNIX y Linux. La API para esta función se obtiene de la página man invocando el comando
man read
en la línea de comando. A continuación aparece una descripción de esta API:
Un programa que usa la función read () debe incluir unistd.h en el encabezado de archivo, ya que este archivo define los tipos de datos ssize t y size t (entre otras cosas). Los parámetros pasados a read () son los siguientes:
• int fd: el descriptor de archivo a leer
• void * buf: un búfer en el que se leerán los datos
• size_t count: el número máximo de bytes que se leerán en el buffer
En una lectura exitosa, se devuelve el número de bytes leídos. Valor de retorno de 0 indica el final del archivo. Si se produce un error, read () devuelve −1.
¿Por qué un programador de aplicaciones preferiría programarde acuerdo con una API en lugar de invocar llamadas reales del sistema? Hay varias razones para hacerlo:
· Un beneficio se refiere a la portabilidad del programa. Un programador de aplicaciones diseña un programa usando una API y puede esperar que su programa compile y se ejecute en cualquier sistema que admita la misma API (aunque, en realidad, las diferencias de arquitectura a menudo hacen que esto sea más difícil de lo que parece). 
· Además, las llamadas reales al sistema a menudo suelen ser más detalladas y difíciles de trabajar que la API disponible para un programador de aplicaciones. 
Sin embargo, a menudo existe una fuerte correlación entre una función en la API y su llamada al sistema asociada
dentro del kernel. De hecho, muchas de las API de POSIX y Windows son similares a las llamadas al sistema nativas proporcionadas por el sistema operativo UNIX, Linux y sistemas Windows.
Otro factor importante en el manejo de las llamadas al sistema es el entorno de tiempo de ejecución (RTE): el conjunto completo de software necesario para ejecutar aplicaciones escritas en un lenguaje de programación dado, incluidos sus compiladores o intérpretes así como otro software, como bibliotecas y cargadores. El RTE proporciona un interfaz de llamada al sistema que sirve como enlace a las llamadas del sistema disponibles en el sistema operativo. La interfaz de llamada al sistema intercepta llamadas de función en API e invoca las llamadas necesarias del sistema dentro del sistema operativo. Por lo general, se asocia un número con cada llamada al sistema y la interfaz de llamada al sistema mantiene una tabla indexada de acuerdo con estos números. La interfaz de llamada al sistema invoca la llamada al sistema prevista en el kernel del sistema operativo y devuelve el estado de la llamada al sistema.
El llamador no necesita saber cómo se implementa la llamada al sistema o lo que hace durante la ejecución. Más bien, la persona que llama solo necesita cumplir con la sintaxis de la API y comprender qué hará el sistema operativo como resultado de la ejecución de esa llamada al sistema. Por lo tanto, la mayoría de los detalles de la interfaz del sistema operativo están ocultos para el programador por la API y son administrados por el RTE. la relación entre una API, la interfaz de llamada al sistema y el sistema operativo se muestra en la Figura 2.6, que ilustra cómo el sistema operativo maneja un aplicación de usuario que invoca la llamada al sistema open ().
Las llamadas al sistema ocurren de diferentes maneras, dependiendo de la computadora en uso. A menudo, se requiere más información que la identidad de la llamada al sistema deseada. El tipo exacto y la cantidad de información varían según el sistema operativo particular y la llamada. Por ejemplo, para solicitar la entrada de información, es posible que necesitemos especificar el archivo o dispositivo que se usará como fuente, así como la dirección y longitud del búfer de memoria del que se debe leer la entrada. Por supuesto, el dispositivo o archivo y la longitud pueden estar implícitos en la llamada.
Se utilizan tres métodos generales para pasar parámetros al sistema operativo. 
· El enfoque más simple es pasar los parámetros en los registros. Sin embargo, puede haber más parámetros que registros. 
· En estos casos, los parámetros generalmente se almacenan en un bloque o tabla, en la memoria, y la dirección del bloque se pasa como parámetro en un registro (Figura 2.7). Linux utiliza una combinación de estos enfoques. Si hay cinco o menos parámetros, Se utilizan registros. Si hay más de cinco parámetros, el método del bloque se usa.
· El programa también puede colocar o insertar parámetros en una pila y ser sacados de la pila por el sistema operativo. Algunos sistemas operativos prefieren el método de bloque o pila porque esos enfoques no limitan el número o longitud de los parámetros que se pasan.
2.3.3 Tipos de llamadas al sistema
Las llamadas al sistema se pueden agrupar aproximadamente en seis categorías principales: 
control de procesos,
gestión de archivos, 
gestión de dispositivos, 
mantenimiento de información, 
comunicaciones,
y protección. 
A continuación, discutimos brevemente los tipos de llamadas al sistema que puede ser proporcionadas por un sistema operativo. La mayoría de estas llamadas al sistema soportan los conceptos y funciones que se analizan en capítulos posteriores.
La Figura 2.8 resume los tipos de llamadas al sistema que normalmente proporciona un sistema operativo. Como se mencionó, en este texto, normalmente nos referimos a llamadas al sistema por nombres genéricos. A lo largo del texto, sin embargo, proporcionamos ejemplos de las llamadas al sistema que proveen UNIX, Linux y Windows
2.3.3.1 Control de proceso
Un programa en ejecución debe poder detener su ejecución normalmente (fin ()) o anormalmente (abortar ()). Si se realiza una llamada al sistema para terminar el programa actualmente en ejecución anormalmente, o si el programa se encuentra con un problema y provoca una trampa de error, a veces se toma un volcado de memoria y se genera un mensaje de error. El volcado se escribe en un archivo de registro especial en el disco y puede ser examinado por un depurador, (un programa del sistema diseñado para ayudar el programador para encontrar y corregir errores o errores), para determinar la causa del problema. En circunstancias normales o anormales, el sistema operativo transfiere el control invocando al intérprete de comandos. El intérprete de comandos lee el siguiente comando. En un sistema interactivo, el intérprete de comandos simplemente continúa con el siguiente comando; se supone que el usuario emitirá un comando apropiado para responder a cualquier error.
• Process control
◦ create process, terminate process
◦ load, execute
◦ get process attributes, set process attributes
◦ wait event, signal event
◦ allocate and free memory
• File management
◦ create file, delete file
◦ open, close
◦ read, write, reposition
◦ get file attributes, set file attributes
• Device management
◦ request device, release device
◦ read, write, reposition
◦ get device attributes, set device attributes
◦ logically attach or detach devices
• Information maintenance
◦ get time or date, set time or date
◦ get system data, set system data
◦ get process, file, or device attributes
◦ set process, file, or device attributes
• Communications
◦ create, delete communication connection
◦ send, receive messages
◦ transfer status information
◦ attach or detach remote devices
• Protection
◦ get file permissions
◦ set file permissions
Figure 2.8 Types of system calls.
En un sistema GUI, una ventana emergente puede alertar al usuario sobre el error y pedir orientación. Algunos sistemas pueden permitir acciones especiales de recuperación en caso que ocurra un error. Si el programa descubre un error en su entrada y quiere terminar anormalmente, también puede querer definir un nivel de error. Los errores más graves se pueden indicar mediante un parámetro de error de nivel superior. Entonces es posible combinar terminación normal y anormal definiendo una terminación normal como un error en el nivel 0. El intérprete de comandos o un programa siguiente puede usar este nivel de error para determinar la siguiente acción automáticamente.
Un proceso que ejecuta un programa puede querer cargar () y ejecutar () otro programa. Esta característica permite al intérprete de comandos ejecutar un programa como lo indica, por ejemplo, un comando de usuario o el clic de un mouse. Una pregunta interesante es dónde devolver el control cuando el programa cargado termina. Esta pregunta está relacionada con: si el programa existente se pierde, se guarda o se le permite continuar la ejecución simultáneamente con el nuevo programa.
LA BIBLIOTECA ESTÁNDAR C
La biblioteca estándar de C proporciona una parte de la interfaz de llamada al sistema para muchas versiones de UNIX y Linux. Como ejemplo, supongamos un programa C que invoca la sentencia printf (). La biblioteca C intercepta estallamada e invoca la llamada (o llamadas) del sistema necesarias en el sistema operativo, en esta instancia, la llamada al sistema write (). La biblioteca C toma el valor devuelto por write () y lo devuelve al programa de usuario:
Si el control vuelve al programa existente cuando finaliza el nuevo programa, debemos guardar la imagen de memoria del programa existente; por lo tanto, tenemos que crear efectivamente un mecanismo para que un programa llame a otro programa. Si ambos programas continúan simultáneamente, hemos creado un nuevo proceso para ser multiprogramado A menudo, hay una llamada al sistema específicamente para este propósito (create_process()).
Si creamos un nuevo proceso, o tal vez incluso un conjunto de procesos, deberíamos ser capaces de controlar su ejecución. Este control requiere la capacidad de determinar y restablecer los atributos de un proceso, incluida la prioridad del proceso, su tiempo máximo de ejecución permitido, y así sucesivamente (obtener atributos de proceso () y establecer atributos de proceso ()). También podemos querer terminar un proceso que creamos (terminate_process ()) si encontramos que es incorrecto o ya no lo es necesario.
Habiendo creado nuevos procesos, es posible que tengamos que esperar a que finalicen su ejecución. Es posible que queramos esperar a que pase cierto tiempo (wait_time()). Lo más probable es que queramos esperar un evento específico que ocurra (wait_event ()). Los procesos deberían indicar (señalar) cuándo ese evento ocurrió (signal_event ()).
Muy a menudo, dos o más procesos pueden compartir datos. Para asegurar la integridad de los datos que se comparten, los sistemas operativos a menudo proporcionan llamadas al sistema permitiendo a un proceso bloquear los datos compartidos. Entonces, ningún otro proceso puede acceder a los datos hasta que se libera el bloqueo. Típicamente, tales llamadas al sistema incluyen adquirir lock () y liberar el lock().Llamadas de Sistema de este tipo, que se ocupa de la coordinación de los procesos concurrentes se analizan en gran detalle en el Capí 6 y el Cap 7.
Hay tantas facetas y variaciones en el control del proceso que nosotros a continuación, usaremos dos ejemplos: uno que involucra un sistema de tarea única y el otro un sistema multitarea, para aclarar estos conceptos: 
Arduino es una simple plataforma de hardware que consiste en un microcontrolador junto con sensores de entrada que responden a una variedad de eventos, como cambios de luz, temperatura y presión barométrica, por nombrar solo algunos. Para escribir un programa para Arduino, nosotros primero escribimos el programa en una PC y luego cargamos el programa compilado (conocido como un boceto (sketch)) desde la PC a la memoria flash de Arduino a través de una conexión USB. La plataforma estándar Arduino no proporciona un sistema operativo; en lugar, un pequeño software conocido como gestor de arranque carga el boceto en una región en la memoria de Arduino (Figura 2.9). 
Una vez que se ha cargado el boceto, éste comienza a ejecutarse, esperando los eventos a los que está programado para responder. Por ejemplo, si el sensor de temperatura del Arduino detecta que la temperatura ha excedido un cierto umbral, el boceto puede hacer que Arduino inicie el Motor para un ventilador. Un Arduino se considera un sistema de tarea única, ya que sólo un boceto puede estar presente en la memoria a la vez; si se carga otro boceto,
se reemplaza el boceto existente. Además, el Arduino no proporciona ninguna interfaz de usuario más allá de los sensores de entrada de hardware.
FreeBSD (derivado de Berkeley UNIX) es un ejemplo de sistema multitarea. Cuando un usuario inicia sesión en el sistema, el shell de la elección del usuario se ejecuta, en espera de comandos y ejecutar programas que solicite el usuario. Sin embargo, como FreeBSD es un sistema multitarea, el intérprete de comandos puede continuar
ejecutando mientras se ejecuta otro programa (Figura 2.10). 
Para comenzar un nuevo proceso, el shell ejecuta una llamada al sistema fork (). Entonces, el programa seleccionado es cargado en la memoria a través de una llamada al sistema exec (), y el programa se ejecuta. Dependiendo de cómo se emitió el comando, el shell esperará al proceso que finalice o ejecutará el proceso "en segundo plano". En el último caso, el shell inmediatamente espera a que se ingrese otro comando. Cuando un proceso se ejecuta en segundo plano, no puede recibir información directamente desde el teclado, porque el shell está usando este recurso. Por lo tanto, la E/S se realiza a través de archivos o a través de una interfaz GUI. Mientras tanto, el usuario puede pedirle al shell que ejecute otros programas, para monitorear el progreso del proceso en ejecución, para cambiar la prioridad del programa, etc. Cuando finaliza el proceso, ejecuta una llamada al sistema exit ()para finalizar, devolviendo al proceso de invocación un código de estado de 0 o un código de error distinto de cero. Este estado o código de error está disponible para el shell u otros programas. Los procesos se discuten en el Capítulo 3 con un ejemplo de programa usando las llamadas al sistema fork () y exec ().
2.3.3.2 Gestión de archivos
El sistema de archivos se analiza con más detalle en el Capítulo 13 al Capítulo 15. Aquí, identificamos varias llamadas comunes al sistema relacionadas con archivos. Primero necesitamos poder créate () y delete() archivos. Cualquiera de las llamadas al sistema requiere el nombre del archivo y quizás algunos de los atributos del archivo. Una vez
el archivo está creado, necesitamos open() y usarlo. También podemos read(), write () o reposition () (saltar hasta el final del archivo, por ejemplo). Finalmente, necesitamos close() el archivo, lo que indica que ya no lo estamos usando.
Es posible que necesitemos estos mismos conjuntos de operaciones para directorios si tenemos una estructura de directorios para organizar archivos en el sistema de archivos. Además, para cualquiera archivos o directorios, necesitamos poder determinar los valores de varios atributos y quizás establecerlos si es necesario. Los atributos del archivo incluyen el nombre del archivo, tipo de archivo, códigos de protección, información contable, etc. Al menos
dos llamadas al sistema, obtener atributos de archivo () y establecer atributos de archivo (), son requeridos para esta función. Algunos sistemas operativos proporcionan muchas más llamadas, como llamadas para mover archivos () y copiar (). Otros pueden proporcionar una API que realiza esas operaciones usando código y otras llamadas al sistema, y ​​otras podrían proporcionar programas del sistema para realizar las tareas. Si los programas del sistema son invocables por otros programas, cada uno puede ser considerado una API por otros programas del sistema 
2.3.3.3 Gestión de dispositivos
Un proceso puede necesitar varios recursos para ejecutarse: memoria principal, unidades de disco, acceso a archivos, etc. Si los recursos están disponibles, se pueden otorgar, y el control puede ser devuelto al proceso del usuario. De lo contrario, el proceso tendrá que esperar hasta que haya suficientes recursos disponibles
Se pueden pensar los diversos recursos controlados por el sistema operativo como dispositivos. Algunos de estos “dispositivos” son dispositivos físicos (por ejemplo, disco unidades), mientras que otros pueden considerarse dispositivos abstractos o virtuales (para ejemplo, archivos). 
El sistema con múltiples usuarios puede requerir primero un request() (solicitud) de un dispositivo, para garantizar su uso exclusivo. Después de que hayamos terminado con el dispositivo, nosotros release() (liberarlo). Estas funciones son similares a las llamadas al sistema open () y close () para archivos. 
Otros sistemas operativos no administran y permiten el acceso a los dispositivos. El peligro entonces es la potencial “pelea” por el dispositivo y quizás dead-lock, que se describen en el Capítulo 8.
Una vez que el dispositivo ha sido solicitado (y asignado anosotros), podemos read(), write() y (posiblemente) reposition() el dispositivo, tal como podemos hacerlo con los archivos. De hecho, la similitud entre los dispositivos de E/S y los archivos es tan grande que muchos sistemas operativos, incluido UNIX, fusionan los dos en una estructura combinada de archivos y dispositivos. En este caso, se utiliza un conjunto de llamadas al sistema tanto en archivos como en dispositivos. A veces, los dispositivos de E/S se identifican por nombres de archivos especiales, con ubicación en directorios y con atributos de archivos.
La interfaz de usuario también puede hacer que los archivos y dispositivos parezcan similares, incluso aunque las llamadas al sistema subyacentes sean diferentes. Este es otro ejemplo de las muchas decisiones de diseño que intervienen en la construcción de un sistema operativo e interfaz de usuario.
2.3.3.4 Mantenimiento de información
Existen muchas llamadas al sistema simplemente con el propósito de transferir información entre el programa de usuario y el sistema operativo. Por ejemplo, la mayoría de los sistemas tienen una llamada al sistema para devolver la hora actual time() y la fecha date(). Otras llamadas al sistema pueden devolver información sobre el sistema, como el número de versión del sistema operativo, la cantidad de memoria libre o espacio en disco, etc.
Otro conjunto de llamadas al sistema es útil para depurar un programa. Muchos sistemas proporcionan llamadas al sistema para volcar la memoria dump(). Esta disposición es útil para la depuración. El programa Strace, que está disponible en sistemas Linux, enumera cada llamada del sistema a medida que la ejecuta. Incluso los microprocesadores proporcionan un modo de CPU, conocido como paso único, en el que la CPU ejecuta una trampa después de cada instrucción. La trampa generalmente es atrapada por un depurador (debugger).
Muchos sistemas operativos proporcionan un perfil de tiempo de un programa para indicar la cantidad de tiempo que el programa se ejecuta en una ubicación o conjunto particular de ubicaciones. Un perfil de tiempo requiere una instalación de rastreo o un temporizador regular de interrupciones. En cada aparición de la interrupción del temporizador, se registra el valor del contador de programa. Con interrupciones de temporizador suficientemente frecuentes, se puede obtener una imagen estadística del tiempo dedicado a varias partes del programa.
Además, el sistema operativo mantiene información sobre todos sus procesos y se utilizan llamadas al sistema para acceder a esta información. En general, las llamadas también son utilizadas para obtener y establecer la información del proceso (obtener atributos del proceso () y establecer atributos de proceso ()). En la Sección 3.1.3, discutimos qué información normalmente se mantiene.
2.3.3.5 Comunicación
Hay dos modelos comunes de comunicación entre procesos: el modelo de paso de mensajes y el modelo de memoria compartida. En el modelo de paso de mensajes, los procesos intercambian mensajes entre sí para comunicación para transferirse información. Los mensajes pueden intercambiarse entre los procesos directa o indirectamente a través de un buzón común. Antes de que la comunicación pueda tener lugar, se debe abrir una conexión. El nombre del comunicador debe ser conocido, ya sea por otro proceso del mismo sistema o un proceso de otra computadora conectada por una red de comunicaciones. Cada computadora en red tiene un nombre de host por el cual se conoce comúnmente. Un host también tiene un identificador de red, como una dirección IP. Del mismo modo, cada proceso tiene un nombre de proceso, y este nombre se traduce en un identificador por el cual el sistema puede referirse al proceso. Las llamadas al sistema get hostid () y get processid ()hacen esta traducción. Los identificadores se pasan por llamadas open() y close() de propósito general proporcionadas por el sistema de archivos o en llamadas de sistema de open_connection() y close_connection (), dependiendo del modelo de comunicación del sistema. El proceso del destinatario generalmente debe dar su permiso para que la comunicación tenga lugar con una llamada accept_connection(). La mayoría de los procesos que recibirán conexiones son procesos llamados demonios de propósito especial, que son programas del sistema provistos para ese propósito. Ejecutan una
llamada de wait_for_connection() y se despierta cuando se realiza la conexión. La fuente de la comunicación, conocida como el cliente, y el demonio receptor, conocido como servidor, luego intercambie mensajes usando llamadas al sistema read_message () y write_message (). La llamada de close_connection () finaliza la comunicación.
En el modelo de memoria compartida, los procesos usan las llamadas de sistema shared_memory_create () y shared_memory_attach () para crear y obtener acceso a regiones de memoria que son propiedad de otros procesos. Recordemos que, normalmente, el sistema operativo intenta evitar que un proceso acceda a la memoria de otro proceso. 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. La importancia de los datos está determinada por los procesos y no está bajo el control del sistema operativo. Los procesos también son responsables de asegurarse de que no están escribiendo en la misma ubicación simultáneamente. Tales mecanismos se discuten en el Capítulo 6. En el Capítulo 4, observamos una variación del esquema de proceso (hilos) en el que parte de la memoria se comparte de manera predeterminada.
Los dos modelos que acabamos de comentar son comunes en los sistemas operativos y la mayoría de los sistemas implementan ambos. El paso de mensajes es útil para intercambiar cantidades más pequeñas de datos, porque no es necesario cuidar los conflictos. También es más fácil implementar que la memoria compartida para la comunicación entre computadoras. La memoria compartida permite la máxima velocidad y mayor rendimiento en la comunicación, ya que se puede hacer a velocidades de transferencia de memoria cuando tiene lugar dentro de una computadora. Sin embargo, existen problemas en las áreas de protección y sincronización entre los procesos que comparten memoria.
2.3.3.6 Protección
La protección proporciona un mecanismo para controlar el acceso a los recursos proporcionados por un sistema informático. Históricamente, la protección era una preocupación solo en Sistemas informáticos multiprogramados con varios usuarios. Sin embargo, con el advenimiento de las redes e Internet, todos los sistemas informáticos, desde servidores hasta dispositivos móviles de mano, deben preocuparse por la protección.
Por lo general, las llamadas al sistema que brindan protección incluyen establecer permisos set_permission ()
y obtener permiso get_permission (), que manipula la configuración de permisos a los recursos como archivos y discos. Las llamadas al sistema permiten al usuario: allow_user() y deniegan al usuario: deny_user () especifican si usuarios particulares pueden, o no, tener acceso permitido a ciertos recursos. Cubrimos la protección en el Capítulo 17 y en el sentido más amplio, las cuestiones de seguridad, que implica el uso de protección contra amenazas externas, en el capítulo 16.
2.4 Servicios del sistema
Otro aspecto de un sistema moderno es su colección de servicios del sistema. La Figura 1.1, que representa la jerarquía informática lógica. En el nivel más bajo está el hardware. El siguiente es el sistema operativo, luego los servicios del sistema y finalmente los programas de aplicación. Los Servicios del sistema, también conocidos como utilidades del sistema, proporcionan un entorno conveniente para el desarrollo y la ejecución del programa.
Algunos de ellos son simplemente interfaces de usuario para llamadas al sistema. Otros son considerablemente
más complejos. Se pueden dividir en estas categorías:
• Gestión de archivos. Estos programas crean, eliminan, copian,renombran, imprimen, enumeran y generalmente acceden y manipulan archivos y directorios.
• Información de estado. Algunos programas simplemente le piden al sistema la fecha, el tiempo, cantidad de memoria disponible o espacio en disco, número de usuarios o Información de estado similar. Otros son más complejos, proporcionando detalles de información de rendimiento, registro y depuración. Típicamente, estos programas formatean e imprimen la salida al terminal u otros dispositivos de salida o archivos o mostrarlo en una ventana de la GUI. Algunos sistemas también tienen registro, que se utiliza para almacenar y recuperar información de configuración.
• Modificación de archivos. Varios editores de texto pueden estar disponibles para crear y modificar el contenido de los archivos almacenados en el disco u otros dispositivos de almacenamiento. Hay también comandos especiales para buscar contenidos de archivos o realizar transformaciones del texto.
• Soporte de lenguaje de programación. Compiladores, ensambladores, depuradores y intérpretes para lenguajes de programación comunes (como C, C ++, Java, y Python) a menudo se proporcionan con el sistema operativo o están disponibles como una descarga por separado.
• Carga y ejecución de programas. Una vez que se ensambla o compila un programa, debe cargarse en la memoria para ejecutarse. El sistema puede proporcionar cargadores absolutos, cargadores reubicables, editores de enlaces y cargadores de superposición (overlay). Los sistemas de depuración para lenguajes de máquina o de lenguaje de alto nivel también son necesarios.
• Comunicaciones. Estos programas proporcionan el mecanismo para crear conexiones virtuales entre procesos, usuarios y sistemas informáticos. Ellos permiten a los usuarios enviar mensajes a las pantallas de los demás, para navegar por páginas web, enviar mensajes de correo electrónico, iniciar sesión de forma remota o transferir archivos desde una máquina a otra.
• Servicios de Procesos demonios. Todos los sistemas de uso general tienen métodos para iniciar ciertos procesos del programa del sistema en el momento del arranque. Algunos de estos los procesos finalizan después de completar sus tareas, mientras que otros continúan y son conocidos como procesos de servicios, llamados demonios. Un ejemplo es el demonio de red discutido en la Sección 2.3.3.5. En ese ejemplo, un sistema necesitaba un servicio para escuchar las conexiones de red para conectar esas solicitudes a los procesos correctos. Otros ejemplos incluyen procesos planificadores que inician procesos de acuerdo con un programa específico, sistema de servicios de monitoreo de errores y servidores de impresión. Los sistemas típicos tienen docenas. de demonios. Además, los sistemas operativos que ejecutan actividades importantes en el contexto del usuario en lugar del contexto del kernel puede usar demonios para ejecutar estas tareas.
Junto con los programas del sistema, la mayoría de los sistemas operativos se suministran con programas que son útiles para resolver problemas comunes o realizar tareas u operaciones comunes. Dichos programas de aplicación incluyen navegadores web, procesadores de texto y formateadores de texto, hojas de cálculo, sistemas de bases de datos, compiladores, ploteo y paquetes de análisis estadístico y juegos.
La visión de un sistema operativo que tienen la mayoría de los usuarios está definida por los programas de aplicación y los de sistema, en lugar de por las llamadas reales del sistema. Considere la PC de un usuario. Cuando la computadora de un usuario ejecuta sistema macOS, el usuario puede ver la GUI, con una interfaz de mouse y ventana. Alternativamente, o incluso en una de las ventanas, el usuario podría tener una shell de línea de comandos UNIX. Ambos usan el mismo conjunto de llamadas al sistema, pero las llamadas al sistema se ven diferente y actúan de diferentes maneras. Confundiendo aún más la vista del usuario, considere que el usuario inicia desde un modo dual desde macOS en Windows. Ahora el mismo usuario con el mismo hardware tiene dos interfaces completamente diferentes y dos conjuntos de aplicaciones usando los mismos recursos físicos. En el mismo hardware, entonces, un usuario puede exponerse a múltiples interfaces de usuario de forma secuencial o simultánea.
2.5 Enlazadores y cargadores
Por lo general, un programa reside en el disco como un archivo binario ejecutable, por ejemplo, a.out o prog.exe. Para ejecutarse en una CPU, el programa debe llevarse a la memoria y asignarse en el contexto de un proceso. En esta sección, describimos los pasos en este procedimiento, desde compilar un programa hasta colocarlo en la memoria, donde se vuelve elegible para ejecutarse en un kernel (core) de CPU disponible. Los pasos se resaltan en
Figura 2.11.
Los archivos de origen se compilan en archivos de objetos que están diseñados para cargarse en cualquier ubicación de memoria física, un formato conocido como objeto reubicable. A continuación, el enlazador (linker) combina estos archivos de objetos reubicables en un solo: Archivo binario ejecutable. Durante la fase de enlace, otros archivos de objetos o bibliotecas pueden ser incluido también, como la biblioteca estándar C o matemática (especificada con la bandera -lm).
Un cargador se utiliza para cargar el archivo ejecutable binario en la memoria, donde será elegido para ejecutarse en un kernel de CPU. Una actividad asociada con el enlazado y carga es la reubicación, que asigna direcciones finales a las partes del programa y ajusta el código y los datos en el programa para que coincidan con esas direcciones para que, por ejemplo, el código puede llamar a las funciones de la biblioteca y acceda a sus variables a medida que se ejecuta. En la figura 2.11, vemos que para ejecutar el cargador, todo lo que se necesita es ingresar el nombre del
archivo ejecutable en la línea de comando. Cuando se ingresa un nombre de programa en la línea de comando en los sistemas UNIX, por ejemplo ./main, el shell primero crea un nuevo proceso para ejecutar el programa utilizando la llamada al sistema fork (). La Shell entonces invoca el cargador con la llamada al sistema exec (), pasando a exec () el nombre del archivo ejecutable. El cargador carga el programa especificado en la memoria utilizando el espacio de direcciones del proceso recién creado. (Cuando una interfaz GUI se usa, haciendo doble clic en el icono asociado con el archivo ejecutable invoca el cargador con un mecanismo similar)
El proceso descrito hasta ahora supone que todas las bibliotecas están vinculadas al archivo ejecutable y cargado en la memoria. En realidad, la mayoría de los sistemas permiten que un programa se vincule dinámicamente con bibliotecas a medida que se carga el programa. Windows, por ejemplo, admite bibliotecas vinculadas dinámicamente (DLL). El beneficio de este enfoque es que evita vincular y cargar bibliotecas que pueden terminar no siendo utilizadas en un archivo ejecutable. En cambio, la biblioteca está vinculada condicionalmente y se carga si es necesario durante el tiempo de ejecución del programa. Por ejemplo, en la figura 2.11, la biblioteca matemática no está vinculada al archivo ejecutable main. Más bien, el enlazador inserta información de reubicación que le permite vincularse dinámicamente y cargado a medida que se carga el programa. Ya veremos en el Capítulo 9 que es posible que múltiples procesos compartan bibliotecas vinculadas dinámicamente, lo que resulta en un ahorro significativo en el uso de la memoria.
Los archivos de objetos y los archivos ejecutables suelen tener formatos estándar que incluyen el código de máquina compilado y una tabla de símbolos que contiene metadatos sobre funciones y variables a las que se hace referencia en el programa. Para UNIX y los sistemas Linux, este formato estándar se conoce como ELF (para ejecutable y formato enlazable). 
FORMATO ELF
Linux proporciona varios comandos para identificar y evaluar archivos ELF. Por ejemplo, el comando de archivo determina un tipo de archivo.Si main.o es un objeto file, y main es un archivo ejecutable, el comando 
file main.o 
informará que main.o es un archivo reubicable ELF, mientras que el comando 
file main
informará que main es un ejecutable ELF. Los archivos ELF se dividen en un número. de secciones y se puede evaluar con el comando readelf
Hay formatos ELF separados para archivos reubicables y ejecutables. Una pieza de información en el archivo ELF para archivos ejecutables es el punto de entrada del programa, que contiene la dirección de la primera instrucción para 
ejecutarse cuando se ejecuta el programa. Los sistemas Windows usan el Formato ejecutable portátil (PE), y macOS usa el formato Mach-O.
2.6 Por qué las aplicaciones son específicas del sistema operativo
Básicamente, las aplicaciones compiladas en un sistema operativo no son ejecutables en otros sistemas operativos. Si lo fueran, el mundo sería un mejor lugar, y nuestra elección de qué sistema operativo usar dependería de la utilidad y características en lugar de qué aplicaciones estaban disponibles.
Según nuestra discusión anterior, ahora podemos ver parte del problema, cada sistema operativo proporciona un conjunto único de llamadas al sistema. Las llamadas al sistema son parte del conjunto de servicios proporcionados por los sistemas operativos para uso de las aplicaciones. Incluso si las llamadas al sistema fueran de alguna manera uniformes, otras barreras dificultarían para que podamos ejecutar programas de aplicación en diferentes sistemas operativos. Pero si ha utilizado múltiples sistemas operativos, puede haber utilizado algunas de los mismas aplicaciones en ellos. ¿Cómo es eso posible? 
Una aplicación puede estar disponible para ejecutarse en múltiples sistemas operativos en una de tres formas:
1. La aplicación se puede escribir en un lenguaje interpretado (como Python o Ruby) que tiene un intérprete disponible para múltiples sistemas operativos. El intérprete lee cada línea del programa fuente, ejecuta instrucciones equivalentes en el conjunto de instrucciones nativas, y hace llamadas al sistema operativo nativo. El rendimiento sufre en relación con el de las aplicaciones nativas, y el intérprete proporciona solo un subconjunto de las funciones de cada sistema operativo, posiblemente limitando los conjuntos de características de las aplicaciones asociadas.
2. La aplicación se puede escribir en un lenguaje que incluya un lenguaje de máquina virtual que contenga la aplicación en ejecución. La máquina virtual es parte del RTE completo del lenguaje. Un ejemplo de este método es Java. Java tiene un RTE que incluye un cargador, un verificador de código de bytes y otros componentes que carga la aplicación Java en la máquina virtual Java. Este RTE ha sido portado, o desarrollado, para muchos sistemas operativos, desde mainframes hasta teléfonos inteligentes, y en teoría cualquier aplicación Java puede ejecutarse dentro del RTE en cualquier lugar que esté disponible. Los sistemas de este tipo tienen desventajas similares a las
de intérpretes, discutido anteriormente.
3. El desarrollador de la aplicación puede usar un lenguaje estándar o API en el que el compilador genere binarios en una máquina y lenguaje específico del sistema operativo. La aplicación debe ser portada a cada sistema operativo
en el que se ejecutará. Esta transferencia puede llevar bastante tiempo y debe hacerse para cada nueva versión de la aplicación, con posterior pruebas y depuración. Quizás el ejemplo más conocido es la API de POSIX y su conjunto de estándares para mantener la compatibilidad del código fuente entre diferentes variantes de sistemas operativos tipo UNIX.
En teoría, estos tres enfoques aparentemente proporcionan soluciones simples para que aplicaciones desarrolladas puedan ejecutarse en diferentes sistemas operativos. Sin embargo, La falta general de movilidad de la aplicación tiene varias causas, todas las cuales todavía hace que desarrollar aplicaciones multiplataforma sea una tarea desafiante. En el nivel de aplicación, las bibliotecas proporcionadas con el sistema operativo contienen API para proporcionar funciones como interfaces GUI y una aplicación diseñada para llamar a un conjunto de API (por ejemplo, las disponibles desde iOS en el iPhone de Apple) no funcionará en un sistema operativo que no proporciona esas API (como Android). Otros desafíos existen en los niveles inferiores del sistema, incluidos los siguientes.
• Cada sistema operativo tiene un formato binario para aplicaciones que dicta el diseño del encabezado, las instrucciones y las variables. Esos componentes necesitan estar en ciertas ubicaciones en estructuras específicas dentro de un ejecutable para que el sistema operativo pueda abrir el archivo y cargar la aplicación para la ejecución correcta.
• Las CPU tienen conjuntos de instrucciones variables y solo aplicaciones que contienen las instrucciones apropiadas pueden ejecutarse correctamente.
• Los sistemas operativos proporcionan llamadas al sistema que permiten que las aplicaciones soliciten diversas actividades, como crear archivos y abrir conexiones de red. Esas llamadas al sistema varían entre sistemas operativos en muchos aspectos, incluidos los operandos específicos y el orden de los operandos utilizados, cómo una aplicación
invoca las llamadas del sistema, su numeración y número, sus significados, y su retorno de resultados.
Hay algunos enfoques que han ayudado a abordar, aunque no completamente resuelven, estas diferencias arquitectónicas. Por ejemplo, Linux, y casi todo sistema UNIX: ha adoptado el formato ELF para archivos ejecutables binarios. Aunque ELF proporciona un estándar común en los sistemas Linux y UNIX, el formato ELF no está vinculado a ninguna arquitectura de computadora específica, por lo que no garantiza que un archivo ejecutable se ejecutará en diferentes plataformas de hardware.
Las API, como se mencionó anteriormente, especifican ciertas funciones a nivel de aplicación. A nivel de arquitectura, se utiliza una interfaz binaria de aplicación (ABI) para definir cómo los diferentes componentes del código binario pueden interactuar para un determinado funcionamiento sistema en una arquitectura dada. Un ABI especifica detalles de bajo nivel, incluidos: ancho de dirección, métodos para pasar parámetros a las llamadas al sistema, la organización de la pila de tiempo de ejecución, el formato binario de las bibliotecas del sistema y el tamaño y tipos de los datos, solo por nombrar algunos. Normalmente, se especifica un ABI para una arquitectura dada (por ejemplo, hay un ABI para el procesador ARMv8). Por lo tanto, un ABI es el equivalente a nivel de arquitectura de una API. Si un archivo ejecutable binario ha sido compilado y vinculado de acuerdo con un ABI particular, debería poder ejecutarse en diferentes sistemas que soportan ese ABI. Sin embargo, porque un ABI particular es definido para un determinado sistema operativo que se ejecuta en una arquitectura determinada, las ABI hacen poco para proporcionar compatibilidad multiplataforma.
En resumen, todas estas diferencias significan que a menos que un intérprete, RTE o el archivo ejecutable binario se escriba y se compile en un sistema operativo específico en un tipo de CPU específico (como Intel x86 o ARMv8), la aplicación no podrá correr. Imagine la cantidad de trabajo que se requiere para un programa como el navegador Firefox se ejecute en Windows, macOS, varias versiones de Linux, iOS y Android, a veces en varias arquitecturas de CPU.
2.7 Diseño e implementación del sistema operativo
En esta sección, discutimos los problemas que enfrentamos al diseñar e implementar un sistema operativo. Por supuesto, no hay soluciones completas para tales problemas, pero hay enfoques que han resultado exitosos.
2.7.1 Objetivos de diseño
El primer problema en el diseño de un sistema es definir objetivos y especificaciones. Al más alto nivel, el diseño del sistema se verá afectado por la elección del hardware y el tipo de sistema: computadora de escritorio/portátil tradicional,móvil, distribuida, o en tiempo real
Más allá de este nivel de diseño más alto, los requisitos pueden ser mucho más difíciles de especificar. Sin embargo, los requisitos se pueden dividir en dos grupos básicos: objetivos de usuario y objetivos del sistema.
Los usuarios quieren ciertas propiedades obvias en un sistema. El sistema debe ser conveniente de usar, fácil de aprender y de usar, confiable, seguro y rápido. Por supuesto, estas especificaciones no son particularmente útiles en el diseño del sistema, ya que no hay un acuerdo general sobre cómo lograrlos. 
Los desarrolladores deben definir un conjunto similar de requisitos que deben diseñar, crear, mantener y operar el sistema. El sistema debería ser fácil de diseñar, implementar y mantener; y debe ser flexible, confiable, libre de errores, y eficiente. Una vez más, estos requisitos son vagos y pueden interpretarse en varias maneras.
En resumen, no existe una solución única al problema de definir los requisitos para un sistema operativo. La amplia gama de sistemas existentes muestra que diferentes requisitos pueden dar como resultado una gran variedad de soluciones para diferentes ambientes. Por ejemplo, los requisitos para Wind River VxWorks, un sistema operativo de tiempo real para sistemas integrados, será sustancialmente diferente de los de Windows Server, un gran sistema operativo de acceso múltiple y diseñado para aplicaciones empresariales.
Especificar y diseñar un sistema operativo es una tarea muy creativa. Aunque ningún libro de texto puede decirle cómo hacerlo, los principios generales han sido desarrollado en el campo de la ingeniería de software, y ahora pasamos a una discusión de algunos de estos principios.
2.7.2 Mecanismos y políticas
Un principio importante es la separación de la política del mecanismo. Las políticas determinan lo que se hará y los Mecanismos se usan para determinar cómo hacer algo. Por ejemplo, la construcción del temporizador (ver Sección 1.4.3) es un mecanismo para asegurar Protección de la CPU, pero decidir cuánto tiempo se configurará el temporizador para un usuario en particular es una decisión política. La separación de políticas y mecanismos es importante para la flexibilidad. Las Políticas es probable que cambien de lugar o con el tiempo. En el peor de los casos, cada cambio en política requeriría un cambio en el mecanismo subyacente. En general es preferible un mecanismo lo suficientemente flexible como para funcionar en una variedad de políticas. Un cambio en la política requeriría la redefinición de solo ciertos parámetros del sistema. Por ejemplo, considere un mecanismo para dar prioridad a ciertos tipos de programas sobre otros. Si el mecanismo está separado adecuadamente de la política, se puede usar, ya sea para apoyar una decisión política, que los programas que requieran mucha E/S deben tener prioridad sobre los que requieren mucha CPU o para admitir una política opuesta
Los sistemas operativos basados ​​en microkernel (discutidos en la Sección 2.8.3) separan mecanismo y política a un extremo mediante la implementación de un Conjunto básico de bloques de construcción primitivos. Estos bloques son casi libres de políticas, lo que permite que Se agreguen mecanismos y políticas más avanzados a través del kernel creado por los módulos de usuario o programas de usuario propios. Por el contrario, considere Windows, un
Sistema operativo comercial enormemente popular disponible por más de tres décadas. Microsoft ha codificado estrechamente tanto el mecanismo como la política en el sistema para imponer una apariencia global en todos los dispositivos que ejecutan sistema operativo Windows. Todas las aplicaciones tienen interfaces similares, porque la interfaz en sí está integrado en el kernel y las bibliotecas del sistema. Apple ha adoptado una estrategia similar con sus sistemas operativos macOS e iOS. 
Podemos hacer una comparación similar entre los sistemas operativos de código comercial y de código abierto. Por ejemplo, contraste Windows, discutido anteriormente, con Linux, un sistema operativo de código abierto que se ejecuta en una amplia gama de dispositivos de computación y ha estado disponible por más de 25 años. El Linux kernel "estándar" tiene un algoritmo de planificación de CPU específico (cubierto en la Sección 5.7.1), que es un mecanismo que respalda una determinada política. Sin embargo, cualquiera es libre de modificar o reemplazar el planificador para admitir una política diferente.
Las decisiones de política son importantes para toda la asignación de recursos. Siempre que sea necesario para decidir si se asigna o no un recurso, una decisión política debe hacerse. Cada vez que la pregunta es cómo y no qué, es un mecanismo y eso debe ser determinado.
2.7.3 Implementación
Una vez que se diseña un sistema operativo, debe implementarse. Porque los sistemas operativos son colecciones de muchos programas, escritos por muchas personas durante un largo período de tiempo, es difícil hacer declaraciones generales sobre cómo serán implementados.
Los primeros sistemas operativos se escribieron en lenguaje ensamblador. Ahora la mayoría están escritos en lenguajes de nivel superior como C o C ++, con pequeñas cantidades del sistema escrito en lenguaje ensamblador. De hecho, más de un nivel superior de lenguaje se usa a menudo. Los niveles más bajos del kernel podrían estar escritos
en lenguaje ensamblador y C. Las rutinas de nivel superior pueden escribirse en C y C ++, y las bibliotecas del sistema pueden estar escritas en C ++ o incluso en lenguajes de nivel superior. Android ofrece un buen ejemplo: su kernel está escrito principalmente en C con algo de lenguaje ensamblador. La mayoría de las bibliotecas del sistema Android están escritas en C o C ++ y sus marcos de aplicación, que proporcionan la interfaz de desarrollador
en el sistema: están escritos principalmente en Java. Cubrimos la arquitectura de Android en más detalles en la Sección 2.8.5.2.
Las ventajas de usar un lenguaje de nivel superior, o al menos una implementación del lenguaje del sistema, para implementar sistemas operativos son los mismos como las obtenidas cuando el lenguaje se usa para programas de aplicación: el código se puede escribir más rápido, es más compacto y es más fácil de entender y depurar.
Además, las mejoras en la tecnología del compilador mejorarán el código generado para todo el sistema operativo mediante una simple compilación. Finalmente, un sistema operativo es mucho más fácil de portar a otro hardware si está escrito en un lenguaje de nivel superior. Esto es particularmente importante para los sistemas operativos.
que están destinados a ejecutarse en varios sistemas de hardware diferentes, como pequeños dispositivos integrados, sistemas Intel x86 y chips ARM que se ejecutan en teléfonos y tabletas.
Las únicas desventajas posibles de implementar un sistema operativo en un lenguaje de nivel superior es que tiene una velocidad reducida y mayores requisitos de almacenamiento. Esto, sin embargo, no es un problema importante en los sistemas actuales. Aunque un experto programador de lenguaje ensamblador puede producir rutinas pequeñas y eficientes, para grandes programas un compilador moderno puede realizar análisis complejos y aplicar sofisticadas optimizaciones que producen un código excelente. Los procesadores modernos tienen un pipe o segmentación de cauce profundo y múltiples unidades funcionales que pueden manejar los detalles de dependencias complejas mucho más fácilmente que la mente humana.
Como ocurre en otros sistemas, las principales mejoras en el rendimiento de un sistema operativo es más probable que sean el resultado de mejores estructuras de datos y algoritmos que de excelente código en lenguaje ensamblador. Además, aunque los sistemas operativos son grandes, solo una pequeña cantidad del código es fundamental para un alto rendimiento; los manejadores de interrupciones, el administrador de E/S, el administrador de memoria y el planificador de CPU son probablemente las rutinas más críticas.Después de que el sistema está escrito y funciona correctamente, los cuellos de botella se pueden identificar y rehacer para operar más eficientemente.
2.8 Estructura del sistema operativo
Un sistema tan grande y complejo como un sistema operativo moderno se debe diseñar cuidadosamente para que funcione correctamente y se modifique fácilmente. Un enfoque común consiste en dividir la tarea en pequeños componentes o módulos, más bien que tener un solo sistema. Cada uno de estos módulos debe estar bien definido como porción del sistema, con interfaces y funciones cuidadosamente definidas. Puede usar un enfoque similar cuando estructura sus programas: en lugar de colocar todo su código en la función main (),separa la lógica en un número de funciones, articula parámetros claramente y devuelva valores, y luego llama a esas funciones de main ().
Discutimos brevemente los componentes comunes de los sistemas operativos en Capítulo 1. En esta sección, discutimos cómo estos componentes están interconectados y moldeados en un kernel (kernel).
2.8.1 Estructura monolítica
Figura 2.12 Estructura tradicional del sistema UNIX.
La estructura más simple para organizar un sistema operativo es ninguna estructura. Es decir, coloque toda la funcionalidad del kernel en un único archivo binario estático que se ejecute en un solo espacio de direcciones. Este enfoque, conocido como estructura monolítica: es una técnica común para diseñar sistemas operativos.
Un ejemplo de estructuración tan limitada es el sistema operativo original de UNIX, que consta de dos partes separables: el kernel y los programas del sistema. El kernel se separa con una serie de interfaces y manejadores de dispositivos, que se han agregado y ampliado a lo largo de los años a medida que UNIX ha evolucionado. Podemos ver el sistema operativo UNIX tradicional hasta cierto punto como en capas, como se muestra en la figura 2.12. Todo debajo de la interfaz de llamadas al sistema y encima del hardware físico está el kernel (kernel). El kernel proporciona el sistema de archivos, la planificación de la CPU, la administración de memoria y otras funciones del sistema operativo a través de llamadas al sistema. En suma, eso es un enorme cantidad de funcionalidad que se combinará en un solo espacio de direcciones.
El sistema operativo Linux se basa en UNIX y está estructurado de manera similar, como se muestra en la figura 2.13. Las aplicaciones suelen utilizar la biblioteca glibc estándar C cuando se comunica con el kernel a través de la interfaz de llamadas al sistemas. El kernel de Linux es monolítico porque se ejecuta completamente en modo kernel en un solo espacio de direcciones, pero como veremos en la Sección 2.8.4, tiene un diseño modular que permite que el kernel se modifique durante el tiempo de ejecución.
A pesar de la aparente simplicidad de los kernels monolíticos, son difíciles para implementar y extender. Los kernels monolíticos tienen una ventaja en el rendimiento, ya que hay hay muy poca sobrecarga en la interfaz de llamada del sistema, y la comunicación dentro del kernel es rápida. Por lo tanto, a pesar de los inconvenientes del kernel monolítico, su velocidad y eficiencia explican por qué todavía vemos esta estructura en los sistemas operativos UNIX, Linux y Windows.
2.8.2 Enfoque en capas
Figura 2.13 Estructura del sistema Linux.
El enfoque monolítico a menudo se conoce como un sistema fuertemente acoplado porque Los cambios en una parte del sistema pueden tener efectos de amplio alcance en otras partes. Alternativamente, podríamos diseñar un sistema débilmente acoplado. Tal sistema es dividido en componentes separados y más pequeños que tienen una funcionalidad específica y limitada. Todos estos componentes juntos comprenden el kernel. La ventaja de este enfoque modular es que los cambios en un componente afectan solo ese componente, y ningún otro, permitiendo a los implementadores de sistemas más libertad en crear y cambiar el funcionamiento interno del sistema.
Un sistema se puede hacer modular de muchas maneras. Un método es el enfoque estratificado, en el que el sistema operativo se divide en varias capas (niveles). La capa inferior (capa 0) es el hardware; el más alto (capa N) es la interfaz de usuario. Esta estructura de capas se representa en la figura 2.14.
Figura 2.14 Un sistema operativo en capas
Una capa del sistema operativo es una implementación de un objeto abstracto compuesto de datos y las operaciones que pueden manipular esos datos. Un sistema operativo típico por capas, por ejemplo, la capa M, consta de estructuras de datos y un conjunto de funciones que pueden ser invocadas por capas de nivel superior. La capa M, a su vez, puede invocar operaciones en capas de nivel inferior.
La principal ventaja del enfoque en capas es la simplicidad de la construcción y depuración. Las capas se seleccionan para que cada una use funciones (operaciones) y servicios de solo capas de nivel inferior. Este enfoque simplifica la depuración y verificación del sistema. La primera capa se puede depurar sin ninguna preocupación para el resto del sistema, porque, por definición, usa solo el hardware básico (que se supone correcto) para implementar sus funciones. Una vez que la primera capa está depurada, se puede suponer su correcto funcionamiento mientras la segunda capa está depurada, y así sucesivamente. Si se encuentra un error durante la depuración de una determinada capa, el error debe estar en esa capa, porque las capas de abajo ya están depuradas Por lo tanto, el diseño y la implementación del sistema se simplifican. Cada capa se implementa solo con operaciones proporcionadas por capas de nivel inferior. Una capa no necesita saber cómo se implementan estas operaciones;
solo necesita saber qué hacen estas operaciones. Por lo tanto, cada capa oculta la existencia de ciertas estructuras de datos, operaciones y hardware de capas de nivel superior.
Los sistemas en capas se han utilizado con éxito en redes informáticas (tal como TCP/IP) y aplicaciones web. Sin embargo, relativamente pocos sistemas operativos usan un enfoque de capas puras. Una razón involucra los desafíos de definir adecuadamente la funcionalidad de cada capa. Además, en general el rendimiento de tales sistemas es pobre debido a la sobrecarga de requerir un programa de usuario para atravesar múltiples capas para obtener un servicio del sistema operativo. Sin embargo, algunas capas son comunes en los sistemas operativos contemporáneos. En general, estos sistemas tienen menos capas con más funcionalidad, proporcionando la mayoría de las ventajas del código modularizado evitando los problemas de Definición de capa e interacción.
2.8.3 Microkernels
Ya hemos visto que el sistema UNIX original tenía una estructura monolítica. A medida que UNIX se expandía, el kernel se hizo grande y difícil de administrar. A mediados de la década de 1980, los investigadores de la Universidad Carnegie Mellon desarrollaron un sistema operativo llamado Mach que modularizó el kernel utilizando el enfoque microkernel. Este método estructura el sistema operativo eliminando todos los componentes no esenciales del kernel y su implementación como nivel de programas de usuario residen en espacios de direcciones separados. El resultado es un pequeño kernel.
Figura 2.15 Arquitectura de un microkernel típico.
Hay poco consenso sobre qué servicios deben permanecer en el kernel y que debe implementarse en el espacio de usuario. Típicamente, los microkernels proporcionan una gestión mínima de procesos y memoria, además de una facilidad de comunicación. La figura 2.15 ilustra la arquitectura de un típico microkernel.
La función principal del microkernel es proporcionar comunicación entre el programa cliente y los diversos servicios que también se ejecutan en espacio de usuario La comunicación se proporciona a través del envío de mensajes, que fue descrita en la Sección 2.3.3.5. Por ejemplo, si el programa cliente desea acceder a un archivo, debe interactuar con el servidor de archivos. El programa y

Continuar navegando