UNIVERSIDAD CENTROAMERICANA ANÁLISIS DEL SISTEMA OPERATIVO GNU/LINUX FACULTAD DE INGENIERÍA Y ARQUITECTURA LICENCIADO EN CIENCIAS DE LA COMPUTACIÓN

Tamaño: px
Comenzar la demostración a partir de la página:

Download "UNIVERSIDAD CENTROAMERICANA ANÁLISIS DEL SISTEMA OPERATIVO GNU/LINUX FACULTAD DE INGENIERÍA Y ARQUITECTURA LICENCIADO EN CIENCIAS DE LA COMPUTACIÓN"

Transcripción

1 UNIVERSIDAD CENTROAMERICANA JOSÉ SIMEÓN CAÑAS ANÁLISIS DEL SISTEMA OPERATIVO GNU/LINUX TRABAJO DE GRADUACIÓN PREPARADO PARA LA FACULTAD DE INGENIERÍA Y ARQUITECTURA PARA OPTAR AL GRADO DE LICENCIADO EN CIENCIAS DE LA COMPUTACIÓN POR: LAURA ROCÍO GIRÓN VELADO MARTHA ELIZABETH HUEZO PÉREZ OCTAVIO RIGOBERTO PARADA RAMÍREZ OCTUBRE 2009 ANTIGUO CUSCATLÁN, EL SALVADOR, C.A.

2

3 RECTOR JOSÉ MARÍA TOJEIRA, S.J. SECRETARIO GENERAL RENÉ ALBERTO ZELAYA DECANO DE LA FACULTAD DE INGENIERÍA Y ARQUITECTURA EMILIO JAVIER MORALES QUINTANILLA COORDINADOR DE LA CARRERA DE LICENCIATURA EN CIENCIAS DE LA COMPUTACIÓN GUILLERMO ERNESTO CORTÉS VILLEDA DIRECTOR DEL TRABAJO HÉCTOR POMPILIO ESCOBAR AMAYA LECTOR ROBERTO ALFONSO ABARCA JUÁREZ

4

5 AGRADECIMIENTOS Agradezco a Dios por permitirme terminar mi carrera y haberme dado la fuerza suficiente para luchar por alcanzar esta meta hasta el último momento; gracias Dios por la sabiduría que me diste en mis estudios y por estar siempre conmigo. A mi madre, Martha Lilian Velado, por amarme tanto, por haberme apoyado en la decisión de estudiar esta carrera y creer en mí en todo momento. Gracias por sus constantes oraciones, por sus palabras de aliento cuando más lo necesitaba y por enseñarme a no darme por vencida a pesar de las adversidades de la vida. Eres mi más grande tesoro en este mundo. A mi hermana Marcela Girón quien siempre me ha apoyado y me ha dado todo su cariño y quien me ha transmitido una gran fortaleza en cada momento de mi vida; gracias porque nunca has dejado que desista de mi sueños y metas, y por tus palabras de consuelo en momentos difíciles. No pude haber pedido mejor hermana en este mundo que tú. Te quiero sis! A mis abuelitas Filomena de Velado y Dinora Guillén a quienes amo con todo mi corazón, Gracias porque siempre me han motivado a seguir adelante con mi carrera universitaria. Son un hermoso regalo de Dios. A mi familia Velado quienes me han colmado de su amor a cada instante; gracias porque siempre estuvieron pendiente de mí y porque me demostraron que la familia es lo más importante en la vida y es quien nunca te abandona. Gracias a mi tía Sonia por quererme como una hija; por apoyarme en mi carrera moral y económicamente, y porque siempre me motivó a seguir adelante; gracias a mi tía Ana que siempre tuvo palabras lindas y sabias cuando me sentía deprimida; también a mi tía Mirna por sus consejos y por enseñarme que la vida siempre tiene circunstancias difíciles pero que nunca hay que dejar de luchar y de ser valiente. A nuestro director de tesis y amigo Ing. Héctor Escobar por enseñarme a siempre dar lo mejor de mí y a no conformarme con hacer las cosas bien, sino hacerlas lo mejor posible; gracias por sus sabios consejos, su constante apoyo, su comprensión y su confianza en mí. Por último, a mi gran amigo Octavio, a quien conozco desde hace muchos años, gracias por tu amistad sincera y desinteresada; me has enseñado a confiar siempre en Dios y a no perder nunca la fe y la perseverancia. Gracias porque en las peores circunstancias siempre lograbas sacarme una sonrisa y me devolvías la fe en que todo saldría bien. Te quiero mucho amigo. A todos mis familiares y amigos que me apoyaron en la carrera. Gracias Laura Rocío Girón Velado

6

7 AGRADECIMIENTOS Reconozco al altísimo Dios todopoderoso, porque sin su ayuda no podría escribir este párrafo, en el cual agradezco el estar culminando mis estudios superiores. Además el haberme brindado salud, sabiduría e inteligencia. Sabiduría para saber cual camino escoger y las decisiones que tomar y la inteligencia para desarrollarme en cada una de mis materias. Pero más le agradezco por mi núcleo familiar, el cual siempre me apoyo en los momentos más difíciles y sin el no hubiera podido lograr mi meta. Agradezco a mi madre por el amor incondicional que siempre me ha demostrado, por sus sabios consejos, por haberme guiado en el camino del bien, por haberme enseñado que cuando las cosas van mal lo mejor es seguir adelante y nunca mirar hacia atrás, por su comprensión, su apoyo y su insistencia firme de llevarme al triunfo. Te amo mami. A mi padre por sus consejos, por enseñarme que de los errores siempre se puede aprender algo bueno, por forjar en mi un carácter de perseverancia y valentía, por su apoyo y su amor durante todo este tiempo. A mi amigo y esposo de mi madre, que me enseño que todo lo puedo en Cristo que me fortalece, que en medio de la adversidad siempre tuvo una palabra de aliento, un consejo y me enseño que la oración del justo puede más que cualquier cosa en este mundo. A mi novio, al que Dios puso en mi camino, para que me ayudará y me apoyará con su amor en los momentos más difíciles y mostrará el carácter de paciencia cuando fue necesario, que con su simpatía y gracia logro alegrar mi corazón en momentos de tristeza y desconsuelo. A mi amiga María Martínez que gracias a su apoyo incondicional que sin ningún interés siempre estuvo pendiente cuando necesite de un consejo y de su apoyo financiero. A Don Luís Perera, quien me inspiro a seguir su ejemplo como profesional y a esforzarme a seguir adelante en las dificultades académicas, hasta lograr el objetivo. Gracias Dios por haber puesto a todas estas personas en mi camino ya que cada una de ellas fue parte importante en este logro. Con mucho amor Martha Elizabeth Huezo Pérez

8

9 AGRADECIMIENTOS Agradezco a Dios porque él me ha dado de su gracia sin la cual hubiese sido imposible llegar al final de esta carrera. A mis padres, Octavio y Evelyn, que son un ejemplo del vivir en Cristo y me han enseñado a enfrentar la vida con la cara en alto; a ver los problemas como una oportunidad para crecer y no rendirme ante ellos. Gracias papá por darme el ejemplo de lo que es un verdadero hombre, gracias mamá por tus incansables cuidos. Gracias a ambos por amarme incondicionalmente. Gracias Señor por la vida de mis padres, los amo. A mis hermanos, Rocío y Josué, por aceptarme tal como soy. Gracias Rocío por ser mi mejor amiga; alguien con quien siempre he podido contar, tú y Josué han sido para mí un ejemplo de dedicación y valor. Josué, no hubiera podido pedir a mejor hermano en el mundo. Los amo mucho. A mis abuelitos, Agüi, mami Carmen y papa Rigo, son personas muy importantes para mi. A mis pastores y amigos, Remberto Arévalo y Carolina de Arévalo, por su constante ejemplo de excelencia. A una persona cuyo nombre no mencionaré porque sé que se dará por aludida, gracias por tu amor y por tu amistad, eres mi inspiración. Te amo. A mis compañeras de este trabajo de graduación, Laura y Martha, por haber puesto el todo por el todo y no haberse rendido hasta finalizar. A mi amiga Laura, por brindarme su confianza y su amistad, y demostrarme que todavía hay verdaderos amigos con quien se pueda contar. A Martha, por siempre meter presión A mi familia y amigos que siempre han estado pendientes de mí y del desarrollo de este trabajo de graduación. Bendito sea Dios por sus vidas. Octavio Rigoberto Parada Ramírez

10

11 DEDICATORIA A Dios mi Padre quien me ha permitido terminar mi carrera universitaria A mi Madre y mi hermana Marcela quienes han sido un verdadero ejemplo de amor, sacrificio y dedicación en mi vida. Las amo. A mi padre José Ricardo Girón quien estoy segura que si estuviera aún con vida, estaría orgulloso de mí, te amo Padre y siempre estarás dentro de mi corazón. A todos aquellos que luchan por sus sueños y nunca se dan por vencidos, arriesgando todo para poder alcanzarlos. Laura Rocío Girón Velado Dedico este triunfo al único merecedor de toda gloria y honor, aquel que me dio la vida y que desde el momento en que mi madre me concibió a cuidado de mi, brindándome salud, energía, perseverancia, carácter, consuelo, sabiduría e inteligencia y poniendo personas en mi camino las cuales me ayudaron a lograrlo y sabiendo que de la mano de él y con su gracia podré seguir cosechando triunfos y dándole la gloria que solo él se merece. Este triunfo también lo dedico a la memoria de hermano, Edermir Mardoqueo Huezo Pérez, el cual fue mi mayor ejemplo de voluntad para salir adelante, quien a su corta de edad dejo de existir en esta tierra pero no en mi corazón, queriendo así compartir mi triunfo con él y demostrarle que en el tiempo que estuvo conmigo fue importante, ya que me enseño que no había nada que no pudiera lograr si me lo proponía, que no había que tener miedo al enfrentarme a lo nuevo, me mostró que el amor de un hermano es limpio, puro, sin envidia y que pase lo que pase ese amor permanecerá para siempre porque el siempre será mi hermanito. Martha Elizabeth Huezo Pérez A Dios, de quien toda mi vida e inteligencia dependen. A mis padres y hermanos, que son altamente importantes para mí. A todos los que amo. Octavio Rigoberto Parada Ramírez

12

13 RESUMEN EJECUTIVO La administración de la memoria es una actividad de alta importancia en el kernel del sistema operativo Linux, es por eso necesario el distinguir y saber clasificar los distintos tipos de memoria existentes en un sistema de computadora. Se toma como base para el estudio de la administración de memoria el triangulo de memoria de William Stalling, en el cual se describe la jerarquía de la memoria en un computador y ayuda a comprender de una mejor manera, las formas en que la memoria debe ser administrado dependiendo de su nivel en la pirámide. Luego se estudia el direccionamiento de la memoria desde la óptica de los microprocesadores Intel, para nuestro caso la familia 80x86. Aquí es notable la importancia que tiene la arquitectura del microprocesador la manera en que se direcciona la memoria y la forma en la cual tiene que ser administrada. Por lo cual, se estudia distintos elementos proporcionados por el microprocesador liderados por la unidad de segmentación y la unidad de paginación; unidades que tienen un papel protagónico en el direccionamiento de la memoria ya que el sistema operativo depende de ellas para poder emplear buenas técnicas de administración de la memoria. Después de comprender el funcionamiento de estas unidades, se podrá estudiar cómo el kernel del sistema operativo administra la memoria, comenzando por la administración de marcos de página y todas las funciones y macros relacionadas para poder realizar su correcta asignación y liberación. Se estudia la técnica utilizada por Linux para la asignación y liberación de los marcos de página conocida como el Sistema Compañero junto con todos los componentes involucrados para poder hacer realidad su implementación. También se aborda el algoritmo Slab Allocator que es utilizado en conjunto con el Sistema Compañero para administrar la memoria. Al igual que se observa como Linux administra las áreas de memoria contigua y no contigua, utilizando también y administrando a su vez la memoria cache interna del microprocesador. Por otra parte existen diversas actividades que un sistema operativo debe cumplir además de estas y una de estas tareas esenciales y que son uno de los objetivos principales que tiene Linux es el manejo de los procesos en el sistema, cada proceso se representa por una estructura de datos llamada task_struct. i

14 La estructura de datos task_struct es una estructura complicada sin embargo sus campos se pueden dividir en áreas funcionales: estado, información de planificación, identificadores, comunicación entre procesos, enlaces, tiempos y temporizadores, sistema de archivos, memoria virtual y el contexto específico del proceso. El estado de un proceso lleva a cabo sus cambios de estado de acuerdo a las circunstancias en las que se encuentra en un momento determinado por lo tanto los estados de un proceso Linux pueden ser: nuevo, listo, ejecución, espera, suspendido y zombie. Lo tratado en el apartado de procesos con respecto a planificación es lo que Linux utiliza para que el planificador pueda decidir imparcialmente que proceso en el sistema merece ejecutarse. Cada proceso en Linux cuenta con identificadores; un identificador de proceso es simplemente un número, además se tiene un identificador de grupo y un identificador de usuario, éstos se utilizan para controlar el acceso de estos procesos a archivos y dispositivos en el sistema. Otra área es la comunicación entre procesos, IPC, Linux soporta los mecanismos clásicos de señalización UNIX IPC, tuberías y semáforos y los mecanismos de memoria compartida del Sistema V IPC semáforos y colas de mensajes. En un sistema Linux ningún proceso es independiente de cualquier otro proceso, por lo que es importante información referente a enlaces de un proceso. Todo proceso en el sistema excepto el proceso inicial tiene un proceso padre. Los procesos nuevos no son creados, éstos son copiados o clonados de procesos previos. Cada task_struct representa un proceso que guarda apuntadores a su proceso padre, a sus hermanos y a sus procesos hijos. El kernel guarda el tiempo de creación de un proceso y el tiempo de CPU que consume durante su tiempo de vida por lo que en la estructura task_struct se debe tener también información acerca de tiempos y temporizadores. Los procesos pueden abrir y cerrar archivos como lo deseen task_struct contiene apuntadores a las descripciones de cada archivo abierto así como apuntadores a dos inodos VFS. Esta información se encierra en el área de sistema de archivos. Los procesos para dar la apariencia de ejecución paralela hacen uso de memoria virtual (los hilos y los demonios no), el kernel de Linux debe dejar huella de cómo se está mapeando la memoria virtual a la memoria física del sistema. Siempre que un proceso se esté ejecutando éste está ejecutando registros, stack, entre otros del procesador. Este es conocido como contexto de un proceso y cuando se suspende un proceso, ii

15 todos los contextos específicos del CPU se deben guardar en la estructura task_struct del proceso. Cuando un proceso se reinicia por el planificador su contexto se restablece. Además de la gestión de procesos también el sistema operativo tiene dentro del kernel una manera diferente de visualizar los archivos por lo que se hace necesario el estudio de archivos en Linux en el cual se habla sobre la base del sistema de archivo en Linux. Los archivos son la estructura que emplea el sistema operativo para almacenar información en un dispositivo físico como el disco duro, un CD-ROM o un DVD. Un archivo puede contener cualquier tipo de información, desde una imagen en formato PNG o JPEG. El sistema de archivos es la estructura que permite que Linux maneje los archivos que contiene. Un sistema de archivos permite realizar una abstracción de los dispositivos físicos de almacenamiento de la información para que sean tratados a nivel lógico, como una estructura de más alto nivel y más sencilla que la estructura de hardware particular. El sistema de archivos se caracteriza porque posee una estructura jerárquica, realiza un tratamiento consistente de los datos de los archivos, permite crear y borrar archivos así como un crecimiento dinámico de los archivos, y un factor muy importante, trata a los dispositivos y periféricos como si fueran archivos. El sistema de archivos utilizado por el kernel de Linux es el segundo sistema de archivos extendido (EXT2); las partes más importantes de este sistema de archivos son el bloque de arranque, la cual ocupa el primer sector de disco y contiene el código de arranque del sistema; el superbloque el cual contiene toda la información de un sistema de archivos; la tabla de inodos que se encuentra a continuación del superbloque, la cual tiene una entrada por cada archivo y en ésta se guarda una descripción del mismo; bloque de datos que comienzan a continuación de la tabla de inodos y utiliza el resto de sistema de archivos. Es en esta parte donde se encuentran el contenido de los archivos a los que hace referencia la tabla de inodos. Luego se estudia el sistema de archivos virtual VFS que es una capa de abstracción que permite a Linux soportar diferentes sistemas de archivos, los cuales son presentados como una interfaz de software común al sistema de archivos. Todos los detalles del sistema de archivos de Linux son traducidos mediante software en el kernel de forma que se ven idénticos en el kernel. Así mismo, el VFS permite al usuario montar de forma transparente distintos sistemas de archivos de forma virtual en Linux al mismo tiempo. A continuación se habla de la gestión de dispositivos de Entrada/Salida, para lo cual se necesita definir conceptos básicos de hardware como dispositivo de E/S y buses; así mismo, es importante iii

16 presentar la la arquitectura de E/S en los microprocesadores intel 80x86, y sus componentes principales como puerto de E/S, interfaz de E/S y controladores de dispositivo. Se habla de los elementos de hardware auxiliares que lo convierten en un subsistema autóctono dentro del bus así como el controlador 8237 El kernel del sistema operativo trabaja con el sistema de archivos a nivel lógico y no trata de forma directa con los dispositivos a nivel físico. Cada dispositivo se abstrae a nivel lógico denominándole el nombre de archivo de dispositivo. A los archivos de dispositivos se les asigna un número mayor y número menor, los cuales se utilizan para acceder al controlador del dispositivo. Un controlador se va a cargar de transformar las direcciones lógicas (kernel) de nuestro sistema de archivos a direcciones físicas del dispositivo. Se habla también de como Linux gestiona de forma diferente los dispositivos de carácter y los dispositivos de bloque y como en general el VFS maneja los archivos de dispositivo; y así como las operaciones de E/S de buffer y de página. Por último pero no menos importante se estudia el capítulo de interrupciones en el cual el objetivo de este es tratar de desarrollar el tema de manejo y tratamiento de las interrupciones, vistos desde el punto del sistema operativo. También se hace referencia al funcionamiento del hardware, ya que en la actualidad éste realiza muchas tareas para dar soporte al sistema operativo y al software propio del sistema de computación. Las interrupciones se clasifican en tres grupos: las interrupciones de hardware, las de software y de procesador. Los distintos sistemas operativos también le dan algunos nombres más específicos así para el sistema operativo estos tres tipos de interrupciones son tomados iguales, solo se diferencian en el origen que causa la interrupción, en el caso de las interrupciones de hardware siempre están relacionadas con los dispositivos o periféricos; las interrupciones de software son las que también se conocen como llamadas al sistema o syscall que le permiten a los programas de usuarios invocar al sistema operativo para utilizar alguno de los servicios que éste les brinda; las interrupciones de procesador son las que el propio procesador puede producir durante la ejecución de instrucciones propiamente dichas, entre los casos más comunes de interrupción o excepción de procesador se puede mencionar la división por cero, cuando una excepción de este tipo ocurre el sistema operativo debe tomar el control inmediatamente para tratarla. El mecanismo de interrupciones es esencial ya que, evita que el procesador encuentre a los dispositivos de E/S, (técnica conocida como Polling), lográndose un mejor uso de los recursos del sistema. iv

17 INDICE RESUMEN EJECUTIVO... i INDICE DE TABLAS... ix INDICE DE FIGURAS... xi SIGLAS... xv PRÓLOGO... xvii CAPÍTULO 1: INTRODUCCIÓN Historia y origen de Linux Concepto de núcleo y distribución Características del sistema operativo Linux Estructura de Linux Historia de Ubuntu Características de ubuntu Definición de la Investigación Antecedentes Objetivos Objetivo General Objetivos Específicos Alcances Límites Breve descripción de cada capítulo del Trabajo de Graduación Metodología CAPÍTULO 2: GESTIÓN DE MEMORIA Tipos de memoria Direccionamiento de la memoria Direcciones de memoria Segmentación en Hardware Registros de segmentación Descriptores de segmento Selectores de Segmento Unidad de Segmentación Segmentación en Linux Paginación en hardware Paginación regular Paginación de tres niveles Cache a bordo Búferes de Traducción Lookaside (TLB) Paginación en Linux Manejo de las tablas de página Marcos de página reservados Tablas de página de los procesos Tablas de Página del Kernel Tablas de Página provisionales del Kernel Tabla de Página Final del Kernel Administración de la memoria en Linux Administración de los marcos de página Solicitando y liberando marcos de página El algoritmo Sistema Compañero Estructuras de datos Asignando un bloque Liberando un bloque Administración de áreas de memoria El asignador de bloques... 45

18 Descriptor de la cache Descriptor de bloque Caches específicas y generales Enlazando el Slab Allocator con el Sistema Compañero Alojando un bloque a la cache Liberando un bloque de la cache Descriptor de objetos Asignando un objeto a la cache Liberando un objeto de la cache Objetos de propósito general Administración de áreas de memoria no contiguas Direcciones lineales de áreas de memoria no contiguas Descriptores de áreas de memoria no contiguas Asignando un área de memoria no contigua Liberando un área de memoria no contigua CAPÍTULO 3: PROCESOS EN LINUX Introducción Supervisión de procesos El comando ps (process status) El comando pstree El comando Top El comando htop Comando nice Jerarquía de procesos y ámbito Los Procesos Padre e Hijo Procesos en primer plano Procesos en segundo plano Pasar procesos en primer plano a segundo plano Pasar procesos en segundo plano a primer plano Estructura PCB de un proceso Campos importantes del PCB Procesos e Hilos Estados de un hilo Ejecución de un proceso El planificador Planificador de Largo Plazo Planificador de Corto Plazo Planificador de Mediano Plazo Señales (signals) Señales Estándar Comunicación entre procesos en Linux Pipes UNIX Semi-duplex Tuberías con Nombre (FIFO - First In First Out) Colas de Mensajes Semáforos Los procesos en memoria Memoria virtual de un proceso Paginación La paginación por demanda Swapping El TLB Memoria virtual compartida CAPÍTULO 4: ARCHIVOS EN LINUX Tipos de archivos: Archivos sencillos u ordinarios Directorios

19 Vínculos (enlaces) simbólicos Archivos especiales (dispositivos) Tuberías con nombre (FIFO) Estándar de directorios en Linux Otros Directorios comunes en sistemas Linux Rutas absolutas y relativas Sistema de archivos Linux Segundo sistema de archivos extendido (the second extended filesystem) EXT Características de ext2fs: Jornaling Estructura física de datos de un Sistema de archivos EXT2 en disco El superbloque EXT Estructura de datos del superbloque EXT Parámetros fijos: Descriptores de grupo EXT Tabla de descriptores de grupo Estructura de datos de un descriptor de grupo Mapa de bits de inodo y mapa de bit de bloques Tabla de inodos (Inode table) EXT Estructura de datos de un inodo Estructura de un inodo en memoria RAM Directorios EXT Estructura de datos de directorios EXT Sistema de Archivos Virtual (The Virtual File System) VFS Modelo común de archivos Sistema de llamadas manejadas por el VFS Estructura de datos en VFS El objeto superbloque (superblock object) Estructura de datos de objeto superbloque La estructura de datos de list_head Operaciones asociadas objeto superbloque El objeto inodo (Inode object) Estados de un inodo El objeto archivo (File Object) Operaciones de un objeto archivo Información específica de procesos La estructura de datos files_struct La estructura de datos llamada struct embedded_fd_set Archivos asociados con procesos El objeto de entrada de directorio (Directory entry object) Estados del objeto de entrada de directorio Operaciones asociadas a un objeto de entrada de directorio Principales objetos VFS y sus relaciones Montaje de un sistema de archivos Registrando un sistemas de archivos Montando-desmontando un sistema de archivos Estructura de montaje VFS CAPÍTULO 5: GESTIÓN DE DISPOSITIVOS DE E/S Arquitectura de E/S Tipo de Buses Puertos de E/S Interfaz de E/S Registros de interfaz de E/S: Controladores de dispositivos Asociando archivos con dispositivos de E/S Archivos de dispositivo:

20 Dispositivos de bloque VS dispositivos de carácter Controlador de dispositivo (device driver) Nivel de soporte de kernel Monitoreando operaciones de E/S Solicitando un IRQ El controlador de dispositivo de Memoria Local Controladores de dispositivos de carácter Controladores de dispositivos de bloque Elementos de archivos de dispositivos en inodos Operaciones de archivos estándar Representando archivos de caracter Manejando dispositivos de bloque Operaciones de E/S de buffer Solicitud de dispositivos de bloque Cache de Discos La buffer cache Cabezas de buffer (Buffer head) Estado de una cabeza de buffer La lista de cabezas buffer inutilizadas Lista de cabezas de buffer para buffers disponibles Lista de cabezas de buffer para buffers en cache La tabla hash de cabezas de buffer cacheadas Lista de cabezas de buffers asíncrona Asignación de buffers Escribiendo buffers sucios a disco CAPÍTULO 6: INTERRUPCIONES La IDT Conceptos para la comprensión del tratamiento de las interrupciones Las IRQ o interrupt request (Pedido de Interrupción) Controlador PIC Interrupciones de hardware Enmascarable No enmascarable Interrupciones por software Tratamiento de las interrupciones de hardware en Linux Bajo Linux, las interrupciones hardware son llamadas IRQs (abreviatura para interrupt Requests). Hay dos tipos de IRQs Las interrupciones se dividen a menudo en síncronas y asíncronas Ejecución anidada de manejadores de interrupciones y excepciones Softirqs y Tasklets Work Queues Estructuras de datos para soportar el sistema de interrupciones de hardware Finalización de las interrupciones RTLINUX Arquitectura de RTLinux CONCLUSIONES RECOMENDACIONES GLOSARIO REFERENCIAS BIBLIOGRAFÍA ANEXOS ANEXO A. ARQUITECTURA DE MICROPROCESADORES1 ANEXO B. DISCO DURO1

21 INDICE DE TABLAS Tabla 2.1 Banderas que describen el estado de un marco de página Tabla 3.1Descripción de algunos campos importantes del PCB Tabla 3.2 niveles de ejecución que se encuentran establecidos en inittab Tabla 3.3 Valor y acciones predeterminadas de las señales en POSIX utilizadas en Linux.[5] Tabla 3.4 Señales que fueron incorporadas en el estándar Posix [5] Tabla 3.5 Descripción de algunas señales (No hay de todas información) Tabla 4.1 Descripción de los campos que son listados a partir del comando ls il Tabla 4.2 Descripción de campos de superbloque Tabla 4.3 Descripción de campos de la estructura de de un descriptor de grupo Tabla 4.4 Campos de la estructura de un inodo en disco Tabla 4.5 Campos de la estructura de un directorio Tabla 4.6 Algunas llamadas al sistema soportadas por VFS Tabla 4.7 Descripción de los campos de objeto superbloque Tabla 4.8 Campos de la Estructura inodo Tabla 4.9 Descripción de los campos de un objeto inodo Tabla 4.10 Descripción de los campos de la estructura de objeto archivo Tabla 4.11 Campos de la estructura files_struct Tabla 4.12 Descripción de los campos de objeto de entrada de directorio Tabla 5.1 Los registros internos del Tabla 5.2 Algunos archivos de dispositivo Tabla 5.3 Campos de la estructura buffer_head Tabla 6.1 Indica el número y la descripción de interrupciones del microprocesador i Tabla 6.2 Muestra cuales de las patillas de los PIC están reservadas y cuales pueden Tabla 6.3 Representa las prioridades dentro del PIC para cada una de las interrupciones Tabla 6.4 Utilización de las softirq indicando su prioridad

22

23 INDICE DE FIGURAS Figura 1.1 Arquitectura de Linux... 4 Figura 2.1Triangulo de jerarquía de memoria [Stallings, 2003:p.100] Figura 2.2 Proceso de traducción realizado por el microprocesador Figura 2.3 Formato de un descriptor de segmento. [Bovet y Cesati, 2000: p.38] Figura 2.4: Funcionamiento de los registros no programables Figura 2.5: Traducción de una dirección Lógica a una dirección Lineal Figura 2.6: Inicialización de la GDT Figura 2.7: Habilitación de la unidad de paginación Figura 2.8: La paginación por los Intel 80x86. [Bovet y Cesati, 2000: p.45] Figura 2.9: La cache del microprocesador. [Bovet y Cesati, 2000: p.50] Figura 2.10: Llamada a la función que inicializa al kernel Figura 2.11: Modelo de paginación de tres niveles. [Bovet y Cesati, 2000: p.53] Figura 2.12 Mapa de memoria. [Bovet y Cesati, 2000: p.151] Figura 2.13 Estructuras de datos utilizadas por el Sistema Compañero. [Bovet y Cesati, 2000: p.157] Figura 2.14 Componentes del Slab Allocator. [Bovet y Cesati, 2000: p.162] Figura 2.15 Descriptores de cache y de bloque. [Bovet y Cesati, 2000: p.165] Figura 2.16 Descriptores de objetos externos. [Bovet y Cesati, 2000: p.169] Figura 2.17 Descriptores de objetos internos. [Bovet y Cesati, 2000: p.169] Figura 2.18 Intervalo de direcciones lineales comenzando desde PAGE_OFFSET Figura 3.1 Ejecución de comando ps Figura 3.2 Ejecución de comando pstree Figura 3.3Ejecución de comando top Figura 3.4: Ejecución del comando bg y utilización de comando Jobs Figura 3.5: Ejecución del comando fg Figura 3.6 El descriptor de procesos en Linux [Bovet y Cesati, 2000: p.542] Figura 3.7 Modelo de un proceso en memoria Figura 3.8 Estados de un proceso Figura 3.9 Representación de donde actuan los diferentes tipos de planificadores Niveles de planificación [planificación.gif, 2009: p.1] Figura 3.10 Comunicación de dos procesos con el kernel [6] Figura 3.11 Comunicación entre procesos padre hijo. [6] Figura 3.12 Comunicación en una sola vía. [6] Figura 3.13 Muestra la entrada a una tabla de página Figura 3.14 Niveles de tablas de página Figura 4.1 Estructura jerárquica de archivos [Negus, 2008:p.66] Figura 4.2 Estructura de una entrada de directorio. [Sarwar et al., 2003:p.151] Figura 4.3 Enlace duro a un archivo Figura 4.4 Lista de inodos asociados a un archivo Figura 4.5 Crear un enlace simbólico a un archivo específico Figura 4.6 Enlace simbólico a un archivo Figura 4.7 El estándar de directorios en Linux Figura 4.8 diseño de una partición EXT2, así como de un grupo de bloque EXT2. [Bovet y Cesati, 2000: p.498] Figura 4.9 Ubicación del superbloque en disco duro Figura 4.10 Apertura de un archivo a través de un inodo. [Sarwar et al., 2003: p.155] Figura 4.11 Punteros de un inodo hacia direcciones de bloque

24 Figura 4.12 El rol de VFS en una operación simple de copiar un archivo. [Bovet y Cesati, 2000: p.329] Figura 4.13 Archivo Proc/Version Figura 4.14 Forma en que los objeto superbloque son enlazados entre sí. [Bovet y Cesati, 2000: p.336] Figura 4.15 El campo f_array Figura 4.16 Interacción entre procesos y objetos VFS Figura 4.17 Consultando sistema de archivos por medio de mount Figura 4.18 Relación entre las estructuras vfsmount, super_block y file_system_type Figura 5.1 Arquitectura de E/S Figura 5.2 Interfaz de E/S Figura 5.3 Puertos de E/S especializados Figura 5.4 Circuito que se utiliza para producir las señales del canal de control en un sistema en el que se emplea DMA. [Brey, 2000: p.499] Figura 5.5 conexiones con terminales del circuito integrado 8237 [Brey, 2000: p.500] Figura 5.6 Conexión en cascada de varios Figura 5.7 Diagrama de bloque interno del controlador 8237 [Brey, 2000: p.500] Figura 5.8 Funcionamiento a nivel de hardware de un DMA Figura 5.9 Representación gráfica de un registro de página Figura 5.10 Esquema general de la relación entre los registros de página, buffer y bus de direcciones Figura 5.11 Archivos de dispositivos en Linux Figura 5.12 Modelo de capa de direccionamiento de periféricos. [Wolfgang, 2008: p.392] Figura 5.13 Diagrama de flujo de código de chrdev_open [Wolfgang, 2008: p.409] Figura 5.14 Acceso de la estructura inodo a la estructura cdev [Wolfgang, 2008: p.410] Figura 5.15 manejador de dispositivos de bloque para una arquitectura de operaciones de E/S de buffer. [Bovet y Cesati, 2000: p.395] Figura 5.16 El descriptor de peticiones, sectores y buffers. [Bovet y Cesati, 2000: p.404] Figura 5.17 Interacción de los manejadores de dispositivo de alto y bajo nivel con el VFS y el controlador de dispositivo Figura 5.18 Una página incluyendo cuatro buffer y sus respectivas cabezas de buffer [Bovet y Cesati, 2000: p.498] Figura 6.1 Representación de lo que ocurre cuando se produce una interrupción Figura 6.2 Representación de una IDT [7] Figura 6.3 Muestra los diferentes dispositivos conectados a los pines del PIC IRQ S[irq s,wikipedia,2006,p:1] Figura 6.4 Representación gráfica que realizan los diferentes entes involucrados cuando se genera una interrupción.[7] Figura 6.5 Ejemplo de anidamiento de caminos de control kernel.[8] Figura 6.6 Representación de las estructuras involucradas en el manejo de las interrupciones Figura 6.7 Flujo que sigue una interrupción en Linux.[7] Figura 6.8 Diagrama de las funciones involucradas para el llenado de la tabla. [7] Figura 6.9 Figura que muestra como se accede a la funcion que contiene el manejador de interrupción. [7] Figura A.1 Arquitectura del Microprocesador A-2 Figura A.2 Memoria Lógica... A-3 Figura A.3 Memoria Física... A-4 Figura A.4 Memoria de una PC... A-5 Figura B.1 Cilindro,cabeza y sector... B-4

25 Figura B.2 Estructura Interna de un disco duro... B-5 Figura B.3 Funcionamiento de un disco duro... B-7

26

27 AFS : Andrew File System CPL : Current privilege Level CPU : Central Processing Uni DMA : Direct Memory Access SIGLAS DMAC : Direct Memory Access Controller DPL : Descriptor Privilege Level DRAM : Dynamic Random Access Memory EISA : European Imaging and Sound Association EOI : Señal de Terminación del Servicio de Interrupción EXT : Extended Filesystem EXT2 : Second Extended Filesystem EXT3 : Third Extended Filesystem FAT : File Allocation Table GDB : GNU Project Debugger GDT : Global Descriptor Table IBM : International Business Machines ISA : Industry Standard Architecture JFS : Journaling File System IDT : Tabla Descriptora de Interrupciones IPC : Internet PC IRET : Interrupción de Retorno IRQ : Línea de Interrupción IMR : Registro de Mascara de Interrupciones IRR : Registro de Solicitud de Interrupción ISR : Registro de Interrupciones Activas LDT : Local Descriptor Table MCA : Micro Channel Architecture MO : Magnetic Optical NCP : Novell NetWare Core protocol NFS : Network File System NT : New Technology NTFS : NT File Systen PCB : Process Control Block PIC : Controlador de Interrupciones RAM : Random Access Memory SGI : Silicon Graphics Inc SMB : Server Message Block SMP : Multi-procesamiento simétrico, también llamada UMA, de Uniform Memory Access SRAM : Static Random Access Memory TLB : Translation Lookaside Buffers TSS : Task State Segment

28 VESA : Video Electronics Standards Association VFAT : Virtual File Allocation Table VFS : Virtual File System VLB VESA : Local Bus WORM : Write Once Read Many

29 PRÓLOGO Cuando se enciende un computador, muchas veces el usuario no se percata de la complejidad que éste desempeña en cada una de las tareas que realiza en el arranque del sistema, ni siquiera se da cuenta de la complejidad que existe dentro de sus dispositivos de hardware y software en cada una de las respuestas que obtenemos de este por medio del sistema operativo. Por lo que se ha considerado de suma importancia el estudio y análisis de los componentes básicos en un sistema operativo como lo son la estructura de los archivos, la gestión de la memoria y los procesos así como conocer la forma en que cada uno de ellos se relaciona entre sí para dar una respuesta optima a cada petición del usuario en el sistema con el fin de evaluar la eficiencia de los algoritmos de gestión de memoria, así como la estructura del sistema de archivos en disco y en memoria RAM, la administración y sincronización de los procesos y finalmente el tema de interrupciones que es necesario para ver como se relacionan cada uno de los dispositivos de hardware con el sistema operativo. De especial interés en este documento, es hablar sobre el sistema operativo Linux, ya que, por ser código abierto, nos permite tener mayor acceso al código del kernel y comprobar muchos conceptos que hasta el momento solo se han aceptado como válidos debido a que no había forma alguna de comprobar su veracidad. Por lo que se espera que este trabajo de graduación sea una herramienta que ayude a un mejor entendimiento de cómo funcionan los sistemas operativos y que le permita al lector identificar las fortalezas y debilidades de la estructura interna del sistema Linux y evalué por sí mismo, las ventajas y desventajas que el sistema ofrece en cuanto a los componentes que son de objeto de estudio en este documento.

30

31 1.1. Historia y origen de Linux CAPÍTULO 1: INTRODUCCIÓN Linux ha sido desde sus principios un sistema operativo completamente gratuito es la creación de Linus B. Torvalds. A comienzos de la década de 1990, Torvalds quiso crear su propio sistema operativo para su proyecto de graduación. Linus Torvalds intentó desarrollar una versión de UNIX que pudiera utilizarse en una arquitectura de tipo Linus Torvalds decidió ampliar las posibilidades de MINIX al desarrollar lo que se convertiría en Linux. Entusiasmados con esta iniciativa, diversas personas contribuyeron para ayudar a Linus Torvalds a hacer de su sistema una realidad. En 1991, la primera versión del sistema salió al mercado. En marzo de 1992 se distribuyó la primera versión, la cual no tenía prácticamente ningún error. Al aumentar la cantidad de desarrolladores que trabajaban en el sistema, éste integró rápidamente nuevos desarrollos gratuitos de herramientas disponibles en sistemas UNIX comerciales. Después, comenzaron aparecer nuevas herramientas para Linux con una velocidad increíble. La originalidad de este sistema radica en el hecho de que Linux no se desarrolló con fines comerciales. De hecho, no se copió ni una sola línea de código de los sistemas UNIX originales (en realidad, Linux se inspira en diferentes versiones comerciales de UNIX: BSD UNIX, System V). Por lo tanto, una vez creado, todos pueden usar Linux gratuitamente e incluso pueden mejorarlo. Si bien en un principio Linux se diseñó para ejecutarse en una plataforma de PC, se ha expandido (es decir, adaptado) para otras plataformas como Macintosh, estaciones SPARC, DEC Alpha e incluso plataformas como las que utilizan los asistentes personales (PDA), hasta consolas de videojuegos Concepto de núcleo y distribución Linux está estructurado alrededor de un núcleo (en inglés kernel) que es responsable de administrar el hardware. El término distribución se refiere al ensamblaje de un conjunto de software alrededor de un núcleo de Linux para brindar un sistema listo para utilizar. El núcleo de una distribución se puede actualizar para permitir la inclusión de hardware reciente. Sin embargo, este paso, que implica la recopilación del núcleo, es delicado ya que requiere de cierto nivel de conocimiento del sistema y hardware. La recopilación del núcleo se debe dejar a cargo de especialistas o usuarios que estén dispuestos a inutilizar su sistema con motivos de aprendizaje. La mayoría de las distribuciones propone también su propia instalación gráfica así como un sistema de administración de paquetes que permite la instalación automática de software por 1

32 medio de la administración de dependencias (en algunos casos, el software en Linux se vincula a bibliotecas externas o se basa en otro software). Cada distribución tiene sus ventajas y sus desventajas: De hecho, algunas son más adecuadas para principiantes y brindan interfaces gráficas sofisticadas, mientras que otras ponen énfasis en la seguridad y la capacidad de desarrollo. Las distribuciones más conocidas son: Red Hat; Debian; SuSe; Knoppix; Slackware; Mandriva. Ubuntu 1.2. Características del sistema operativo Linux Su licencia GPL. Garantiza que permanecerá LIBRE, lo que significa que los documentos que produzca siempre estarán disponibles y no son objeto de políticas corporativas ni decisiones que no se puedan controlar. Acceso a los códigos fuentes y derecho a modificación. Esto ayuda la participación de miles de programadores a mejorar y si es necesario modificar el software. Además es muy útil en el momento de eliminar errores o bugs y mejorar la seguridad. GNU/Linux es realmente un sistema operativo multiusuario, multitarea que permite que múltiples usuarios trabajen con múltiples aplicaciones. Hoy en día la mayoría de los servidores de empresas medianas y pequeñas se ejecutan sobre GNU/Linux. Es extremadamente estable, robusto, escalable y seguro. Puede ser actualizado sin necesidad de reiniciar y sus actualizaciones son fáciles. Su naturaleza de Libre permite que los administradores sepan con exactitud la capacidad de un programa y los riesgos de seguridad que presenta o puede presentar. Aplicaciones libres no mantienen secreto de marcas ni colectan información para asistirse de combatir la competencia. Un gran número de aplicaciones ya disponibles para su uso LIBRE con licencia GPL y gratuitas. Compatibilidad con aplicaciones comerciales privativas que ayudan a abaratar costos de operaciones, sin sacrificar calidad ni seguridad. 2

33 Entorno completamente gráfico para su fácil integración con usuarios que necesitan de este recurso pero no obligando a su uso para aumentar el consumo de recursos que aumentan nuestros presupuestos y no se traducen a productividad Estructura de Linux Linux consta de varios componentes de alto nivel responsables de la gestión de procesos, gestión de memoria, gestión de módulos, controladores de dispositivos, gestión y manejadores de sistemas de archivos, gestión de red, y otras tareas. Componentes tales como el Sistema de Archivos Virtual (VFS) y el componente de Red tienen una estructura de capas, mientras que los componentes responsables de la gestión de memoria, de procesos y gestión de módulos, utilizan demonios. La figura 1.1 muestra una visión general del sistema. Si bien esta figura puede parecer que muestra una estructura de capas en la mayoría de sus componentes, esto no es totalmente cierto. Si se desciende en el nivel de abstracción mostrado por la figura, y se estudia con más detalle las dependencias entre los principales componentes del sistema, lo que se denomina arquitectura concreta, se observa cómo las interacciones entre módulos son bastantes más complejas de lo que parece inicialmente. Por razones de simplicidad y eficiencia, el kernel de Linux, como la mayoría de sistemas Unix, es monolítico. Algunos de los problemas que presentan estas arquitecturas son abordados mediante la técnica de carga de módulos que permiten el enlazado de código objeto dentro del kernel en tiempo de ejecución. Un módulo consta de código objeto que puede ser enlazado, o eliminado, dinámicamente con el resto del código del kernel mediante el programa /sbin/insmod, que en realidad es un enlazador dinámico. También, existe un demonio kernel, que puede cargar un módulo durante la ejecución de otro componente que lo necesite. Linux utiliza este mecanismo para la implementación de manejadores de dispositivos, implementaciones de sistemas de archivos, y componentes de red. 3

34 Figura 1.1 Arquitectura de Linux La funcionalidad de algunos de estos módulos se describirá en cada uno de los siguientes capítulos, pero vale la pena mencionar que nuestro caso de estudio se basa en la distribución de Ubuntu, por lo que a continuación se presenta una pequeña reseña de la versión de Ubuntu. Ubuntu es una distribución Linux que ofrece un sistema operativo predominantemente enfocado a ordenadores de escritorio aunque también proporciona soporte para servidores. Basada en Debian GNU/Linux, Ubuntu concentra su objetivo en la facilidad de uso, la libertad en la restricción de uso, los lanzamientos regulares (cada 6 meses) y la facilidad en la instalación. Ubuntu es patrocinado por Canonical Ltd., una empresa privada fundada y financiada por el empresario sudafricano Mark Shuttleworth. El nombre de la distribución proviene del concepto zulú y xhosa de ubuntu, que significa "humanidad hacia otros" o "yo soy porque nosotros somos". Ubuntu es un movimiento sudafricano encabezado por el obispo Desmond Tutu, quien ganó el Premio Nobel de la Paz en 1984 por sus luchas en contra del Apartheid en Sudáfrica. El sudafricano Mark Shuttleworth, favorecedor del proyecto, se encontraba muy familiarizado con la corriente. Tras ver similitudes entre los ideales de los proyectos GNU, Debian y en general con el movimiento del software libre, decidió 4

35 aprovechar la ocasión para difundir los ideales de Ubuntu. El eslogan de Ubuntu Linux para seres humanos (en inglés "Linux for Human Beings") resume una de sus metas principales: hacer de Linux un sistema operativo más accesible y fácil de usar Historia de Ubuntu El 8 de julio de 2004, Mark Shuttleworth y la empresa Canonical Ltd. anunciaron la creación de la distribución Ubuntu. Ésta tuvo una financiación inicial de 10 millones de dólares. El proyecto nació por iniciativa de algunos programadores de los proyectos Debian, GNOME porque se encontraban decepcionados con la manera de operar del proyecto Debian, la distribución Linux sin ánimo de lucro más popular del mundo. De acuerdo con sus fundadores, Debian era un proyecto demasiado burocrático donde no existían responsabilidades definidas y donde cualquier propuesta interesante se ahogaba en un mar de discusiones. Asimismo, Debian no ponía énfasis en estabilizar el desarrollo de sus versiones de prueba y sólo proporcionaba auditorías de seguridad a su versión estable, la cual era utilizada sólo por una minoría debido a la poca o nula vigencia que poseía en términos de la tecnología Linux actual. Tras formar un grupo multidisciplinario, los programadores decidieron buscar el apoyo económico de Mark Shuttleworth, un emprendedor sudafricano que vendió la empresa Thawte a VeriSign, cuatro años después de fundarla en el garaje de su domicilio, por 575 millones de dólares estadounidenses. Shuttleworth vio con simpatía el proyecto y decidió convertirlo en una iniciativa auto sostenible, combinando su experiencia en la creación de nuevas empresas con el talento y la experiencia de los programadores de la plataforma Linux. De esta forma nació la empresa Canonical, la cual se encarga de sostener económicamente el proyecto mediante la comercialización de servicios y soporte técnico a otras empresas. Mientras los programadores armaban el sistema, Shuttleworth aprovechó la ocasión para aplicar una pequeña campaña de mercadotecnia para despertar interés en la distribución sin nombre (en inglés: the no-name-distro). Tras varios meses de trabajo y un breve período de pruebas, la primera versión de Ubuntu (Warty Warthog) fue lanzada el 20 de octubre de Características de ubuntu Basada en la distribución Debian. 5

36 Disponible en 4 arquitecturas: Intel x86, AMD64, PowerPC (hasta la versión 7.04) y SPARC (para esta última sólo existe la versión servidor). Los desarrolladores de Ubuntu se basan en gran medida en el trabajo de las comunidades de Debian y GNOME. Las versiones estables se liberan cada 6 meses y se mantienen actualizadas en materia de seguridad hasta 18 meses después de su lanzamiento. La nomenclatura de las versiones no obedece principalmente a un orden de desarrollo, se compone del dígito del año de emisión y del mes en que esto ocurre. La versión 4.10 es de octubre de 2004, la 5.04 es de abril de 2005, la 5.10 de octubre de 2005, y así sucesivamente. El entorno de escritorio oficial es GNOME y se sincronizan con sus liberaciones. Para centrarse en solucionar rápidamente los gusanos, conflictos de paquetes, etc. se decidió eliminar ciertos paquetes del componente main, ya que no son populares o simplemente se escogieron de forma arbitraria por gusto o sus bases de apoyo al software libre. Por tales motivos inicialmente KDE no se encontraba con más soporte de lo que entregaban los mantenedores de Debian en sus repositorios, razón por la que se sumó la comunidad de KDE distribuyendo la distribución llamada Kubuntu. De forma sincronizada a la versión 6.06 de Ubuntu, apareció por primera vez la distribución Xubuntu, basada en el entorno de escritorio Xfce. El navegador web oficial es Mozilla Firefox. El sistema incluye funciones avanzadas de seguridad y entre sus políticas se encuentra el no activar, de forma predeterminada, procesos latentes al momento de instalarse. Por eso mismo, no hay un cortafuego predeterminado, ya que no existen servicios que puedan atentar a la seguridad del sistema. Para labores/tareas administrativas en terminal incluye una herramienta llamada sudo (similar al Mac OS X), con la que se evita el uso del superusuario. Mejora la accesibilidad y la internacionalización, de modo que el software está disponible para tanta gente como sea posible. En la versión 5.04, el UTF-8 es la codificación de caracteres en forma predeterminada. No sólo se relaciona con Debian por el uso del mismo formato de paquetes Deb, también tiene uniones muy fuertes con esa comunidad, contribuyendo con cualquier cambio directa e inmediatamente, y no sólo anunciándolos. Esto sucede en los tiempos de lanzamiento. Muchos de los desarrolladores de Ubuntu son también responsables de los paquetes importantes dentro de la distribución de Debian. Todos los lanzamientos de Ubuntu se proporcionan sin costo alguno. Los CD de la distribución se envían de forma gratuita a cualquier persona que los solicite mediante el servicio ShipIt (la versión 6.10 no se llegó a distribuir de forma gratuita en CD, pero la 6

37 versión 7.04 sí). También es posible descargar las imágenes ISO de los discos por transferencia directa o bajo la tecnología Bittorrent. Ubuntu no cobra honorarios por la suscripción de mejoras de la "Edición Enterprise" Definición de la Investigación El siguiente documento es un estudio sistemático para comprender el funcionamiento de los sistemas operativos que utiliza como modelo GNU/Linux, específicamente la distribución Ubuntu 8.04, profundizando en su arquitectura interna, sus componentes básicos, administración de recursos e interacción con el usuario. Por años, GNU/Linux se concentró en producir un sistema operativo estable, seguro y robusto, a diferencia de Microsoft Windows y MacOS que se concentraron en producir sistemas operativos con capacidades gráficas superiores agradables a la vista del usuario mientras que el sistema operativo GNU/Linux permaneció principalmente en el Consola, su entorno de texto. Por este motivo, Windows atrajo más usuarios para su sistema a diferencia de GNU/Linux. GNU/Linux comenzó su estrategia para acercarse a los usuarios a través del diseño e implementación de una interfaz gráfica amigable sin perder de vista la robustez que lo caracteriza, esta nueva filosofía busca perfeccionarse en la distribución Ubuntu. Este trabajo de graduación desarrolla un estudio claro del funcionamiento y estructura interna del sistema operativo tomando como caso de estudio GNU/Linux, el cual cuenta con la principal característica de ser código abierto, debido a esto se obtiene suficiente información de distintas fuentes, se obtiene una recopilación que permite crear un documento confiable y de gran utilidad para describir el funcionamiento interno del Sistema Operativo. De este modo, este trabajo de graduación pueda servir de apoyo o referencia técnica para aquellos usuarios, informáticos o estudiosos en el tema que se han visto con la necesidad de relacionar los conceptos de teoría en los diversos libros de texto sobre sistemas operativos con la realidad del funcionamiento del sistema Antecedentes En principios se tuvo como preámbulo el trabajo de graduación llamado Drivers el cual fue realizado con el objeto de demostrar la forma en que se comunica el hardware con el software dentro del computador, pero se tuvo la limitante que no se conocía muchos de los aspectos relacionados con los sistemas operativos por lo que se dio paso a realizar una investigación correspondiente a los sistemas operativos. Es así como surge el anterior trabajo de graduación realizado en el ciclo II del 2008 que 7

38 corresponde al título de: Análisis de sistemas operativos en el cual se desarrollaron temas importantes en el estudio de los sistemas operativos profundizando en el sistema operativo Windows XP específicamente. Algunos de los apartados más destacados que se desarrollaron en el trabajo de graduación mencionado fueron: conceptos fundamentales de los sistemas operativos así como su estructura y evolución, el estudio de algunos componentes de hardware destacados, proceso de arranque, gestión de procesos, memoria, máquina virtual y seguridad. Algunos de estos apartados se retoman en el presente trabajo de graduación, aplicados al sistema operativo GNU/Linux. A este trabajo de graduación se le ha dado un nuevo enfoque de estudio, debido a que el trabajo de graduación anterior, desarrollado en base a Microsoft Windows 9X y XP, no pudo descubrir a fondo el comportamiento del sistema operativo ya que Microsoft, por sus políticas empresariales, no permite el acceso a toda la información necesaria para detallar a profundidad la estructura del sistema, la descripción de los procesos, y administración de algunos componentes debido a que es un sistema de código cerrado. GNU/Linux presenta una filosofía diferente dentro del mundo de los sistemas operativos; de los cuales se considera necesario destacar dos factores de alto interés para el desarrollo del trabajo de graduación: 1. Libre distribución: Se puede obtener el SO sin coste alguno y escoger la versión a conveniencia. 2. Código Abierto: Linux es desarrollado por una comunidad mundial de programadores lo que da lugar a que se encuentre mucha documentación disponible y poder profundizar en aspectos que serian secreto de estado en SO comerciales Objetivos Objetivo General Desarrollar un estudio a profundidad de los sistemas operativos para una mejor comprensión del funcionamiento del mismo, utilizando como modelo GNU/Linux a partir de la investigación de sus principales componentes y herramientas y así recopilar información que pueda servir a lector. 8

39 Objetivos Específicos Estudiar a profundidad las técnicas que utiliza GNU/Linux para la administración de la memoria. Identificar como el Sistema operativo Linux administra y sincroniza los procesos. Comprender el funcionamiento de manejo de archivos, servicios y permisos. Investigar las estructuras que Linux utiliza para el manejo de archivos. Dar a conocer como maneja las interrupciones el microprocesador y las estructuras que Linux utiliza para el manejo de las mismas. Conocer la estructura y manejo general de los controladores de dispositivos en Linux. Describir la arquitectura de entrada y salida de los microprocesadores Intel 80X Alcances Profundizar en el estudio de las técnicas utilizadas por GNU/Linux en cuanto a la administración de la memoria, aprovechando la abundante documentación acerca de este sistema operativo, conciliando así los conceptos teóricos con la realidad actual de la distribución Ubuntu; guiándonos por el conocido triangulo de memoria de William Stallings. Estudiar la existencia de varias herramientas que permiten ver y hacer el seguimiento de los proceso en Ubuntu Linux, entre las cuales se tienen dos en terminal que consiste en los comandos top, htop. De los cuales se dará detalle a lo largo de los capítulos de este trabajo de graduación. Conocer la estructura del directorio de archivos incluyendo permisos de usuarios. Así mismo investigar los diferentes tipos de servicios y su funcionamiento. Saber cuáles son las estructuras que Linux utiliza para el manejo de interrupciones, lo cual no se puede hacer con los demás sistemas operativos. Estudiar como la VFS (sistema de archivos virtual) interactúa con los controladores de Límites dispositivos de carácter y los controladores de dispositivos de bloque. Se dan a conocer solo los algoritmos que Linux utiliza para la administración de la memoria. En el anexo de microprocesadores se hace mención de cada uno de sus componentes y la manera cómo interactúan cada uno de ellos entre sí, se ofrece un estudio muy escueto de los mismos. En el capítulo de gestión de dispositivos de entrada y salida se habla del modo de trasferencia DMA en general. 9

40 En el capítulo de interrupciones se hace un estudio de poca profundidad del microkernel RTLinux Breve descripción de cada capítulo del Trabajo de Graduación 1. Sistema Operativo Linux: Este capítulo define el concepto de sistema operativo; su origen, evolución y estructura además los componentes y elementos que dan soporte al sistema operativo. 2. Estudio de Interrupciones: En este apartado se estudian los tipos de interrupciones, cuales son las razones por las que se generan, la manera en que el microprocesador las atiende y la forma en que GNU/Linux hace uso de ellas. 3. Manejo de Procesos: En este apartado se define qué es un proceso, la forma en que GNU/Linux lo gestiona y las diferentes herramientas que el Sistema Operativo proporciona para dicha gestión. 4. Gestión de la Memoria: Se aborda este capítulo iniciando con la distribución actual de los diferentes tipos de memoria, basándonos en el triangulo de Stallings. Asimismo se presentan los requisitos para una correcta administración de la memoria y cómo es administrada en GNU/Linux. También se estudia la Memoria Virtual, sus técnicas y políticas. 5. Gestión de Archivos: En este apartado se profundiza en el estudio del sistema de archivos de GNU/Linux para comprender y dar a conocer la gestión que éste da a los archivos, percibiendo de esta manera la peculiaridad de este sistema de archivos Metodología A cada integrante se le asignan temas de interés de acuerdo a las motivaciones personales de cada uno de ellos. Se presentan avances semanales del tema específico de investigación, al director de tesis en forma escrita o detallada. Se entrega documentación Oficial: Toda información obtenida de libros, documentos, sitios Web y foros oficiales de la comunidad GNU/Linux así como documentación respaldada por profesionales en el tema. Desarrollar pruebas utilizando GNU/Linux para corroborar conceptos generales de los sistemas. 10

41 CAPÍTULO 2: GESTIÓN DE MEMORIA La administración de la memoria es la actividad más compleja que realiza el kerne operativo. Por lo consiguiente, esto nos sugiere la relevancia que tiene la memoria como recurso principal dentro de éste. En este capítulo se estudia esta importante actividad, que al parecer su complejidad, está involucrada en cada tarea que un sistema operativo (para nuestro caso Linux Ubuntu) puede realizar; pero para hacerlo es necesario que se comprendan algunos conceptos fundamentales que están amarrados a la gestión de la memoria y que son vitales para poder estudiarla Tipos de memoria Primero, se debe de saber que existen distintos tipos de memoria y todos estos necesitan ser administrados. Para dar a conocer estos tipos y la relación que tienen con el sistema operativo; se hace uso de un diagrama conocido como el triángulo de jerarquía de memoria de William Stallings (véase figura 2.1). Mayor capacidad de almacenamiento Lento tiempo de acceso Figura 2.1Triangulo de jerarquía de memoria [Stallings, 2003:p.100] Como se puede ver, el triángulo está dividido horizontalmente en tres partes y se interpreta de la siguiente manera: mientras más se suba en la pirámide, el tiempo de acceso será más rápido pero la capacidad de almacenamiento se reducirá; y mientras más se baje en la pirámide, la capacidad de almacenamiento será mayor pero el tiempo de acceso será más lento. Es por eso que en el nivel más alto de la pirámide se tiene, en primer lugar, a los registros del microprocesador que representan el tiempo de acceso más rápido y la capacidad de almacenamiento más baja, seguidos por la memoria caché y la memoria principal (RAM). Este nivel es el de interés de análisis en este capítulo, aunque es posible que se tenga referencia a los niveles inferiores. 11

42 En el segundo nivel se tiene a los dispositivos de almacenamiento; encabezado por los discos duros seguido de los dispositivos ópticos. Aquí el tiempo de acceso es significativamente más lento pero se incrementa la capacidad de almacenamiento con respecto al primer nivel. En el tercer nivel se encuentran los dispositivos de almacenamiento masivo; comenzando por las cintas magnéticas pasando por los discos MO (Magnetic-Optical por sus siglas en Inglés) y llegando hasta los WORM (Write Once Read Many por sus siglas en Inglés). Donde la capacidad de almacenamiento prevalece sobre el tiempo de acceso. Como se mencionó anteriormente, en este capítulo se centrará en el primer nivel de la pirámide. Para esto, es necesario introducirse al hardware y observar la manera en que éste direcciona la memoria Direccionamiento de la memoria Es necesario saber que hoy en día los microprocesadores incluyen hardware integrado especializado, con el fin de ayudar a los sistemas operativos a administrar la memoria; porque debido a su complejidad esta se volvería una actividad insostenible si solo el sistema operativo lidiara con ella. A causa de esto es necesario que se estudien las técnicas de direccionamiento implementadas por las unidades de hardware provistas por el microprocesador, y la forma en que Linux saca provecho de ellas. Esto deja entredicho que la administración de la memoria, es altamente dependiente de la arquitectura del microprocesador ya que el sistema operativo deberá adaptarse a las reglas que éste imponga para el direccionamiento y administración de la memoria. En nuestro caso, se estudiarán las unidades provistas por el microprocesador Intel 80x Direcciones de memoria Un programador percibe una dirección de memoria como la manera de acceder al contenido de una celda de memoria; pero no es tan simple cuando se habla de cómo los modelos de microprocesadores Intel 80x86 definen una dirección de memoria ya que para ellos hay que diferenciar entre tres tipos de memoria: Direcciones Lógicas Incluidas en las instrucciones de lenguaje de máquina (ensamblador) para especificar la dirección de un operando o de una instrucción. Están compuestas por un identificador de segmento y un desplazamiento que denota la distancia desde el comienzo de un segmento hasta la dirección actual. 12

43 Direcciones Lineales Son enteros de 32 bits sin signo capaces de direccionar hasta 4GB de memoria. Usualmente son representadas en notación hexadecimal; cuyos rangos van desde 0x hasta la 0xffffffff. Direcciones Físicas Utilizadas para direccionar las celdas de memoria de los chips de memoria. Corresponden a las señales eléctricas enviadas a través de los pines de direcciones del microprocesador al bus de direcciones la memoria. Son representadas por un entero sin signo de 32 bits. La CPU traduce una dirección lógica a una dirección lineal a través de un circuito llamado unidad de segmentación; sucesivamente, traduce una dirección lineal a una dirección física a través de otro circuito llamado unidad de paginación (véase figura 2.2). Dirección Lógica Unidad de Segmentación Dirección Lineal Unidad de Paginación Dirección Física Figura 2.2 Proceso de traducción realizado por el microprocesador Ahora se puede ver en detalle los componentes y la forma en que trabajan ambas unidades y cómo Linux se beneficia de ellas Segmentación en Hardware Comenzando con el modelo 80386, los microprocesadores Intel llevan a cabo la traducción de direcciones en dos vías llamadas modo real y modo protegido. Este capítulo se enfoca en el modo protegido debido a que el modo real se conserva únicamente para mantener la compatibilidad con otros procesadores; cabe mencionar que cuando la máquina se inicializa, todas las rutinas básicas se ejecutan en modo real; en este modo no existen direcciones lógicas, lineales o físicas; simplemente existen direcciones llamadas de modo real que tienen una estructura diferente a las antes mencionadas Registros de segmentación Se necesita conocer lo que el microprocesador nos ofrece para esta importante actividad; primero, se debe recordar que una dirección lógica está compuesta de dos partes los primeros 16 bits son un identificador de segmento conocido como el selector de segmento y los 32 bits restantes serán el desplazamiento. Para facilitar la extracción de los selectores de segmento de manera rápida, el procesador provee de unos registros llamados registros de segmentación cuyo único propósito es el de almacenar 13

44 selectores de segmento; los primeros tres, el CS, SS y DS, tienen un propósito definido; el primero es el registro de segmento de código y contiene a un selector de segmento que apunta a un segmento que contiene instrucciones de programa, el segundo es el registro de segmento de pila y su selector de segmento apunta a un segmento que contiene la pila del programa actual y el tercero es el de datos que apunta a un segmento que contiene datos estáticos o externos. Los tres registros restantes, ES, FS y GS, son para propósitos generales y pueden contener selectores de segmento que apunten a un segmento cualquiera. Si bien solo se proveen seis registros éstos pueden ser reutilizados por un programa haciendo el paso de registro a memoria y de memoria a registro. También es necesario que se conozca que el registro CS tiene una función muy especial; este registro incluye un campo de 2 bits que especifica el nivel de privilegio actual CPL (Current Privilege Level por sus siglas en Inglés) de la CPU, donde 0 es el nivel de privilegio más alto y 3 el más bajo. Esto es importante tenerlo en mente debido a que Linux, que es nuestro objeto de estudio, solo utiliza dos de estos niveles el 0 conocido como Modo Kernel y el 3 Modo Usuario. El nivel en que se encuentre este campo CPL afectará el contenido de los registros de segmentación Descriptores de segmento Un descriptor de segmento es un entero de 8 bytes que describe las características del segmento al que representa. Cada segmento es representado por un descriptor de segmento y éstos son almacenados ya sea en una Tabla Global de Descriptores (GDT por sus siglas en Inglés), cuya dirección física en memoria viene dada por el registro gdtr del microprocesador, ó en una Tabla Local de Descriptores (LDT por sus siglas en Inglés) direccionada por el registro ldtr también del microprocesador. La figura 2.3 ilustra el formato de un descriptor de segmento. Figura 2.3 Formato de un descriptor de segmento. [Bovet y Cesati, 2000: p.38] A continuación se explica cada uno de los campos que componen al descriptor de segmento: 14

45 Base: Es un campo de 32 bits que contiene la dirección lineal del primer byte del segmento. G: Es una bandera de granularidad que si esta a cero expresa el tamaño del segmento en bytes; de lo contrario lo hace en múltiplos de 4096 bytes. Limite (Limit): Este campo de 20 bits denota el tamaño del segmento en bytes y trabaja en conjunto con la bandera G. S: Bandera del sistema, que si esta a cero, el segmento es un segmento del sistema que contiene las estructuras de datos del kernel; si no lo está, entonces es un segmento normal de código o datos. Tipo (Type): Campo de 4 bits que define el tipo de segmento y sus permisos de acceso. DPL (Descriptor Privilege Level): Es un campo de dos bits utilizado para restringir el acceso al segmento y trabaja juntamente con el campo CPL del registro de segmentación CS que se mencionó anteriormente. Segmento Presente (Segment-Present): Es una bandera que está puesta en cero si el segmento no está actualmente almacenado en memoria. Es importante saber que Linux siempre pone esta bandera a 1 ya que nunca mueve segmentos completos a disco. D ó B: Es una bandera que se llama dependiendo si el segmento contiene código o datos. Bit 53: Es un bit reservado siempre puesto en 0. AVL: Bandera que está reservada para el sistema operativo, pero Linux no hace uso de ella Selectores de Segmento Para que la traducción de direcciones lógicas a físicas no sea lenta, Intel, en este caso el procesador 80386, provee adicionalmente seis registros no programables; esto quiere decir que no pueden ser modificados por un programador, para que formen pareja con cada uno de los seis registros de segmentación. Cuál es el propósito de estos registros? Figura 2.4: Funcionamiento de los registros no programables. 15

46 Cuando un selector de segmento es cargado en un registro de segmentación, éste tiene que buscar su correspondiente descriptor de segmento en la tabla de descriptores, que podría ser la GDT o la LDT, pero cuando se accede a la tabla de descriptores se está accediendo a la memoria principal cuyo tiempo de acceso es más lento que el tiempo de acceso a los registros del microprocesador. Es aquí donde entran en juego los registros no programables; éstos sirven para que el acceso a la tabla de descriptores sea únicamente cuando el contenido de un registro de segmentación cambie, Cómo? Cuando un selector de segmento es cargado en un registro de segmentación su correspondiente descriptor de segmento es ubicado en la tabla de descriptores y una vez encontrado es cargado en el registro no programable y es posible tenerlo a disposición de futuros accesos; esto acelera la traducción a la velocidad del microprocesador (véase figura 2.4) Unidad de Segmentación La unidad de segmentación es un hardware incluido por los microprocesadores Intel 80x86, y es el que hace uso de todos los componentes que hasta ahora se ha visto en este capítulo de la siguiente manera (véase figura 2.5): Primero, examina el campo TI del selector de segmento hay que recordar que una dirección lógica está compuesta por un identificador de segmento llamado selector de segmento de 16 bits, el cual se divide así: a los 13 bits más significativos se les llama índice (index) y a los 3 bits restantes Indicador de Tabla (TI) cuya información nos indica en qué tabla de descriptores se debe buscar (si en la GDT ó LDT) y extrae la dirección del gdtr o ldtr según corresponda. Calcula la dirección del descriptor de segmento multiplicando el campo índice del selector de segmento por 8, que es el tamaño del descriptor, sumando el resultado al contenido del gdtr o ldtr. Suma al campo Base del descriptor de segmento el desplazamiento de la dirección lógica y se obtiene la dirección lineal. Figura 2.5: Traducción de una dirección Lógica a una dirección Lineal. 16

47 2.5. Segmentación en Linux Después de conocer la unidad de segmentación provista por el microprocesador 80386, se debe conocer cómo Linux la utiliza para llevar a cabo la segmentación a nivel de sistema operativo pero es importante saber que Linux solo hace uso de la segmentación cuando lo requiere la arquitectura de la familia Intel 80x86 y prefiere la paginación sobre ésta por dos razones: 1. La administración istración de la memoria es mucho más simple cuando se usan los mismos valores de los registros de segmentación, esto es, cuando comparten el mismo conjunto de direcciones lineales. 2. Uno de los objetivos de Linux es la compatibilidad con las arquitecturas más populares y muchas de estas arquitecturas soportan la segmentación de manera muy limitada. De hecho, en Linux, todos los procesos usan el mismo espacio de direcciones lógicas y por ende el mismo conjunto de direcciones lineales, por lo que el número total de segmentos que se van a definir obviamente será muy bajo y será posible almacenar todos los descriptores de segmento en la GDT. Esta tabla es inicializada por la instrucción en ensamblador lgdt (véase figura 2.6). Figura 2.6: Inicialización de la GDT Lo que hace esta instrucción es cargar en el registro gdtr la dirección de la Tabla Global de Descriptores. En esta figura 2.7, la GDT ha sido proporcionada por el archivo ensamblador Trampoline.s y este fragmento de código pertenece al archivo ensamblador Head_32.s. Se hace la aclaración: el kernel de Linux no hace uso de las LDT pero permite que los procesos creen una propia. Linux utiliza los siguientes segmentos para llevar a cabo esta tarea junto con la unidad de segmentación: Segmento de código del kernel. Segmento de datos del kernel. Segmento de código de usuario. Que es compartido por todos los procesos. Segmento de datos de usuario. También compartido por todos los procesos. Segmento de estado de tarea (TSS) por cada procesos. 17

48 Segmento LDT que usualmente también es compartido por todos los procesos. Ahora todos estos segmentos tienen la misma estructura Qué es lo que los hace diferentes entre ellos? Es la información contenida en cada uno de sus campos la que los define y les da su identidad en el kernel. A su vez, se debe recordar que el campo CPL del registro CS contiene el nivel de privilegio actual, este campo refleja si el procesador esta en modo kernel o modo usuario; cuando el contenido de este registro cambia, es decir, cuando se pasa de un modo a otro; algunos registros de segmentación deben de ser actualizados. Por ejemplo, cuando se pasa de modo usuario a modo kernel debe de ser cargado en el registro ds el segmento de datos del kernel en vez del segmento de datos de usuario, esto es lo que debe suceder cuando se cambia el CPL de 3 a Paginación en hardware Hasta ahora se ha visto cómo la unidad de segmentación se encarga de hacer la traducción de una dirección lógica a una dirección lineal. Ahora se puede estudiar la forma en que se traduce una dirección lineal a una dirección física; traducción que lleva a cabo la unidad de paginación, pero para esto se tiene que introducir dos nuevos conceptos: Página: Por razones de eficiencia, las direcciones lineales son agrupadas en intervalos de longitud fija, lo que se conoce como página. Se le llama página tanto al grupo de direcciones que la conforman como a los datos contenidos en ellas. Marco de página: La unidad de paginación mira toda la RAM en longitudes fijas llamadas marcos de página, también llamados páginas físicas, cada marco de página contiene a una página. De allí que la longitud de un marco de página coincide con lo que es una página. Es importante no confundir el concepto de una página y un marco de página; el primero es un bloque de datos mientras que el segundo está constituido de memoria principal y por lo tanto es un área de almacenamiento en donde se guardará el bloque de datos que es la página. También se hará uso de una estructura llamada Tabla de Página que es la que servirá para mapear direcciones lineales en direcciones físicas, estas son almacenadas en la memoria principal y deben de inicializarse correctamente por el kernel antes de habilitar la unidad de paginación. La paginación se habilita en los procesadores Intel configurando la bandera PG del registro CR0. Cuando PG=0 las direcciones lineales son interpretadas directamente como direcciones físicas. 18

49 Figura 2.7: Habilitación de la unidad de paginación. En el fragmento de código de la figura 2.7 se puede ver cómo es habilitada la unidad de paginación modificando icando el bit PG del registro CR0 del microprocesador Paginación regular También conocida como paginación de dos niveles. Comenzando con el i386, la unidad de paginación de los procesadores Intel manejan páginas de 4KB y divide los 32 bits de la dirección lineal en tres campos: Directorio: o: Los 10 bits más significativos. Tabla: Los 10 bits intermedios. Desplazamiento: Los 12 bits menos significativos. La traducción de las direcciones lineales en este modelo se lleva a cabo en dos pasos, cada uno basado en un tipo de tabla de traducción. La primera llamada Directorio de Página y la segunda Tabla de Página. La dirección física del Directorio de Página que está siendo actualmente utilizado se encuentra en registro CR3. Y el campo Directorio de la dirección lineal determina la entrada en el Directorio de Página que apunta a la Tabla de Página correspondiente. El campo Tabla de la dirección, determina la entrada en la Tabla de Página que es la que contiene la dirección física del marco de página que contiene a la página. Y el desplazamiento determina ermina la posición relativa en el marco de página. Por ser el desplazamiento de 12 bits, cada página consiste de 4096 bytes de datos (véase Figura 2.8). 19

50 Figura 2.8: La paginación por los Intel 80x86. [Bovet y Cesati, 2000: p.45] Tanto las entradas del Directorio de Página como el de la Tabla de Página tienen la misma estructura que incluye los siguientes campos: Presente (present): Bandera que indica si la página referida (o tabla de página) está contenida en memoria. Campo que contiene los 20 bits más significativos de la dirección física de un marco de página. Accedido (accesed): Bandera que se pone en 1 cada vez que la unidad de paginación direcciona el marco de página correspondiente. Esta bandera tiene que ser usada obligadamente por el sistema operativo cuando éste selecciona páginas para trasladarlas a disco. Sucia (dirty): Bandera válida solo para las entradas de las tablas de página. Puesta en 1 cada vez que se realiza una operación de escritura sobre un marco de página. Leer/Escribir (Read/Write): Bandera que contiene los permisos de acceso de una página o de la tabla de página. Usuario/Supervisor (User/Supervisor): Bandera que contiene el nivel de privilegio requerido para acceder a la página o a la tabla de página. PCD y PWT: Banderas que controlan la manera en que la página o la tabla de página son manipuladas por la caché. Tamaño de Página (Page Size): Bandera que solo aplica para las entradas de los Directorios de Página. 20

51 Si la entrada de una Tabla de Página o Directorio de Página necesita realizar una traducción tiene la bandera Present a cero. La unidad de paginación almacena la dirección lineal en el registro CR2 y genera la excepción 14 que es conocida como la page fault, esta excepción es importante porque Linux trabaja con un sistema de administración de memoria llamado demand paging en el cual no es necesario que una página este en memoria para que sea referenciada; sino que al momento de generada la excepción se carga en memoria la página solicitada Paginación de tres niveles La paginación de dos niveles es utilizada por las arquitecturas de 32 bits, pero últimamente muchos microprocesadores han adoptado una arquitectura de 64 bits. Es por la misma razón por la cual Linux utiliza de manera pobre la segmentación que también prefiere la paginación de tres niveles a la de dos niveles. Pero antes de ver el funcionamiento de este modelo de paginación, con el cual se entrará en detalle cuando se vea como lo hace directamente Linux, se necesita ver qué injerencia tienen los circuitos de memoria caché en estos procesos de traducción Cache a bordo Se debe conocer que actualmente los microprocesadores tienen tazas de reloj en el orden de los gigahertz, mientras que la RAM dinámica tiene tiempos de acceso de decenas de ciclos de reloj. Esta diferencia es negativamente significativa a la hora de que el microprocesador interactúa con la RAM para extraer/insertar datos en ella, no obteniendo el máximo provecho de la velocidad de la CPU. Es por eso que las memorias cache a bordo fueron introducidas para reducir esa marcada diferencia de velocidad entre la CPU y la RAM, basadas en el principio de localidad, que es la tendencia a referenciar elementos de memoria cercanos a los últimos que han sido referenciados, gracias a este principio se puede introducir una memoria pequeña pero de rápido acceso que contenga los códigos y datos utilizados con más frecuencia. Es por esto que los microprocesadores Intel introducen una nueva unidad llamada la línea; esta consiste de algunas docenas de bytes contiguos que son transferidos en modo burst entre la DRAM y la SRAM utilizada para implementar la cache. La cache se subdivide en subconjuntos de líneas. La figura 2.9 nos muestra el papel que juega la cache en el proceso de traducción. 21

52 SRAM cache CPU Unidad de paginación Memoria principal DRAM Controlador de la cache Figura 2.9: La cache del microprocesador. [Bovet y Cesati, 2000: p.50] La memoria cache en este diagrama almacena las líneas de memoria actuales mientras que el controlador de la cache almacena un arreglo de entradas, una por cada línea de la memoria cache. Cada entrada incluye una etiqueta y unas cuantas banderas que describen el estado de la línea cache; la etiqueta le permite al controlador de la cache reconocer la localidad de memoria actualmente mapeada por la línea. De allí que los bits de la dirección física de memoria se divide en tres grupos: los más significativos corresponden a la etiqueta, los intermedios son un subconjunto índice del controlador de la cache y los menos significativos son el desplazamiento. Cuando se accede a una celda de memoria RAM, el procesador extrae el subconjunto índice de la dirección física y compara las etiquetas de todas las líneas en el subconjunto con bits de orden superior de la dirección física. Si la etiqueta de una línea coincide con los bits de orden superior de la dirección, la CPU tiene entonces un cache hit; de otra manera, sería un cache miss. La bandera CD del registro CR0 (este mismo registro nos sirve para habilitar o deshabilitar la unidad de paginación a través de su bandera PG) es utilizada para habilitar o deshabilitar el circuito caché. La bandera NW, del mismo registro, especifica si la cache está utilizando una estrategia write-through o write-back. Una estrategia write-through es aquella que cuando se realiza una operación de escritura, esta escribe tanto en la RAM como en la cache, mientras que en una write-back solo la cache es escrita y la RAM no se toca de manera inmediata, ya que luego la RAM necesitará ser actualizada. Ahora aquí viene lo interesante de la cache integrada en la arquitectura de los Intel, y es que ésta deja que el sistema operativo asocie una política de administración diferente por cada marco de página. Es para esto que se utilizan las banderas PCD y PWT, incluidas en las entradas del Directorio de Página y Tabla de Página. La PCD indica cuando la cache debe estar habilitada o deshabilitada mientras se accede a los datos incluidos en el marco de página; la PWT especifica cuando una estrategia write-throuhg o write-back debe ser aplicada mientras se está escribiendo datos en el marco de página. Linux pone en cero las banderas PCD y PWT de todas las entradas de los Directorios de Página y Tablas de Página; y como resultado, el cacheo permanece 22

53 habilitado para todos los marcos de página y la estrategia write-back es siempre adoptada para una operación de escritura. Para optimizar la tasa de cache hits, el kernel de Linux adopta las siguientes reglas: Los campos de una estructura de datos utilizados con mayor frecuencia son ubicados en el desplazamiento bajo de la estructura de datos para que puedan ser cacheados en la misma línea. Cuando se está asignando un conjunto grande de estructuras de datos, el kernel trata de almacenar cada uno de ellos en memoria para que todas las líneas cache sean uniformemente usadas Búferes de Traducción Lookaside (TLB) Además de los caches de propósitos generales, los Intel 80x86 proveen otros caches llamados TLB para acelerar la traducción de direcciones lineales (este es un mecanismo parecido a los registros no programables cuando se está llevando a cabo la segmentación). Cuando una dirección es utilizada por primera vez, la correspondiente dirección física es calculada a través de accesos lentos a la tabla de página en RAM. La dirección física es entonces almacenada en la entrada de la TLB, para que en futuras referencias a la misma dirección lineal esta pueda ser traducida más rápido. En algunos casos, como durante la inicialización del kernel, es necesario invalidar todas las entradas de las TLB; esto se logra sobrescribiendo el registro CR3 que apunta al directorio de página utilizado actualmente, esto lo hace una función llamada flush_tlb() que es invocada por el kernel. Después de haber visto las facilidades que proporciona el microprocesador a los sistemas operativos y después de haber arrancado tanto la unidad de segmentación como la unidad de paginación (y claro después de haber hecho las inicializaciones correspondientes ajenas a la memoria), es hora de darle el control al kernel de Linux, lo cual en código ensamblador se hace con la siguiente instrucción (véase figura 2.10). Figura 2.10: Llamada a la función que inicializa al kernel 23

54 2.7. Paginación en Linux Como se dijo anteriormente, Linux adopta un modelo de paginación de tres niveles y lograr compatibilidad con las arquitecturas de 64 bits. La figura 2.11 muestra por qué se le llama de tres niveles. Figura 2.11: Modelo de paginación de tres niveles. [Bovet y Cesati, 2000: p.53] Se definen tres tipos de tablas de paginación: Directorio Global de Página: Contiene las direcciones de muchos Directorios Medios de Página. Directorio Medio de Página: Contiene las direcciones de muchas Tablas de Página. Tabla de Página: Cada una de sus entradas apunta a un marco de página. La dirección lineal es partida en cuatro partes no fijas, ya que el tamaño de cada porción depende de la arquitectura del procesador. Es de suma importancia conocer que el manejo que Linux hace de los procesos recae mucho sobre la paginación. La traducción de direcciones lineales a físicas hace factibles los siguientes objetivos de diseño: Asignar un espacio de direcciones físicas diferente para cada proceso, esto para evitar los errores de direccionamiento. Distinguir las páginas de los marcos de páginas. Esto le permite a la página misma ser almacenada en un marco de página, después ser almacenada en disco, y luego ser 24

55 recargada en un marco de página diferente. Que es el principio medular del mecanismo de memoria virtual. Ahora se está comenzando a visualizar la importancia de la administración de la memoria en cada área de un sistema operativo, es por esto que la actividad más compleja que un kernel podía realizar es la gestión de la memoria; es lógico que lo primero que salga a relucir al iniciar el kernel sean los procesos; ya que son los primeros en comenzar a solicitar este preciado recurso que es la memoria. Cada proceso tiene su propio Directorio Global de Página y sus propias Tablas de Página. Es válido hacerse la pregunta Cómo puede Linux implementar este modelo en una arquitectura de 32 bits si se ha dicho que está basado en una arquitectura de 64 bits? Lo que Linux hace, es eliminar el campo del Directorio Medio de Página diciendo que contiene cero bits, pero la posición del Directorio Medio de Página en la secuencia de punteros se conserva y de esta manera el código puede ejecutarse tanto en una arquitectura de 32 bits como en una de 64. Para facilitar la manipulación de las tablas de página, Linux utiliza las siguientes macros (en este momento solo se listarán y se detallará de su funcionamiento cuando sean utilizados en una tarea de gestión de memoria): PAGE_SHIFT. PMD_SHIFT. PGDIR_SHIFT. PTRS_PER_PTE, PTRS_PER_PMD y PTRS_PER_PGD Manejo de las tablas de página En Linux se tienen 3 tipos de datos de 32 bits utilizados para describir las entradas de las Tablas de Página, Directorio Medio de Página y Directorio Global de Página; estos son pte_t, pmd_t y pgd_t respectivamente. El kernel también provee de varias macros y funciones para leer o modificar las entradas de las tablas de página. Dos macros importantes son pmd_bad() y la pdg_bad(); éstas son utilizadas por las funciones para verificar las entradas del Directorio Global de Página y del Directorio Medio de Página que son pasadas como parámetros de entrada a una Tabla de Página errónea, esto es, cuando aplica al menos una de las siguientes condiciones: La página no está en la memoria principal (la bandera presente está limpia). La página admite solo accesos de lectura (bandera Lectura/Escritura está limpia). 25

56 Puede ser que las banderas Accedido o Sucia están limpias pero Linux siempre obliga a estas banderas a estar seteadas para cualquier tabla de página existente. Hay que observar que no se define una macro pte_bad() porque en Linux no es causa de error que una Tabla de Página haga referencia a una página que no está presente en la memoria principal, esto por la estrategia de administración que implementa Linux, se considera error cuando se hace referencia a Tabla de Página errónea o que no existe. Se tiene una serie de funciones para obtener el valor de cualquiera de las banderas incluidas en la entrada de una Tabla de Página: Pte_read(). Pte_write(). Pte_exec(). Pte_dirty)(). Pte_young(). Las siguientes funciones son para configurarlas: Pte_wrprotect(). Pte_rdprotect() y pte_exprotect(). Pte_mkwrite(). Pte_mkread() y pte_mkexec(). Pte_mkdirty() y pte_mkclean(). Pte_mkyoung() y pte_mkold(). Pte_modify(p,v). Set_pte. Cada Tabla de Página es almacenada en un marco de página; y como se verá más adelante, la asignación de marcos de página es una operación muy costosa. En este apartado se ha descrito de manera general las macros y funciones que administran a las tablas de página, para poder retomarlo cuando se hable de administración de la memoria Marcos de página reservados El código del kernel y sus estructuras de datos son almacenados en un grupo de marcos de páginas reservadas. Una página contenida en uno de estos marcos de página nunca puede ser asignada dinámicamente o movida a disco. Como regla general y esto esta especificado así en el archivo: 26

57 arch/x86/boot/compressed/head_32.s en donde se encuentra el código ensamblador que carga la imagen del kernel a la memoria, se instala el kernel de Linux en RAM a partir de la dirección física 0x , esto es, a partir del segundo megabyte. El total de marcos de página requeridos por el kernel depende de cómo haya sido configurado, pero en la configuración más típica el kernel puede ser cargado en menos de 2 MB de RAM. El kernel no puede ser cargado debido a particularidades de la arquitectura del microprocesador que deben ser tomadas en cuéntalas cuales son: Un marco de página es utilizada por la BIOS para almacenar la configuración del sistema de hardware detectado durante la POST. El rango de direcciones físicas desde la 0x000a0000 hasta la 0x000fffff están reservadas para las rutinas de la BIOS. Marcos de páginas adicionales en el primer megabyte podrían ser reservados por modelos de computadora específicos. Entonces para evitar la carga del kernel en grupos de marcos de páginas no contiguos, es que Linux prefieren saltarse el primer megabyte de la RAM Tablas de página de los procesos El espacio de direcciones lineales de un proceso es dividido en dos partes: Las direcciones lineales desde 0x hasta PAGE_OFFSET-1 pueden ser direccionados cuando el procesador esta en modo kernel o modo usuario. Las direcciones lineales desde PAGE_OFFSET hasta 0xffffffff pueden ser direccionadas solo cuando el procesador esta en modo Kernel. El valor que arroja la macro PAGE_OFFSET es 0xc esto significa que el cuarto gigabyte de direcciones lineales está reservado para el kernel, mientras que los primeros tres gigabytes son accesibles tanto para el kernel como para el usuario. El contenido de la primera entrada del Directorio Global de Página que mapea la dirección lineal más baja que el PAGE_OFFSET depende del proceso en particular. Mientras que las entradas restantes son las mismas para todos los procesos Tablas de Página del Kernel Inmediatamente de haber sido cargado el kernel en memoria, el procesador sigue corriendo en modo real, de allí que la unidad de paginación no está habilitada todavía y el kernel tiene que inicializar sus propias tablas de página esto se hace en dos fases. 27

58 La primera fase, el kernel crea un espacio de direcciones de 4MB, que es suficiente para instalarse el mismo en la RAM. La segunda fase, el kernel toma el control de toda la RAM disponible y configura de manera apropiada las tablas de página. Se describen estas fases en detalle a continuación: Tablas de Página provisionales del Kernel Tanto como el Directorio Global de Página como la Tabla de Página son estáticamente inicializadas durante la compilación del kernel. El Directorio Global de Página está contenido en la variable swapper_pg_dir, mientras que la Tabla de Página que se extiende en los primeros 4 MB de RAM es contenida por la variable pg0. El objetivo de esta primera fase de paginación es permitir que estos 4 MB sean fácilmente direccionados tanto en modo real como en modo protegido. El kernel durante su primera fase de inicialización puede direccionar los primeros 4 MB de RAM ya sea utilizando las direcciones lineales como físicas o utilizando las direcciones lineales equivalentes a 4 MB comenzando desde PAGE_OFFSET. El kernel crea el mapeo deseado llenando todas las entradas con ceros, excepto de la 0 a la 0x300, este rango de entradas es inicializado como sigue: El campo de dirección apunta hacia la dirección de pg0. Las banderas Presente, Lectura/Escritura, y Usuario/Supervisor están en 1. Las banderas Accedido, Sucia, PCD, PWD y Tamaño de Página están a 0. La tabla de página en pg0 también esta estáticamente inicializada, para que la i-ésima entrada direccione el i-ésimo marco de página Tabla de Página Final del Kernel El mapeo final provisto por las tablas de página del kernel debe traducir direcciones lineales que comienzan desde PAGE_OFFSET en direcciones físicas que comiencen desde 0. La macro _pa es utilizada para convertir una dirección lineal con las características mencionadas arriba en su correspondiente dirección física, mientras que la macro _va hace lo contrario. El Directorio Global de Página sigue almacenado en la variable swapper_pg_dir. Es inicializado por la función pagin_init() la cual actúa en dos parámetros de entrada: 28

59 Start_mem, la dirección lineal del primer byte de RAM justo después del código del kernel y las áreas de datos. End_mem, la dirección lineal del final de la memoria (esta dirección es calculada por las rutinas del BIOS durante la fase de POST). Linux explota la característica de paginación extendida de los procesadores Pentium, habilitando marcos de página de 4 MB que permite hacer de una manera más eficiente el mapeo desde PAGE_OFFSET en una dirección física haciendo las tablas de página del kernel superfluas. De esta manera la Tabla de Página provisional ya no es utilizada una vez que se inicializa swapper_pg_dir. Hasta aquí se han dado a conocer las bondades que nos ofrece la arquitectura de un microprocesador para manejar el direccionamiento de la memoria y en nuestro caso también se observó cómo Linux hace uso de estas características; ahora se puede estudiar cómo Linux administra la memoria Administración de la memoria en Linux Se ha visto, cómo Linux se beneficia de los circuitos de segmentación y paginación integrados en los procesadores Intel para traducir las direcciones lógicas en físicas. Se ha mencionado que una porción de la RAM es asignada permanentemente al kernel y utilizada para almacenar el código del kernel como sus estructuras estáticas de datos. La parte restante de la RAM se le llama memoria dinámica. Es un recurso preciado, necesitado no solo por los procesos sino también por el mismo kernel. De hecho, el desempeño del sistema entero depende de la eficiencia con la que se administre la memoria. Por tanto, todos los sistemas operativos multitarea actuales tratan de optimizar el uso de la memoria dinámica, asignándola solo cuando es necesario y liberándola lo más pronto posible. Esta sección describe como el kernel asigna la memoria dinámica para su propio uso. Se muestran también dos técnicas diferentes para manejar áreas de memoria física contiguas y una tercera técnica que maneja las áreas de memoria no contigua. Primer paso, ver como son administrados los marcos de página Administración de los marcos de página Se ha visto como los procesadores Intel Pentium pueden utilizar dos tamaños de marcos de páginas diferentes (dependiendo del tipo de paginación que se esté utilizando): 4 KB y 4 MB. Linux 29

60 adopta el tamaño más pequeño 4 KB como la unidad estándar de asignación de memoria. Esto hace las cosas más simples por dos razones: La circuitería de paginación automáticamente revisa si la página que está siendo direccionada está contenida en algún marco de página; además, cada marco de página tiene una protección de hardware a través de las banderas incluidas en la entrada de la Tabla de Página que apunta a él. Escogiendo una unidad de asignación de 4 KB, el kernel puede determinar directamente la unidad de asignación de memoria asociada con la página donde la excepción page fault ocurrió. El tamaño de 4 KB es un múltiplo de la mayoría de los tamaños de bloque de los discos, así que la transferencia de datos entre la memoria principal y los discos es más eficiente. Éste tamaño es mucho más manejable que el de 4 MB. El kernel debe mantener la pista del estado actual de cada marco de página. Por ejemplo, debe ser capaz de distinguir los marcos de página utilizados para contener páginas pertenecientes a los procesos de aquellos que contienen código o estructuras de datos del kernel; al mismo tiempo, debe poder determinar si un marco de página en la memoria dinámica está disponible o no. Este tipo de estado de información es guardado en un arreglo de descriptores, uno por cada marco de página. Los descriptores del tipo struct page tienen el siguiente formato: typedef struct page { struct page *next; struct page *prev; struct inode *inode; unsigned long offset; struct page *next_hash; atomic_t count; unsigned long flags; struct wait_queue *wait; struct page **pprev_hash; struct buffer_head * buffers; } mem_map_t; Se describen solo los campos que son de interés para este capítulo: Count 30

61 Puesto en 0 si el marco de página correspondiente está disponible; y configurada con un valor mayor si el marco de página ha sido asignado a uno o varios procesos o si está siendo utilizado por algunas estructuras de datos del kernel. Prev, next Flags Utilizados para insertar el descriptor en una lista circular doblemente enlazada. El significado de estos campos depende del uso actual del marco de página. Un arreglo de 32 banderas que describen el estado del marco de página. Por cada bandera PG_xyz, una macro PageXyz ha sido definida para leer o modificar su valor (véase tabla 2.1). Banderas Significado PG_decr_after PG_dirty PG_error PG_free_after PG_DMA PG_locked PG_referenced PG_reserved PG_skip PG_slab PG_swap_cache No se utiliza Un error de E/S ocurrió mientras se transfería la página Para revisar si el contador de descriptor de página fue decrementado Utilizado por ISA DMA La página no puede ser trasladada a disco (swap) El marco de página ha sido accedido a través de la tabla hash de la página cache Marco de página reservado para el código del kernel o inutilizable Utilizado solo por las arquitecturas SPARC/SPARC64 Incluido en un bloque Incluido en la swap cache PG_swap_unlock_after 31

62 PG_uptodate Seteada después de completada una operación de lectura Tabla 2.1 Banderas que describen el estado de un marco de página La bandera PG_DMA existe debido a una limitación en los procesadores de Acceso Directo a Memoria (DMA) en los buses ISA: tales procesadores DMA están capacitados para direccionar solo los primeros 16 MB de RAM, por lo tanto los marcos de página son divididos en dos grupos dependiendo de si pueden ser direccionados por el DMA o no. Todos los descriptores de marcos de página en el sistema son incluidos en un arreglo llamado mem_map. Ya que cada descriptor es de un tamaño menor a los 64 bytes, mem_map requiere alrededor de cuatro marcos de página por cada megabyte de RAM. La macro MAP_NR calcula el número del marco de página cuya dirección es pasada como parámetro, y por lo tanto el índice del descriptor correspondiente en mem_map. #define MAP_NR(addr) ( pa(addr) >> PAGE_SHIFT) Ésta macro hace uso de la macro pa, la cual convierte una dirección lógica en una física. La memoria dinámica, y los valores utilizados para referirse a ella, son ilustrados en la figura Los descriptores de los marcos de página son inicializados por la función free_area_init(), la cual actúa en dos parámetros: start_mem denota la primera dirección lineal de la memoria dinámica inmediatamente después de la memoria del kernel, mientras que end_mem denota la ultima dirección lineal de la memoria dinámica más 1. La función free_area_init() también considera la variable i386_endbase, la cual almacena la dirección inicial de los marcos de página reservados. La función asigna un área de memoria de tamaño adecuado a mem_map. Luego la función inicializa el área poniendo todos los campos a 0, excepto por los campos flags, en el cual se setea las banderas PG_DMA y PG_reserved: mem_map = (mem_map_t *) start_mem; p = mem_map + MAP_NR(end_mem); start_mem = ((unsigned long) p + sizeof(long) - 1) & ~(sizeof(long)-1); memset(mem_map, 0, start_mem - (unsigned long) mem_map); do { --p; p->count = 0; p->flags = (1 << PG_DMA) (1 << PG_reserved); 32

63 } while (p > mem_map); Figura 2.12 Mapa de memoria. [Bovet y Cesati, 2000: p.151] Posteriormente, la función mem_init() limpia la bandera PG_reserved del marco de página, para que pueda ser usado como memoria dinámica, y la bandera PG_DMA de todos los marcos de página que tienen una dirección física mayor o igual a 0x Esto es hecho por el siguiente fragmento de código: start_low_mem = PAGE_SIZE + PAGE_OFFSET; num_physpages = MAP_NR(end_mem); while (start_low_mem < i386_endbase) { clear_bit(pg_reserved,&mem_map[map_nr(start_low_mem)].flags); start_low_mem += PAGE_SIZE; } while (start_mem < end_mem) { clear_bit(pg_reserved,&mem_map[map_nr(start_mem)].flags); start_mem += PAGE_SIZE; } for (tmp = PAGE_OFFSET ; tmp < end_mem ; tmp += PAGE_SIZE) { if (tmp >= PAGE_OFFSET+0x ) clear_bit(pg_dma, &mem_map[map_nr(tmp)].flags); if (PageReserved(mem_map+MAP_NR(tmp))) { if (tmp >= (unsigned long) &_text && tmp < (unsigned long) &_edata) if (tmp < (unsigned long) &_etext) 33

64 } codepages++; else datapages++; else if (tmp >= (unsigned long) & init_begin && tmp < (unsigned long) & init_end) initpages++; else if (tmp >= (unsigned long) & bss_start && tmp < (unsigned long) start_mem) datapages++; else reservedpages++; continue; } mem_map[map_nr(tmp)].count = 1; free_page(tmp); Primero, la función mem_init() determina el valor de num_physpages, el número total de marcos de página presentes en el sistema. También cuenta el número de marcos de página del tipo PG_reserved. Una buena cantidad de símbolos producidos durante la compilación del kernel habilitan la función para contar el número de marcos de página reservados para el hardware, el código del kernel y los datos del kernel y el número de marcos página utilizados durante la inicialización del kernel que pueden ser sucesivamente liberados. Finalmente, mem_init() establece el campo count de cada descriptor de marco de página asociado con la memoria dinámica a 1 y llama a la función free_page(). Ésta función incrementa el valor de la variable nr_free_pages, esa variable contendrá el número total de marcos de página en la memoria dinámica al final del ciclo Solicitando y liberando marcos de página Después de haber visto como el kernel asigna e inicializa las estructuras de datos para el manejo de los marcos de página, se verá ahora cómo los marcos de página son asignados y liberados. Los marcos de página pueden ser solicitados haciendo uso de cuatro funciones y macros ligeramente diferentes: get_free_pages(gfp_mask,order) Función utilizada para solicitar 2 order marcos de página contiguos. 34

65 get_dma_pages(gfp_mask,order) Macro utilizada para obtener marcos de página adecuados para el DMA; extiende a: get_free_pages(gfp_mask GFP_DMA,order) get_free_page(gfp_mask) Macro utilizada para obtener un marco de página; extiende a: get_free_pages(gfp_mask,0) Get_free_page(gfp_mask) Función que invoca: get_free_page(gfp_mask) y después llena el marco de página obtenido con ceros. El parámetro gfp_mask especifica como buscar por marcos de página disponibles. Consiste de las siguientes banderas: GFP_WAIT GFP_IO Se setea si al kernel se le ha permitido descartar el contenido de los marcos de página para poder liberar memoria antes de satisfacer la solicitud. Se setea si al kernel se le ha permitido escribir páginas en el disco para poder liberar los marcos de página correspondientes. Ya que el swapping puede bloquear el proceso en modo kernel, esta bandera debe estar limpia cuando se manejan interrupciones o se modifican estructuras de datos criticas del kernel. GFP_DMA Se setea si los marcos de página solicitados deben ser los adecuados para el DMA. GFP_HIGH, GFP_MED, GFP_LOW Especifica la prioridad de la solicitud. GFP_LOW es usualmente asociada a las solicitudes de memoria dinámica hechas por los procesos en modo usuario, mientras que las otras prioridades son asociadas con las peticiones del kernel. En la práctica, Linux utiliza combinaciones predefinidas de los valores de estas banderas. 35

66 Los marcos de página pueden ser liberados a través de cualquiera de las siguientes tres funciones y macros: Free_pages(addr,order) Ésta función verifica que el descriptor de página del marco de página tenga una dirección física addr; si el marco de página no está reservado (si la bandera PG_reserved es igual a 0), éste decrementa el campo contador del descriptor. Si el contador llega a 0, se asume que los 2 order marcos de página contiguos comenzando desde la dirección addr ya no serán utilizados. En ese caso, la función invoca a free_ pages_ok() para insertar el descriptor del marco de página de la primera pagina disponible en la lista propia de marcos de página disponibles. free_page(p) Igual que la función anterior, excepto que ésta libera marcos de página cuyo descriptor esta apuntado por el parámetro p. Free_page(addr) Ésta es una macro utilizada para liberar un marco de página que tiene una dirección addr; expande a: to free_pages(addr,0) El algoritmo Sistema Compañero Ahora que se ha visto la manera en que el kernel tendrá el control de los marcos de página, hay que introducir el algoritmo que utiliza para asignar estos marcos de página. El kernel debe establecer una estrategia robusta y eficiente para asignar grupos de marcos de página contiguos. Haciendo eso, debe lidiar con un muy bien conocido problema de administración de memoria llamado la fragmentación externa: frecuentes peticiones y liberaciones de grupos de marcos de página contiguos de diferentes tamaños puede guiar a una situación en la cual muchos bloques pequeños de marcos de página libres son dispersos dentro de bloques de marcos de página asignados. Como resultado, es posible que se vuelva imposible asignar un bloque grande de marcos de página contiguos, aun cuando haya suficientes páginas para satisfacer la petición. En esencia hay dos maneras de evitar la fragmentación externa: Hacer uso de la circuitería de paginación para mapear grupos de marcos de página no contiguos en intervalos de direcciones lineales contiguas. Desarrollar una técnica adecuada para mantener la pista de los bloques existentes de marcos de página contiguos disponibles, evitando tanto como se pueda la necesidad de 36

67 dividir un bloque grande disponible con fin de satisfacer una petición que demanda menos espacio. La segunda manera es la preferida por el kernel por las siguientes dos buenas razones: En algunos casos, marcos de página contiguos son realmente necesarios, desde que las direcciones lineales contiguas nos son suficiente para satisfacer la petición. Un ejemplo típico es una petición de memoria por los búferes para ser asignada a los procesadores DMA. Desde que DMA ignora la circuitería de paginación y accede al bus de direcciones directamente mientras que transfiere muchos sectores de disco en una simple operación de E/S, los búferes solicitados deben ser alojados en marcos de página contiguos. Aun si la asignación de un marco de página contiguo no es estrictamente necesaria, ofrece la gran ventaja de dejar las tablas de página del kernel intactas. Qué hay de malo con modificar las tablas de página del kernel? Como se sabe, la frecuente modificación de las tablas de página se traduce en un mayor número de veces en los que se accede a la memoria, ya que hicieron que el CPU dejara ir los contenidos de los TLB. La técnica adoptada por Linux para resolver el problema de la fragmentación externa está basada en el muy bien conocido algoritmo Sistema Compañero. Todos los marcos de página disponibles son agrupados en 10 listas de bloques que contienen grupos de 1, 2, 4, 8, 16, 32, 64, 128, 256 y 512 marcos de página contiguos, respectivamente. La dirección física del primer marco de página de un bloque es múltiplo del tamaño del grupo: por ejemplo, la dirección inicial de un bloque de marco de página 16 es un múltiplo de 16x2 12. Se describe cómo el algoritmo funciona por medio de un ejemplo sencillo. Se asume que hay una petición para un grupo de 128 marcos de página contiguos (la mitad de un megabyte). El algoritmo primero chequea si existe un bloque disponible en la lista de marco de página 128. Si no existe dicho bloque, el algoritmo busca el siguiente bloque más grande, eso es, un bloque libre en la lista de marcos de página 256. Si dicho bloque existe, el kernel asigna 128 de los 256 marcos de página para satisfacer la petición e insertar los restantes 128 marcos de página dentro de la lista de bloques de marco de página 128. Si no hay un bloque 256 página disponible, entonces mira en lo bloque más grande siguiente, eso es, un bloque marco de página 512 disponible. Si tal bloque existe, este asigna 128 de los 512 marcos de página para satisfacer la petición, inserta los primeros 256 de los 384 marcos de página restantes en la lista de bloques de marcos de página de 256 disponibles, e inserta los últimos 128 de los restantes 384 marcos de página dentro de la lista de bloques de 128 marcos de página disponibles. Si la lista de bloques de 512 marcos de página está vacía, el algoritmo se detiene y emite una señal de condición de error. 37

68 La operación contraria, liberar bloques de marcos de página, da lugar al nombre de este algoritmo. El kernel intenta fusionar pares de bloques compañero libres de tamaño b en un único bloque de tamaño de 2b. Dos bloques son considerados compañero si: Ambos bloques tienen el mismo tamaño, b. Están localizados en direcciones físicas contiguas. La dirección física del primer marco de página del primer bloque es múltiplo de 2 x b x El algoritmo es iterativo; si tiene éxito en fusionar los bloques liberados, dobla a b y trata otra vez con el fin de crear bloques más grandes Estructuras de datos Linux hace uso de dos Sistema Compañeros diferentes: Uno maneja los marcos de página adecuados para ISA DMA, mientras que el otro maneja los marcos de página restantes. Cada Sistema Compañero se apoya en las siguientes estructuras de datos principales: El arreglo mem_map. Un arreglo que contiene 10 elementos de tipo free_area_struct, un elemento para cada tamaño de grupo. La variable free_area[0] apunta al arreglo utilizado por el Sistema Compañero para los marcos de página que no son adecuados para ISA DMA, mientras que free_area[1]apunta al arreglo utilizado por el Sistema Compañero para marcos de página adecuados para ISA DMA. Diez arreglos binarios llamados bitmaps, uno por cada tamaño de grupo. Cada Sistema Compañero tiene su propio conjunto de bitmaps, los cuales usa para llevar el control de los bloques que ha asignado. Cada elemento de los arreglos free_area[0] y free_area[1] es una estructura del tipo free_area_struct, que se define como sigue: struct free_area_struct { struct page *next; struct page *prev; unsigned int *map; unsigned long count; }; Nótese que los primeros dos campos de esta estructura concuerdan con los campos correspondientes de un descriptor de página; de hecho, punteros hacia estructuras del tipo free_area_struct a veces son utilizados como punteros a descriptores de página. 38

69 El k-ésimo elemento tanto como del arreglo free_area[0] y el free_area[1] es asociado con una lista circular doblemente enlazada de bloques del tamaño 2 k, implementados por los campos next y prev. Cada miembro de dicha lista es el descriptor del primer marco de página de un bloque. El campo count de cada estructura free_area_struct almacena el número de elementos en la lista correspondiente. El campo map apunta aun bitmap cuyo tamaño depende del número de los marcos de página existentes. Cada bit del mapa de bits de la k-ésima entrada del free_area[0] o del free_area[1] describe el estado de dos bloques compañero de tamaño 2 k marcos de página. Si un bit del mapa es igual a 0, ambos bloques compañero están disponibles o ambos están ocupados; si es igual a 1, exactamente uno de los bloques está ocupado. Cuando dos bloques compañero están disponibles, el kernel los trata como un bloque único disponible de tamaño 2 k+1. Como se va a considerar, por el bien de la ilustración, una RAM de 128 y los mapas de bits asociados con marcos de página no DMA. Los 128 MB pueden ser divididos en páginas individuales, grupos de 2 páginas cada uno o 8192 grupos de 4 páginas cada uno así como también 64 grupos de 512 páginas cada uno. Así que el mapa de bits correspondiente a free_area[0][0] consiste de bits, uno por cada par de los marcos de página existentes; el mapa de bits correspondiente a free_area[0][1] consiste de 8192 bits, uno para cada par de bloques de dos marcos de página consecutivos; el ultimo mapa de bits correspondiente a free_area[0][9] consiste de 32 bits, uno para cada par de bloques de 512 marcos de página contiguos. La figura 2.13 ilustra con un ejemplo sencillo el uso de las estructuras de datos introducidas por el algoritmo Sistema Compañero. El arreglo mem_map contiene nueve marcos de página disponibles agrupados en un bloque de uno (eso es, un solo marco de página) en el tope y dos bloques de cuatro más abajo. Las dobles flechas denotan la lista circular doblemente enlazada implementada por los campos next y prev. 39

70 Figura 2.13 Estructuras de datos utilizadas por el Sistema Compañero. [Bovet y Cesati, 2000: p.157] Asignando un bloque La función get_free_ pages() implementa la estrategia Sistema Compañero para asignar marcos de página. Esta función primero chequea si hay suficientes páginas disponibles, eso es, si nr_free_ pages es más grande que freepages.min. Si no, debe decidir reclamar los marcos de página. De otra manera, continúa con la asignación ejecutando el código incluido en la macro RMQUEUE_TYPE: if (!(gfp_mask & GFP_DMA)s) RMQUEUE_TYPE(order, 0); RMQUEUE_TYPE(order, 1); El parámetro order denota el logaritmo del tamaño del bloque de páginas disponibles requerido (0 para un bloque de una página, 1 para un bloque de dos páginas, y así sucesivamente). El segundo parámetro es el índice dentro de free_area, 0 es para bloques no DMA y 1 para bloques DMA. Entonces el código chequea a gfp_mask para ver si los bloques no DMA son permitidos y, si estos, tratan de obtener bloques de esa lista (índice 0), porque sería mucho mejor reservar bloques DMA para peticiones que realmente los necesiten. Si los marcos de página son exitosamente asignados, el código en la macro RMQUEUE_TYPE ejecuta una instrucción return, esto termina a la función get_free_ pages(). De otra manera, el código en la macro RMQUEUE_TYPE es 40

71 ejecutado otra vez con el segundo parámetro igual a 1, eso es, la petición de asignación de memoria puede ser satisfecha utilizando marcos de página adecuados para DMA. El código lanzado por la macro RMQUEUE_TYPE es equivalente a los siguientes fragmentos. Primero, unas cuantas variables locales son declaradas e inicializadas: struct free_area_struct * area = &free_area[type][order]; unsigned long new_order = order; struct page *prev; struct page *ret; unsigned long map_nr; struct page * next; La variable type representa al segundo parámetro de la macro: estas son iguales a cuando la macro realiza operaciones en el Sistema Compañero para marcos de página no DMA y a 1. La macro entonces realiza una búsqueda cíclica de un bloque disponible a través de cada lista (denotado por una entrada que no apunta a la entrada en sí), comenzando con la lista del order requerido y continuando si es necesario con los de orden mayor. Este ciclo es equivalente a la siguiente estructura: do { prev = (struct page *)area; ret = prev->next; if ((struct page *) area!= ret) goto block_found; new_order++; area++; } while (new_order < 10); Si el lazo mientras termina, quiere decir que no han sido encontrados bloques adecuados, entonces get_free_pages() retorna un valor nulo. Si no, ha sido encontrado un bloque disponible adecuado; en este caso, el descriptor del primer marco de página es removido de la lista, se actualiza el correspondiente mapa de bits, y es decrementado el valor de nr_free_ pages: block_found: prev->next = ret->next; prev->next->prev = prev; map_nr = ret-mem_map; change_bit(map_nr>>(1+new_order), area->map); 41

72 nr_free_pages -= 1 << order; area->count--; Si el bloque encontrado proviene de una lista con tamaño new_order mayor al requerido tamaño order, un ciclo mientras es ejecutado. El razonamiento tras estas líneas de código es el siguiente: cuando se vuelve necesario usar bloques de 2 k marcos de página para satisfacer una petición por 2 h marcos de página (h < k), el programa asigna los últimos 2 h marcos de página e iterativamente reasigna los primeros 2 k 2 h marcos de página hacia las listas free_area que tienen índices entre h y k. size = 1 << new_order; while (new_order > order) { area--; new_order--; size >>= 1; /* insert *ret as first element in the list and update the bitmap */ next = area->next; ret->prev = (struct page *) area; ret->next = next; next->prev = ret; area->next = ret; area->count++; change_bit(map_nr >> (1+new_order), area->map); /* now take care of the second half of the free block starting at *ret */ map_nr += size; ret += size; } Finalmente, RMQUEUE_TYPE actualiza el campo count del descriptor asociado con los bloques seleccionados y ejecuta una instrucción return: ret->count = 1; return PAGE_OFFSET + (map_nr << PAGE_SHIFT); Como resultado, la función get_free_pages() retorna la dirección del bloque encontrado. 42

73 Liberando un bloque La función free_ pages_ok() implementa la estrategia Sistema Compañero para liberar marcos de página. Hace uso de tres parámetros de entrada: Map_nr Order El número de página de uno de los marcos de página incluidos en el bloque que va a ser liberado. Type El tamaño logarítmico del bloque. Igual a 1 si los marcos de página son adecuados para DMA y 0 si no lo son. La función comienza declarando e inicializando unas cuantas variables locales: struct page * next, * prev; struct free_area_struct *area = &free_area[type][order]; unsigned long index = map_nr >> (1 + order); unsigned long mask = (~0UL) << order; unsigned long flags; La variable mask contiene los dos complementos de 2 order. Es utilizado para transformar map_nr en el número del primer marco de página del bloque a ser liberado y para incrementar nr_free_ pages: map_nr &= mask; nr_free_pages -= mask; La función inicia ahora un ciclo ejecutado al menos (9-order) una vez por cada posibilidad de fusionar a un bloque con su compañero. La función comienza con el bloque de tamaño más pequeño y lo mueve al tamaño más alto. La condición que dirige al lazo mientras es: (mask + (1 << 9)) Donde el bit único establecido en mask es cambiado hacia la izquierda en cada iteración. El cuerpo del lazo chequea si el bloque compañero del bloque conteniendo el número map_nr está disponible: 43

74 if (!test_and_change_bit(index, area->map)) break; Si el bloque compañero no está libre, la función rompe el ciclo iterativo; si está disponible, la función se separa de la lista de bloques libres correspondiente. El número de bloque del compañero es derivado de map_nr cambiando un solo bit: area->count--; next = mem_map[map_nr ^ -mask].next; prev = mem_map[map_nr ^ -mask].prev; next->prev = prev; prev->next = next; Al final de cada iteración, la función actualiza las variables mask, area, index y map_nr: mask <<= 1; area++; index >>= 1; map_nr &= mask; La función entonces continúa con la siguiente iteración. Intentando fusionar bloques libres el doble de largo como los considerados en el ciclo anterior. Cuando el ciclo termina, el bloque libre que se obtuvo no puede ser más fusionado con los otros bloques disponibles. Es entonces insertado en la propia lista: next = area->next; mem_map[map_nr].prev = (struct page *) area; mem_map[map_nr].next = next; next->prev = &mem_map[map_nr]; area->next = &mem_map[map_nr]; area->count++; Administración de áreas de memoria Esta sección trata con las áreas de memoria, eso es, con secuencias de celdas de memoria que tienen direcciones físicas contiguas y una longitud arbitraria. 44

75 El algoritmo Sistema Compañero adopta a un marco de página como el área de memoria básica. Esto está bien para tratar con peticiones de memoria relativamente grandes, pero seguramente el kernel tendrá que tratar con peticiones para áreas pequeñas de memoria, dígase algunas pocas decenas o centenas de bytes. Claramente, es un desperdicio asignar un marco de página completo para almacenar unos cuantos bytes. El enfoque correcto consiste en introducir nuevas estructuras de datos que describen como pequeñas áreas de memoria son asignadas en el mismo marco de página. Al hacerlo, se introduce un nuevo problema llamado fragmentación interna. Es causado por un desajuste entre el tamaño de la petición de memoria y el tamaño del área de memoria asignado para satisfacer la petición. Una solución clásica adoptada por Linux consiste en proveer áreas de memoria cuyos tamaños han sido distribuidos geométricamente: en otras palabras, el tamaño depende de una potencia 2 veces más grande de los datos a ser almacenados. De esta manera, no importa cuál sea el tamaño de la petición de memoria que se está realizando, se puede afirmar que la fragmentación interna es siempre más pequeña que el 50%. Siguiendo con este acercamiento, el kernel 2.0 de Linux crea 13 listas geométricamente distribuidas de áreas de memoria disponibles cuyos tamaños varían desde los 32 hasta bytes. El Sistema Compañero es invocado tanto para obtener marcos de página adicionales necesarios para almacenar nuevas áreas de memoria y viceversa para liberar marcos de página que ya no contienen áreas de memoria. Una lista dinámica es utilizada para mantener la pista de las áreas de memoria libres contenidas en cada marco de página El asignador de bloques Corriendo un algoritmo de asignación de áreas de memoria en la parte superior del Sistema Compañero no es particularmente eficiente. A partir del kernel 2.2 Linux reexamina la asignación de áreas de memoria desde cero y regresa con algunas mejoras bastante inteligentes. El nuevo algoritmo es derivado del esquema slab allocator desarrollado en 1994 por el sistema operativo Solaris 2.4 de Sun Microsystems. Y está basado en las siguientes premisas: El tipo de datos a ser almacenado va a afectar en cómo las áreas de memoria son asignadas; por ejemplo, cuando se asigna un marco de página a un proceso en modo usuario, el kernel invoca la función get_free_page(), la cual llena la página con ceros. El concepto de un slab allocator amplía esta idea y mira las áreas de memoria como objetos que consisten tanto de un conjunto de estructuras de datos y un par de funciones o métodos llamados constructor y destructor: el primero inicializa las áreas de memoria mientras que el segundo las des inicializa. 45

76 Con el propósito de evitar la inicialización de objetos repetidamente, el slab allocator no descarta los objetos que han sido asignados y luego liberados pero almacenados en memoria. Cuando un nuevo objeto es luego solicitado, puede ser tomado de la memoria sin tener que ser reinicializado. En la práctica, las áreas de memoria manejadas por Linux no necesitan ser inicializadas o des inicializadas. Por razones de eficiencia, Linux no descansa sobre los objetos que necesitan un método constructor o destructor; la motivación principal para introducir el slab allocator es la de reducir el número de llamadas al Sistema Compañero. Así, aunque el kernel soporte en su totalidad métodos constructores y destructores, los punteros a estos métodos son NULL. Las funciones del kernel tienden a solicitar áreas de memoria del mismo tipo repetidamente. Por ejemplo, todas las veces que el kernel crea un proceso, asigna áreas de memoria para algunas tablas de tamaño fijo como la de los descriptores de procesos, la de objeto archivo abierto, etcétera. Cuando un proceso termina, las áreas de memoria utilizadas para contener esas tablas pueden ser reutilizadas. Ya que los procesos son creados y destruidos con frecuencia, versiones previas del kernel de Linux desperdician tiempo asignando y liberando los marcos de página conteniendo las mismas áreas de memoria repetidamente; A partir de la versión 2.2 del kernel de Linux son almacenadas en una cache y reutilizadas luego. Las peticiones de áreas de memoria pueden ser clasificadas de acuerdo a su frecuencia. Las peticiones de un tamaño en particular que se espera que ocurran frecuentemente pueden ser manejados con mayor eficiencia creando un conjunto de objetos de propósitos especiales teniendo el tamaño correcto, evitando así la fragmentación interna. Mientras tanto, los tamaños que son raramente encontrados pueden ser manejados a través de un esquema de asignación basado en objetos en una serie de tamaños geométricamente distribuidos (como los de potencias de 2 utilizadas en Linux 2.0), aun si esta solución lleva a la fragmentación interna. Hay otro bono sutil en introducir objetos cuyos tamaños no son geométricamente distribuidos: la dirección inicial de las estructuras de datos son menos propensas a ser concentradas en direcciones físicas cuyos valores son una potencia de 2. Esto, a su vez, lleva a un mejor desempeño del procesador cache. El desempeño del procesador cache crea una razón adicional para limitar las llamadas al Sistema Compañero tanto como sea posible: cada llamada a una función del Sistema Compañero ensucia el hardware cache, incrementando así el promedio de tiempos de acceso a memoria. 46

77 El área de memoria principal que es contenida por una cache es dividida en bloques; cada bloque consiste de uno o más marcos de páginas contiguos que contienen tanto objetos asignados como disponibles (véase figura 2.14). Figura 2.14 Componentes del Slab Allocator. [Bovet y Cesati, 2000: p.162] El slab allocator nunca libera los marcos de página de un bloque vacío. No sabe cuando es necesario liberar memoria, y no hay beneficio en liberar objetos cuando están todavía llenos de memoria disponible para nuevos objetos. Por tanto, las liberaciones ocurren solo cuando el kernel está buscando marcos de página adicionales Descriptor de la cache Cada cache es descrita por una tabla de tipo struct kmem_cache_s. Los campos más significativos de esta tabla son: C_name Apunta al nombre de la cache. C_firstp, c_lastp Apunta, respectivamente, al primero y al último descriptor de bloque de la cache. Los descriptores de bloque de la cache están relacionados a través de una lista circular doblemente enlazada y parcialmente ordenada: los primeros elementos de la lista incluyen bloques sin objetos disponibles, luego vienen los bloques que incluyen objetos utilizados con al menos un objeto disponible, y finalmente los bloques que solo incluyen objetos disponibles. C_freep 47

78 Apunta al campo s_nextp del primer descriptor de bloque que incluye por lo menos un objeto disponible. C_num C_offset Número de objetos empaquetados en único bloque. (Todos los bloques de la cache tienen el mismo tamaño) C_gfporder Tamaño de los objetos incluidos en la cache. (Éste tamaño debe ser redondeado si la dirección inicial de los objetos alineada a memoria). C_tor, c_dtor Logaritmo del número de marcos de página contiguos incluidos en único bloque. C_nextp Apunta, respectivamente, a los métodos constructor y destructor asociados con los objetos cache. Que están actualmente puestos en NULL, como dijimos anteriormente. C_flags Apunta al descriptor de cache siguiente. Todos los descriptores de cache están relacionados en una lista simple a través de este campo. Un arreglo de banderas que describen algunas propiedades permanentes de la cache. Estas son, por ejemplo, una bandera que especifica cuál de dos posibles alternativas han sido escogidas para almacenar los descriptores de objetos en la memoria. C_magic Un número mágico seleccionado de conjunto de valores predeterminados. Utilizados para chequear tanto el estado actual de la cache y su consistencia Descriptor de bloque Cada bloque de la cache tiene su propio descriptor de tipo struct kmem_slab_s. Los descriptores de bloque pueden ser almacenados en dos posibles lugares, la elección depende normalmente del tamaño de los objetos en ese bloque. Si el tamaño del objeto es menor que

79 bytes, el descriptor de bloque es almacenado al final del bloque; de lo contrario, es almacenado a fuera del bloque. Esta última opción es preferible para objetos grandes cuyos tamaños son submúltiplos del tamaño del bloque. En algunos casos, el kernel puede violar esta regla configurando el campo c_flags del descriptor de la cache de manera diferente. Los campos más significativos de un descriptor de bloque son: S_inuse S_mem Número de objetos en el bloque que están actualmente asignados. S_freep Apunta al primer objeto (ya sea asignado o libre) dentro del bloque. Apunta al primer objeto disponible en el bloque. S_nextp, s_prevp Apunta, respectivamente, al siguiente y anterior descriptor de bloque. El campo s_nextp de la último descriptor de bloque en la lista apunta al campo c_offset del correspondiente descriptor de la cache. S_dma Bandera que se setea si el objeto incluido en el bloque puede ser utilizado por el procesador DMA. S_magic Igual que el campo c_magic del descriptor de cache. Contiene un número mágico seleccionado de un conjunto de valores predefinidos y es utilizado para chequear tanto el estado actual del bloque y su consistencia. Los valores de este campo son diferentes de aquellos del correspondiente campo c_magic del descriptor de cache. El desplazamiento de s_magic con el descriptor de bloque es igual al desplazamiento de c_magic con respecto a c_offset dentro del descriptor de la cache; las rutinas de chequeo se basan en el mismo. 49

80 Figura 2.15 Descriptores de cache y de bloque. [Bovet y Cesati, 2000: p.165] La figura 2.15 ilustra las relaciones mayores entre la cache y los descriptores de bloque. Bloques completos preceden a bloques parcialmente completos que preceden a bloques vacíos Caches específicas y generales Las caches se dividen en dos tipos: generales y específicas. Las caches generales son utilizadas solo por el slab allocator para sus propios propósitos, mientras que las caches específicas son utilizadas por las partes restantes del kernel. Las caches generales son: Una primera cache contiene los descriptores de cache de las caches restantes utilizadas por el kernel. La variable cache_cache contiene tales descriptores. Una segunda cache contiene los descriptores de bloque que no están almacenados dentro de los bloques. La variable cache_slabp apunta a estos descriptores. 13 caches adicionales contienen áreas de memoria geométricamente distribuidas. La tabla llamada cache_sizes cuyos elementos son del tipo cache_sizes_t apuntan a los 13 descriptores de cache asociados con áreas de memoria de tamaño 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, y bytes, respectivamente. La tabla 50

81 cache_sizes es utilizada para derivar eficientemente las direcciones cache correspondientes a un tamaño dado. Las funciones kmem_cache_init() y kmem_cache_sizes_init() son invocadas durante la inicialización del sistema para configurar las caches generales. Las caches específicas son creadas por la función kmem_cache_create(). Dependiendo de los parámetros, la función primero determina la mejor manera de manejar la nueva cache (por ejemplo, si se incluye el descriptor de bloque adentro o afuera del bloque); crea entonces un nuevo descriptor cache e inserta el descriptor cache en la cache general cache_cache. Debemos notar que una vez que una cache ha sido creada, no puede ser destruida. Los nombres de todas las caches tanto específicas como generales pueden ser obtenidos en tiempo de ejecución leyendo el archivo slabinfo en el directorio del kernel; este archivo también específica el número de objetos disponibles y el número de objetos asignados en cada cache Enlazando el Slab Allocator con el Sistema Compañero Cuando el slab allocator crea nuevos bloques, se auxilia del algoritmo Sistema Compañero para obtener un grupo de marcos de página contiguos disponibles. Para ese propósito, invoca a la función kmem_getpages(). void * kmem_getpages(kmem_cache_t *cachep, unsigned long flags, unsigned int *dma) { void *addr; *dma = flags & SLAB_DMA; addr = (void*) get_free_pages(flags, cachep->c_gfporder); if (!*dma && addr) { struct page *page = mem_map + MAP_NR(addr); *dma = 1<<cachep->c_gfporder; while ((*dma)--) { if (!PageDMA(page)) { *dma = 0; break; } page++; } } return addr; 51

82 } Los parámetros tienen el siguiente significado: Cachep Flags Apunta al descriptor de la cache que necesita marcos de página adicionales (el número de marcos de página requeridos está en el campo cachep->c_gfporder). Dma Específica como el marco de página es solicitado. Apunta a una variable que esta seteada en 1 por la función kmem_getpages() si los marcos de página son adecuados para ISA DMA. En la operación contraria, los marcos de página asignados a un slab allocator pueden ser liberados invocando la función kmem_freepages(): void kmem_freepages(kmem_cache_t *cachep, void *addr) { unsigned long i = (1<<cachep->c_gfporder); struct page *page = &mem_map[map_nr(addr)]; while (i--) { PageClearSlab(page); page++; } free_pages((unsigned long)addr, cachep->c_gfporder); } La función libera los marcos de página, comenzando desde uno que tenga la dirección física addr, que ha sido alojado para el bloque de la cache identificado como cachep Alojando un bloque a la cache Una cache recién creada no contiene ningún bloque y por lo tanto tampoco objetos disponibles. Nuevos bloques son asignados a una cache solo cuando de las siguientes razones ambas son ciertas: 52

83 Cuando se ha emitido una petición para asignar un nuevo objeto. La cache no incluye ningún objeto disponible. Cuando esto ocurre, el slab allocator asigna un nuevo bloque a la cache invocando a kmem_cache_grow(). Ésta función llama a kmem_ getpages() para obtener un grupo de marcos de página del Sistema Compañero; ésta llama a kmem_cache_slabmgmt() para obtener un nuevo descriptor de bloque. Luego, llama a kmem_cache_ini_objs(), la cual aplica el método constructor (si está definido) a todos los objetos contenidos en el nuevo bloque, también llama a kmem_slab_link_end(), el cual inserta el descriptor de bloque al final de la lista de bloques cache: void kmem_freepages(kmem_cache_t *cachep, void *addr) { unsigned long i = (1<<cachep->c_gfporder); struct page *page = &mem_map[map_nr(addr)]; while (i--) { PageClearSlab(page); page++; } free_pages((unsigned long)addr, cachep->c_gfporder); } La macro kmem_slab_end devuelve la dirección del campo c_offset del descriptor de cache correspondiente (como se afirmó anteriormente, el último elemento de una lista de bloques apunta a ese campo). Después de insertar el nuevo descriptor de bloque dentro de la lista, kmem_cache_ grow() carga los campos next y prev, respectivamente, de los descriptores de todos los marcos de página incluidos en el nuevo bloque con la dirección del descriptor de la cache y la dirección del descriptor de bloque. Esto trabaja correctamente porque los campos next y prev son utilizados por funciones del Sistema Compañero solo cuando el marco de página está disponible, mientras los marcos de página que son manejados por las funciones del slab allocator no estén disponibles en lo que respecta al Sistema Compañero. Por lo tanto, el Sistema Compañero no será confundido por este uso especializado del descriptor de marco de página Liberando un bloque de la cache Como se dijo anteriormente, el slab allocator nunca libera los marcos de página de un bloque vacío por sí mismo. De hecho, un bloque es liberado solo si las dos condiciones siguientes se mantienen: 53

84 El Sistema Compañero es incapaz de satisfacer una nueva petición para un grupo de marcos de página. El bloque está vacío, eso es, todos los objetos incluidos en él están disponibles. Cuando el kernel busca por marcos de página adicionales disponibles, llama a try_to_free_pages(); esta función, a su vez, puede invocar a kmem_cache_reap(), la cual selecciona una cache que contiene al menos un bloque vacío. La función kmem_slab_unlink() entonces remueve el bloque de la lista de bloques de la cache: void kmem_slab_unlink(kmem_slab_t *slabp) { kmem_slab_t *prevp = slabp->s_prevp; kmem_slab_t *nextp = slabp->s_nextp; prevp->s_nextp = nextp; nextp->s_prevp = prevp; } Subsecuentemente, el bloque- junto con los objetos en él-es destruido invocando a kmem_slab_destroy(): void kmem_slab_destroy(kmem_cache_t *cachep, kmem_slab_t *slabp) { if (cachep->c_dtor) { unsigned long num = cachep->c_num; void *objp = slabp->s_mem; do { (cachep->c_dtor)(objp, cachep, 0); objp += cachep->c_offset; if (!slabp->s_index) objp += sizeof(kmem_bufctl_t); } while (--num); } slabp->s_magic = SLAB_MAGIC_DESTROYED; if (slabp->s_index) kmem_cache_free(cachep->c_index_cachep, slabp->s_index); kmem_freepages(cachep, slabp->s_mem-slabp->s_offset); if (SLAB_OFF_SLAB(cachep->c_flags)) kmem_cache_free(cache_slabp, slabp); } 54

85 La función chequea si la cache tiene un método destructor para sus objetos (el campo c_dtor no es NULL), en cuyo caso aplica el destructor a todos los objetos en el bloque; la variable local objp mantiene la pista del objeto actualmente examinado. Luego, llama a kmem_freepages(), la cual retorna todos los marcos de página contiguos utilizados por el bloque por el Sistema Compañero. Finalmente, si el descriptor de bloque es almacenado afuera del bloque (en este caso los campos s_index y c_index_cachep no son NULL, como se explica luego en este capítulo), la función lo libera de la cache de los descriptores de bloque. Algunos módulos de Linux pueden crear caches. En vías de evitar el desperdicio de espacio de memoria, el kernel debe destruir todos los bloques en todas las caches creadas por un modulo antes de removerlo (se dijo anteriormente que Linux no destruye caches. Así, cuando se está enlazando un nuevo módulo, el kernel debe chequear sí el nuevo descriptor de cache solicitado por él ha sido creado en una instalación previa de ese modulo o de otro). La función kmem_cache_shrink() destruye todos los bloques en una cache invocando a kmem_slab_destroy() repetidamente. El campo c_growing del descriptor de la cache es utilizado para prevenir que kmem_cache_shrink() contraiga una cache mientras otro control de ruta del kernel intenta asignar un nuevo bloque para él Descriptor de objetos Cada objeto tiene un descriptor de objeto de tipo struct kmem_bufctl_s. Parecidos a los mismo descriptores de segmento, el descriptor de objetos de un bloque puede ser almacenado de dos maneras posibles. Figura 2.16 Descriptores de objetos externos. [Bovet y Cesati, 2000: p.169] 55

86 Figura 2.17 Descriptores de objetos internos. [Bovet y Cesati, 2000: p.169] Descriptores de objetos externos Almacenados fuera del bloque, en una de las caches generales apuntadas por cache_sizes. En este caso, el primer descriptor de objeto en el área de memoria describe el primer objeto en el bloque y así sucesivamente. El tamaño del área de memoria, y así en particular la cache general utilizada para almacenar descriptores de objetos, depende del número de objetos almacenados en el bloque (el campo c_num del descriptor de la cache). La cache que contiene los objetos en si está atada a la cache que contiene sus descriptores a través de dos campos. Primero, el campo c_index_cachep de la cache que contiene el bloque que apunta al descriptor de la cache que contiene el descriptor de objetos. Segundo, el campo s_index del descriptor de bloque que apunta al área de memoria que contiene los descriptores de objeto (véase figura 2.17). Descriptores de objetos internos Almacenados dentro del bloque, inmediatamente después de los objetos que ellos describen. En este caso, el campo c_index_cachep del descriptor de la cache y el campo s_index del descriptor de bloque ambos son NULL (véase figura 2.18). El slab allocator elige la primera solución cuando el tamaño de los objetos es un múltiplo de 512, 1024, 2048 o 4096: en este caso, almacenar estructuras de control dentro del bloque debería resultar en un alto nivel de fragmentación interna. Si el tamaño de los objetos es menor que 512 bytes o que no sean múltiplos de 512, 1024, 2048 o 4096 el slab allocator almacena el descriptor de objetos dentro del bloque. Los descriptores de objetos son estructuras simples que consisten de un único campo: typedef struct kmem_bufctl_s { union { 56

87 struct kmem_bufctl_s * buf_nextp; kmem_slab_t * buf_slabp; void * buf_objp; } u; } kmem_bufctl_t; #define buf_nextp u.buf_nextp #define buf_slabp u.buf_slabp #define buf_objp u.buf_objp Este campo tiene el siguiente significado, dependiendo en el estado del objeto y de las locaciones de los descriptores de objetos: Buf_nextp Buf_objp Si el objeto está disponible, apunta al siguiente objeto disponible en el bloque, implementando así una lista simple de objetos disponibles dentro del bloque. Buf_slabp Si el objeto está asignado y su descriptor de objeto es almacenado fuera del bloque, apunta al objeto. Si el objeto está asignado y su descriptor de objetos está almacenado dentro del bloque, apunta al descriptor del bloque en cuyo objeto está almacenado. Esto se mantiene si el descriptor de bloque es almacenado dentro o fuera del bloque. La figura 2.17 ilustra la relación entre los bloques, descriptores de bloque, objetos y descriptores de objetos. Nótese que, aunque la figura 2.17 sugiere que el descriptor de bloque está almacenado fuera del bloque, se mantiene sin cambios si el descriptor está almacenado dentro de él Asignando un objeto a la cache Nuevos objetos tal vez sean obtenidos por la invocación de la función kmem_cache_alloc(). El parámetro cachep apunta al descriptor de la cache de donde el objeto nuevo debe ser obtenido. Kmem_cache_alloc() primero revisa si el descriptor de la cache existe; luego extrae del campo c_freep la dirección del campo s_nextp del primer bloque que incluye al menos un objeto disponible: slabp = cachep->c_freep; 57

88 Si slabp no apunta al bloque, entonces salta a alloc_new_slab e invoca a kmem_cache_grow() para agregar un nuevo bloque a la cache: if (slabp->s_magic!= SLAB_MAGIC_ALLOC) goto alloc_new_slab; El valor SLAB_MAGIC_ALLOC en el campo s_magic indica que el bloque contiene al menos un objeto disponible. Si el bloque está lleno, slabp apunta al campo cache->c_offset, y por lo tanto slabp->s_magic coincide con cachep->c_magic: en este caso, sin embargo, este campo contiene un número mágico para la cache diferente de SLAB_MAGIC_ALLOC. Después un bloque con un objeto disponible, la función incrementa el contador que contiene el número de objetos actualmente asignados en el bloque: slabp->s_inuse++; Luego carga bufp con la dirección del primer objeto disponible dentro del bloque, y correspondientemente, actualiza el campo slabp->s_freep del descriptor de bloque que apunta al siguiente objeto disponible: bufp = slabp->s_freep; slabp->s_freep = bufp->buf_nextp; Si slabp->s_freep se hace NULL, el bloque ya no incluye más objetos libres, así que el campo c_freep del descriptor de la cache debe ser actualizado: if (!slabp->s_freep) cachep->c_freep = slabp->s_nextp; Nótese que allí no es necesario cambiar la posición del descriptor de bloque dentro de la lista desde que permanece parcialmente ordenada. Ahora la función debe derivar la dirección del objeto disponible y actualizar el descriptor del bloque. Si el campo slabp->s_index es nulo, el descriptor del objeto es almacenado inmediatamente después de los objetos dentro del bloque. En este caso, la dirección del descriptor de bloque es primero almacenada en campo del único del descriptor de objeto para denotar el hecho de que el objeto no está más disponible; luego la dirección del objeto es derivada sustrayendo de la dirección del descriptor del objeto el tamaño del objeto incluido en el campo cachep->c_offset: 58

89 if (!slabp->s_index) { bufp->buf_slabp = slabp; objp = ((void*)bufp) - cachep->c_offset; } Si el campo slabp->s_index no es cero, esté apunta a un área de memoria fuera del bloque donde los descriptores de bloque son almacenados. En este caso, la función primero calcula la posición relativa del descriptor del objeto en el área de memoria externa; luego multiplica este número por el tamaño del objeto; finalmente, suma el resultado a la dirección del primer objeto en el bloque, arrojando así la dirección del objeto a ser retornado. Como en el caso anterior, el campo único del descriptor de objeto es actualizado y ahora apunta al objeto: if (slabp->s_index) { objp = ((bufp-slabp->s_index)*cachep->c_offset) + slabp->s_mem; bufp->buf_objp = objp; } La función termina retornando la dirección del objeto nuevo: Return objp; Liberando un objeto de la cache La función kmem_cache_free() libera un objeto previamente obtenido por el slab allocator. Sus parámetros son cachep, la dirección del descriptor de la cache, y objp, la dirección del objeto a ser liberado. La función comienza chequeando los parámetros, después determina la dirección del descriptor del objeto y la del bloque que contiene al objeto. Utiliza la bandera cachep->c_flags, incluida en el descriptor de la cache, para determinar si el descriptor del objeto está localizado dentro o fuera del bloque. En el primer caso, éste determina la dirección del descriptor del objeto sumando el tamaño del objeto a su dirección inicial. La dirección del descriptor del bloque es luego extraída del campo apropiado en el descriptor del objeto: if (!SLAB_BUFCTL(cachep->c_flags)) { bufp = (kmem_bufctl_t *)(objp+cachep->c_offset); slabp = bufp->buf_slabp; } 59

90 En el segundo caso, determina la dirección del descriptor del bloque del campo prev del descriptor del marco de página que contiene al objeto. La dirección del descriptor del objeto es derivada primero calculando la secuencia numérica del objeto dentro del bloque (la dirección del objeto menos la dirección del primer objeto dividida entre la longitud del objeto). Este número es utilizado luego para determinar la posición del descriptor del objeto comenzando desde el inicio del área externa apuntada por el campo slabp->s_index del descriptor del bloque. Para estar en el lado seguro, la función chequea que la dirección del objeto pasada como parámetro coincida con la dirección que el descriptor del objeto dice que debe tener: if (SLAB_BUFCTL(cachep->c_flags)) { slabp = (kmem_slab_t *)((&mem_map[map_nr(objp)])->prev); bufp = &slabp->s_index[(objp - slabp->s_mem) / cachep->c_offset]; if (objp!= bufp->buf_objp) goto bad_obj_addr; } Ahora la función chequea si el campo slabp->s_magic del descriptor del bloque contiene el número mágico correcto si el campo slabp->s_inuse es mayor que 0. Si todo está bien, decrementa el valor de slabp->s_inuse e inserta el objeto dentro de la lista de objetos disponibles del bloque: slabp->s_inuse--; bufp->buf_nextp = slabp->s_freep; slabp->s_freep = bufp; Si bufp->buf_nextp es NULL, la lista de objetos disponibles incluye solo un elemento: el objeto que está siendo liberado. En este caso, el bloque fue llenado previamente hasta el límite de su capacidad y puede ser necesario reinsertar su descriptor de bloque en una nueva posición en la lista de descriptores de bloque. (Recordar que los bloques completamente llenos aparecen antes de los bloques con algunos objetos disponibles en la lista parcialmente ordenada.) Esto es realizado por la función kmem_cache_one_free(): if (!bufp->buf_nextp) kmem_cache_one_free(cachep, slabp); Si el bloque incluye otros objetos disponibles a parte del que se está liberando, es necesario revisar si todos los objetos están disponibles. Como en el caso anterior, esto hará necesario reinsertar el descriptor del bloque en una nueva posición en la lista de descriptores de bloque. El movimiento es llevado a cabo por la función kmem_cache_full_free(): 60

91 if (bufp->buf_nextp) if (!slabp->s_inuse) kmem_cache_full_free(cachep, slabp); La función kmem_cache_free() termina aquí Objetos de propósito general Como se ha visto, peticiones no frecuentes por áreas de memoria son manejadas a través de un grupo de caches generales cuyos objetos tienen tamaños geométricamente distribuidos con un rango desde 32 como mínimo hasta un máximo de bytes. Los objetos de este tipo son obtenidos invocando la función kmalloc(): void * kmalloc(size_t size, int flags) { cache_sizes_t *csizep = cache_sizes; for (; csizep->cs_size; csizep++) { if (size > csizep->cs_size) continue; return kmem_cache_alloc(csizep->cs_cachep, flags); } printk(kern_err "kmalloc: Size (%lu) too large\n", (unsigned long) size); return NULL; } La función utiliza la tabla cache_sizes para localizar el descriptor de la cache que contiene a los objetos de tamaño correcto. Luego llama a kmem_cache_alloc() para asignar el objeto (actualmente el código de esta última ha sido agregado en el cuerpo de kmalloc()). Los objetos obtenidos invocando kmalloc() pueden ser liberados llamando a kfree(): void kfree(const void *objp) { struct page *page; int nr; if (!objp) goto null_ptr; nr = MAP_NR(objp); 61

92 } if (nr >= num_physpages) goto bad_ptr; page = &mem_map[nr]; if (PageSlab(page)) { kmem_cache_t *cachep; cachep = (kmem_cache_t *)(page->next); if (cachep && (cachep->c_flags & SLAB_CFLGS_GENERAL)) { kmem_cache_free(cachep, objp); return; } } bad_ptr: printk(kern_err "kfree: Bad obj %p\n", objp); *(int *) 0 = 0; /* FORCE A KERNEL DUMP */ null_ptr: return; El propio descriptor de la cache es identificado leyendo el contenido del campo next del descriptor del primer marco de página que contiene el área de memoria. Si este campo apunta a un descriptor válido, el área de memoria es liberada invocando la función kmem_cache_free() Administración de áreas de memoria no contiguas Hasta ahora se sabe que es preferible mapear áreas de memoria en conjuntos de marcos de página contiguos, así haciendo mejor uso de la cache y logrando un bajo promedio de veces en que se accede a la memoria. No obstante, si las peticiones de áreas de memoria son infrecuentes, tiene sentido considerar un sistema de asignación basado en marcos de página no contiguos accedidos a través de direcciones lineales no contiguas. La ventaja principal de este esquema es la de evitar la fragmentación externa, mientras que la desventaja es que es necesario manipular las tablas de página del kernel. Claramente, el tamaño de un área de memoria no contigua debe ser múltiplo de Linux utiliza áreas de memoria no contiguas con moderación, por ejemplo, para asignar estructuras de datos para áreas de swap activas, para asignar espacio para un modulo, o para asignar buffers a algún driver E/S Direcciones lineales de áreas de memoria no contiguas Para encontrar un rango disponible de direcciones lineales, se busca en el área comenzando desde PAGE_OFFSET (usualmente en el comienzo del cuarto gigabyte). Como se vio 62

93 anteriormente el kernel reserva toda esta área de memoria superior para mapear la RAM disponible para el uso del kernel. Pero la RAM disponible ocupa solo una pequeña fracción del gigabyte, comenzando en la dirección de PAGE_OFFSET. Todas las direcciones lineales por encima de esa área reservada está disponible para mapear áreas de memoria no contigua. La dirección lineal que corresponde al final de la memoria física es almacenada en la variable high_memory. La siguiente figura 2.18 muestra como las direcciones lineales son asignadas a áreas de memoria no contiguas. Un intervalo seguro de 8 MB (macro VMALLOC_OFFSET) es insertado entre el final de la memoria física y la primera área de memoria; el propósito es capturar la memoria fuera de los límites de acceso. Por la misma razón, intervalos seguros adicionales de un tamaño de 4 KB son insertados para separar áreas de memoria no contiguas (véase figura 2.18). Figura 2.18 Intervalo de direcciones lineales comenzando desde PAGE_OFFSET La macro VMALLOC_START define la dirección inicial del espacio lineal reservado para áreas de memoria no contiguas. Es definida como sigue: #define VMALLOC_START (((unsigned long) high_memory + \ VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)) Descriptores de áreas de memoria no contiguas Cada área de memoria no contigua es asociada con un descriptor de tipo struct vm_struct: struct vm_struct { unsigned long flags; void * addr; unsigned long size; struct vm_struct * next; }; 63

94 Estos descriptores son insertados en una lista simple por medio del campo next; la dirección del primer elemento de la lista es almacenado en la variable vmlist. El campo addr contiene la dirección lineal de la primera celda de memoria del área; el campo size contiene el tamaño del área más 4096 (el tamaño del previamente mencionado intervalo seguro). La función get_vm_area() crea nuevos descriptores de tipo struct vm_struct; su parámetro size especifica el tamaño de la nueva área de memoria: struct vm_struct * get_vm_area(unsigned long size) { unsigned long addr; struct vm_struct **p, *tmp, *area; area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL); if (!area) return NULL; addr = VMALLOC_START; for (p = &vmlist; (tmp = *p) ; p = &tmp->next) { if (size + addr < (unsigned long) tmp->addr) break; addr = tmp->size + (unsigned long) tmp->addr; if (addr > 0xffffd000-size) { kfree(area); return NULL; } } area->addr = (void *)addr; area->size = size + PAGE_SIZE; area->next = *p; *p = area; return area; } La función primero llama a kmalloc() para obtener un área de memoria para el nuevo descriptor. Ella luego escanea la lista de descriptores del tipo struct vm_struct buscando por un rango disponible de direcciones lineales que incluye al menos size+4096 direcciones. Si dicho intervalo existe, la función inicializa los campos del descriptor y termina retornando la dirección inicial del 64

95 área de memoria no contigua. De lo contrario, cuando addr+size excede el límite de 4 GB, get_vm_area() libera el descriptor y devuelve NULL Asignando un área de memoria no contigua La función vmalloc() asigna un área no contigua de memoria al kernel. El parámetro size denota el tamaño del área requerida. Si la función es capaz de satisfacer la petición, entonces retorna la dirección lineal inicial del área nueva; de lo contrario, retorna un puntero NULL: void * vmalloc(unsigned long size) { void * addr; struct vm_struct *area; size = (size+page_size-1)&page_mask; if (!size size > (num_physpages << PAGE_SHIFT)) return NULL; area = get_vm_area(size); if (!area) return NULL; addr = area->addr; if (vmalloc_area_pages((unsigned long) addr, size)) { vfree(addr); return NULL; } return addr; } La función inicia redondeando al entero mayor el valor del parámetro size a un múltiplo de 4096 (el tamaño del marco de página). También realiza un chequeo de sanidad para asegurarse que el tamaño es mayor que 0 y menor o igual que el número existente de marcos de página. Si el tamaño se ajusta a la memoria disponible, vmalloc() invoca a get_vm_area(), la cual crea un descriptor nuevo y retorna la dirección lineal asignada al área de memoria. Luego vmalloc() invoca a vmalloc_area_pages() para solicitar marcos de páginas no contiguos y finaliza retornando la dirección lineal inicial del área de memoria no contigua. La función vmalloc_area_pages() hace uso de dos parámetros: address, la dirección lineal inicial del área, y size, que es su tamaño. La dirección lineal del final del área es asignada a la variable local end: end = address + size; 65

96 La función luego usa la macro pgd_offset_k para derivar la entrada en Directorio Global de Página relacionado a la dirección lineal inicial del área: dir = pgd_offset_k(address); La función luego ejecuta el siguiente lazo: while (address < end) { pmd_t *pmd = pmd_alloc_kernel(dir, address); if (!pmd) return -ENOMEM; if (alloc_area_pmd(pmd, address, end - address)) return -ENOMEM; set_pgdir(address, *dir); address = (address + PGDIR_SIZE) & PGDIR_MASK; dir++; } En cada iteración, primero invoca a pmd_alloc_kernel() para crear el Directorio Medio de Página para el área nueva. Luego llama a alloc_area_pmd() para asignar todas las Tablas de Página asociadas con el nuevo Directorio Global de Página. Después, invoca a set_pgdir() para actualizar la entrada correspondiente al nuevo Directorio Global de Página en todos los Directorios Globales de Página existentes. Añade la constante 2 22, eso es, el tamaño del rango de direcciones lineales provistas por un Directorio Medio de Página, del valor actual de address, e incrementa el puntero dir hacia el Directorio Global de Página. El lazo se repite hasta que todas las entradas de la tabla de página que hacen referencia al área de memoria no contigua estén configuradas. La función alloc_area_pmd() ejecuta un ciclo similar para todas las Tablas de Página a las que un Directorio Medio de Pagina apunta: while (address < end) { pte_t * pte = pte_alloc_kernel(pmd, address); if (!pte) return -ENOMEM; if (alloc_area_pte(pte, address, end - address)) return -ENOMEM; address = (address + PMD_SIZE) & PMD_MASK; pmd++; 66

97 } La función pte_alloc_kernel() asigna una nueva Tabla de Página y actualiza la entrada correspondiente en el Directorio Medio de Página. Después, alloc_area_pt() asigna todos los marcos de página correspondientes a las entradas en la Tabla de Página. El valor de address es incrementado por 2 22, eso es, el tamaño del rango de direcciones lineales provisto por una única Tabla de Página, y el lazo es iterado. El lazo principal de alloc_area_pte() es: while (address < end) { unsigned long page; if (!pte_none(*pte)) printk("alloc_area_pte: page already exists\n"); page = get_free_page(gfp_kernel); if (!page) return -ENOMEM; set_pte(pte, mk_pte(page, PAGE_KERNEL)); address += PAGE_SIZE; pte++; } Cada marco de página es asignado a través de get_free_page(). La dirección física del nuevo marco de página se escribe dentro de la Tabla de Página por las macros set_pte y mk_pte. Luego el ciclo es repetido después de sumar la constante 4096, eso es, la longitud del marco de página hasta address Liberando un área de memoria no contigua La función vfree() libera áreas de memoria no contiguas, su parámetro addr contiene la dirección lineal inicial del área a ser liberada. Vfree() primero escanea la lista apuntada por vmlist para encontrar la dirección del descriptor de área asociado con el área a ser liberada: for (p = &vmlist ; (tmp = *p) ; p = &tmp->next) { if (tmp->addr == addr) { *p = tmp->next; vmfree_area_pages((unsigned long)(tmp->addr), tmp->size); kfree(tmp); return; 67

98 } } El campo size del descriptor específica el tamaño del área a ser liberada. El área es liberada invocando vmfree_area_pages(), mientras que el descriptor es liberado invocando kfree(). La función vmfree_area_pages() toma dos parámetros: la dirección lineal inicial y el tamaño del área. Y ejecuta el siguiente ciclo para revertir las acciones realizadas por vmalloc_area_pages(): while (address < end) { free_area_pmd(dir, address, end - address); address = (address + PGDIR_SIZE) & PGDIR_MASK; dir++; } A su vez, free_area_pmd() revierte las acciones de alloc_area_pmd() en el ciclo: while (address < end) { free_area_pte(pmd, address, end - address); address = (address + PMD_SIZE) & PMD_MASK; pmd++; } Otra vez, free_area_pte() revierte la actividad de alloc_area_pt() en el ciclo: while (address < end) { pte_t page = *pte; pte_clear(pte); address += PAGE_SIZE; pte++; if (pte_none(page)) continue; if (pte_present(page)) { free_page(pte_page(page)); continue; } } Cada marco de página asignado a un área de memoria no contigua es liberado por medio de la función del Sistema Compañero free_ page(). La entrada correspondiente en la Tabla de Página es puesta a 0 por la macro pte_clear. 68

99 CAPÍTULO 3: PROCESOS EN LINUX 3.1. Introducción Un proceso es un programa o servicio normalmente en estado de ejecución o el objeto abstracto que crea el sistema operativo Linux para manejar el acceso de ese programa a los recursos del sistema (memoria, CPU, dispositivos de E/S). Los procesos que se encuentren en ejecución en un determinado momento son, en general, de diferente naturaleza. Por lo cual se pueden encontrar procesos de sistema o bien procesos asociados al funcionamiento local de la maquina y del kernel, o procesos (denominados daemons) asociados al control de diferentes servicios, ya sean locales o de red, porque cuando se está ofreciendo el servicio (actúa de servidor) o se está recibiendo el servicio (actúa de cliente). La mayoría de estos procesos son asociados al usuario root, aunque este no esté presente en ese momento como usuario. En caso de actuar como root, los procesos interactivos o aplicaciones en ejecución también aparecerán como procesos asociados a root. También se tienen los procesos de usuario del sistema; asociados a la ejecución de sus aplicaciones, ya sea tareas iterativas en modo texto o en modo gráfico. Pueden coexistir varias instancias de un mismo programa ejecutándose ya que Linux es un sistema multiproceso por tiempo compartido. Por lo cual un proceso puede crear a su vez otros procesos. Al proceso que genera otro proceso se le llama proceso padre. Al proceso generado por otro proceso se le llama proceso hijo. A la vista de un usuario en un momento dado hay múltiples programas en ejecución, cada uno de ellos avanzando en su tarea. Sin embargo, en una máquina con un solo procesador hay en cada instante solamente un proceso ejecutándose. Es el sistema operativo el que va rotando el uso del procesador a intervalos breves entre los procesos definidos en el sistema creando la ilusión que todos avanzan simultáneamente (más detalles en apartado de planificación). El sistema operativo Linux mantiene por cada proceso una serie de estructuras de información que permiten identificar las características de éste. En esta última categoría entran los descriptores de los segmentos de memoria asignados (Los cuales se describen en el capítulo correspondiente a gestión de memoria), los descriptores de archivos abiertos, los descriptores de los puertos de comunicación, etcétera. A la vez, el administrador del sistema dispone de herramientas para supervisar el estado de los procesos y eventualmente tomar acciones para suspender o detener la ejecución de un proceso o simplemente modificar su comportamiento. 69

100 3.2. Supervisión de procesos En Linux existen ciertos comandos y en la versión de Ubuntu una herramienta gráfica que permite observar y dar seguimiento a los procesos, ya que se necesita del conocimiento de información relacionada a éstos o manipular algunas de sus propiedades, por lo que la herramienta gráfica llamada, Monitor de procesos es una de las novedades para los usuarios de esta versión porque permite la manipulación de manera rápida y fácil. También existen algunos comandos que son utilizados a través de una consola (Conocida como terminal), para aquellos que deseen seguir trabajando en modo consola; como lo son top, htop, ps y pstree los cuales nos permiten hacer las mismas acciones que el monitor de procesos. El último permite la visualización de los procesos de forma jerárquica. A continuación, se detallará la funcionalidad de cada uno de estos comandos El comando ps (process status). El comando ps facilita información sobre los procesos que se están ejecutando en el sistema. Permite conocer el estado de los procesos, está basado en el sistema de archivos /proc, es decir, lee directamente la información de los archivos que se encuentran en este directorio. También es la herramienta básica para diagnosticar problemas relacionados con procesos. Este comando genera un reporte de un renglón por cada proceso, brindando información relevante sobre cada uno. Si se ejecuta ps sin parámetros se brinda información básica sobre los procesos que pertenecen al usuario actual. A través del uso de parámetros adecuados se pueden agregar más columnas (más información sobre cada proceso) o más filas (más procesos) al reporte. Los campos de información más importantes desplegados por ps con parametros para cada proceso son: Usuario (USER) Identificadores de proceso (PID, PPID) Uso de recursos reciente y acumulado (%CPU, %MEM, TIME) Estado del proceso (STAT, S) comando invocado (COMMAND) A continuación, se muestra en la figura 3.1 el significado de cada una de las columnas que se muestran cuando se ejecuta el comando ps. 70

101 Figura 3.1 Ejecución de comando ps La primera columna es el PID o identificador de proceso. Como está definido anteriormente, cada proceso tiene un identificador asociado que es único, es decir que no puede haber dos procesos con el mismo identificador. La segunda columna brinda información de la terminal en el que se está ejecutando el proceso. Si aparece un signo de interrogación (?), el proceso no tiene asociada ninguna terminal. La tercera columna indica el tiempo total que ha estado ejecutándose el proceso. La cuarta columna es el nombre del proceso. Como se mencionó antes, también al comando ps se le admiten algunos parámetros tales como: ps-e ps-ef El parámetro -e devuelve un listado de todos los procesos que se están ejecutando. El parámetro - f devuelve un listado extendido. En este último caso se verá en pantalla el PPID del proceso (identificador del proceso padre) y la hora en la que se ejecutó el proceso (STIME). 71

102 Los parámetros pueden aparecer juntos en el mismo comando, es decir que se podría llamar al comando ps de la siguiente forma: ps -ef. De esta manera se obtiene un listado extendido de todos los procesos que se están ejecutando en el sistema. El parámetro -u informa de los procesos creados por un determinado usuario El comando pstree En algunas versiones del sistema operativo Linux, está disponible el comando pstree, que lista los procesos y sus descendientes en forma de árbol. Esto permite visualizar rápidamente los procesos que están corriendo en el sistema (ver Figura 3.2). Figura 3.2 Ejecución de comando pstree El comando Top El comando top devuelve un listado de los procesos de forma parecida como lo hace ps, con la diferencia que la información mostrada se va actualizando periódicamente lo que permite ver la evolución del estado de los procesos. Además, en la parte superior muestra información adicional, como el espacio en memoria ocupado por los procesos, el espacio ocupado por la memoria de intercambio o swap, el número total de tareas o procesos que se están ejecutando, el número de usuarios o el porcentaje de uso del procesador, todo lo anterior se observa en la Figura

103 $ top Figura 3.3Ejecución de comando top Aunque el tema de las prioridades de los procesos se tratará más adelante, mientras el comando top está en marcha se puede cambiar fácilmente la prioridad de los procesos. A modo de resumen cada proceso en Linux tiene un nivel de prioridad que va de -20 (prioridad más alta) hasta 19 (prioridad más baja). Cuanto mayor sea el nivel de prioridad, más lentamente se ejecutará el proceso. Para poder cambiar la prioridad de un proceso a través del comando top lo que se hace es que cuando se está ejecutando el comando pulsamos la tecla r. A continuación se introduce el PID del proceso al que se le cambiará la prioridad, luego el nivel de prioridad que se le va a asignar. Se debe tener en cuenta que solamente el superusuario puede asignar valores negativos a la prioridad de un proceso. 73

104 El comando htop La versión moderna de top. Además de las funcionalidades de top permite ordenar los procesos utilizando distintos parámetros, como el uso de la CPU, por ejemplo. Incorpora color en la visualización, lo que facilita la lectura. No viene instalado por defecto. Para instalar se debe ejecuta $ sudo apt-get install htop Otros comandos que se incluyen por su importancia en la manipulación de procesos son Comando nice El administrador del sistema o el usuario dueño de un proceso pueden influir en el algoritmo de scheduling a través del llamado valor nice. Este es un número que se asigna a cada proceso e indica que tan "nice" (que tanta prioridad) tiene el proceso para con los demás. Este valor es considerado por el algoritmo de scheduling de manera que un proceso con valor nice alto estará en desventaja frente a otro con valor nice menor a la hora de decidir a quién asignar la CPU. Con el valor nice se puede cambiar la prioridad de un proceso. Por defecto, todos los procesos tienen una prioridad igual ante la CPU que es de 0. Con nice es posible iniciar un programa (proceso) con la prioridad modificada, más alta o más baja según se requiera. Las prioridades van e -20 (la más alta) a 19 la más baja, como se indico anteriormente. Solo el superusuario puede establecer prioridades negativas que son las más altas. Con la opción -l de ps es posible observar la columna NI que muestra este valor.#> nice (sin argumentos, devuelve la prioridad por defecto )0#> nice -n -5 comando (inicia comando con una prioridad de -5, lo que le da más tiempo de CPU). Comando renice Así como nice establece la prioridad de un proceso cuando se inicia su ejecución, renice permite alterarla en tiempo real, sin necesidad de detener el proceso. Por ejemplo: Primero se ejecuta el comando ps para saber la prioridad que tiene el proceso antes de cambiarlo con el comando renice. (Obsérvese el campo NI en el primer caso en -5) #> ps el F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 4 S write_ pts/200:00:00 yes. Luego con el comando renice se cambia la prioridad a 7 de la siguiente manera. (Con renice el NI quedó en 7, en tiempo real) 74

105 #> renice : prioridad antigua -5, nueva prioridad 7 #> ps el F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD4 S write_ pts/2 00:00:15 yes Con esto se puede concluir que cada uno de los procesos dentro del sistema tiene prioridad de ejecución cuando hay más de un proceso en el estado "listo para ejecutar", el kernel le asigna el uso de la CPU al de mayor prioridad en ese momento. Entre las características se tienen que: Procuran ser justos con los diferentes procesos Procuran dar buena respuesta a programas interactivos Para esto los algoritmos consideran parámetros como cuánto uso de CPU ha hecho el proceso recientemente, si pasa mucho tiempo dormido a la espera de un evento de teclado (sería un proceso interactivo), etcétera Jerarquía de procesos y ámbito Los Procesos Padre e Hijo Cuando un proceso es lanzado o ejecutado mediante la llamada a la función fork(), dentro del sistema se crea una copia similar del proceso que lo lanzo, lo que se conocerá como procesos padre e hijo. El proceso hijo que es la copia exacta a la de su padre (más adelante se mencionan las diferencias entre un proceso hijo y su padre), tiene como objetivo servir de respaldo de toda la información del proceso por si hay algún problema con el padre. Un ejemplo de lo escrito anteriormente es que el proceso consola es llamado proceso padre, todos los comandos que corran dentro del proceso consola son hijos del proceso consola, por eso la importancia del comando ps -l, ya que nos muestra las opciones PPID, S, R y UID. Como el sistema de archivos, los procesos también están organizados en una jerarquía. A medida que se producen más procesos, se crean más procesos hijos. Se puede concebir una jerarquía de procesos encadenados juntos por PID y PPID. Un ejemplo para ver esto mejor: /home/martha$ bash /home/martha$ ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 S pts/0 00:00:00 bash 0 S pts/0 00:00:00 bash 0 R pts/0 00:00:00 ps 75

106 El comando bash crea automáticamente un proceso consola hijo dentro del proceso consola padre, a pesar de que ambos se llaman bash, pero vemos que difieren sus PIDs. Atributos que hereda el proceso hijo. Entorno. Bit FD_CLOEXEC para cada descriptor de fichero. Señales capturadas. SUID y SGID. Estado de privilegios y prioridades. Librerías compartidas y segmentos de memoria compartida. PGID y TTYGID. Directorio actual y directorio raíz. Máscara y límites de medida para archivos. Eventos y estado de auditoría. Estado de depuración. Atributos diferenciadores entre padre e hijo: PID único. PPID distintos (el PPID del hijo coincide con el PID del padre). El proceso hijo tiene su propia copia de los descriptores de archivos del padre, pero comparte con éste un puntero a archivos para cada descriptor del proceso padre. Bloqueos de proceso, texto y datos no se heredan. Las subrutinas times se ponen a 0. Las alarmas pendientes toman su valor inicial. Se eliminan las señales pendientes para el proceso hijo [1]. 76

107 Procesos en primer plano El proceso que está en primer plano es aquel proceso iniciado por el usuario o interactivo, la ejecución en primer plano es la ejecución normal, es decir, que el intérprete no admite otro comando hasta que se haya terminado de ejecutar el proceso en curso. Si se ejecuta, por ejemplo, el comando ls -l, se obtiene por pantalla el resultado, y hasta que no acabe de mostrarse el listado no se podrá ejecutar ningún otro comando. En este caso el terminal permanece bloqueado, sin poder introducir ningún otro comando, hasta que el proceso en primer plano (foreground), termine Procesos en segundo plano Un proceso en segundo plano (background) es muy útil, este es un proceso no interactivo el cual no necesita ser iniciado por el usuario. Se pondrá un proceso en segundo plano añadiendo el símbolo ampersand (&) al final del comando. Cuando se ejecuta un proceso en segundo plano, se permite al usuario iniciar y trabajar con otros procesos. Los procesos tipo demonio no corren en primer plano, sino en segundo, no son llamados desde una terminal sino desde un puerto, y no aparecen en pantalla. Al ejecutar un proceso en segundo plano se devuelve un número entre corchetes seguido de otro número. El número entre corchetes indica el número de procesos que se tienen ejecutándose en segundo plano. El segundo número es el PID o identificador del proceso. Así por ejemplo, para arrancar el editor xemacs en segundo plano se debe escribir xemacs &. Al ejecutar este comando se arranca el editor, pero simultáneamente el intérprete está listo para recibir más comandos. (Para ver que trabajos se están ejecutando en segundo plano, se usa el comando jobs). Comando nohup y & Cuando se trata de ejecutar procesos en background (segundo plano) se utiliza el comando nohup o el operador & (mencionado anteriormente). Aunque realizan una función similar, no son lo mismo. Si se desea liberar la terminal de un programa que se espera durará un tiempo considerable ejecutándose, entonces se usa (&). Esto funciona mejor cuando el resultado del proceso no es necesario mandarlo a la salida estándar (stdin), como por ejemplo cuando se ejecuta un respaldo o se abre un programa Xwindow desde la consola o terminal. Para lograr esto basta con escribir el comando en cuestión y agregar al final el símbolo & (ampersand). Sin embargo lo anterior produce que el padre del proceso PPID que se invocó desde la terminal tenga dificultades si se cierra la terminal o se sale de la sesión ya que en consecuencia también se 77

108 terminarán los procesos hijos que dependan de la terminal, lo que no es muy conveniente si se desea que el proceso continué en ejecución. Para solucionar lo anterior, se utiliza el comando nohup que permite al igual que '&' mandar al proceso a segundo plano y que este quede inmune (de ahí su nombre nohup) cuando se cuelga o termina la consola de la cual se ejecutó el proceso. Así se evita que el proceso se "cuelgue" al cerrar la consola. Comando jobs En un determinado instante, el intérprete o lo que se conoce comúnmente como consola puede tener un conjunto de tareas ejecutándose en segundo plano por lo que se necesita conocer muchas veces cuales son los procesos que están corriendo en segundo plano. El comando jobs muestra la lista de estos procesos así como cual es el estado de éste Pasar procesos en primer plano a segundo plano Algunas veces se necesita poner en ejecución algún otro proceso pero también se requiere el que se está ejecutando. El comando bg permite pasar procesos desde primer plano a segundo plano. Para pasar un proceso que se encuentra en primer plano a segundo plano, debe suspenderse primero utilizando la combinación de teclas Crtl+Z. cuando se pulsa esa combinación de teclas, el proceso en ejecución se para y no vuelve a ejecutarse hasta que se pasa a primer o segundo plano. Con bg se cambia el proceso a segundo plano. Ejemplo ver Figura

109 Figura 3.4: Ejecución del comando bg y utilización de comando Jobs Pasar procesos en segundo plano a primer plano Al igual que lo anterior en muchas ocasiones es necesario pasar procesos en segundo plano a primer plano, se utiliza el comando fg, seguido de %n, donde n es el número de proceso que se quiere pasar a primer plano. Por ejemplo, fg %2 pondría en primer plano la tarea número 2. En la Figura 3.5 se ejecuta sleep 90 en segundo plano, y después pasa a primer plano con el comando fg. 79

110 Figura 3.5: Ejecución del comando fg Estructura PCB de un proceso Un proceso es una entidad dinámica, cambiando constantemente a medida que el procesador ejecuta las instrucciones de código máquina lo cual hace que mucha de su información cambie, por lo que se hace necesario el guardar la información relacionada a cada uno de los procesos, es asi como se constituye la estructura PCB. En Linux un proceso es representado por el PCB (Process Control Block) que es el bloque de control del proceso o descriptor del proceso, este bloque está descrito a su vez por la estructura de datos llamada struct task_struct donde se guarda toda la información relevante de cada proceso Pablo Garaizar Sagarminaga [2006], programación GNU/LINUX.Creative Commons, España. Cada proceso del sistema tiene una estructura del tipo struct task_struct asociada. El conjunto de procesos en el sistema Linux está representado como una colección de estructuras struct task_struct, las cuales están enlazadas de dos formas principalmente: Como una tabla hash, ordenados por el pid y como una lista circular doblemente enlazada usando los punteros p- next_task y p-prev_task. La tabla hash es usada para encontrar rápidamente una tarea por su pid usando find_task_pid(). 80

111 La lista circular doblemente enlazada que usa p-next_task/ prev_task es mantenida para poder ir fácilmente a través de todas las tareas del sistema. En el fichero <linux/shed.h> no sólo está declarada la estructura task sino que también están declaradas las estructuras que son direccionadas por algún campo de ésta (punteros) como por ejemplo la estructura struct files_struct y que están asociadas a diferentes partes del kernel, como por ejemplo, el sistema de ficheros (ver Figura 3.6). Figura 3.6 El descriptor de procesos en Linux [Bovet y Cesati, 2000: p.542] La Figura 3.6 muestra algunos de los campos que se encuentran en el PCB (también llamado task struct) entre los cuales se puede observar el estado de un proceso (state), si tiene señales pendientes(signal pending), los archivos utilizados por él, auxiliándose de los punteros direccionados hacia las estructuras relacionadas con archivos y con memoria de esta última se puede mencionar que cuando un proceso se ejecuta el sistema reserva un espacio de direcciones 81

112 logicas, las cuales las reparte de la siguiente manera. La zona del usuario, normalmente ocupa los tres primeros gigabytes de las direcciones lógicas, mientras que la zona del núcleo ocupa el cuarto gigabyte. (Ver figura.3.7). Lo cual se considera con mayor precisión dentro del capítulo de gestión de memoria dentro del apartado de Tablas de Página provisionales del Kernel. La zona del kernel de Linux está "mapeada" en todos los procesos del sistema. Zona de Núcleo Zona de Usuario Figura 3.7 Modelo de un proceso en memoria La estructura, en la que la pila y el PCB comparten la memoria ocupa 8 KB. El vector task es una lista de punteros a estructuras task_struct en el sistema [1]. A medida que se crean procesos, se crean nuevas estructuras task_struct a partir de la memoria del sistema y se añaden al vector task. Para encontrar fácilmente el proceso en ejecución Campos importantes del PCB Tipo Campo Descripción struct_task struct next_task Puntero al siguiente proceso en la lista struct_task struct prev_task Puntero al anterior proceso en la lista 82

113 struct_task struct next_run Puntero al siguiente proceso en la lista de procesos Preparados para ser ejecutados struct_task struct prev_run Puntero al anterior proceso en la lista de procesos Preparados para ser ejecutados struct_task struct p_pptr, p_opptr, p_ysptr, p_cptr, p_osptr Punteros a los procesos padre, padre original, hermano más reciente, hijo más reciente y hermano más antiguo Long Counter Número de ciclos de reloj durante los que está autorizado a ejecutarse unsigned short Timeout Máximo tiempo de espera en estado de Espera unsigned short Policy Política de planificación asociada al proceso Long Priority Prioridad estática del proceso unsigned short rt_priority Prioridad estática para procesos en tiempo real unsigned long Signal Señales en espera Struct signal_struct * Sig Puntero a los descriptores de las acciones asociadas a las señales Int exit signal Código de la señal a enviar al padre al finalizar Int exit code Código a devolver al padre: bits (0..7):número de la señal que provocó el final del proceso, bits(8..15):código devuelto por la función exit Tabla 3.1Descripción de algunos campos importantes del PCB Otra información importante que se guarda acerca de un proceso es: 83

114 Process ID (PID) Al crear un nuevo proceso se le asigna un identificador de proceso el cual es único para cada uno de los procesos dentro del sistema. Este es el número con el cual se diferenciara a la hora de ejecutar un comando. Los PID son asignados por el sistema a cada nuevo proceso en orden creciente comenzando desde cero. Si antes de una nueva inicialización del sistema se llega al número. Máximo, se vuelve a comenzar desde cero, no tomando en cuenta los procesos que aún estén activos. Parent Process ID (PPID) La creación de nuevos procesos en Linux se realiza por la vía de duplicar un proceso existente invocando al comando fork(). El PPID de un proceso es el PID de su proceso padre. UID y EUID Normalmente estos dos identificadores coinciden pero hay excepciones. El User ID (UID) del proceso identifica al creador del proceso. Este usuario y root son los únicos que pueden modificar al proceso. El Effective User ID (EUID) en cambio se utiliza para determinar si el proceso tiene permiso para acceder archivos y otros recursos del sistema. GID y EGID Es igual que los identificadores de usuario pero para grupos de usuarios. El GID se hereda del proceso padre. El EGID puede utilizarse igual que el EUID para controlar el acceso del proceso a archivos. En la mayoría de las versiones actuales de Linux el proceso puede estar en varios grupos y se revisa contra toda la lista de grupos para definir si el proceso puede acceder o no a un recurso. Terminal de control. En general los procesos están asociados a una consola de control. Esta consola determina el valor por defecto de los archivos stdin, stdout y stderr del proceso. Una excepción a esto son los procesos llamados daemons (procesos en segundo plano), que una vez lanzados se desvinculan de su terminal de control y siguen ejecutándose inclusive después de cerrada la sesión de usuario desde la cual se lanzaron a correr. 84

115 Información de E/S El kernel mantiene descriptores de los archivos abiertos por el proceso. Están siempre definidos los archivos de entrada, salida y error estándar (stdin, stdout y stderr). Por defecto están asociados con el teclado y la pantalla del terminal de control del proceso pero pueden ser redireccionados a un archivo cualquiera. Todos la consolas se preveen de un mecanismo para hacer este redireccionamiento en el momento de lanzar a correr un programa. Volatile long state Contiene el estado del proceso. Que la variable esté declarada como tipo de dato volatile le indica al compilador que su valor puede cambiarse de forma asíncrona (Por ejemplo desde una rutina de tratamiento de interrupción). int sigpending Indica si el proceso tiene señales pendientes. Un valor distinto de cero (típicamente 1). Se reacciona a estas señales al volver de una llamada al sistema, o de un bloqueo o suspensión. mm_segment_taddr_limit Indica el tamaño máximo del espacio de direcciones de dicho proceso. El valor típico es de 3 GB para procesos de usuario y 4 GB para los hilos de ejecución del kernel (ver fig.2.7). Volatile long need_resched Con su valor a 1 indica que este proceso, posiblemente, debe abandonar la CPU y por lo tanto se deberá invocar al planificador en el momento adecuado (en vez de llamar a schedule() directamente). Long start_time Indica el instante de creación de este proceso. Unsigned long blocked Contiene un mapa de bits con las señales que están temporalmente bloqueadas. struct sigpending pending Contiene la información sobre las señales que este proceso tiene pendientes [2]. 85

116 3.1.1 Ciclo de vida de un proceso La creación de un proceso en Linux se hace invocando a una función del sistema operativo llamada fork() [3]. La función fork () crea una copia idéntica del proceso que la invoca con excepción de lo siguiente: El nuevo proceso tiene un PID diferente El PPID del nuevo proceso es el PID del proceso original Se reinicia la información de la cantidad de tiempo del proceso (uso de CPU, etcétera.). Para distinguir entre los dos procesos la función fork() devuelve un cero al proceso hijo y el PID del nuevo proceso al proceso padre. Normalmente el proceso hijo lanza luego un nuevo programa ejecutando alguna variante de comando exec(). Se dará a conocer un proceso el cual no necesita de ninguna creación. Luego del boot del sistema, el kernel instala y deja corriendo un proceso llamado init con PID=1. Init es el "padre" de todos los procesos. Una de las funciones principales de init, es inicializar una serie de procesos, los cuales están especificados en el archivo inittab, donde se encuentran los scripts de inicialización del sistema y los procesos de los usuarios; el procedimiento que este sigue es el siguiente, su papel primario es crear procesos a partir de un script guardado en el archivo /etc/inittab es el fichero encargado de establecer los niveles de ejecución disponibles que van desde 0 al 6 S o s, A, B, C, los Niveles de Ejecución 0, 1, y 6 están reservados (Ver Tabla 3.2). Nivel Descripción 0 Para detener el sistema 1 se usa para llevar al sistema al modo monousuario 2-5 Operación normal (definidas para los usuarios). 6 Reiniciar el sistema. Tabla 3.2 niveles de ejecución que se encuentran establecidos en inittab Para que pueda ser leído por init los niveles de ejecución se encuentran configurados en el archivo /etc/inittab en el cual se encuentran líneas como la siguiente: id:niveles_de_ejecución:acción:proceso 86

117 Id: una secuencia de hasta 4 caracteres que identifica la entrada. niveles_de_ejecución: detalla los niveles para los cuales se van a ejecutar las acciones correspondientes a la entrada. Acción: detalla la acción que se llevará a cabo cuando se ejecute la entrada. Proceso: detalla el proceso que se va a ejecutar. Ejemplo de una línea que se encuentra dentro del archivo inittab. l2:2:wait:/etc/init.d/rc 2 El primer campo es una etiqueta arbitraria, el segundo indica que la línea se aplica al nivel de ejecución 2. El tercer campo significa que init debe ejecutar el comando especificado en el cuarto campo una sola vez, cuando se ingrese al nivel de ejecución. Además, indica que init debe esperar hasta que la ejecución de dicho comando finalice. El script de shell /etc/init.d/rc ejecuta los comandos necesarios para iniciar y parar servicios al ingresar al nivel de ejecución 2. Las acciones válidas para el campo acción son: respawn: El proceso se reinicia cuando termine. wait: El proceso se inicia cuando se entre en el nivel de ejecución específico e init esperará a su terminación. once: El proceso se ejecuta cuando se entre en el nivel de ejecución especificado. boot: El proceso se ejecuta durante el arranque del sistema. bootwait: El proceso se ejecuta durante el arranque del sistema, mientras init espera su terminación. off: Esto no realiza ninguna acción. ondemand: Un proceso marcado con un nivel de ejecución ondemand se ejecuta cuando se llame al nivel de ejecución especificado ondemand. Sin embargo, no se produce cambio de nivel de ejecución. initdefault: Una entrada initdefault especifica el nivel de ejecución en el cual se entra tras el arranque del sistema. Si no existe ninguno, init pedirá un nivel de ejecución en la consola. El campo proceso se ignora. 87

118 sysinit: El proceso se ejecuta durante el arranque del sistema. Se ejecuta antes de cualquier entrada boot o bootwait. powerwait: El proceso se ejecuta cuando init reciba la señal SIGPWR, indicando que hay algún problema con la alimentación eléctrica. Init espera a que el proceso termine antes de continuar. powerfail: Como en powerwait, excepto que init no espera que el proceso se complete. powerokwait: El proceso se ejecuta cuando init reciba la señal SIGPWR, con la condición de que haya un fichero llamado /etc/powerstatus que contenga la palabra OK. Esto significa que la alimentación eléctrica ha vuelto. ctrlaltdel: El proceso se ejecuta cuando init reciba la señal SIGINT. Esto significa que alguien en la consola del sistema ha pulsado la combinación de teclas CTRL-ALT-DEL. Normalmente se quiere ejecutar algún tipo de shutdown bien para entrar en modo monousuario o reiniciar la máquina. kbrequest: El proceso se ejecuta cuando init reciba una señal del gestor de teclado que se ha pulsado una combinación especial de teclas en el teclado de la consola [4]. Este archivo normalmente tiene entradas que harán que se levante gettys (función utilizada para que los usuarios puedan logearse) en cada línea en que los usuarios puedan conectarse. También controla procesos autónomos requeridos por un sistema particular. Un nivel de ejecución es una configuración de software del sistema que permite existir sólo a un grupo de procesos seleccionado. El cambio de nivel de ejecución se hace mediante un usuario con privilegio que ejecute telinit, que envía las señales apropiadas a init, diciéndole a qué nivel de ejecución tiene que cambiar. Además de init el kernel realiza algunos procesos más cuyo nombre y función varía en las diferentes versiones de Linux. A excepción de estos procesos que son proporcionados por el kernel al inicio, todos los demás son descendientes de init. Normalmente un proceso termina invocando a la función exit() pasando como parámetro un código de salida o exit code. El destinatario de ese código de salida es el proceso padre. El proceso padre puede esperar la terminación de su proceso hijo invocando la función wait(). Esta función manda al padre a dormir hasta que el hijo ejecute su exit() y devuelve el exit code del proceso hijo. 88

119 Cuando el proceso hijo termina antes que el padre, el kernel debe conservar el valor del exit code para pasarlo al padre cuando ejecute wait(), cuando el padre invoca esta función ella se encarga de verificar de que todo lo que el proceso hijo a creado haya sido cerrado o destruido, sino es así esta función se encarga de destruir todo lo que los hijos han dejado sin eliminar y sin utilizar. En esta situación se dice que el proceso hijo está en el estado zombie. El kernel devuelve todas las áreas de memoria solicitadas por el proceso pero debe mantener alguna información sobre el proceso (al menos su PID y el exit code). Cuando el proceso padre termina el Kernel primero encarga a init la tarea de ejecutar el wait() necesario para terminar todo en forma ordenada. A menudo init falla en esta función y suelen quedar procesos en estado zombie hasta una nueva reinicialización del sistema. Dado que un proceso zombie no consume recursos fuera de su PID, esto por lo general no provoca problemas Procesos e Hilos Un proceso en Linux es cualquier programa en ejecución y es totalmente independiente de otros procesos, mientras que un hilo es un proceso creado dentro de otro, llamado proceso ligero o subproceso que comparte una serie de recursos tales como el espacio de memoria, los archivos abiertos, etcétera. Estos son creados para cumplir una tarea determinada por lo que pueden afectar archivos, datos de memoria y otros; lo que es diferente es la pila de este y su contexto. Además estos al igual que los procesos se ejecutan concurrentemente, por lo que si un hilo afecta un dato, otro de los hilos creados o el mismo proceso accederá al dato que afecto el primero Estados de un hilo Los hilos al igual que los procesos tienen estados para su ejecución, la diferencia es que estos no son iguales para los procesos, como se observó anteriormente estos comparten algunos recursos con el proceso creador. Creado: Cuando se crea un proceso se crea un hilo para ese proceso (una imagen semejante al padre). Luego, este hilo puede crear otros hilos dentro del mismo proceso, proporcionando un puntero de instrucción y los argumentos del nuevo hilo. El hilo tendrá su propio contexto y su propio espacio de pila como se menciono antes, y pasara a la cola de listos. Bloqueado: Cuando un hilo necesita esperar por un suceso, se bloquea (salvando sus registros de usuario, contador de programa y punteros de pila). Ahora el procesador podrá pasar a ejecutar otro hilo que esté en la cola de listos mientras el anterior permanece bloqueado. 89

120 Desbloqueo: Cuando el suceso por el que el hilo se bloqueó se produce, el mismo pasa a la cola de Listos. Terminado: Cuando un hilo finaliza se liberan tanto su contexto (El conjunto de los registros) como sus pilas. No tiene sentido hablar del estado suspendido ya que al suspenderse el proceso, se suspenden todos los hilos porque comparten el mismo espacio de direcciones, por lo que para un hilo no existe este estado. Hay dos tipos de hilos que soporta Linux: Hilos a nivel de usuario Para crear estos se utiliza bibliotecas las cuales contienen código para crear y destruir hilos, intercambiar mensajes y datos entre hilos, para planificar la ejecución de hilos, para salvar y restaurar el contexto de los hilos. Hilos a nivel de núcleo Antes de la versión 2.6 del Kernel de Linux no había soporte para manejar hilos a nivel de Kernel en el área de la aplicación, no hay código de gestión de hilos, en el Kernel 2.6 se dispone de una API (interfaz de programas de aplicación) para la ejecución de hilos a nivel de Kernel. Linux utiliza un método muy particular en que no hace diferencia entre procesos e hilos, para linux si varios procesos creados con la llamada al sistema "clone" comparten el mismo espacio de direcciones virtuales, el sistema operativo los trata como hilos y lógicamente son manejados por el kernel. Existen diferencias entre hilo y proceso entre ellas están las siguientes: Es más fácil ejecutar un hilo que un proceso ya que un hilo comparte el mismo espacio de memoria por lo que cuando se quiere ejecutar se pierde un tiempo despreciable en el cambio de contexto porque no necesita ser cargado nuevamente en memoria. Se tarda mucho menos en terminar un hilo que un proceso, ya que cuando se elimina un proceso se debe eliminar la estructura PCB del mismo de manera que se debe buscar en el vector task el PCB correspondiente al proceso que termino, mientras que un hilo se elimina su contexto y pila Ejecución de un proceso La responsabilidad principal del sistema operativo Linux es el control de la ejecución de los procesos; esto incluye la determinación de las pautas de intercambio que se van a seguir y la asignación de recursos a los procesos. 90

121 Por tanto, el sistema operativo debe intercambiar la ejecución de un conjunto de procesos, para maximizar la utilización del procesador ofreciendo a la vez un tiempo de respuesta razonable. Por lo que se debe asignar los recursos a los procesos en conformidad con una política específica (por ejemplo, ciertas funciones o aplicaciones son de prioridad más alta), evitando, al mismo tiempo, el interbloqueo. Además el sistema operativo podría tener que dar soporte a la comunicación entre procesos y la creación de procesos por parte del usuario. Puesto que el proceso es fundamental, especialmente en Linux que es nuestro caso de estudio, este apartado se abre con una discusión sobre los estados del proceso, que caracterizan el comportamiento de los mismos. El estado de un proceso en un momento determinado, indica qué actividad está realizando en ese momento. Los posibles estados en que puede estar un proceso son: Ejecutándose: Los registros generales de la CPU están cargados con los valores correspondientes a este proceso y la CPU está ejecutando una instrucción del mismo. Preparado: El proceso puede ser ejecutado, pero existe otro proceso en ejecución en ese momento. En espera: El proceso está a la espera de un recurso, por ejemplo en espera de una entrada/salida. Parado: El propio proceso ha solicitado su parada. Zombie: El proceso ha finalizado pero el sistema operativo sigue manteniendo toda la información relativa al mismo. El siguiente esquema muestra como el proceso pasa por los diferentes estados. 91

122 Figura 3.8 Estados de un proceso La manera que se intercambia el proceso de un estado a otro es la siguiente 1. Nulo Nuevo: Se crea un nuevo proceso para ejecutar un programa. Este suceso se produce por alguna razón. 2. Nuevo Listo: El sistema operativo pasa un proceso del estado Nuevo al estado Listo cuando esté preparado para aceptar un proceso más. Linux ponen un límite en función del número de procesos existente o en la cantidad de memoria virtual dedicada a los procesos existentes. El motivo de este límite es asegurar que no haya tantos procesos activos como para degradar el rendimiento. 3. Listo Ejecución: Cuando se selecciona un nuevo proceso para ejecutar, el sistema operativo elige a uno de los procesos del estado Listo. 4. Ejecución Terminado: El proceso que se está ejecutando es finalizado por el sistema operativo si indica que terminó, 0 Si se abandona. 5. Ejecución Listo: La razón más común de esta transición es que el proceso que está en ejecución ha alcanzado el tiempo máximo permitido de ejecución interrumpida; otra causa para esta transición es por ejemplo, si el sistema operativo asigna diferentes niveles de prioridad a los diferentes procesos. Supóngase que un proceso A está ejecutándose con un cierto nivel de prioridad y que el proceso B, con un nivel de prioridad mayor, está bloqueado. Si el sistema operativo se entera de que el suceso que B estaba esperando se ha producido, pasando así B ha estado Listo, entonces puede interrumpir al proceso A y 92

123 expedir a B. Por último, otro caso es que un proceso ceda voluntariamente el control del procesador. 6. Ejecución Bloqueado: Un proceso se pone en el estado Bloqueado si solicita algo por lo que debe esperar. Las solicitudes al sistema operativo suelen ser en forma de llamadas a servicios del sistema, es decir, llamadas desde el programa que está ejecutándose a un procedimiento que forma parte del código del sistema operativo. Por ejemplo, un proceso puede solicitar un servicio que el sistema operativo no está preparado para llevar a cabo de inmediato. Puede pedir un recurso, tal y como un archivo o una sección compartida de memoria virtual, que no esté inmediatamente disponible. O bien el proceso puede iniciar una acción, como una operación de E/S, que debe terminarse antes de que el proceso pueda continuar. Al comunicarse los procesos unos con otros, uno se puede quedar bloqueado cuando espera a que otro proceso le proporcione una cierta entrada o cuando espera un mensaje del otro proceso. 7. Bloqueado Listo: Un proceso que está en el estado Bloqueado pasará al estado Listo cuando se produzca el suceso que estaba esperando. 8. Listo Terminado: En algunos sistemas, un padre puede terminar con un proceso hijo en cualquier momento. Además, si el padre termina, todos los procesos hijos asociados con él pueden ser finalizados. Para que estos cambios se puedan dar es necesario que el sistema operativo tenga lo que se conoce comúnmente como planificador, este es el que se encarga de escoger el proceso que se ejecutará después de haberle quitado el procesador al otro, por lo que a continuación se dará un trato especial al tema de planificación El planificador La planificación se lleva a cabo mediante la función Schedule del archivo kernel/sched.c. En Linux, para la administración de procesos se realiza por una estructura de datos llamada task_struct que representa al PCB (Process Control block) que para nuestro caso sería como un vector llamado task, que guarda punteros en una estructura task_struct de procesos, habiéndose mencionado anteriormente esta definición, entonces si su administración es por medio de un vector su capacidad debe ser limitada a el tamaño de dicho vector. Para que un proceso se ejecute implica que éste debe estar cargado en memoria principal, y que se le debe asignar tiempo de procesamiento parcial o completo. Debido a que Linux tiene la característica de ser multiprogramado debe poder suspender y reanudar procesos, y por ende hacer cambio de contexto eficiente del procesador. Además deben 93

124 existir mecanismos de sincronización de procesos y de comunicación de procesos (de lo cual se dará más adelante detalles). La sincronización de procesos se realiza mediante un elemento comúnmente llamado Planificador, que define el estado del proceso. En realidad se utiliza tres tipos de planificador según sea la acción que tenga un proceso en determinado momento, por lo que se hace necesaria la definición de los siguientes conceptos: Planificador de Largo Plazo Controla el grado de multiprogramación. En pocas palabras, es quien carga el proceso en memoria. Consiste en una cola de spool que espera a que el procesador (y el Planificador de Corto Plazo) pueda atender a alguno de los procesos que contiene. Estos procesos no han sido iniciados, es decir, no han recibido tiempo de la CPU aún Planificador de Corto Plazo Es quien decide que proceso asignarle a la CPU. Consiste generalmente en una cola de listos que indica que aquellos procesos están dispuestos a recibir tiempo de la CPU. Se ejecuta muy seguido (cada vez que un proceso sale de la CPU) y es el responsable de la interactividad del sistema Planificador de Mediano Plazo Cuando un proceso está ocupando la CPU, pero está en espera (de que termine un proceso de Entrada/Salida por ejemplo), cae en el planificador de medio plazo. Consiste en una cola de swapped-out que controla el grado de multiprogramación aumentándolo o disminuyéndolo. Ocupa la técnica de swapping, para la información de los procesos. Cuando la cola listo se ve sobrepasada (overflow), los procesos con mayor demanda de memoria pasan al planificador de mediano plazo hasta que la cola listo se desocupe. En la figura 3.9 se puede observar cuando es implementado cada uno de los planificadores. 94

125 Figura 3.9 Representación de donde actuan los diferentes tipos de planificadores Niveles de planificación [planificación.gif, 2009: p.1] 95

126 Con lo que se llega a la conclusión de que la planificación de procesos está basada en tres colas, dos de ellas para procesos de tiempo real y la tercera para los procesos normales. Procesos en tiempo real: Estos son procesos que tienen prioridad sobre cualquier otro ya que son muchas veces llamadas al sistemas que se ejecutan en modo Kernel (ver la figura.3.7). Procesos normales: son aquellos que son creados por los usuarios. En un sistema con un único procesador solamente puede haber un proceso en ejecución, mientras que los demás están en estado listo, estos para poder ser ejecutados se colocan en una de las tres colas que tiene el planificador. Dentro de cada cola los procesos se colocan en un orden determinado en función de un valor que determina la prioridad, es decir en cada cola los procesos no se colocan según llegan, sino en función de un atributo del proceso que marca las prioridades. La prioridad de un proceso es un valor formado por otros dos, por un lado la prioridad fija que es adjudicada a cada proceso por medio de una llamada al sistema y la prioridad variable que se ajusta de manera dinámica. La prioridad variable ó dinámica es gestionada por el sistema operativo en función de los recursos consumidos por el proceso, penalizando en alguna forma a aquellos procesos que consumen más recursos. También es posible modificarla desde la consola por medio del comando nice, aunque este tipo de prioridad está definida para los sistemas de planificación que no son de tiempo real. Luego el planificador permite a cada proceso ejecutarse durante poco tiempo, 200 ms (milisegundos), y cuando ese tiempo ha pasado, otro proceso se selecciona para ejecutarse y el proceso original tiene que esperar un tiempo antes de ejecutarse otra vez. Esa pequeña cantidad de tiempo se conoce como una quantum (porción de tiempo). Cuando el planificador tiene que elegir el proceso que más merece ejecutarse entre todos los procesos que se pueden ejecutar en el sistema. Lo hace de la siguiente manera: La manera en que el planificador funciona es conocida como Round Robin con prioridad esta no es aplicable a procesos en tiempo real. Pero existen además de esta planificación, políticas de planificación que son manejadas a través de la las colas mencionadas anteriormente que consisten en SCHED_FIFO SCHED_RR SCHED_OTHER 96

127 SCHED_FIFO Está política funciona como su nombre lo dice primero en llegar primero en ser servido, cuando a un proceso se le coloca en su atributo de Policy Shed_fifo este se colara en la cola de shed_fifo y su ejecución tendrá el siguiente orden Se elige el proceso de la cola SCHED_FIFO de más alta prioridad y se ejecuta a menos que se dé una de las siguientes condiciones: Existe otro proceso en dicha cola de más alta prioridad. El proceso genera una interrupción. SCHED_RR Esta política funciona como la que es por definición en Linux, a cada proceso se le proporciona una porción de tiempo llamada quantum que son aproximadamente 200 ms y se ejecuta, no se interrumpe hasta que se dé una de las siguiente condiciones: Existe un proceso de cualquier prioridad en la cola SCHED_ FIFO. Existe un proceso de prioridad igual o superior en la cola SCHED_RR. El proceso genera una interrupción. SHED_OTHER Es la planificación de Linux por defecto Round Robin con prioridades, en este caso sólo se interrumpe cuando exista un proceso de más alta prioridad en cualquiera de las tres colas. Las Funciones dentro del Kernel que permiten modificar la política de planificación y las prioridades de un proceso son: int sched_setscheduler (pid_t pid, int politica, const struct sched_param *parametros); int sched_getscheduler (pid_t pid); Los parámetros que las definen cada una de las funciones son los siguientes: Pid: identificador del proceso al que se desea cambiar la prioridad. 97

128 politica: es un valor entero que puede admitir los valores: SCHED_ FIFO (1), SCHED_RR (2) ó SCHED_ OTHER (0). parametros: se trata de una estructura sobre la que se pueden modificar otros parámetros de la planificación del proceso. Los prototipos de las funciones que gestionan la prioridad estática son: int setpriority (int grupo, int ident, int prio); int getpriority (int grupo, int ident); 3.7. Señales (signals) Las señales de Linux son un mecanismo para anunciar a un proceso que ha sucedido cierto evento que debe ser atendido. Linux permite tanto las señales POSIX estándar, como las señales POSIX en tiempo real Señales Estándar Linux soporta las señales estándar listadas a continuación. Muchos valores de señales dependen de la arquitectura, tal como se indica en la columna "Valor". Donde aparezcan tres valores, el primero de ellos es válido normalmente para alpha y sparc, el segundo para i386, ppc y sh, y el último para mips. Un (-) indica que una señal no está presente en la arquitectura correspondiente. Para corroborar el valor de la señal correspondiente a la arquitectura del microprocesador (ver tabla 3.3). Señales descritas en el estándar POSIX.1 original. Señal Valor Acción SIGHUP 1 SIGINT 2 A SIGQUIt 3 C SIGILL 4 C SIGABRT 6 C SIGFPE 8 C 98

129 SIGKILL 9 AEF SIGSEGV 11 C SIGPIPE 13 A SIGALRM 14 A SIGTERM 15 A SIGUSR1 30,10,16 A SIGUSR2 31,12,17 A SIGCHLD 20,17,18 B SIGCONT 19,18,25 SIGSTOP 17,19,23 DEF SIGTSTP 18,20,24 D SIGTTIN 21,21,26 D SIGTTOU 22,22,27 D Tabla 3.3 Valor y acciones predeterminadas de las señales en POSIX utilizadas en Linux.[5] Observación: (Para los casos SIGSYS, SIGXCPU, SIGXFSZ y, en algunas arquitecturas, también SIGBUS, la acción por omisión en Linux hasta ahora (2.6) es A (terminar). Otras señales que más adelante se incorporaron en el estándar POSIX y por lo tanto aceptadas para Linux están a continuación (ver tabla 3.4) Señal Valor Acción SIGEMT 7,-,7 SIGSTKFLT -,16,- A SIGIO 23,29,22 A SIGCLD -,-,18 SIGPWR 29,30,19 A SIGINFO 29,-,- SIGLOST -,-,- A SIGWINCH 28,28,20 B Tabla 3.4 Señales que fueron incorporadas en el estándar Posix [5] 99

130 Las letras en la columna de acción significan lo siguiente: A: La acción por omisión es terminar el proceso. B: La acción por omisión es no hacer caso de la señal. C: La acción por omisión es terminar el proceso y hacer un volcado de memoria. D: La acción por omisión es parar el proceso. E: La señal no puede ser capturada. F: La señal no puede ser pasada por alto. La lista de posibles señales a comunicar a los procesos es fija, con algunas variaciones de unas versiones con otras de Linux. La recepción de una señal en particular por parte de un proceso provoca que se ejecute una subrutina encargada de atenderla. A esa subrutina se le llama el "manejador de la señal (signal handler). Un proceso puede definir un manejador diferente para sus señales o dejar que el kernel tome las acciones predeterminadas para cada señal. Cuando un proceso define un manejador para cierta señal, se dice que "captura" (catch) esa señal. Si se desea evitar que determinada señal sea recibida por un proceso, se puede solicitar que dicha señal sea ignorada o bloqueada. Una señal ignorada, simplemente se descarta sin ningún efecto posterior. Cuando alguien envía a cierto proceso una señal que está bloqueada, la solicitud se mantiene en cola hasta que esa señal es explícitamente desbloqueada para ese proceso. Cuando la señal es desbloqueada, la subrutina de manejo de la señal es invocada una sola vez aunque la señal haya sido recibida más de una vez mientras estaba bloqueada. Las señales se pueden generar de varias maneras: Interrupciones de hardware: Estas se dan cuando un dispositivo quiere llamar la atención del microprocesador. Llamada al sistema kill: Esta llamada genera que al proceso al cual se le envio la señal termine. Evento gestionado por el núcleo (alarmas): Existen señales que se mandan cuando son generados algunos alarmas o relojes dentro del sistema operativo ejemplos de reloj como por ejemplo ciclos de reloj y alarmas 100

131 Interacción del usuario y el terminal (Ctrl-z): cuando se da esta señal se obtiene una detención de los procesos en curso. Las estructuras relacionadas con las señales son: Task_struct: La estructura de procesos en cuatro campos. Signal_struct: La estructura de las señales en sí. K_sigaction: La estructura en la que se define el manejador de cada señal. Signal_queue: Una lista de señales. itimerval: Estructura de las alarmas. Las funciones que modifican estas estructuras son: Signal (): Es la función que permite a un proceso cambiar el manejador por defecto de una señal. Kill(): Es la función y llamada al sistema que envía una señal para que un proceso termine su ejecución se hace la llamada por medio de parámetro. Sigsuspend(): Permite especificar la espera de una señal en particular Sigaction(): La función signal es limitada en cuanto al control del proceso en la recepción de una señal. Posix introduce sigaction que permite también desviar una señal y resuelve estos problemas. Sigprocmask(): Cada proceso dispone de una máscara de señales que le permite definir qué señales se encuentran bloqueadas. Si bien un proceso tiene ciertas libertades para configurar cómo reacciona frente a una señal (capturando, bloqueando o ignorando la señal), el kernel se reserva ciertos derechos sobre algunas señales. En función del sistema operativo, bien el kernel del sistema operativo o bien los procesos normales, los procesos pueden elegir entre un conjunto de señales predefinidas, siempre que tengan los privilegios necesarios. Es decir, no todos los procesos se pueden comunicar con procesos privilegiados mediante señales. 101

132 Entre las señales más conocida y que no pueden ser bloqueadas se tiene: Kill Una señal puede enviarse desde un programa utilizando llamadas al sistema operativo Linux, o desde la línea de comandos de una consola utilizando el comando kill (definido más abajo). Al comando kill se le pasa como parámetro el número o nombre de la señal y el PID del proceso. La lista de posibles señales puede obtenerse para cada sistema a través del comando man kill o kill -l de la función kill() del sistema operativo Linux. El kill, que literalmente quiere decir matar, sirve no solo para matar o terminar procesos sino principalmente para enviar señales (signals) a los procesos. La señal por default (cuando no se indica ninguna es terminar o matar el proceso). Las señales llamadas KILL no pueden ser capturadas, ni bloqueadas, ni ignoradas. La señal KILL provoca la terminación de un proceso. El uso más habitual del comando es para terminar un proceso, de ahí su nombre. Esto provoca que un usuario con privilegios en el sistema sea capaz de matar un proceso importante mandando una señal SIGKILL, por ejemplo. Para mostrar las señales que proporcionan el núcleo y su identificador numérico asociado, se usará el siguiente comando: $ kill l SEÑALES Esta señal es enviada por bash a todas las tareas que se 1 SIGHUP ejecutan en segundo plano. Normalmente un proceso 34 SIGRTMIN Ordena al proceso que termine terminará cuando reciba esta señal 2 SIGINT Señal que se produce al presionar Crtl-C. Interrupción procedente del teclado 35 SIGRTMIN+1 Ordena al proceso que termine Su efecto es análogo al de 3 SIGQUIT SIGINT pero además actúa como si el programa hubiera 36 SIGRTMIN+2 Ordena al proceso que termine provocado algún error interno 4 SIGILL Informa sobre la ejecución de 37 SIGRTMIN+3 Ordena al proceso que 102

133 Una instrucción ilegal por parte del programa. termine 5 SIGTRAP Trampa de traceo o depuración 38 SIGRTMIN+4 Ordena al proceso que termine 6 SIGABRT Señal de aborto procedente de abort(3) 39 SIGRTMIN+5 Ordena al proceso que termine 7 SIGBUS Error de dirección mal alineada 40 SIGRTMIN+6 Ordena al proceso que termine 8 SIGFPE Excepción de punto flotante 41 SIGRTMIN+7 Ordena al proceso que termine 9 SIGKILL Termina el proceso que la recibe de forma inmediata 42 SIGRTMIN+8 Ordena al proceso que termine 10 SIGUSR1 Señal definida por el usuario 43 SIGRTMIN+9 Ordena al proceso que termine 11 SIGSEGV Violación de segmento de memoria. 44 SIGRTMIN+10 Ordena al proceso que termine 12 SIGUSR2 Señal definida por el usuario2 45 SIGRTMIN+11 Ordena al proceso que termine 13 SIGPIPE Tubería rota: escritura sin lectores 46 SIGRTMIN+12 Ordena al proceso que termine 14 SIGALRM Señal de alarma 47 SIGRTMIN+13 Ordena al proceso que termine 15 SIGTERM Señal de terminación 48 SIGRTMIN+14 Ordena al proceso que 16 SIGSTKFL Fallo en la pila del procesador Proceso hijo terminado o detenido y continua si estaba 17 SIGCHLD detenido 49 SIGRTMIN+15 termine Ordena al proceso que termine 18 SIGCONT Continua después de detenido estar 50 SIGRTMAX-14 Ordena al proceso que termine 103

134 19 SIGSTOP Reanuda un proceso suspendido previamente por la señal SIGTSTP 51 SIGRTMAX-13 Ordena al proceso que termine 20 SIGTSTP Señal de stop escrita en la TTY 52 SIGRTMAX-12 Ordena al proceso que termine La misma señal producida por 21 SIGTTIN Control-z, su efecto es suspender la ejecución de un 53 SIGRTMAX-11 Ordena al proceso que termine proceso 22 SIGTTOU Salida a la tty para un proceso de fondo 54 SIGRTMAX-10 Ordena al proceso que termine Salida a la TTY para un 23 SIGURG proceso 55 SIGRTMAX-9 24 SIGXCPU Para terminar un proceso 56 SIGRTMAX-8 25 SIGXFSZ Excedio el tamaño permitido para un archivo porque se llego al maximo tamaño permitido para el usuario. 57 SIGRTMAX-7 26 SIGVTALR M Se envía cuando un contador que cuenta el tiempo de ejecución del proceso, excluyendo el tiempo de las llamadas al kernel. Si el proceso está dormido no se decremento, es decir, es tiempo que no cuenta. 58 SIGRTMAX-6 27 SIGPROF Señal enviada cuando Un contador cuenta el tiempo de ejecución del proceso, incluidas las llamadas al kernel. 59 SIGRTMAX-5 28 SIGWINCH 60 SIGRTMAX-4 104

135 Se envía cuando se produce un 29 SIGIO evento de E/S. 61 SIGRTMAX-3 30 SIGPWR Escritura sin lectores 62 SIGRTMAX-2 31 SIGSYS Argumento de rutina inválido 63 SIGRTMAX-1 64 SIGRTMAX Tabla 3.5 Descripción de algunas señales (No hay de todas información) Linux implementa las señales, usando información almacenada en la task_struct del proceso. El número de señales soportadas está limitado normalmente al tamaño de palabra del procesador. Anteriormente, sólo los procesadores con un tamaño de palabra de 64 bits podían manejar hasta 64 señales, pero en la versión actual del kernel (2.6) disponemos de 64 señales incluso en arquitecturas de 32bits. Una limitación importante de las señales es que no tienen prioridades relativas, es decir, si dos señales llegan al mismo tiempo a un proceso puede que sean tratadas en cualquier orden, no podemos asegurar la prioridad de una en concreto. Otra limitación es la imposibilidad de tratar múltiples señales iguales: si nos llegan 14 señales SIGCONT a la vez, por ejemplo, el proceso funcionará como si hubiera recibido sólo una. Cuando se quiere que un proceso espere a que le llegue una señal, se usa la función pause(). Esta función provoca que el proceso (o thread) en cuestión duerma hasta que le llegue la señal. Para capturar esa señal, el proceso deberá haber establecido un tratamiento de la misma con la función signal() que se encuentra en el archivo de cabecera signal.h. STOP Provoca la detención del proceso que queda en el estado "stopped" hasta que alguien le envíe la señal CONT. Además la señal STOP no puede ser capturada, ni bloqueada, ni ignorada. CONT Se utiliza cuando se ha detenido un proceso con el comando STOP y este pueda continuar con lo que estaba haciendo por lo que CONT no puede ser bloqueada, ni ignorada por ninguna otra señal. 105

136 3.8. Comunicación entre procesos en Linux Los medios de comunicación entre procesos (Inter-Process Communication o IPC) que Linux proporciona son un método para que múltiples procesos se comuniquen unos con otros. Hay varios métodos de IPC disponibles en Linux: 1. Pipes UNIX semi-duplex 2. FIFOs (pipes con nombre) 3. Colas de mensajes estilo SYSTEM V 4. Semáforos estilo SYSTEM V o Segmentos de memoria compartida estilo SYSTEM V Pipes UNIX Semi-duplex La forma más simple de IPC en Linux son los pipes o tuberías, han estado presentes desde los primeros orígenes del sistema operativo UNIX y proporcionan un método de comunicaciones en un sentido (unidirecional, semi-duplex) entre procesos. Una tubería (pipe) es simplemente un método de conexión que une la salida estándar de un proceso a la entrada estándar de otro. Para esto se utilizan descriptores de archivos reservados, los cuales en forma general son: 0: entrada estándar (stdin). 1: salida estándar (stdout). 2: salida de error (stderr). Este mecanismo es ampliamente usado, incluso en la línea de comandos UNIX (en la shell): ls sort lp Lo anterior es un ejemplo de pipeline, donde se toma la salida de un comando ls como entrada de un comando sort, quien a su vez entrega su salida a la entrada de lp. Los datos corren por la tubería semi-duplex, viajando (virtualmente) de izquierda a derecha por la tubería. Cuando un proceso crea una tubería, el kernel instala dos descriptores de archivos para que los use la tubería. Un descriptor se usa para permitir un camino de entrada a la tubería (write), mientras que la otra se usa para obtener los datos de la tubería (read). A estas alturas, la tubería tiene un pequeño uso práctico, ya que la creación del proceso solo usa la tubería para 106

137 comunicarse consigo mismo. Se podría considerar esta presentación de un proceso y del kernel después de que se haya creado una tubería: Figura 3.10 Comunicación de dos procesos con el kernel [6] Del diagrama anterior, es fácil ver como se conectan los descriptores. Si el proceso envía datos por la tubería (fd0), tiene la habilidad obtener (leer) esa información de fd1. Sin embargo, hay un objetivo más amplio sobre el esquema anterior. Mientras una tubería conecta inicialmente un proceso a sí mismo, los datos que viajan por la tubería se mueven por el kernel. En el caso que solo hubiera un proceso no tendría sentido la creación de una tubería ahora, el proceso de creación bifurca un proceso hijo. Como un proceso hijo hereda cualquier descriptor de archivo abierto del padre, ahora tenemos la base para la comunicación multiprocesos (entre padre ehijo). La versión actualizada del esquema simple quedaría como: Figura 3.11 Comunicación entre procesos padre hijo. [6] Arriba, se ve que ambos procesos ahora tienen acceso al descriptor del archivo que constituye la tubería. En ésta fase se debe tomar una decisión crítica. En qué dirección se quiere que viajen los datos? El proceso hijo envía información al padre, o viceversa? Se debe proceder a "cerrar" el extremo de la tubería que no interesa. Suponiendo que el hijo ejecuta su código, y devuelve información por la tubería al padre. La figura ya revisado aparecería como: 107

138 Figura 3.12 Comunicación en una sola vía. [6] Ahora la construcción de la tubería esta completa. Lo único que queda por hacer es usar la tubería. Para acceder a una tubería directamente, se puede usar la misma llamada al sistema que se usa para un archivo E/S de bajo nivel. Para enviarle datos a la tubería, se usa la llamada al sistema write(), y para recuperar datos de la tubería, se usa la llamada al sistema read(). Se debe tener presente que ciertas llamadas al sistema, como por ejemplo lseek(), no trabaja con descriptores a tuberías. De las tuberías semi-duplex se puede decir que: Se pueden crear tuberías de dos direcciones abriendo dos tuberías, y reasignando los descriptores de archivo al proceso hijo. La llamada a pipe() debe hacerse ANTES de la llamada a fork(), o los hijos no heredaran los descriptores. Con tuberías semi-duplex, cualquier proceso conectado debe compartir el ancestro indicado. Como la tubería reside en el kernel, cualquier proceso que no sea ancestro del creador de la tubería no tiene forma de direccionarlo. Este no es el caso de las tuberías con nombre (FIFOS) Tuberías con Nombre (FIFO - First In First Out) Un FIFO también se conoce como una tubería con nombre. El nombre es de un archivo que múltiples procesos pueden abrir, leer y escribir. Una tubería con nombre funciona como una tubería normal, pero tiene algunas diferencias notables: Las tuberías con nombre existen en el sistema de archivos como un archivo de dispositivo especial. Los procesos de diferentes padres pueden compartir datos mediante una tubería con nombre. 108

139 Una vez finalizadas todas las operaciones de E/S, la tubería con nombre permanece en el sistema de archivos para un uso posterior. Operaciones con FIFOs Las operaciones E/S sobre un FIFO son esencialmente las mismas que para las tuberías normales, con una gran excepción. Se debe usar una llamada del sistema open () o una función de librería para abrir físicamente un canal para la tubería. Con las tuberías semi-duplex, esto es innecesario, ya que la tubería reside en el kernel y no en un sistema de archivos físico. Acciones Bloqueantes en una FIFO Normalmente, el bloqueo ocurre en un FIFO. En otras palabras, si se abre el FIFO para lectura, el proceso estará "bloqueado" hasta que cualquier otro proceso lo abra para escritura. Esta acción funciona al revés también. Si este comportamiento no nos interesa, se puede usar la bandera O_NONBLOCK en la llamada a open() para desactivar la acción de bloqueo por defecto. Al tener el nombre del pipe que se encuentra en el disco hace que todo sea mucho más fácil. Procesos no relacionados entre ellos pueden ahora comunicarse mediante los pipes Colas de Mensajes Una cola de mensajes funciona como una FIFO, pero con algunas diferencias. Generalmente, los mensajes son sacados de la cola en el orden en que se pusieron. Sin embargo, hay maneras de sacar cierto mensaje de la cola antes de que alcance llegar al inicio de la cola. Un proceso puede crear una nueva cola de mensajes, o se puede conectar a una ya existente. De esta forma, dos procesos pueden compartir información mediante la misma cola de mensajes. Una vez que se crea una cola de mensajes, esta no desaparece hasta que se destruya. Todos los procesos que alguna vez la usaron pueden finalizar, pero la cola todavía existirá. Una buena costumbre sería usar el comando ipcs para verificar si existe alguna cola de mensajes que ya no esté en uso y destruirla con el comando ipcrm. Primero que nada cuando requiere conectarse a una cola, o crearla si no existe. Se debe utilizar la llamada al sistema msgget(): int msgget(key_t key, int msgflg); msgget() devuelve el ID de la cola de mensajes en caso de éxito 0 y -1 en caso de error. 109

140 1. El primer argumento, key, es un identificador único que describe la cola a la cual uno se quiere conectar (o crear). Cualquier otro proceso que quiera conectarse a esta cola deberá usar el mismo key. 2. El segundo argumento, msgflg, le dice a msgget() qué hacer con la cola. Para crear una cola, este campo debe ser igual a IPC_CREAT junto a los permisos para la cola. (Los permisos de la cola son los mismos que los permisos estándar de archivos las colas reciben los user-id y group-id del programa que las creó). Para crear una key (llave) se realiza por el tipo key_t es un long, se puede usar cualquier número que se quiera. Pero qué pasaría si otro programa utiliza el mismo número pero quiere usar otra cola? La solución es usar la función ftok() la cual genera un identificador a partir de dos argumentos: key_t ftok(const char *path, int id); o o path debe ser un archivo que el proceso pueda leer. El otro argumento, id es normalmente un char escogido arbitrariamente, como por ejemplo 'A'. La función ftok() usa información sobre el archivo y el id para generar un identificador, probablemente único, para ser usado en msgget(). Los programas que quieran usar la misma cola deben generar el mismo identificador key, así que deben pasarle algunos parámetros a ftok() Semáforos Los semáforos de Linux son una estructura de datos con un contador asociado, el cual es verificado por cada thread antes de acceder a dicha estructura. Posee un contador entero, una lista de procesos en espera y dos operaciones básicas up(), down(). La estructuras de semaphore están definidos en <asm/semaphore.h> y un controlador sólo debe acceder a su estructura utilizando las primitivas provistas Los semáforos deben inicializarse antes de su uso pasando un valor numérico a sema init. Si bien la primitiva DOWN está implementada con varios comportamientos, nosotros sólo veremos la función down_interruptible que permite al proceso ser interrumpido mientras lleva a cabo la operación. Si se interrumpe, el proceso no habrá adquirido el semáforo y entonces no será necesario ejecutar UP. Funciones para manejar los semáforos 110

141 void sema init(struct semaphore *sem, int val): Inicializa el semáforo sem con el valor val. int down interruptible(struct semaphore *sem): Esta función permite que éste reciba señales en el transcurso de la llamada. En caso de éxito retorna 0 y en caso de error: - EINTR (llamada interrumpida). Un problema que puede surgir durante una lectura del dispositivo es que no haya datos en ese momento pero no se haya llegado al fin del archivo. Es decir, se puede asegurar que llegarán datos. La solución a esto es dormir esperando datos. Los semáforos se usan para asegurar la exclusividad mutua en un recurso compartido. Este acceso exclusivo evita desastres que pueden ocurrir cuando varios procesos están leyendo o escribiendo en un mismo lugar. Los procesos tendrán que esperar su turno hasta que su acceso sea permitido. Los semáforos se pueden utilizar para controlar el acceso a los archivos, memoria compartida, y en general cualquier otro recurso compartido. Obtener semáforos Con IPC de System V, se obtienen conjuntos de semáforos. Obviamente, se puede obtener un conjunto de semáforos que sólo contiene un semáforo, pero la idea es que se puede tener una gran cantidad de semáforos al crear un solo conjunto de semáforos. La creación de un semáforo se hace mediante la llamada a la función semget(), que devuelve el id (identificador) del semáforo (de aquí en adelante será referido como semid): #include <sys/sem.h> int semget(key_t key, int nsems, int semflg); o o o El parametro key que se utilice en esta función es un identificador único que es usado por procesos diferentes para identificar este conjunto de semáforos. (El key será generado usando ftok(), descrito en los párrafos anteriores sobre las colas de mensajes). El siguiente argumento, nsems, es la cantidad de semáforos en este conjunto de semáforos. Finalmente, el argumento semflg le indica a semget() entre otras cosas; qué permisos tendrá el nuevo conjunto de semáforos, si se está creando un nuevo 111

142 conjunto o si sólo se quiere conectar a un conjunto ya existente. Para crear un nuevo conjunto, se puede combinar los permisos con el argumento IPC_CREAT. Todas las operaciones sobre semáforos utilizan la llamada al sistema semop(). Esta llamada al sistema es de propósito general, y su funcionalidad es dictada por una estructura que se le pasa, struct sembuf: struct sembuf { ushort sem_num; short sem_op; short sem_flg; }; sem_num es el número del semáforo en el conjunto que se quiere manipular. sem_op es lo que se quiere hacer con ese semáforo. Esto tiene varios significados, dependiendo si sem_op es positivo, negativo, o cero, tal como se muestra a continuación. Cuando se tiene la función básicamente lo que hay es que si la función retorna un valor positivo, el valor de sem_op es sumado al valor del semáforo. Así es como un programa usa un semáforo para marcar un recurso como ocupado. Por el contrario si es un valor negativo; si el valor absoluto de sem_op es mayor que le valor del semáforo, el proceso que lo llama se bloqueará hasta que el valor del semáforo alcanza el valor absoluto de sem_op. Finalmente, el valor absoluto de sem_op será restado del valor del semáforo. Asi es como un proceso entrega un recurso vigilado por el semáforo y si el valor es cero este proceso esperará hasta que le semáforo en cuestion alcance el 0. Así que, básicamente, lo que se hace es cargar un struct sembuf con los valores que se quieran, y luego llamar a semop (), de esta manera: int semop(int semid,struct sembuf *sops, unsigned int nsops); o o El argumento semid es el número obtenido de la llamada a semget(). Sops es un puntero a una estructura struct sembuf que ya se haya rellenado con los comandos de los semáforos. Incluso se puede crear un arreglo de struct sembufs, para realizar muchas operaciones al mismo tiempo. 112

143 El argumento sem_flg permite al programa especificar modificadores que alteran más aún los efectos de una llamada a semop(). Uno de estos modificadores es IPC_NOWAIT, que causa que la llamada semop() devuelva el error EAGAIN si se encuentra con una situación donde normalmente se bloqueará. Esto sirve para cuando se quiere sondear con el fin de verificar si se puede ocupar algún. Otro modificador muy útil es SEM_UNDO. Este causa que semop() grabe, de cierta forma, el cambio realizado en el semáforo. Cuando el programa termina su ejecución, el kernel automáticamente deshace todos los cambios que fueron marcados con el modificador SEM_UNDO. Destruir un semáforo Hay dos maneras de deshacerse de un conjunto semáforos: Una es usando el comando ipcrm de UNIX. La otra manera es a través de la llamada semctl() con los argumentos adecuados. La función union semun, junto con la llamada a semctl() se utilizan para destruir el semáforo: union semun { int val; /* usado sólo para SETVAL */ struct semid_ds *buf; /* para IPC_STAT y IPC_SET */ ushort *array; /* usado para GETALL y SETALL */ }; int semctl(int semid, int semnum, int cmd, union semun arg); Básicamente, se pone en semid el ID del semáforo que se quiere destruir. El cmd debe estár en IPC_RMID, lo que le indica a semctl() que debe sacar este conjunto de semáforos. Los parámetros semnum y arg no tienen ningún significado en el contexto de IPC_RMID y se puede dejar en cualquier cosa. Cuando recién se crearon los semáforos, estos quedan inicializados en cero. Esto es grave, ya que significa que todos están marcados como ocupados; y se necesita otra llamada (ya sea a semop() o a semctl() para marcarlos como libres). Qué significa todo esto? Esto significa que la 113

144 creación de semáforos no es atómica. Si dos procesos están tratando de crear, inicializar y usan un semáforo al mismo tiempo, se puede producir una condición de carrera. Este problema se puede resolver teniendo un solo proceso que crea e inicializa el semáforo. El proceso principal sólo lo accesa, pero nunca lo crea o lo destruye. Los semáforos son muy útiles en una situación de concurrencia. Sirven para controlar el acceso a un archivo pero también a otro recurso como por ejemplo la memoria compartida. Siempre cuando se tienen múltiples procesos ejecutándose dentro de una sección crítica de código, se necesitan los semáforos Los procesos en memoria Como se mencionó anteriormente Linux ve la memoria como un solo bloque, que se encuentra dividido en dos espacios, uno para la ejecución de los procesos en modo kernel (generalmente 1 G) y el otro para la ejecución en modo usuario (Generalmente 3 G). Pero cuando todos los procesos que genera un usuario y también el sistema operativo deben de estar ejecutándose surge la pregunta. Cómo hace el sistema operativo para que estos puedan estar ejecutándose al mismo tiempo y residiendo en la memoria, lo cual da paso al concepto de memoria virtual del cual se apoya la memoria física, para poder realizar lo antes mencionado. La técnica llamada memoria virtual se conceptualizará a continuación y se tratará de proporcionar una información resumida de cómo funciona Memoria virtual de un proceso La memoria virtual es una técnica que se utiliza para dar la ilusión de un espacio de memoria mucho mayor que la memoria física de una máquina. Esto permite que los programas se ejecuten sin tener en cuenta el tamaño exacto de la memoria física. La ilusión de la memoria virtual está soportada por el mecanismo de traducción de memoria mencionado anteriormente en el capítulo de memoria, junto con una gran cantidad de almacenamiento rápido en disco duro. En cualquier momento el espacio de direcciones virtual, está mapeado de tal forma que una pequeña parte de él, está en memoria real y el resto almacenado en el disco. Debido a que sólo la parte de memoria virtual que está almacenada en la memoria principal, es accesible a la CPU, según un programa va ejecutándose necesita un espacio de memoria que por lo general es el controlador el que se encarga de asignar memoria a los distintos procesos y a sus datos, ofrecer mecanismos de protección, utilizar técnicas de swapping, etcétera. 114

145 Este espacio de direcciones está completamente aislado de otros procesos, de forma que un proceso no puede interferir con otro. También, el mecanismo de memoria virtual ofrecido por el hardware permite proteger determinadas áreas de memoria contra operaciones de escritura. Esto protege el código y los datos de ser sobrescritos por aplicaciones perversas. En sistemas multiprogramados como Linux, se debe dividir la memoria en múltiples procesos, para que cada proceso del sistema se ejecute con una cantidad de memoria justa de toda la memoria física disponible, de forma que todos los procesos dispongan de los recursos disponibles. Existen procesos con un tiempo de vida del orden del nanosegundo, mientras que otros duran días. El mecanismo de hardware que Linux utiliza para esta tarea es llamado paginación la cual se apoya de tablas de mapas de proceso y tablas de página. Simplemente se habilita por el sistema operativo en su carga inicial, al situar un bit, en un registro especial de la CPU que es sólo accesible desde el sistema operativo Paginación En lo mencionado anteriormente sobre el mecanismo de hardware llamado Paginación se refiere al método que Linux en un sistema Intel x86 utiliza que es una de las estrategias la cual consiste en crear páginas de tamaño de 4 KB(Esto significa que el mecanismo de paginación divide el espacio de direcciones lineal de 2 elevado a 32 bits (4 GB) en 2 elevado a 20 páginas de 2 elevado a 12 bytes cada una (4 KB).) y marcos de pagina, para retroalimentar este concepto remitirse al capítulo de Gestión de memoria. El tamaño de la página Hay varios factores que considerar. Uno es la fragmentación interna. Sin duda, cuanto menor sea el tamaño de página, menor será la cantidad de fragmentación interna. Para optimizar el uso de la memoria principal, es positivo reducir la fragmentación interna. Por otro lado, cuanto menor sea la página, mayor será el número de páginas que se necesitan por proceso. Un número mayor de páginas por proceso significa que las tablas de páginas que se necesitan por proceso serán mayores.así pues, pueden suceder dos fallos de página para una única referencia a la memoria: primero, para traer la parte necesaria de la tabla de páginas y, segundo, para traer la página del proceso. Se puede considerar el efecto que tiene el tamaño de página en el porcentaje de fallos de página y se basa en el principio de cercanía. Si el tamaño de página es muy pequeño, normalmente estarán disponibles en la memoria principal un gran número de páginas para cada proceso. Después de un tiempo, todas las páginas de la memoria contendrán parte de las referencias más recientes del proceso y la tasa de fallos de página será menor. Cuando de incrementa el tamaño de la página, cada página individual contendrá posiciones cada vez más distantes de cualquier referencia 115

146 reciente; se atenúa el efecto de principio de cercanía y comienza a aumentar la tasa de fallos de página, que comenzará a bajar cuando, finalmente, el tamaño de página se aproxime al tamaño de todo el proceso. Cuando una sola página abarca todo el proceso, no hay fallos de página. Una dificultad más es que la tasa de fallos de página viene determinada también por el número de marcos asignados a un proceso. Por último el diseño del tamaño de página está relacionado con el tamaño de la memoria física principal. Al mismo tiempo que la memoria principal se hace mayor, el espacio de direcciones que emplean las aplicaciones también crece. Esta tendencia es más evidente en las computadoras personales y estaciones de trabajo, donde las aplicaciones se hacen cada vez más complejas. En este apartado se estudiará el concepto de paginación por demanda en Linux el cual está relacionado con los procesos para dividir la memoria y darle cabida a cada uno de los procesos dentro de sistema para comenzar definiremos lo que significa paginación por demanda La paginación por demanda Es un concepto que nace a raíz de que en muchas ocasiones un proceso no utiliza todo el código y datos contenidos en su memoria virtual dentro de un período de tiempo determinado. La memoria virtual del proceso puede que tenga código que sólo se usa en ciertas ocasiones, como en la inicialización o para procesar un evento en particular. Puede que sólo haya usado unas pocas rutinas de sus bibliotecas compartidas. Sería un mal uso de la memoria cargar todo su código y datos en la memoria física donde podría terminar sin usarse. El sistema no funcionaría eficientemente si se multiplica ese gasto de memoria por el número de procesos en el sistema. La paginación por demanda trata de corregir lo anterior y lo que hace es que copia una página de memoria virtual de un proceso en la memoria física del sistema cuando el proceso trata de acceder a ella. De esta manera, en vez de cargar el código y los datos en la memoria física de inmediato, el núcleo de Linux altera la tabla de páginas del proceso, designando las áreas virtuales como existentes, pero no en memoria. Cuando el proceso trata de acceder al código o a los datos, el hardware del sistema generará un fallo de página (page fault) y le sede el control al núcleo para que arregle el fallo de página. Por lo tanto, para cada área de memoria virtual en el espacio de direccionamiento de un proceso, Linux necesita saber de dónde viene esa memoria y cómo ponerla en memoria para arreglar los fallos de página. La tabla de página es un mecanismo de control que se tiene dentro del concepto de paginación ya que en esta se lleva el control de las paginas activas y las que no se encuentran todavía en utilización, para saber cuáles son las paginas activas este necesita de un bit para indicar si la página esta en memoria principal o no, también se necesita un bit para saber si se ha modificado el contenido de la página desde que se cargo en memoria principal; por lo tanto sino ha sido 116

147 modificada la página no tendrá que volverse a escribir en disco cuando haya que sacarla de memoria pero si sucede lo contrario y la página ha sido modificada, el sistema operativo debe preservar el contenido de la página para que sea accedida después. Este tipo de pagina es conocido como pagina sucia (dirty page) y cuando es removida de la memoria es guardada en un archivo especial llamado archivo swap, además se encuentra información sobre un número de página y un desplazamiento en la página, el primero se utiliza para tomarlo como índice en la tabla de pagina, lo que proporciona una dirección física de página en memoria central luego a esta se le suma el desplazamiento para obtener la dirección física, como se obtienen las direcciones físicas se puede encontrar con más detalle en el capítulo de gestión de memoria. En realidad el objetivo principal de las tablas de página es establecer una correspondencia entre las páginas virtuales y los marcos de páginas. Vale la pena mencionar que cada proceso dentro del sistema cuenta con una tabla de página. Estructura de una entrada de la tabla de página La organización exacta de una entrada de la tabla de página depende mucho de la máquina, pero el tipo de información presente es casi lo mismo en todas las computadoras. (Ver figura 3.13). El tamaño varia de una computadora a otra pero el de 32 bits es común. El campo más importante es el de marco de página. Después de todo, el objetivo de la correspondencia de páginas es averiguar este valor, luego tenemos el bit de presente/ausente si este bit es 1, la entrada es válida y puede usarse; si es 0 la página virtual a ala que la entrada corresponde no está en memoria, como se mencionó anteriormente querer acceder a una página que no está cargada produce un fallo de página, además los bits de protección indican cuáles tipos de acceso están permitidos. En su forma más simple, este campo contiene un bit, que es 0 si se permite leer y escribir, y 1 si solo se permite leer, los bits de solicitada y modificada llevan el control del uso de la página, cuando se escribe en una página el hardware enciende en forma automática el bit de modificada. Éste es útil cuando el sistema operativo decide usar un marco de página para cargar una nueva página a la memoria. El bit solicitada se enciende cada vez que se hace referencia a una página ya sea para leer o par escribir, este bit ayudará al sistema operativo a escoger la página que se desaloja cuando se presenta un fallo de página. El último bit permite el uso de caché con la página. Está característica es importante en el caso de páginas que corresponden a registros de los dispositivos no a memoria. 117

148 La Figura 3.13 muestra como es la entrada típica de las tablas de páginas. Figura 3.13 Muestra la entrada a una tabla de página Debido al tamaño del espacio de memoria direccional los procesadores, la tabla de páginas raramente se implementa en un espacio de memoria contigua. Como la tabla de páginas debe ser residente en memoria, necesitaría mucha memoria por lo que también las tablas de páginas deberán almacenarse en memoria virtual. Por esta razón la tabla de páginas a menudo se descompone en varios niveles, con un mínimo de 2. El interés de esta tabla de páginas por niveles se basa en que la tabla de páginas no necesita cargarse completamente en memoria. Linux gestiona la memoria central y las tablas de páginas utilizadas para convertir las direcciones virtuales en direcciones físicas. Implementa una gestión de memoria que es ampliamente independiente del procesador sobre el que se ejecuta. En realidad, la gestión de la memoria implementada por Linux considera que dispone de una tabla de páginas a tres niveles: La tabla global, cuyas entradas contienen las direcciones de páginas que contienen tablas intermedias; Las tablas intermedias, cuyas entradas contienen las direcciones de páginas que contienen tablas de páginas. Las tablas de páginas, cuyas entradas contienen las direcciones de páginas de memoria que contienen el código o los datos utilizados por el núcleo o los procesos de usuario. (Ver figura 3.14) 118

149 Figura 3.14 Niveles de tablas de página Ya que posee esta paginación Linux intenta sacarle el máximo partido y cuando varios procesos acceden a los mismos datos Linux intenta compartir al máximo las páginas. Como ya se mencionó anteriormente solo algunas de las páginas que se utilizan están activas, lo que da paso al concepto llamado swapping que es el encargado de realizar esta operación de carga y descarga de página Swapping Es un mecanismo para mover procesos entre memoria principal y secundaria, normalmente disco (dispositivo se swap). Con swapping, los procesos pueden salir y entrar de la memoria durante su tiempo de ejecución. Normalmente, un programa abandona la memoria para dejar espacio a otro. El swapping modifica la transición de los estados de los procesos en un sistema multiprogramado, desbloqueando los estados de bloqueado y preparado en dentro y fuera de memoria. La función del sistema operativo que gestiona el intercambio entre disco y memoria se denomina intercambiador o swapper. La operación de escribir el proceso en disco se conoce como swap-out, mientras que leer el proceso de disco se denomina swap-in. El swapping aporta las siguientes ventajas: Permite influir en la gestión de procesos para controlar el grado de multiprogramación (planificación a medio plazo). Proporciona flexibilidad en la gestión de la memoria, permitiendo una utilización más eficiente del espacio. Para soportar swapping se requiere espacio para el intercambio en almacenamiento secundario, generalmente disco. Se puede utilizar un dispositivo específico independiente, una partición del disco, o incluso compartir la misma del sistema de ficheros. El direccionamiento de los procesos 119

150 debe ser relativo a un registro base (reubicación dinámica). El swapper establece el nuevo valor del registro base para un proceso cada vez que lo carga en memoria. El sacar un proceso de memoria está motivado por la necesidad de obtener espacio libre, generalmente para ejecutar otro proceso (quizás uno más prioritario). El swapper debe seleccionar cuidadosamente qué procesos van a salir. Ya que, como se ha comentado, el swapping condiciona la planificación de procesos, algunos de los criterios a aplicar para seleccionar el proceso a sacar están relacionados con los parámetros de la planificación. Los criterios suelen ser: El estado del proceso. Los procesos bloqueados no plantean una necesidad inmediata de proceso. La prioridad del proceso. El tamaño del proceso. Los procesos mayores liberan más espacio al salir de memoria, pero será más costoso cargarlos de nuevo. El tiempo que el proceso lleva en memoria. Linux usa la técnica LRU, pagina menos usada recientemente (Least Recently Used page) para de forma justa elegir las paginas que deben ser removidas del sistema. Las páginas viejas son buenas candidatas para el swapping. Cada referencia a memoria virtual puede causar dos accesos a memoria física: una consulta en la tabla de páginas una para buscar el dato Para superar este problema se coloca un caché de alta velocidad para entradas de tablas de página llamada TLB (Translation Lookaside Buffer) El TLB Se trata de una pequeña memoria interna asociada a la MMU que mantiene información sobre las últimas páginas accedidas. Cuando se presenta una dirección virtual a la MMU para que la traduzca, el hardware verifica primero si su número de página virtual está presente en el TLB o no, comparándolo con todas las entradas de manera simultánea (es decir en paralelo). Si encuentra el número y el acceso no viola los bits de protección, el número de marco se toma directamente del TLB sin recurrir a la tabla de 120

151 página si el número de página está presente el TLB, pero la instrucción está tratando de escribir en una página de sólo lectura, se generará un fallo de protección. Lo interesante es cuando una página virtual no se encuentra en el TLB. La MMU detecta esto y realiza una consulta ordinaria de la tabla de páginas, luego desaloja una de las entradas de la TLB y la sustituye por la entrada de la tabla de páginas que acaba de buscar. De este modo si se vuelve a usar nuevamente la página se hallará en el TLB. Cuando una entrada se desaloja del TLB el bit modificado se copia de nuevo en la entrada correspondiente de la tabla de páginas en la memoria. Los demás valores ya están ahí. Cuando el TLB se carga de la tabla de páginas, todos los campos se toman de la memoria Memoria virtual compartida Con la memoria virtual se puede conseguir fácilmente que varios procesos compartan memoria. Todos los accesos a memoria se realizan través de las tablas de páginas y cada proceso tiene su propia tabla de páginas. Para que dos procesos compartan una misma página de memoria física, el número de marco de esta página ha de aparecer en las dos tablas de páginas. 121

152

153 CAPÍTULO 4: ARCHIVOS EN LINUX Los archivos son obligatorios en un sistema de computación: El sistema operativo debe tener una forma de identificar y acceder a sus datos, así como a las aplicaciones que necesita para funcionar, y además acceder a cualquier archivo de configuración que son utilizados por dichas aplicaciones; sin embargo, a medida que se requieren más y más archivos, que a su vez son utilizados para propósitos diferentes y creados por usuarios diferentes, se hace evidente la necesidad de un mecanismo intermedio para la organización de archivos: los directorios. [Hagen, 2007:p.90] Definición de archivo Un archivo (fichero) es una estructura de datos empleada por el sistema operativo para almacenar información en un dispositivo físico como un disco duro, un disquete o un DVD. Un archivo en Linux es un contenedor de información estructurada como una secuencia de bytes. Desde este punto de vista, absolutamente todo, desde un impresor, una tarjeta de interfaz de red, disco duro se trata como un archivo en Linux. [Sarwar et al., 2001:p.150] Los directorios (también llamados carpetas) contienen grupos de archivos relacionados entre sí. Los directorios a su vez, pueden contener directorios (subdirectorios); esto es lo que se conoce como una colección jerárquica de archivos. La ubicación de cualquier archivo se describe mediante la identificación de la serie de directorios que realmente contienen el archivo que se está buscando. En Linux, el directorio en el cual comienzan todas las búsquedas de un archivo se conoce como directorio raíz (root directory) y su nombre es una barra (/). Este directorio raíz es el que se conoce como directorio inicial o de partida. El directorio en el que se está trabajando en un determinado momento se conoce como directorio de trabajo o directorio actual. Los archivos y directorios en Linux se pueden representar como un diagrama de árbol al revés: En la parte superior del árbol se encuentra el directorio raíz, y a continuación, el conjunto de directorios principales del sistema operativo Linux, (tales como bin, dev, home, lib, y tmp, entre otros). Cada uno de esos directorios, así como los directorios añadidos a la raíz, puede contener subdirectorios. 123

154 Figura 4.1 Estructura jerárquica de archivos [Negus, 2008:p.66] La figura 4.1 ilustra la forma en que el sistema de archivos de Linux está organizado como una jerarquía. Para demostrar la forma en que los directorios están conectados, la figura 4.1 muestra el directorio home/ que contiene tres subdirectorios para los usuarios: octavio, martha y laura. En el directorio de octavio, se tiene a la vez dos subdirectorios: personal y música. Además se almacena el archivo memoria.odt. La serie de directorios que conducen a un archivo dado es denominada ruta (path) Tipos de archivos: Linux identifica cinco tipos de archivos los cuales son: Archivos sencillos u ordinarios Directorios Vínculos simbólicos Archivos especiales (dispositivos) Tuberías con nombre (FIFO) 124

155 Archivos sencillos u ordinarios Se emplean para guardar información y datos en dispositivos de almacenamiento secundario. Algunos ejemplos de este tipo de archivos son: programas fuentes en C, C++, Java, programas ejecutables, software para generar gráficos, imágenes, sonidos, etcétera. Linux no aplica un tratamiento especial a ninguno de estos archivos; no asocia significado alguno al contenido de un archivo porque todo archivo es simplemente, una sucesión de bytes. De hecho es la aplicación que utiliza o procesa determinado archivo el que asocia un significado al contenido de este archivo. En Linux, los nombres de archivos pueden tener hasta un máximo de 255 caracteres Directorios Contienen archivos u otros directorios. Es una colección de entradas de directorios cuyo contenido puede variar de un sistema operativo a otro. Los directorios en Linux tienen la siguiente estructura: N de inodo Nombre de archivo Figura 4.2 Estructura de una entrada de directorio. [Sarwar et al., 2003:p.151] El número de inodo tiene 4 bytes de longitud y es el valor de un índice que forma parte de una lista de inodos en disco. El kernel de Linux reserva un inodo único siempre que se crea un archivo Vínculos (enlaces) simbólicos Existen dos tipos de archivos de enlaces: los enlaces simbólicos y los enlaces duros. Cuando se crea un enlace simbólico significa que se crea un puntero a un archivo o directorio específico. Si se borra dicho archivo o directorio el enlace quedará apuntando a nada. Por otra parte, si se crea un enlace duro (hard link), lo que hace es crear una copia del archivo o directorio. La ventaja de este tipo de enlace es que si se modifica alguno de los dos, siempre se modificarán ambos y si se borra uno de los dos, no se pierde la información. 125

156 Para crear enlaces duros: Se digita en la consola los siguientes comandos: $ ln ruta_directorio nombre_enlace. Figura 4.3 Enlace duro a un archivo. Donde: ln es el comando que se utiliza para crear enlaces duros; ruta_directorio es la ruta donde se encuentra el archivo al cual se creará un enlace y nombre_enlace es el nombre del enlace a crear. En la figura 4.3, se accede a la carpeta stuff que se encuentra en Documentos del usuario, usuario y a continuación se verifica con el comando ls el contenido de dicho directorio. Después, se hace un enlace duro al archivo inode.pdf con el nombre inodec, luego, se revisa con el comando ls el contenido de la dicha carpeta. Se puede ver en la figura 4.3 que el enlace ha sido creado exitosamente. Si a continuación se digita en la consola: $ ls -il 126

157 Figura 4.4 Lista de inodos asociados a un archivo. De la figura 4.4, se tiene la siguiente información: CAMPO Primero Segundo Tercero Cuarto Quinto Sexto Séptimo Octavo SIGNIFICADO Número de inodo asignado al archivo. Permiso de acceso del propietario. Número de vínculos. Nombre de conexión del propietario Nombre del grupo del propietario. Tamaño del archivo en bytes Fecha y hora de la ultima modificación Nombre del archivo Tabla 4.1 Descripción de los campos que son listados a partir del comando ls il En el primer campo que corresponde con el número del inodo, se puede apreciar que el archivo inode.pdf y inodec.pdf tienen asignado el mismo inodo. De lo cual se puede concluir que el inodo es el mismo para los archivos que se crean como enlaces duros. 127

158 Por lo tanto, un archivo no se borra hasta que se haya eliminado hasta la última de las instancias de sus nombres de archivo. Para crear enlaces simbólicos: $ ln -s ruta_directorio nombre_enlace Donde: ln s es el comando que se utiliza para crear enlaces simbólicos; ruta_directorio es la ruta donde se encuentra el archivo al cual se le creará un enlace y nombre_enlace es el nombre del enlace a crear. Figura 4.5 Crear un enlace simbólico a un archivo específico. En la figura 4.5, se revisa el contenido de la carpeta stuff por medio del comando ls. Luego, con el comando pwd, se obtiene la ruta del directorio actual y después, se hace un enlace simbólico al archivo otro.odt con el nombre otroc.odt. Luego se revisa con el comando ls el contenido de la carpeta para verificar que el enlace ha sido creado; a continuación se digita en la consola: $ ls -il 128

159 Figura 4.6 Enlace simbólico a un archivo El enlace muestra el archivo al cual está apuntando actualmente. Se puede ver en la figura 4.6 que el enlace (otroc.odt) y el archivo (otro.odt) no son referenciados por el mismo inodo. Los permisos que se utilizan por el archivo de enlace simbólico, son los del archivo al que se está apuntando. Se puede observar que en la primera posición de los permisos hay una letra l; esto significa que es un enlace simbólico. Otra particularidad de los enlaces simbólicos es que se muestran el archivo al cual están apuntando, no así con los enlaces duros. Al borrar el archivo al cual apunta el enlace simbólico, éste enlace queda roto, es decir, no apunta a nada Archivos especiales (dispositivos) Un archivo especial es un medio que permite acceder a dispositivos de hardware como el disco duro, CD-ROM, impresora etcétera. Los archivos especiales pueden ser de dos tipos: archivos especiales de caracteres como el teclado, y archivos especiales de bloque como el disco duro. Comúnmente, los archivos especiales se ubican en /dev el cual contiene como mínimo, un archivo por cada dispositivo que esté conectado a la computadora. Las distintas aplicaciones escriben y leen en los archivos de dispositivo periféricos tal y como lo harían en un archivo simple u ordinario. Debido a esta capacidad del manejo de dispositivos es la razón principal para decir que la E/S en Linux es independiente del dispositivo. 129

160 Tuberías con nombre (FIFO) Linux posee varios mecanismos que permiten a los diferentes procesos comunicarse entre sí. Estos mecanismos reciben el nombre de comunicación entre procesos (IPC). Las tres primitivas de uso más común son las tuberías, tuberías con nombre y sockets. Una tubería es una zona de la memoria del kernel (un buffer del kernel) que permite a dos procesos comunicarse entre sí, siempre y cuando estos procesos se ejecuten en la misma computadora y estén relacionados entre sí; comúnmente, es una relación padre hijo. Un archivo de tubería con nombre permite a dos procesos comunicarse entre sí; ambos procesos se encuentran en el mismo computador pero éstos no necesitan estar relacionados entre sí. Un socket es una estructura de datos que reside en la memoria del kernel y se utiliza por parte de procesos residentes en computadoras con objeto de comunicarse entre sí por medio de la red. Ya que las computadoras pueden formar parte de una intranet o pueden estar en internet Estándar de directorios en Linux Figura 4.7 El estándar de directorios en Linux. Todos los sistemas operativos Linux proporcionan un conjunto estándar de los principales directorios. 130

161 Los siguientes directorios se utilizan para mantener programas que se deben ejecutar cuando se está arrancando el sistema Linux, los archivos de configuración para los programas, bibliotecas utilizadas por los programas, los archivos temporales creados por los programas en ejecución, y así sucesivamente: /: Es el directorio de mayor nivel de un sistema Linux, este directorio debe existir. Los otros directorios se pueden ubican dentro de él. /bin: Un directorio que contiene las aplicaciones básicas utilizadas por un sistema Linux. Todos los archivos de este directorio son archivos ejecutables o vínculos simbólicos de archivos ejecutables que residen en otro directorio. Algunas de las órdenes que residen en este directorio son: bash, cat, chmod, cp, date, mkdir, rmdir entre otros. /boot: Este directorio contiene todos los archivos necesarios para el arranque del sistema Linux, incluyendo en él, la imagen binaria del kernel de Linux. /dev: Un directorio que contiene archivos especiales, conocidos como nodos de dispositivos que se utilizan para acceder a cualquier dispositivo (terminales, unidades de disco, lector de CD-ROM, módem, impresora etcétera) que esté conectado a un sistema Linux. /etc: Un directorio que contiene información de configuración del sistema. Contiene los archivos que describen la secuencia de las aplicaciones que se ejecutan en un sistema Linux, como parte del proceso de arranque y almacena archivos de configuración de algunas de las aplicaciones que se ejecutan en Linux. Los archivos en este directorio son fundamentalmente para uso exclusivo del administrador del sistema. /lib: Directorio que contiene las bibliotecas de funciones de un lenguaje dado que pueden ser llamados por otras aplicaciones. Esto permite a los desarrolladores de aplicaciones utilizar funciones previamente escritas en sus programas. Un sistema Linux típico tendrá bibliotecas para C, C++ y FORTRAN. /lost+found: Este directorio contiene todos los archivos del sistema que no están asociados a ningún directorio. /media: Provee una ubicación para el montaje y auto montaje de dispositivos, tales como sistemas de archivos remotos y medios extraíbles. /mnt: Este directorio se utiliza sobre todo por los administradores de sistemas para montar, temporalmente, sistemas de archivos (empleando la orden mount). Este directorio 131

162 es el que contiene los puntos de montaje del lector de CD-ROM, del disco duro y disco flexible. /proc: Un directorio en el cual el kernel de Linux monitoriza los procesos activos y la información sobre el estado general del sistema. /sbin: Un directorio que contiene las aplicaciones que se ejecutan normalmente sólo por el superusuario. Contiene herramientas de administración de sistema, utilidades y órdenes generales de aplicación exclusiva del superusuario. /sys: Es un directorio con el que el kernel de Linux monitoriza el estado del hardware del sistema y las interfaces de hardware relacionados. /tmp: Un directorio que contiene los archivos temporales creados por varias aplicaciones que se ejecutan en un sistema Linux. Se pueden encontrar estos directorios estándar en la mayoría de sistemas Linux, independientemente del tipo de distribución o el tamaño del disco que está utilizando Otros Directorios comunes en sistemas Linux Dependiendo del número de archivos que ha instalado en su sistema y su configuración, es probable encontrar varios directorios en todo sistema Linux. Algunos directorios utilizados en sistemas Linux son: /home: Un directorio que contiene los subdirectorios donde diferentes usuarios almacenar sus archivos. Por ejemplo, la mayoría (si no todos) de los archivos propiedad del usuario "abc" se almacenan en el directorio / home / abc (o en subdirectorios de ese directorio). /opt: Un directorio que se usa normalmente para instalar paquetes de software adicionales. Su nombre proviene de la idea de que contiene programas "opcionales" ("optional" en inglés) que pueden variar de computadora a computadora. /usr: Una jerarquía de subdirectorios que contiene datos no modificables que se pueden compartir entre distintos computadores. Este directorio es uno de los más extensos en los sistemas Linux. /var: Directorio que contiene datos variables, es decir, los datos que van cambiando a medida que funciona el sistema. Estos datos se mantienen en varios subdirectorios. 132

163 Por ejemplo, el directorio /var/log contiene los archivos bitácora para llevar el registro de aplicaciones del sistema y sucesos. Los archivos se crean cuando el sistema está funcionando, y pueden crecer enormemente al pasar el tiempo. Como se puede ver, un sistema Linux proporciona una gran colección jerárquica de archivos y directorios que se organiza para simplificar la localización de ciertos tipos de archivos como ejecutables, las bibliotecas, los archivos de configuración, el sistema información sobre la situación, y así sucesivamente Rutas absolutas y relativas En un sistema jerárquico de archivos, cualquier archivo o directorio es accedido mediante una ruta. Las rutas se definen: A partir del directorio raíz. A partir del directorio de trabajo A partir del directorio inicial del usuario. Cuando se especifica una ruta que comienza desde el directorio raíz, se dice que la ruta es absoluta. Las rutas que comienzan en el directorio de trabajo actual o en el directorio inicial de un usuario se les denomina ruta relativa. En Linux, es posible montar varias unidades de disco o particiones en la misma estructura de archivos que permite acceder a ellos como directorios y no como unidades de disco con un identificador propio como A: C: etcétera. Se puede acceder a los archivos y directorios de estos discos o particiones especificando su ruta como si formaran parte de la estructura de archivos en un único disco o partición. Gracias a esta ventaja, se dispone de una visión única de todos los archivos y directorios del sistema, y no se necesita preocupar de recordar los nombres de las unidades de disco. 133

164 4.3. Sistema de archivos Linux Segundo sistema de archivos extendido (the second extended filesystem) EXT2 La primera versión del sistema de archivos de GNU/Linux fue basada en el sistema de archivos MINIX. Fue implementado como una extensión al sistema operativo MINIX y sus primeras versiones incluyen soporte para el sistema de archivos MINIX únicamente. El sistema de archivos MINIX contiene una serie de limitaciones: El bloque de direcciones guarda enteros de 16 bit por lo que el tamaño máximo del sistema de archivo es limitado a 64 MB, y los directorios contienen entradas de tamaño fijo y la longitud máxima de un nombre de archivo es de 14 caracteres. Resultaba más fácil compartir discos entre los dos sistemas operativos (GNU/Linux y MINIX), que diseñar un nuevo sistema de archivos. Fue por eso que Linus Torvalds decidió implementar soporte para sistema de archivos MINIX en Linux. El sistema de archivos MINIX era eficiente y relativamente libre de errores de software. Sin embargo, las restricciones existentes en el sistema de archivos MINIX eran demasiado limitadas por lo que se comenzó a pensar en la forma de implementar otros sistemas de archivos en el kernel de Linux. Fue entonces cuando se desarrolló un sistema de archivos virtual (virtual file systems VFS) la cual fue escrita por Chris Provenzano y reescrita más adelante por Linus Torvalds antes de ser integrada al kernel de Linux. Después de la integración de la VFS en el kernel de Linux, un nuevo sistema de archivos llamado ``Extended File System'' (EXT FS) fue implementado en abril de 1992 y agregado a la versión 0.96c de Linux. Este nuevo sistema de archivos corrige las dos grandes limitaciones del sistema operativo MINIX: El tamaño máximo es aumentado a 2 GB y el tamaño máximo de nombres de archivos es de 255 caracteres. Esta es una mejora considerable al sistema de archivos base; sin embargo, aún se encuentran algunas deficiencias al sistema de archivos EXT: no existía respaldo para el acceso independiente, no se podían modificar los inodos y no podía modificar la secuencia de caracteres, que denotan la hora y fecha (o alguna de ellas) en la cual ocurría un determinado evento (timestamps). Como respuesta a este problema, dos nuevos sistemas de archivos fueron publicados en la versión Alfa en Enero de 1993: Xia Filesystem y Second Extended Filesystem (Ext2fs). 134

165 El sistema de archivos Xia estaba basado en el código del kernel del sistema de archivo MINIX y solo se agregaron unas cuantas mejoras al sistema de archivos. Por otra parte, Ext2fs es basado en el código de Extfs con algunas reorganizaciones y muchas mejoras; incluso fue diseñado pensando en la evolución a futuro de este sistema de archivos y proporcionando espacio para futuras mejoras. Cuando estos dos nuevos sistemas de archivos fueron publicados, ellos proveían en esencia las mismas características, pero debido a su diseño tan limitado, Xia era más estable que Ext2fs. Con el tiempo los errores de Ext2fs fueron corregidos y nuevas características fueron integradas, por lo que ahora es un sistema de archivos más estable, eficiente y robusto. Cada sistema de archivos Linux implementa un conjunto básico de conceptos derivados del sistema operativo Unix; los directorios son archivos que contienen una lista de entradas y los dispositivos pueden ser accedidos mediante solicitudes de E/S de archivos especiales. EXT2 se ha llevado a su límite. De hecho, muchos de los requisitos más comunes de hoy en día como grandes particiones de discos duros, recuperación rápida de caídas del sistema, rendimiento alto en operaciones de Entrada/Salida (E/S) y la necesidad de almacenar miles y miles de archivos representando Terabytes de información, ha superado las posibilidades del EXT2. La principal desventaja hoy en día de EXT2 es que no implementa el registro diario (jornaling) que implementa su sucesor, EXT3. En esta línea se ha desarrollado EXT3, (que es una nueva versión del sistemas de archivos de uso común en GNU/Linux EXT2) que tiene como principal novedad el uso de Journaling. Esto se hace en respuesta a las necesidades de alta disponibilidad que se han ido demandando años atrás y sigue la línea de iniciativas independientes y empresariales por crear nuevos sistemas de archivos con Journaling o portar los existentes en otros sistemas como Unix. EXT3 lleva bastante tiempo en proyecto y ya ha comenzado a ofrecer las primeras versiones lo suficientemente estables para su uso generalizado, hasta el punto que algunas distribuciones la establecen como el tipo de partición por defecto en la instalación, sustituyendo así a EXT2. La principal ventaja de éste, respecto a otros JFSs como ReiserFS, JFS de IBM, o XFS de SGI, es que EXT2 es un sistema ampliamente usado y se puede emigrar de EXT2 a EXT3 muy fácilmente con unas herramientas creadas para tal fin. Otra ventaja es la posibilidad de usar de usar un sistema EXT3 como EXT2. 135

166 Características de ext2fs: Al crear el sistema de archivos, el administrador puede seleccionar el tamaño óptimo de bloque (desde 1024 a 4096 bytes) dependiendo de la longitud media esperada de los archivos. Por ejemplo, seleccionar un tamaño de bloque de 1024 bytes es preferible cuando el tamaño medio de los archivos es inferior a unos miles de bytes, ya que esto implica una menor fragmentación interna. Por otro lado, bloques de mayor tamaño se recomiendan cuando el tamaño medio de los archivos es mayor a unos miles de bytes, dado que producen una menor cantidad de transferencias de disco, reduciendo así la sobrecarga (overhead) del sistema. Al crear el sistema de archivos, el administrador puede elegir la cantidad de inodos a permitir en una partición, esta cantidad depende del número de archivos almacenados en él. El sistema de archivos particiona los bloques de disco en grupos. Cada grupo incluye bloques de datos e inodos almacenados en pistas adyacentes. Gracias a esta estructura, los archivos en un único grupo de bloques pueden ser accedidos en un menor tiempo promedio de búsqueda en disco. El sistema de archivos preasigna bloques de datos de disco a archivos regulares antes que estos sean utilizados. De esta forma, cuando un archivo incrementa su tamaño, varios bloques se encuentran previamente reservados en posiciones físicas adyacentes, reduciendo la fragmentación del archivo. Soporta enlaces simbólicos rápidos. Si el nombre de la ruta del enlace simbólico tiene 60 bytes o menos, se almacena en el inodo y puede así traducirse sin leer un bloque de datos. Soporte para comprobaciones automáticas de consistencia sobre el estado del sistema de archivos en el arranque del sistema. Estas comprobaciones se realiza mediante el programa /sbin/e2fsck, que puede activarse no solo tras caídas del sistema, sino después de un número predefinido de montajes (se incrementa un contador después de cada operación de montaje), o después de cierta cantidad de tiempo transcurrida desde la comprobación más reciente. Soporte para los archivos inmutables (archivos que no pueden ser modificados, borrados o renombrados) y para archivos append-only (archivos a los cuales solo se puede añadir datos al final). Ni el superusuario puede sobrepasar estas clases de protección. 136

167 Jornaling Jornaling es una forma de trabajar de algunos sistemas de archivos conocidos como sistemas de archivos transaccionales. La idea de Journaling implica dos conceptos importantes: los Datos y los Metadatos. Los datos representan la información que se quiere almacenar, es decir, el contenido, mientras que los metadatos se utilizan para que el sistema de archivos maneje los datos; ejemplo de ellos son: tamaño de un archivo, su posición, atributos, entre otros. Cada vez que un programa modifica los datos de un archivo específico, el sistema de archivos se ve obligado a realizar modificaciones en los datos (verificar los cambios que se han realizado), y en los metadatos, (debido a que el tamaño del archivo, la fecha, ubicación en disco o permisos cambien). En un sistema de archivos sin Journaling, para realizar estas operaciones se debe comenzar a modificar los datos y metadatos directamente hasta completar las modificaciones requeridas. La desventaja se observa cuando el sistema falla durante la realización de los cambios en los datos, ya que se produce un archivo corrupto y lo más probable es que resulte inaccesible debido a que su estructura interna no coincide con la esperada. Si esto ocurre en el momento de modificar los metadatos se obtendrá un sistema de archivos corrupto. Puede ocurrir que incluso no exista una estructura válida de directorios y archivos. Para minimizar las inconsistencias en los sistemas de archivos así como minimizar el tiempo de arranque, los sistemas de archivos Journaling mantienen un seguimiento de los cambios que se llevarán a cabo en el sistema de archivos antes de que realmente se realicen. Estos registros se almacenan en una parte separada del sistema de archivos normalmente conocida como journal (diario). Una vez que los registros journal (también conocidos como registros log ) se han escrito correctamente, se aplican esos cambios sobre el sistema de archivos y después elimina esas entradas del registro journal correspondiente. Los sistemas de archivos Journaling maximizan la consistencia del sistema de archivos ya que los logs se escriben antes de que los cambios se realicen; esto se logra almacenando esos registros hasta que los cambios hayan sido completados satisfactoriamente sobre el sistema de archivos. Cuando se reinicia un ordenador que utiliza un sistema de archivos Journaling, el programa de montaje puede garantizar la consistencia del sistema de archivos simplemente buscando en el registro journal los cambios pendientes (sin acabar) y finalizarlos sobre el sistema de archivos. 137

168 Existen varios sistemas de archivos Journaling disponibles para el sistema operativo Linux. Los más conocidos son; el XFS desarrollado por Silicon Graphics pero no es open source (código abierto), el ReiserFS especialmente desarrollado para Linux, el JFS originalmente desarrollado por IBM del cual se dispone actualmente una versión open source, y el sistema de archivos EXT Estructura física de datos de un Sistema de archivos EXT2 en disco. Antes de describir la estructura física de datos en disco, es necesario definir los siguientes conceptos. Conceptos básicos: Un BLOQUE lógico es la unidad mínima de almacenamiento en un sistema de archivos (Un bloque lógico se mide en bytes).un archivo se almacenará en uno o varios bloques. Un VOLUMEN lógico es una PARTICIÓN de un dispositivo el cual puede ser un disco físico o un subconjunto del espacio de un disco físico. Las particiones lógicas se dividen en grupos de bloques. GRUPOS DE BLOQUES son una colección de bloques secuenciales. FRAGMENTACIÓN INTERNA ocurre cuando un archivo no ocupa por completo un bloque. FRAGMENTACIÓN EXTERNA ocurre cuando los bloques lógicos que forman un archivo se encuentran esparcidos por todo el disco (es decir no están contiguos). Ext2fs divide el disco en pequeñas unidades lógicas llamadas bloques. Los bloques son las unidades mínimas de disco que pueden ser asignadas. El Ext2fs divide las particiones lógicas que ocupa en Grupo de Bloques (BG). Realmente, el sistema de archivos es manejado globalmente como una serie de Grupos de Bloques, lo que mantiene la información relacionada físicamente cercana en el disco y simplifica las tareas de gestión. Como resultado, la mayor parte de la gestión del sistema de archivos se reduce a la gestión de un sólo grupo de bloques. El primer bloque en cualquier partición EXT2 nunca es manejada por el sistema de archivos EXT2 ya que es reservada para el sector de arranque. El resto de la partición EXT2 es dividida en grupo de bloques. 138

169 Figura 4.8 diseño de una partición EXT2, así como de un grupo de bloque EXT2. [Bovet y Cesati, 2000: p.498] Algunas estructuras de datos deben ser asignadas exactamente en un bloque mientras las demás pueden requerir más de un bloque. Todos los grupos de bloques en el sistema de archivos tienen el mismo tamaño y son almacenados de forma secuencial. De este modo, el kernel puede obtener la ubicación de un grupo de bloque en disco por medio de un número entero. Los grupos de bloques reducen la fragmentación externa, dado que el kernel intenta mantener los bloques de datos de un archivo en el mismo grupo de bloques, si es posible. Cada grupo de bloque contiene: Una copia del superbloque del sistema de archivos. Una copia del grupo de descriptores de grupos de bloques. Un mapa de bits de bloque. Un mapa de bits de inodos. Una tabla de inodos. Un bloque de datos. El superbloque y los descriptores de grupo están duplicados en cada grupo de bloques. Sólo el superbloque y los descriptores de grupos incluidos en el grupo de bloques 0 son utilizados por el kernel, mientras que las demás copias nunca se modifican. Cuando el programa /sbin/e2fsck realiza una comprobación de consistencia de datos en el sistema de archivos, hace referencia al superbloque y los descriptores de grupos del grupo de bloque 0, luego hace una copia del superbloque y los descriptores de grupo en el resto de grupos de 139

170 bloques. Si se produce una corrupción de datos, y el superbloque y los descriptores de grupos del grupo 0 se hacen inválidos, el administrador puede indicar a /sbin/e2fsck que haga referencia a las copias anteriores de los otros grupos de bloque diferentes del 0. A continuación, se describen las estructuras de datos utilizadas para almacenar datos en el disco duro. Las estructuras en disco duro tienen su contraparte en memoria. Además se utilizan estructuras para el sistema de archivos virtual (VFS) para dar soporte a la comunicación entre distintos sistemas de archivos y simplificar la gestión de datos. También se utiliza un buffer para acelerar el trabajo con los sistemas de archivos El superbloque EXT2 El superbloque es la estructura central en la cual se almacenan todas las características de los datos de un sistema de archivos. Básicamente, es una estructura de datos que contiene toda la información de control del sistema de archivos. Se encuentra en el offset fijo 1024 del disco y ocupa 1024 bytes. DISCO Sector de arranque superbloque Figura 4.9 Ubicación del superbloque en disco duro. El superbloque contiene una descripción del tamaño y forma del sistema de archivos. La información contenida permite al administrador del sistema de archivos usar y mantener el sistema de archivos. Se almacenan copias del superbloque dentro de cada Grupo de Bloques, aunque cuando se monta el sistema de archivos sólo se lee el superbloque del grupo de Bloque 0 (el cual está situado al inicio del disco), aunque se mantienen las demás por si se corrompe la copia primaria. 140

171 Estructura de datos del superbloque EXT2 La estructura de datos que define el formato del superbloque en disco se llama ext2_super_block y se encuentra en usr/src/linux-source /include/linux/ext2_fs.h Los campos de la estructura son los siguientes: TIPO CAMPO DESCRIPCION _le32 s_inodes_count Número total de i-nodos. _le32 s_blocks_count Número total de bloques en el sistema de archivos EXT2. _le32 s_r_blocks_count Número de bloques reservados al superusuario. Permite asignar bloques al superusuario cuando el resto de usuarios no tienen. _le32 s_free_blocks_count Número de bloques libres. _le32 s_free_inodes_count Número de i-nodos libres. _le32 s_first_data_block Número del primer bloque de datos. _le32 s_log_block_size Tamaño lógico de los bloques en bytes. (0->1KB; 1->2KB; 2->4KB). _le32 s_log_frag_size Tamaño lógico de los fragmentos en bytes. _le32 s_blocks_per_group Número de bloques por grupo. _le32 s_frags_per_group Número de fragmentos por grupo. _le32 s_inodes_per_group Número de i-nodos por grupo 141

172 _le32 s_mtime Fecha del último montaje del sistema de archivos. _le32 s_wtime Fecha de la última escritura del superbloque. _le16 s_mnt_count Número de montajes del sistema de archivos. _s16 s_max_mnt_count Número máximo de montajes. Cuando s_mnt_count es igual a este valor, se muestra el mensaje de aviso «maximal mount count reached, running e2fsck is recommended». Estos dos últimos campos permiten al sistema determinar si el sistema de archivos fue montado correctamente. _le16 s_magic Identifica al superbloque y a la versión del sistema de archivos. Sistemas de archivos Ext2fs con diferentes números mágicos son incompatibles entre sí. _le16 s_state Estado del sistema de archivos. (si es 0 el sistema está montado o fue incorrectamente desmontado, si es 1 el sistema esta desmontado correctamente y si es 2 existen errores). _le16 s_errors Comportamiento del sistema de archivos cuando se detectan errores. _le16 s_minor_rev_level Número de revisión menor. 142

173 _le32 s_lastcheck Fecha de la última verificación del sistema de archivos. _le32 s_checkinterval Tiempo máximo entre dos verificaciones. _le32 s_creator_os Identificador del sistema operativo bajo el cual se ha creado el sistema de archivos. _le32 s_rev_level Nivel de revisión. _le16 s_def_resuid Identificador del usuario que puede usar los bloques reservados al superusuario de modo predeterminado. _le16 s_def_resgid Identificador del grupo que puede usar los bloques reservados al superusuario de modo predeterminado. _le32 s_first_ino Primer i-nodo no reservado. _le16 s_inode_size Tamaño de la estructura de i- nodos. _le16 s_block_group_nr Nº del grupo de bloques donde se encuentra este superbloque. _le32 s_feature_compat Conjunto de características compatibles. _le32 s_feature_incompat Conjunto de características incompatibles. _le32 s_feature_ro_compat Características compatibles _u8 s_uuid[16] UUID de 128-bit para volumen. char s_volume_name[16] Nombre del volumen. 143

174 char s_last_mounted[64] Directorio donde se montó la última vez el sistema de archivos. _le32 s_algorithm_usage_ bitmap Para compresión. _u8 s_prealloc_blocks Nº de bloques que se intentan preasignar. _u8 s_prealloc_dir_blocks Nº de bloques a preasignar para directorios. _u16 s_padding1 Nº de bloques de relleno. _u8 s_journal_uuid[16] UUID del journal. _u32 s_journal_inum Número del inodo del archivo journal. _u32 s_journal_dev Número del dispositivo que contiene el archivo journal. _u32 s_last_orphan Inicio de la lista de inodos a borrar. _u32 s_hash_seed[4] Semilla hash de HTREE. _u8 s_def_hash_version Versión por defecto de hash para usar. _u8 s_reserved_char_pad Reservado para futuras extensiones. _u16 s_reserved_word_pad Reservado para futuras extensiones. _le32 s_default_mount_opts Reservado para futuras extensiones. _le32 s_first_meta_bg Primer grupo de bloque. 144

175 _u32 s_reserved[190] Reserva de bloques. Tabla 4.2 Descripción de campos de superbloque. De algunos campos de la estructura no se han encontrado la definición. Dentro de esta estructura, existen ciertos parámetros fijos que se establecen cuando se crea el sistema de archivos y no pueden ser modificados Parámetros fijos: Los parámetros a continuación son los definidos durante la creación del sistema de archivos y no pueden ser modificados. Tamaño del bloque (s_log_block_size). Número de inodos disponibles (s_inodes_count). Número de bloques disponibles (s_block_count). Número del primer bloque de datos (s_first_data_block). Número de bloques por BG (s_blocks_per_group). Número de inodos disponibles en cada BG (s_inodes_per_group). Sistema operativo que creó el sistema de archivos (s_creator_os). Número de revisión actual (s_rev_level). Tamaño y cantidad de fragmentos por grupo de bloque (s_log_frag_size y s_frags_per_group). Como ya se mencionó anteriormente, el superbloque está situado en el offset fijo 1024, pero la numeración de bloques del dispositivo puede variar. Si el tamaño de bloque del dispositivo es 1024, el superbloque está en el bloque 1 del dispositivo, mientras que si el bloque es por ejemplo 2048, el bloque 0 del dispositivo contendrá el superbloque. El campo ds_log_block_size de la estructura no tiene una interpretación inmediata. En realidad, el tamaño del bloque será 2 s_log_block_size, y las posibilidades son 1 KB, 2 KB y 4 KB. Algunos elementos de la estructura no son utilizados por el código de EXT2, ya que se ofrecen para futuras mejoras, previstas cuando se diseñó la estructura. 145

176 Descriptores de grupo EXT2 Cada grupo de bloque tiene un descriptor de grupo el cual está ubicado inmediatamente después del superbloque. Los grupos de bloque contienen información que refleja el estado de cada uno de los grupos de bloques del sistema de archivos como, por ejemplo, la información sobre el número de bloques e inodos libres por grupo de bloque Tabla de descriptores de grupo Los descriptores de grupo se colocan juntos en una estructura, y forman la tabla de descriptores de grupo, de la cual se almacena una copia en cada grupo de bloque, justo detrás de la copia del superbloque. De todas estas copias, sólo se usa la del grupo de bloque 0. Las demás sólo se emplean en caso de que el sistema resulte dañado Estructura de datos de un descriptor de grupo La estructura de datos que describe a los descriptores de grupo se llama ext2_group_desc y se encuentra en /usr/src/linux-source /include/linux/ext2_fs.h Los campos de la estructura son los siguientes: TIPO CAMPO DESCRIPCION le32 bg_block_bitmap Dirección del Bloque del mapa de bits de bloques. le32 bg_inode_bitmap Dirección del Bloque del mapa de bits de inodos. le32 bg_inode_table Dirección del primer bloque de la tabla de inodos. le16 bg_free_blocks_count Número de bloques libres. le16 bg_free_inodes_count Número de inodos libres. le16 bg_used_dirs_count Número de directorios. le16 bg_pad No utilizado. le32 bg_reserved[3] Reservado. Tabla 4.3 Descripción de campos de la estructura de de un descriptor de grupo. 146

177 Todos estos campos son inicializados al momento de creación del sistema de archivos y posteriormente actualizados por el sistema de archivos. Los bloques utilizados como mapa de bits de inodos y de bloque no se repiten en todos los grupo de bloque; de hecho, sólo hay una ocurrencia de ellos en el sistema Mapa de bits de inodo y mapa de bit de bloques. Mapa de bit de bloques: Se utiliza para identificar los bloques que están siendo utilizados o que están libres en un grupo de bloque. Este bloque contiene una tabla de bits: a cada bloque del grupo se le asocia un bit indicando si el bloque está asignado (el bit está entonces a 1) o disponible (el bit está a 0). Mapa de bits de inodos: Se utiliza para identificar los inodos utilizados o que están libres en un determinado grupo de bloque. Este bloque contiene una tabla de bits: a cada bloque del grupo se le asocia un bit indicando si el inodo está asignado (el bit está entonces a 1) o disponible (el bit está a 0) Tabla de inodos (Inode table) EXT2 La tabla de inodos se forma de una serie de bloques consecutivos, cada uno de los cuales contiene un número predefinido de inodos. El número del primer bloque de la tabla de inodos se almacenan en el campo bg_inode_table de la tabla de descriptores de grupo. En el sistema de archivos EXT2, el inodo es el bloque básico de construcción. Todos los archivos y directorios en el sistema de archivos son descritos por un y sólo un inodo. Los inodos EXT2 para grupos de bloque se mantienen en la tabla de inodos, junto con un mapa de bits que permite al sistema realizar un seguimiento de los inodos asignados y no asignados. Un inodo contiene las características (permisos, fechas, ubicación, pero no el nombre) de un archivo regular, directorio, o cualquier otro objeto que pueda contener el sistema de archivos. 147

178 Figura 4.10 Apertura de un archivo a través de un inodo. [Sarwar et al., 2003: p.155] Estructura de datos de un inodo La estructura de datos que describe los inodos EXT2 se llama ext2_inode y se encuentra en /usr/src/linux-source /include/linux/ext2_fs.h Los campos de la estructura se describen a continuación: TIPO CAMPO DESCRIPCIÓN _le16 i_mode Modo del i-nodo. _le16 i_uid Identificador del propietario. _le32 i_size Tamaño del archivo en bytes. _le32 i_atime Fecha del último acceso al archivo. _le32 i_ctime Fecha de creación del Archivo. _le32 i_mtime Fecha de última modificación del archivo. _le32 i_dtime Fecha de supresión del archivo. 148

179 _le16 i_gid Identificador de grupo. _le16 i_links_count Número de enlaces asociados al i-nodo. _le32 i_blocks Número de bloques de 512 bytes asignados al i-nodo _le32 i_flags Atributos asociados al archivo _le32 i_reserved1 Reservado para uso futuro _le32 i_block[ext2_n_blocks] Direcciones de bloque de datos asociados al i-nodo. _le32 i_generation Número de versión asociado al i- nodo. _le32 i_file_acl Dirección del descriptor de la lista de control de acceso asociada al archivo. _le32 i_dir_acl Dirección del descriptor de la lista de control de acceso asociada a un directorio. _le16 i_faddr Dirección de fragmento. _le32[2] i_reserved2 Campo reservado para extensión futura. _u8 l_i_frag Número de fragmentación. _u8 l_i_fsize Tamaño de fraccionamiento. _u16 i_pad1 No utilizado. _le16 l_i_uid_high No utilizado. _u32 l_i_reserved2 Campo reservado para extensión futura. Tabla 4.4 Campos de la estructura de un inodo en disco. 149

180 El campo i_blocks contiene las direcciones de bloques de datos asociadas al i-nodo. Es de tamaño EXT2_N_BLOCKS. Además de la estructura de datos, existen algunas macros las cuales se describen a continuación: #define EXT2_NDIR_BLOCKS 12 #define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS #define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1) #define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1) #define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1) EXT2_NDIR_BLOCKS: Esta macro, da a conocer el valor del número total de elementos que contienen direcciones de bloques de datos comenzando por el principio de la tabla. Por lo tanto, se tienen 12 bloques al principio totalmente reservados y fijos de acceso directo. EXT2_IND_BLOCK: Dirección de un bloque que direcciona a otros bloques de datos indirectamente. EXT2_DIND_BLOCK: Dirección de un bloque que direcciona doblemente otros bloques de datos. EXT2_TIND_BLOCK: Dirección de un bloque que direcciona triplemente otros bloques de datos. En general, en una estructura de datos de un inodo se pueden distinguir dos partes importantes: Atributos Son todos los campos que caracterizan al archivo (tipo de archivo, permisos, nombre del propietario, tamaño del archivo, fechas de creación, modificación y acceso). Localización Almacenamiento de las direcciones de los bloques de datos en el ínodo. Son las direcciones de los bloques ocupados por el archivo (objeto en general) en el sistema de archivos. Es una tabla indexada, cuyo nivel de indexación aumenta con el tamaño del archivo. Dispone de una serie de direcciones directas (alrededor de 10) y de otras tres entradas de nivel de indexación creciente (simple, doble y triple indirecta) que apuntan a otros bloques de datos (que se denomina bloques indirectos) usados para contener direcciones de bloque. Los archivos no necesariamente ocupan siempre bloque de datos sucesivos sino que se extienden en el mismo bloque. 150

181 El tamaño máximo de un archivo es limitado por el número de bloques que pueden ser ocupados en la estructura de un inodo. Si este valor es muy pequeño, menos espacio es necesario para manejar la estructura del inodo, pero al mismo tiempo, solo un archivo de pequeño tamaño puede ser representado. Para solventar este problema se utiliza la indirección; por medio de la indirección solo unos pocos bytes de inodos mantienen punteros a bloques (lo cual no significa que solo archivos pequeños pueden ser representados). Los punteros a bloque de datos individuales son guardados de manera indirecta como se ilustra a continuación: Figura 4.11 Punteros de un inodo hacia direcciones de bloque Este enfoque permite la flexibilidad para guardar ya sea archivos pequeños o grandes porque el tamaño del área en la cual los punteros a bloque de datos son guardados varía dinámicamente en función del tamaño actual del archivo. El inodo en sí mismo siempre es de un tamaño fijo y los bloque de datos necesarios con fines de indirección son asignados dinámicamente Estructura de un inodo en memoria RAM Todo directorio o archivo que está siendo accedido, independientemente del sistema de archivos real donde esté ubicado, es representado dentro del sistema de archivos virtual (VFS) por una estructura inodo dentro de la Memoria Principal. 151

182 Cada inodo se llena mediante funciones específicas del sistema de archivos real con la información del inodo en el disco más información del sistema. Dentro de la estructura inodo se encuentra un campo importante que es el struct inode_operations *i_op. Básicamente, es un puntero a las diferentes operaciones que se van a permitir realizar sobre un archivo Directorios EXT2 Los directorios son un tipo de archivo especial con punteros a inodos y nombres de archivos y subdirectorios del directorio actual Estructura de datos de directorios EXT2 Los directorios se componen también de bloques de datos. Estos bloques se estructuran lógicamente en una serie de entradas. La estructura que los define es ext2_dir_entry y se encuentra en /usr/src/linux-source /include/linux/ext2_fs.h: La siguiente tabla define los campos de la estructura de directorio: TIPO CAMPO DESCRIPCIÓN _le32 inode Número del i-nodo del archivo. _le16 rec_len Tamaño en bytes de la entrada del directorio. _le16 name_len Número de caracteres que componen el nombre del archivo. _u8 file_type Tipo de archivo almacenado. char[] name Nombre del archivo. Tabla 4.5 Campos de la estructura de un directorio. 152

183 4.4. Sistema de Archivos Virtual (The Virtual File System) VFS Cada sistema operativo tiene al menos un sistema de archivos estándar que ofrece funciones necesarias para llevar a cabo tareas de administración de sistema de archivos de manera eficiente y fiable. [Mauerer, 2008: p.519] El Sistema de archivos extendido que viene con Linux es una especie de sistema de archivos estándar que ha demostrado ser muy robusto y conveniente para el empleo diario durante los últimos años. Para apoyar varios sistemas de archivos y, al mismo tiempo, permitir el acceso a los archivos de otros sistemas operativos, El kernel de Linux incluye una capa de abstracción entre procesos de usuario y la implementación de sistema de archivos. Esta capa se conoce como el sistema de archivos virtual o VFS. Esto es posible debido a una de las principales características de Linux, que es la de dar soporte para diferentes sistemas de archivos y de esta forma permitir el acceso de archivos de otros sistemas operativos. Un sistema de archivos virtual (VFS) es una capa de abstracción sobre un sistema de archivos específico. Fundamentalmente, VFS especifica un interfaz entre el kernel y un sistema de archivos determinado. Su rol principal es proveer una interfaz común para manejar distintos tipos de sistemas de archivos, por eso una de las claves del éxito que ha tenido Linux es su capacidad para coexistir con otros sistemas de archivos. Por ejemplo, si se asume que un usuario realiza la siguiente operación desde consola: $ cp /floppy/test /tmp/test (Donde el comando cp se utiliza para copiar archivos o carpetas en Linux y tiene el siguiente formato: cp origen destino) En este caso, /floppy es el punto de montaje de un disquete MS-DOS y /tmp es un directorio EXT2 (Second Extended Filesystem) de Linux. 153

184 Figura 4.12 El rol de VFS en una operación simple de copiar un archivo. [Bovet y Cesati, 2000: p.329] Como se muestra en la Figura 4.12, el VFS es una capa de abstracción entre el programa de aplicación y las implementaciones del sistema de archivos. En esta figura 4.12, se puede apreciar que el comando cp no necesita conocer el tipo de sistema de archivos de /floppy/test y /tmp/test. Simplemente, cp interactúa con el VFS por medio de llamadas que se realizan al sistema. Los sistemas de archivos soportados por VFS pueden agruparse en tres clases principales: Sistema de archivos basados en disco: Gestiona el espacio de memoria disponible en una partición de disco. El sistema de archivos basado en disco oficial de Linux es EXT2. Es la clásica forma de almacenar los archivos de los medios de comunicación no volátil para retener su contenido entre sesiones. De hecho, la mayoría de los sistemas de archivo han evolucionado a partir de esta categoría. Otros sistemas de archivos conocidos los cuales son basados en disco y son soportados por VFS son: o o Sistemas de archivos para UNIX: como System V y variantes como BSD. Sistema de archivos para Microsoft como MS-DOS, VFAT (Windows 98), y NTFS (Windows NT). 154

185 o SO9660 sistema de archivos de CD-ROM. Sistema de archivos virtuales: Se generan en el propio kernel y es una forma sencilla de permitir que programas de espacio de usuario (userspace) puedan comunicarse con un usuario. El sistema de archivos /proc es el mejor ejemplo de esta clase, ya que éste no requiere espacio de almacenamiento en ningún tipo de dispositivo de hardware; en cambio, crea una estructura de datos jerárquica cuyas entradas contienen información de una determinada parte de un sistema. El archivo /proc/version por ejemplo, tiene una longitud de 0 bytes cuando es vista por medio del comando ls como se muestra a continuación: Figura 4.13 Archivo Proc/Version Sin embargo, si el contenido del archivo se imprime con el comando cat (comando para mostrar el contenido de un archivo en pantalla), el kernel genera un listado de información en el sistema procesador. Este listado es extraído de la estructura de datos en la memoria del kernel. El sistema de archivos /proc provee una interfaz que permite a los usuarios acceder al contenido de algunas estructura de datos del kernel. Se puede utilizar para obtener información sobre el sistema y cambiar determinados parámetros del kernel en tiempo de ejecución. El esfuerzo del sistema de archivos /proc es proporcionar una forma fácil de ver información sobre el kernel y los procesos en ejecución actualmente. El sistema de archivos /proc existe sólo como un reflejo de la memoria del kernel en las estructuras de datos que muestra. Esta es la razón por la mayoría de los archivos y directorios en /proc son de 0 bytes de tamaño. Red de sistemas de archivos: Es un camino intermedio entre el sistema basado en disco y el sistema de archivos virtuales. Permite el acceso a datos en una computadora la cual está conectada al equipo local a través de una red. Esto significa que el kernel no tiene por qué preocuparse de los detalles de acceso a archivos, datos de organización, y hardware de la otra computadora ya que esto es atendido por el 155

186 kernel del equipo remoto. Todas las operaciones en archivos de estos sistemas de archivos se llevan a cabo a través de una conexión de red. Cuando un proceso escribe datos en un archivo, los datos se envían al equipo remoto utilizando un protocolo específico. Algunos sistemas de archivos de red apoyados por el VFS son: NFS, Coda, AFS (sistema de archivos Andrew's), SMB (Windows de Microsoft y de IBM OS / 2 LAN Manager), y el NCP (Novell NetWare Core protocol). Se puede consultar el archivo /proc/filesystems para obtener la lista de sistemas de archivos registrados actualmente. En Linux como en sistemas Unix, los distintos sistemas de archivos no pueden ser accedidos como identificadores de dispositivo (como un número o nombre de una unidad), en lugar de eso, se combinan en una sola estructura jerárquica de datos que es montado. Todos los sistemas de archivos, cualquiera que sea el tipo, son montados en un directorio y los archivos de los diferentes sistemas de archivos forman parte del directorio que se ha montado. Este directorio es conocido como el directorio de montaje o punto de montaje. (Mas adelante en este capítulo se habla sobre el montaje de sistemas de archivos) Modelo común de archivos VFS es orientado a objetos. La clave del VFS consiste en introducir un modelo de archivo común (common file model) capaz de representar todos los sistemas de archivos soportados. Cada sistema de archivos específico debe trasladar su organización física en un modelo común de archivos VFS. Por ejemplo, en el modelo común de archivos cada directorio es considerado como un archivo normal, el cual contiene una lista de archivos u otros directorios. Varios sistemas de archivos basados en disco que no pertenecen a UNIX hacen uso de FILE ALLOCATION TABLE (FAT) la cual almacena la posición de cada archivo en el árbol de directorio. En estos sistemas de archivos los directorios no son archivos. Linux implementa algo parecido a un FAT que debe ser capaz de construir en tiempo real y cuando sea necesario los archivos correspondientes con los directorios. Tales archivos existen como objetos en la memoria del kernel. El modelo de archivo común consta de los siguientes tipos de objetos: El objeto superbloque (superblock object): Guarda información relacionada con un sistema de archivos montado. Para sistemas de archivos basados en disco, este objeto 156

187 usualmente corresponde a un bloque de control de archivos guardado en disco (el superbloque en disco). El objeto inodo (inode object): Almacena información general acerca de un archivo específico. Para sistemas de archivos basados en disco, este objeto generalmente corresponde a un bloque de control de archivos almacenados en el disco (el inodo en disco). El objeto archivo (file object): Guarda información acerca de la interacción entre un archivo abierto y un proceso. Esta información existe únicamente en la memoria del kernel el tiempo que cada proceso accede a un archivo. Objeto de entrada de directorio (dentry object): Almacena la información sobre el enlace de una entrada de directorio con el archivo correspondiente. Otros objetos VFS Además de las estructuras principales definidas anteriormente, VFS utiliza otras estructuras de datos: a) file_system_type Existe una estructura de este tipo por cada uno de los sistemas de archivos registrados en el sistema. Ésta define cada sistema de archivos y sus capacidades. b) vfsmount Representa un punto de montaje. Contiene información sobre el punto de montaje, su ubicación, y las banderas. c) file_struct, fdtable, fs_struct Son tres estructuras por proceso y describen los sistemas de archivos y archivos asociados con un proceso. Cada sistema de archivos basados en disco almacena esta información en su propio disco. Se puede pensar en el modelo común de archivos como orientado a objetos donde un objeto es una construcción a nivel de software que define una estructura de datos y los métodos que operan en él. Por razones de eficiencia, Linux no es codificado en un lenguaje orientado a objetos como C++; los objetos son implementados como estructura de datos con algunos campos que apuntan a funciones que corresponden con los métodos de los distintos objetos Sistema de llamadas manejadas por el VFS Sistemas operativos ofrecen a los procesos ejecutarse en modo usuario un conjunto de interfaces para interactuar con dispositivos (driver) de hardware como la CPU, discos entre otros. La 157

188 implementación de estas interfaces se realiza por medio de llamadas al sistema que se encuentran definidas en el kernel. Básicamente, cuando se realiza una llamada al sistema, la CPU cambia a modo kernel por medio de la instrucción en ensamblador $0x80 la cual ejecuta una excepción que se encuentra en el vector 128. En este capítulo no se habla en detalle del manejo de las llamadas al sistema, simplemente se mencionarán las llamadas al sistema que se utilizan por el VFS. NOMBRE DE LA LLAMADA AL Mount() umount() SISTEMA DESCRIPCIÓN Montar/desmontar sistema de archivos. Sysfs() Obtener la información del sistema de archivos. Statfs() fstatfs() ustat() Chroot() Obtener estadísticas del sistema de archivos. Cambiar el directorio raíz. Chdir() fchdir() getcwd() Manipular el directorio actual. Mkdir() rmdir() Crear o destruir directorios. Getdents() readdir() link() unlink() rename() Manipular entradas de directorio. Readlink() syslink() Manipular enlaces simbólicos. Chown() fchown() lchown () Modificar propietarios de archivos. Chmod() fchmod() utime() Modificar atributos del archivo. Open() close() creat() umask() Abrir, cerrar crear un 158

189 archivo. Dup() dup2() fcntl() Select() poll() Truncate() ftruncate() Read() write() sendfile() Manipular descriptores de archivos. Notificar E/S asíncronas. Cambiar el tamaño de archivo. Operaciones de E/S con archivos. Mmap() mmpa2() munmap() Manejar mapeo de archivos. Tabla 4.6 Algunas llamadas al sistema soportadas por VFS La tabla 4.6 ilustra las llamadas al sistema VFS que se refieren a sistemas de archivos, archivos regulares, directorios, y los enlaces simbólicos. Otras llamadas al sistema que son manejadas por VFS son: ioperm ( ), ioctl ( ), pipe ( ), y mknod( ); éstas hacen referencia a archivos de dispositivos y tuberías.un ultimo grupo de llamadas al sistema que se manejan en VFS como socket ( ), connect ( ), bind ( ), y protocols ( ), son utilizadas en la implementación de redes. Como se ha visto anteriormente, VFS es una capa entre programas de aplicación y sistemas de archivos específicos. En algunos casos la operación sobre un archivo puede ser realizado por el mismo VFS sin necesidad de invocar a un procedimiento de un nivel inferior. Por ejemplo, cuando un proceso abre o cierra un archivo, el archivo en disco usualmente no necesita ser tocado, en cambio, el VFS simplemente libera el correspondiente objeto archivo. Similarmente, cuando la llamada lseek () modifica el puntero al archivo el cual es un atributo relacionado con la interacción entre un archivo abierto y un proceso, el VFS necesita modificar solamente el objeto archivo correspondiente sin accesar al archivo en disco y por lo tanto no tiene que invocar un procedimiento específico de un sistema de archivos. 159

190 Estructura de datos en VFS Cada objeto VFS se almacena en una estructura de datos adecuada, que incluye tanto los atributos del objeto como un puntero a una tabla de métodos del objeto El objeto superbloque (superblock object) Los distintos sistemas de archivos soportados son vinculados por medio de un objeto del kernel llamado superbloque (superblock). Se utiliza un objeto superbloque para cada sistema de archivos en uso, y éste describe el sistema de archivos específico. El objeto superbloque contiene información clave del sistema de archivos como tamaño del bloque, tamaño máximo de un archivo, etcetera. Así mismo, el objeto superbloque contiene punteros a funciones de lectura, escritura y manipulación de inodos. El kernel crea una lista de las instancias de superbloques de todos los sistemas de archivos activos Estructura de datos de objeto superbloque En el kernel, el objeto superbloque se encuentra en: /usr/src/linux-source /include/linux/fs.h El objeto superbloque consiste en una estructura de datos super_block cuyos campos son: TIPO CAMPO DESCRIPCION Struct list_head s_list Una lista doblemente enlazada de todos los superbloques activos. dev_t s_dev Identificador del dispositivo. unsigned long s_blocksize Tamaño del bloque del sistema de archivos en bytes. unsigned char s_blocksize_bits Tamaño de bloque del sistema de archivos en bits. unsigned char s_dirt Indica si el superbloque ha sido modificado y limpiado cuando se vuelve a escribir en disco. 160

191 unsigned long long s_maxbytes Tamaño máximo de un archivo. struct file_system_type *s_type Puntero a struct file_system_type del tipo de sistema de archivos. const super_operations struct *s_op Puntero a la estructura super_operations, la cual contiene métodos específicos del superbloque. struct dquot_operations *dq_op Métodos de quota de disco. struct quotactl_ops *s_qcop Métodos de administración quota de disco. const struct *s_export_op Métodos export de sistemas de export_operations archivos en red. unsigned long s_flags Banderas de montaje. unsigned long s_magic Número mágico del sistema de ficheros. Es utilizado para diferenciar entre múltiples tipos del mismo. struct dentry *s_root Objeto de entrada de directorio de la raíz del sistema de archivos. struct rw_semaphore s_umount Semáforo de montaje. struct mutex s_lock Semáforo del superbloque, indica si esta actualmente bloqueado o no por lock_super()/unlock_super() Int s_count Contador de referencias. Int s_syncing Bandera de sincronización de un sistema de archivos. 161

192 int s_need_sync_fs Bandera de no -esta - sincronizado aún. atomic_t s_active Contador referencia activo. #ifdef CONFIG_SECURITY void *s_security Puntero a la estructura seguridad. #endif struct xattr_handler **s_xattr Puntero a atributos extendidos de seguridad struct list_head s_inodes Lista todos los inodos. struct list_head s_dirty Lista todos los inodos sucios (dirty). struct list_head s_io Lista inodos que esperan escritura en disco. struct list_head s_anon Dentry anonimas para exportar en nfs struct list_head s_files Lista de objeto archivo. struct block_device *s_bdev Dispositivo de bloque asociado. struct list_head s_instances Lista de instancias de este sistema de archivos. struct quota_info s_dquot Descriptor para quota disco. int s_frozen Bandera frozen de un sistema de archivos wait_queue_head_t s_wait_unfrozen Cola de espera mientras se descongela un sistema de archivos. 162

193 char s_id[32] Nombre dispositivo. Void *s_fs_info Información privada del sistema de archivos. struct mutex s_vfs_rename_m utex Renombrar semáforo. _u32 s_time_gran c/m/atime en ns. No puede ser más de un Segundo. Char *s_subtype Tipos de sistemas de archivos. Si el campo de tipo de archivo no esta vacio, en en/proc/mounts debe estar "type.subtype" Tabla 4.7 Descripción de los campos de objeto superbloque El campo s_vfs_rename_mutex es solamente para el VFS; el sistema de archivos no debe manejarlo. Todos los objetos superbloque (montados por un sistema de archivos) son enlazados entre sí por medio de una lista circular doblemente enlazada. La dirección del primer y último elemento de la lista son guardados en los campos next y prev respectivamente, del campo s_list. Este campo pertenece a una estructura de datos llamada struct list_head y consiste en un puntero al next y prev elemento de la lista. El campo s_list de un objeto superbloque incluye un puntero a los dos superbloques adyacentes en la lista. 163

194 Figura 4.14 Forma en que los objeto superbloque son enlazados entre sí. [Bovet y Cesati, 2000: p.336] La figura 4.14 ilustra como los campos de list_head nex y prev encadenan los objeto superbloque. Correspondiente a cada sistema de archivos montado La estructura de datos de list_head La estructura list_head describe así: está definida en /usr/src/linux-source /include/linux/list.h y se struct list_head { struct list_head *next, *prev; }; El campo s_fs_info mencionado en la tabla 4.7, incluye información del superbloque que pertenece a un sistema de archivos específico. Por ejemplo, si el objeto superbloque hace referencia un sistema de archivos EXT2, el campo almacena una estructura ext2_sb_info, que incluye los mapas de bits de asignación de bloque y otros datos que no conciernen al modelo de archivo común de VFS, solamente al sistemas de archivos específico (el EXT2 en este caso) básicamente, la estructura ext2_sb_info contiene el superbloque para EXT2 en memoria. En general, los datos del campo s_fs_info contienen la información de disco, duplicada en memoria por razones de eficiencia. Cualquier sistema de archivos basados en disco necesita acceder y actualizar sus mapas de bits de asignación con el fin de asignar o liberar bloques de discos. VFS permite actuar directamente en el campo s_fs_info del superbloque en memoria sin acceder al disco. 164

195 Sin embargo, esto ocasiona un problema: el objeto superbloque podría no estar sincronizado con el correspondiente superbloque de disco. Por lo tanto, es necesario introducir un campo s_dirt que especifica si el objeto superbloque esta sucio (dirty); esto es, si el dato en el disco debe ser actualizado. La falta de sincronización es un preambulo al problema de sistemas de archivos corruptos cuando hay una caída del sistema sin brindarle al usuario la oportunidad de apagar el sistema limpiamente. Linux minimiza este problema al copiar periódicamente todos los superbloques sucios (s_dirt) en el disco. Cabe mencionar que en Linux, la información sobre los sistemas de archivos montados se mantiene en dos estructuras separadas (super_block y vfsmount). El motivo para esto es que Linux permite montar el mismo sistema de archivos (dispositivo de bloque) bajo múltiples puntos de montaje, lo cual significa que el mismo super_block puede corresponder a múltiples instancias de la estructura vfsmount Operaciones asociadas objeto superbloque Las operaciones asociadas con un objeto superbloque son descritas en la estructura super_operations declarada en /usr/src/linux-source /include/linux/fs.h cuya dirección está incluida en el campo s_op. Algunas operaciones son: void (*read_inode) (struct inode *): Lee el inodo desde el sistema de archivos. Rellena los campos del objeto inodo, cuya dirección se pasa como parámetro, desde los datos de disco; el campo i_ino del objeto inodo identifica el inodo específico a leer de disco. int (*write_inode) (struct inode *, int): Escribe el inodo en disco. Actualiza un inodo de sistema de archivos con los contenidos del objeto inodo pasado como parámetro; el campo i_ino del objeto inodo identifica el inodo en disco que está siendo afectado. void (*put_inode) (struct inode *): Invocado cuando se libera el inodo (se decrementa su contador de referencias) para realizar las operaciones específicas del sistema de archivos. void (*delete_inode) (struct inode *): El sistema de archivos borra la copia en disco del inodo. Elimina los bloques de datos que contiene el archivo, el inodo en disco, y el inodo VFS. 165

196 void (*put_super) (struct super_block *): Libera el objeto superbloque cuya dirección se pasa como parámetro (debido a que se ha desmontado el correspondiente sistema de archivos). void (*write_super) (struct super_block *): El superbloque necesita volver a escribirse en disco. Actualiza un superbloque de sistema de archivos con los contenidos del objeto indicado. int (*statfs) (struct dentry *, struct kstatfs *): Retorna estadísticas del sistema de archivos. int (*remount_fs) (struct super_block *, int *, char *): Remonta el sistema de archivos con las nuevas opciones (se invoca cuando una opción de montaje debe ser cambiada). void (*clear_inode) (struct inode *): Como put_inode, pero también libera todas las páginas que contienen datos concernientes con el archivo que corresponde con el inodo indicado. void (*umount_begin) (struct vfsmount *, int): Interrumpe una opción de montaje, debido a que la correspondiente operación de desmontaje ha sido iniciada (utilizada solo en sistemas de archivos de red). Los métodos anteriores están disponibles para todos los sistemas de archivos posibles. Sin embargo, solo un subconjunto de ellas se aplica a un sistema de archivos específico. Los campos correspondientes a los métodos no implementados apuntan a NULL El objeto inodo (Inode object) Toda la información necesaria por los sistemas de archivos para manejar un archivo es incluido en una estructura de datos llamada inodo. El inodo es único para un archivo y sigue siendo el mismo siempre y cuando el archivo exista. Así como en el sistema de archivos EXT2, cada archivo y directorio en el sistema de archivo es representado por un único inodo VFS. Estructura de datos de un objeto inodo (inode object) La estructura de un inodo está definida en /usr/src/linux-source /include/linux/fs.h y se llama struct inore. Un objeto inodo consiste en una estructura inodo cuyos campos son descritos a continuación: 166

197 TIPO CAMPOS DESCRIPCION struct hlist_node i_hash Punteros a tabla hash. struct list_head i_list Punteros lista de estado del inodo. struct list_head i_sb_list Lista inodos del superbloque. struct list_head i_dentry Lista entrada de directorio que referencia el inodo. unsigned long i_ino Número de inodo. atomic_t i_count Contador de referencia. unsigned int i_nlink Contador que guarda el total de enlaces duros que son usados por el inodo. uid_t i_uid Identificador del propietario. gid_t i_gid Identificador del grupo. dev_t i_rdev Es necesario cuando el inodo representa un número mayor o menor en un archivo de dispositivo unsigned long i_version versión loff_t i_size Tamaño del archivo en bytes 167

198 #ifdef NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount Contador de secuencias #endif struct timespec i_atime Hora de ultimo acceso. struct timespec i_mtime Hora de última modificación. struct timespec i_ctime Hora en el que el último inodo cambió. unsigned int i_blkbits Tamaño de bloque en bits. blkcnt_t i_blocks Número de bloques del inodo. unsigned short i_bytes Longitud del archivo en bytes. umode_t i_mode Tipo archivo (bloque o caracter). spinlock_t i_lock Bloque de i_blocks, i_bytes, i_size. struct mutex i_mutex Semáforo del inodo. struct rw_semaphore i_alloc_sem 168

199 const struct inode_operations *i_op Operaciones sobre el inodo. const struct file_operations *i_fop Operaciones por defecto. struct super_block *i_sb Puntero al objeto superbloque. struct file_lock *i_flock Puntero a la lista de archivos bloqueados struct address_space *i_mapping Puntero al objeto address_space. struct address_space i_data Objeto address_space del archivo. #ifdef CONFIG_QUOTA struct dquot *i_dquot[maxquotas] Quotas disco inodos. #endif struct list_head i_devices Puntero a la lista inodos de archivos de dispositivo. Tabla 4.8 Campos de la Estructura inodo union { struct pipe_inode_info tuberias (pipes)*/ *i_pipe; /*Contiene la información de los inodos usados para implementar struct block_device *i_bdev; /*es usada para dispositivos de bloque */ struct cdev *i_cdev; /*para dispositivos de caracter*/ }; Ya que un inodo no puede representar más de un tipo de dispositivo a la vez, es más seguro mantener los campos i_pipe, i_bdev y i_cdev en un union 169

200 int i_cindex Indice del dispositivo u32 i_generation Número version de inodo ifdef CONFIG_DNOTIFY unsigned long i_dnotify_mask Eventos de notificacion de directorio struct dnotify_ struct *i_dnotify Para notificaciones de directorio #endif #ifdef CONFIG_INOTIFY struct list_head inotify_watches Relojes sobre el inodo struct mutex inotify_mutex Protección de la lista #endif de relojes.. unsigned long i_state Banderas estado del inodo. unsigned long dirtied_when Tiempo de limpieza 170

201 unsigned int i_flags Bandera de montaje del sistema de archivos. atomic_t i_writecount Número de accesos de escritura. #ifdef CONFIG_SECURITY Void #endif *i_security Puntero a la estructura seguridad. void *i_private Puntero a un dispositivo o sistema de archivos privado Tabla 4.9 Descripción de los campos de un objeto inodo Cada objeto inodo duplica los datos de su correspondiente inodo en disco. Así, cuando se modifica alguno de estos campos del inodo en memoria, el campo i_state se ajusta a I_DIRTY_SYNC (el inodo está sucio), I_DIRTY_DATASYNC (los cambios en el inodo están pendientes) o I_DIRTY_PAGES (el inodo tiene páginas sucias; por lo que el inodo en sí mismo puede ser limpio). Cuando se indica que el inodo está dirty (sucio), el correspondiente inodo de disco debe actualizarse. Otros valores del campo i_state son I_LOCK (que significa que el objeto inodo esta involucrado en una operación de E/S) y el I_FREEING (que significa que el objeto inodo esta liberándose). Cada inodo VFS de un sistema de archivos especifico, es identificado por un número único almacenado en i_ino. i_count es un contador que se utiliza para especificar la forma en que varios procesos tienen acceso a la misma estructura de un inodo. Los inodos son usados de manera simultánea cuando, por ejemplo, un proceso se duplica a sí mismo con la instrucción fork () 171

202 El código para crear, manejar, y destruir objeto inodos reside en /usr/src/linux-source /fs/inode.c Cada inodo tiene un i_list (que es un elemento de la estructura list_head) que guarda el inodo en una lista. Cuando un proceso accede a directorios o archivos, las rutinas del sistema son llamadas a través de objeto inodo. Los archivos y directorios son representados en el sistema a través del inodo VFS por lo que si un archivo es accesado constantemente. Estos inodos son guardados en una especie de cache que permite el acceso más rápidamente. Si el inodo no esta en la cache, entonces el sistema de archivos especifica rutinas que deben ser llamadas para leer apropiadamente el inodo. Cuando el inodo es leído, se guarda dentro de la cache de inodos (inode cache) para futuros accesos. Los objeto inodo menos usados son removidos de la cache Estados de un inodo. Dependiendo del estado del inodo, cualquiera de los siguientes tres casos son posibles: Lista de inodos válidos no utilizados: El primer y último elemento de la lista son referenciados por el campo next y prev respectivamente, de la variable inode_unused (de inode.c). El inodo existe en memoria pero no es vinculado a ningún tipo de archivo. Estos inodos contienen la misma información que sus correspondientes inodos de disco y no están actualmente utilizados por ningún proceso. No están sucios, y su campo i_count es cero. Esta lista actúa como una memoria cache. Lista de inodos en uso: Éstos contienen información de inodos de disco válidos y que están siendo utilizados por algún proceso. No están sucios y su campo i_count es positivo. El primer y último elemento de la lista son referenciados por la variable inode_in_use (en inode.c). La estructura del inodo esta en memoria y es utilizado por una o más tareas que usualmente representan un archivo. El valor de los contadores (i_count y i_nlink) deben ser mayores que cero. El contenido del archivo y los metadatos del inodo son idénticos a la información sobre los dispositivos de bloque. Esto significa que el inodo no ha sido cambiado desde la última sincronización con el medio de almacenamiento. Lista de inodos sucios (dirty): Los elementos primero y último de la lista son referenciados por el campo s_dirty del objeto superbloque correspondiente. En este caso, el inodo está activo. El contenido de los datos ha sido cambiado y, por lo tanto, difiere del contenido en el medio de almacenamiento. Los inodos en este estado son descritos como 172

203 sucios (en i_state, de la structura inode); cuando un inodo es marcado como sucios, se agrega a la lista i_sb->s_dirty para sincronizar inodos. Nótese que se utilizan los campos 1) inode_unused para inodos válidos no utilizados, 2) inode_in_use para todos los inodos usados pero que aún no han sido modificados y 3) inodos sucios que son guardados en el superbloque específico de la lista. Existe un cuarto pero es menos frecuente que se dé; es cuando todos los inodos asociados con un superbloque son inválidos. Los objetos inodos están también incluidos en una tabla hash denominada inode_hashtable. Esta tabla acelera la búsqueda de objetos inodo cuando el kernel conoce tanto el número de inodo como la dirección del objeto superbloque correspondiente al sistema de archivos que contiene el archivo. Como la técnica hash puede inducir colisiones, el objeto inodo incluye un campo i_hash que contiene un puntero adelante y atrás a otros inodos que coinciden en la misma posición; este campo crea una lista doblemente enlazada de éstos. Cada inodo aparece en una tabla hash que soporta rápido acceso, por referencia, al número del inodo y el superbloque. Esta combinacion es unica en todo el sistema. La tabla hash es un arreglo (array) que puede ser accedido con la ayuda de la variable global inode_hashtable que también se encuentra en usr/src/linux-source /fs/inode.c.la tabla hash es inicializada durante el arranque del sistema por la función inode_init (también en inode.c).el tamaño del arreglo es calculado en base a la RAM disponible. La función hash de fs/inode.c combina el número de inodo con la dirección del objeto superbloque en un número único que es garantizado a residir dentro del rango del índice reservado de la tabla hash. Las colisiones son resueltas como es habitual, por medio de un desbordamiento de la lista. El elemento i_hash es usado para manejar los elementos con desbordamiento (overflow). Adicionalmente a la tabla hash, los inodos también se pueden mantener en una lista encabezada por super_blocks->s_inodes (fs.h).el campo i_sb_list de la estructura inode es usado como un elemento de la lista.sin embargo, el superbloque maneja mas listas de inodos independientemente de i_sb_list. Si un inodo esta sucio (dirty), es decir, el contenido del archivo ha sido modificado, se encuentra enumerada en una lista dirty accedida por super_block->s_dirty con el elemento de la lista i_list. Esto tiene la ventaja que no se necesita escanear todos los inodos del sistema cuando se escriben de nuevo datos (sincronización); es suficiente con considerar todos los inodos de la lista dirty. Dos listas mas encabezadas por super_block-> s_io y super_block->s_more_io usan el mismo 173

204 elemento de lista i_list. Ellos contienen inodos que han sido seleccionados para ser escritos en disco y están esperando que esto suceda. Operaciones en un objeto inodo Las funciones de lectura y escritura de inodos en disco son específicadas en inode_operations. Los métodos asociados con un objeto inodo se denominan operaciones inodo. Estas operaciones están descritas por la estructura inode_operations, cuya dirección está incluida en el campo i_op. La estructura inode_operations se puede encontrar en: /usr/src/linuxsource /include/linux/fs.h Algunas operaciones son: int (*create) (struct inode *, struct dentry *, int, struct nameidata *): Crea un nuevo inodo en disco para un archivo regular asociado con un objeto que forme parte de un directorio. struct dentry * (*lookup) (struct inode *, struct dentry *, struct nameidata *): Busca en un directorio un inodo cuyo nombre corresponde al contenido de un objeto de entrada de directorio. int (*link) (struct dentry *, struct inode *, struct dentry *): Crea un nuevo enlace duro (hard link) que hace referencia al archivo especificado por el primer parámetro en el inodo indicado; el nuevo enlace duro tiene el nombre indicado por el tercer parámetro. int (*unlink) (struct inode *, struct dentry *): Elimina el enlace duro del archivo especificado por el objeto de entrada de directorio de un directorio. int (*mkdir) (struct inode *, struct dentry *, int): Crea un nuevo inodo para un directorio asociado con un objeto de entrada de directorio. int (*rmdir) (struct inode *, struct dentry *): Elimina de un directorio el subdirectorio cuyo nombre esta incluido en un objeto de entrada de directorio. Estos métodos están disponibles para todos los tipos de inodos y sistemas de archivos. Sin embargo, sólo un subconjunto de ellos se aplica a un inodo y a un sistema de archivos específicos. Los campos correspondientes a métodos no implementados se ponen a NULL. 174

205 El objeto archivo (File Object) Un objeto archivo describe la forma en que interactúa un proceso con el archivo que ha abierto. El objeto se crea cuando se abre el archivo y consiste en una estructura file, cuyos campos serán descritos a continuación. Estructura de datos de un objeto archivo La estructura de un objeto archivo está definida en /usr/src/linux-source /include/linux/fs.h y se llama struct file. Los campos de esta estructura se describen a continuación: TIPO CAMPO DESCRIPCION union { struct list_head fu_list Punteros a una lista generica objetos file. struct rcu_head } f_u; fu_rcuhead struct path f_path Estructura path que representa la ruta del archivo. const struct *f_op Puntero a estructura de file_operations operaciones de objeto archivo. atomic_t f_count Cuenta el número de procesos que están utilizando el objeto archivo unsigned int f_flags Banderas especificadas al abrir el archivo. mode_t f_mode Modo de apertura del 175

206 archivo. loff_t f_pos Posición actual del puntero al archivo. struct fown_struct f_owner Contiene la informacion del proceso que trabaja con un archivo. unsigned int f_uid, f_gid Especifica el UID y GID del usuario. struct file_ra_state f_ra Estado de lectura anticipada u64 f_version Número de versión. void *f_security Puntero a la estructura seguridad. void *private_data Es necesario para controladores (drivers). spinlock_t f_ep_lock spinlock para la lista f_ep_links. struct address_space *f_mapping Punteros al espacio de direcciones del archivo. struct list_head f_ep_links Cabeza de la lista de los procesos que esperan eventos de sondeo para este archivo Tabla 4.10 Descripción de los campos de la estructura de objeto archivo Se nota que los objeto archivo no tienen una imagen correspondiente en disco y por tanto, no tienen campo sucio (dirty) en la estructura file para indicar que ha sido modificado. La principal información almacenada en un objeto archivo es el puntero de archivo (file pointer), esto es, la actual posición en el archivo en la cual tendrá lugar la siguiente operación. Dado que 176

207 varios procesos pueden acceder al mismo archivo al mismo tiempo, el puntero del archivo no se puede mantener en el objeto inodo. El campo f_count es el contador de referencias: lleva el conteo del número de procesos que están utilizando el objeto archivo (cada proceso ligero creado con la bandera CLONE_FILES comparte la tabla que identifica los archivos abiertos, así que utilizan los mismos objetos archivo). El contador se incrementa cuando el objeto es utilizado por el kernel, por ejemplo, cuando se inserta el objeto - en una lista. En cuanto al campo f_path, éste encapsula dos tipos de información: Información acerca del sistema de archivos montado en el cual reside el archivo. Una asociación entre el nombre del archivo y el inodo La estructura de datos se puede definir así: struct path { struct vfsmount *mnt; struct dentry *dentry; }; Cada objeto archivo (file object) puede ser siempre incluido en una de las siguientes listas circulares doblemente enlazadas: La lista de no utilizados (unused): Esta lista actúa como una memoria cache para el objeto archivo y es reservada para el superusuario. Permite al superusuario abrir un archivo incluso si la memoria dinámica en el sistema se ha agotado. Ya que los objetos no son utilizados, el campo f_count es NULL. La lista de en uso (in use): Cada elemento en la lista es usado por al menos un proceso; eso significa que el f_count no tiene un valor NULL. Los objetos archivo en uso se agrupan en varias listas enraizadas en el superbloque del sistema de archivos que los contienen. Cada objeto superbloque almacena en el campo s_files la cabeza de la lista de objeto archivo; de este modo, los objeto archivo que pertenecen a diferentes sistemas de archivos están incluidos en diferentes listas. Cada sistema de archivos incluye su propio conjunto de operaciones sobre archivos que realizan actividades tales como leer y escribir un archivo. 177

208 Cuando el kernel carga un inodo en memoria desde disco, almacena un puntero a esas operaciones de archivo en la estructura file_operations cuya dirección está en el campo i_fop del objeto inodo. Cuando un proceso abre un archivo, VFS inicializa el campo f_op del nuevo objeto archivo con la dirección almacenada en el inodo de forma que llamadas posteriores a las operaciones de archivo pueden usar estas funciones. Si es necesario, el VFS puede modificar posteriormente el conjunto de operaciones de archivos almacenando un nuevo valor en f_op. Los métodos asociados con un objeto archivo se denominan operaciones de archivos Operaciones de un objeto archivo Los archivos no solamente son capaces de almacenar información, también deben permitir que la información pueda ser manipulada. Desde el punto de vista del usuario, la manipulación de archivos se realiza mediante las funciones de la biblioteca estándar. Estas instrucciones ejecutan llamadas al sistema que, a continuación, realizan las operaciones requeridas. Claro esta que la interfaz no puede ser diferente para cada sistema de archivos. Por lo que La capa VFS provee una abstracción de operaciones que vincula los objetos archivo con el mecanismo de bajo nivel del sistema de archivos fundamental. La estructura utilizada para abstraer las operaciones de archivos debe ser lo más general posible para que pueda entender una amplia gama de archivos; al mismo tiempo, debe proveer suficientes características que pueden ser útiles para un determinado tipo de archivo pero no para otros tipos. Debe tener especial cuidado en que las necesidades especiales de los distintos tipos de archivos que se han mencionado al principio de este capítulo (archivos regulares, especiales, etcétera) deben cumplirse con el fin de aprovechar sus capacidades por completo. Cada instancia de un archivo incluye un puntero a una instancia en la estructura file_operations que mantiene punteros a funciones de todas las posibles operaciones sobre archivos. La estructura para operaciones de archivos se llama file_operations y se describe en /usr/src/linux-source /include/linux/fs.h Algunas de ellas son: struct module *owner: Es utilizado unicamente si el sistema de archivos ha sido cargado como un modulo y no es compilado dentro del kernel. Esta entrada apunta a la estructura de datos que representa el modulo en memoria. ssize_t (*read) (struct file *, char user *, size_t, loff_t *); and ssize_t (*write) (struct file *, const char user *, size_t, loff_t *): Lee y escribe datos. Hacen uso del descriptor de archivos, de un buffer en el que la lectura/escritura de datos reside y un 178

209 desplazaminento (offset) para especificar la posición dentro del archivo, así como un puntero que indica el número de bytes a ser leídos o escritos. ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t): Se utiliza para operaciones de lectura asíncronas. int (*readdir) (struct file *, void *, filldir_t): Lee el contenido de directorios y por lo tanto, solo esta disponible para los objetos de este tipo. unsigned int (*poll) (struct file *, struct poll_table_struct *): Se utiliza este método cuando la llamada al sistema necesita implementar operaciones de E/S asíncronas. La función de lectura es utilizada cuando un proceso esta en espera de una entrada de datos de un objeto archivo. Si no hay datos disponibles, la llamada se bloquea hasta que los datos esten disponibles. Esto puede resultar una situación inacaptable si no hay más datos para ser leídos y la función de lectura se bloquea para siempre. int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long): Es utilizado para comunicarse con dispositivos de hardware y puede ser aplicado también a archivos de dispositivo. Este método es utilizado cuando es necesario mandar comandos de control a un dispositivo. int (*mmap) (struct file *, struct vm_area_struct *): Los archivos pueden ser accedidos mas facilmente si su contenido se mapea dentro de un espacio de direcciones virual de un proceso. int (*open) (struct inode *, struct file *): Abre un archivo; éste corresponde con el objeto archivo asociado con un inodo. int (*flush) (struct file *, fl_owner_t id): Es invocado cuando un descriptor de archivos es cerrado, int (*release) (struct inode *, struct file *): Se utiliza cuando el contador de uso de un objeto archivo llega a cero, es decir cuando nadie esta utilizando el archivo. Esto permite que las aplicaciones de bajo nivel puedan liberar contenido memoria y cache cuando ya no es necesario. int (*lock) (struct file *, int, struct file_lock *): Permite bloquear un archivo. Esto permite sincronizar el acceso concurrente de varios procesos. 179

210 Información específica de procesos Los descriptores de archivos (que no son más que números enteros) se utilizan para identificar archivos abiertos dentro de un proceso. Se supone que el kernel es capaz de establecer un vínculo entre los descriptores en el proceso de usuario y las estructuras utilizadas internamente. El sistema de archivos de datos relacionados con un proceso se almacenan en el campo fs de la task_struct la cual se definió anteriormente en el capítulo de procesos La estructura de datos files_struct La tabla files_struct, cuya dirección esta en el campo files del descriptor de proceso en la task_struct, especifica los archivos que están actualmente abiertos por el proceso. Basicamente, el sistema le asocia a cada proceso una tabla de descriptores de archivos abiertos en el campo files dentro de la estructura task_struct. Esta estructura se encuentra en /usr/src/linux-source /include/linux/ file.h y se describe a continuación: struct files_struct { atomic_t count; struct fdtable *fdt; struct fdtable fdtab; spinlock_t file_lock cacheline_aligned_in_smp; int next_fd; struct embedded_fd_set close_on_exec_init; struct embedded_fd_set open_fds_init; struct file * fd_array[nr_open_default]; }; 180

211 TIPO CAMPO DESCRIPCION atomic_t Count Número de procesos que comparten esta tabla. struct fdtable *fdt Puntero a la tabla de descriptores de archivos. struct fdtable Fdtab Puntero a la matriz de punteros de objeto archivo. spinlock_t file_lock cach eline_aligned_in_ smp Int next_fd Indica el número del descriptor de archivo que se utiliza cuando un archivo nuevo se abre. Struct embedded_fd_set close_on_exec_i nit Conjunto inicial de descriptores a cerrar con exec(). Struct embedded_fd_set open_fds_init Mapa de bits que identifica los descriptores de archivo de los archivos abiertos actualmente. struct file *fd_array[nr_op EN_DEFAULT]; Vector de punteros. Es un arreglo (array) que contiene un puntero a una instancia de la estructura de archivos (file struct) por cada archivo abierto. Tabla 4.11 Campos de la estructura files_struct Por defecto, el kernel permite a cada proceso abrir NR_OPEN_DEFAULT archivos. NR_OPEN_DEFAULT es el número máximo de punteros a objeto archivo. En un sistema de 32 bits, el valor de éste es constante de 32. Si un proceso trata de abrir más archivos al mismo 181

212 tiempo, el kernel debe incrementar el espacio de memoria para varios elementos de files_struct que son usados para manejar información de todos los archivos asociados con el proceso La estructura de datos llamada struct embedded_fd_set Consiste solamente de un unsigned long encapsulado en una estructura especial. struct embedded_fd_set { unsigned long fds_bits[1]; }; Para cada archivo con una entrada en el arreglo fd_array, el índice del arreglo es el descriptor de archivo. Normalmente, el primer elemento (índice 0) del arreglo se asocia con la entrada estándar del proceso, el segundo con la salida estándar (entrada 1), y el tercero con el error estándar (entrada 2). Los procesos en Linux utilizan el descriptor de archivo como el principal identificador de archivo. Figura 4.15 El campo f_array La información más importante figura 4.15 en el campo **fd. El kernel define otra estructura de datos llamada struct fdtable. struct fdtable { unsigned int max_fds; struct file ** fd; /* current fd array */ fd_set *close_on_exec; fd_set *open_fds; struct rcu_head rcu; 182

213 struct fdtable *next; }; Donde: max_fds, Especifica el número máximo actual de objeto archivo y de descriptores de archivos que el proceso puede manejar. fd, Es un puntero a un arreglo que apunta a la estructura de archivos que maneja la información de un archivo abierto (este es el fd_array de la files_struct). El descriptor de archivos de un proceso de usuario actua como un indice de un arreglo. El tamaño actual de este arreglo es definido por max_fds. close_on_exec Es también un puntero a un campo de bits que contiene los descriptores de todos los archivos que se cierran con la llamada al sistema exec (). open_fds Es un puntero al campo de bits que maneja los descriptores de todos los archivos abiertos actuamente. Solo existe un bit por cada descriptor de archivo; si el valor de éste es 1, el descriptor esta en uso, en otro caso, esta sin uso. A primera vista, da la impresión que la información se duplica entre la struct fdtable y la files_struct (específicamente los campos open_fds, close_on_exec y fd); pero este no es el caso, debido a que los elementos en la estructura file_struct son instancias reales de algunas estructura de datos, mientras que los elementos en la fdtable son punteros; de hecho, fd, open_fds y close_on_exec se inicializan a fin de que apunten a estos tres elementos en la estructura Archivos asociados con procesos Cada proceso tiene su propio directorio de trabajo actual y su propio directorio raíz Esta información es guardada en la tabla del kernel llamada fs_struct cuya dirección es contenida en el campo fs del descriptor de procesos. La estructura fs_struct se encuentra en /usr/src/linux-source /include/linux/fs_struct.h struct fs_struct { atomic_t count; rwlock_t lock; int umask; 183

214 struct dentry * root, * pwd, * altroot; struct vfsmount * rootmnt, * pwdmnt, * altrootmnt; }; Donde: count Específica el número de procesos que comparten la misma tabla fs_struct (ver llamadas al sistema clone (), fork (), y vfork () en el apartado de procesos). umask Es utilizado por la llamada unmask () para inicializar los permisos de archivos recién creados. Los objetos dentry root y pwd representan al directorio raíz y el directorio de trabajo, respectivamente. root. Descriptor del inodo correspondiente a la raíz del sistema de archivos para el proceso. pwd. Descriptor del inodo correspondiente al directorio actual del proceso. altroot. Descriptor del inodo del directorio raíz alternativo o emulado. rootmnt. Descriptor del objeto del sistema de archivos montado del directorio raíz. pwdmnt. Descriptor del objeto del sistema de archivos montado del directorio de trabajo actual. altrootmnt. Descriptor del del objeto del sistema de archivos montado del directorio raíz emulado. Los elementos de la estructura de datos dentry apuntan al nombre del directorio mientras que los elementos de la estructura vfsmount representan el sistema de archivo montado El objeto de entrada de directorio (Directory entry object) Cada directorio es considerado por VFS como un archivo que contiene una lista de archivos y otros directorios. Una vez que una entrada de directorio ha sido leída en memoria, VFS la transforma en un objeto de entrada de directorio. El objeto de entrada de directorio también es llamado objeto dentry. El kernel crea un objeto dentry para cada ruta de archivo al que accede un proceso. El objeto de entrada de directorio asocia esa ruta con el correspondiente inodo. 184

215 Por ejemplo, si se busca la ruta, /tmp/ejm el kernel crea un objeto de entrada de directorio para el directorio raíz (/) luego, un segundo objeto de entrada de directorio para el directorio tmp y un tercer objeto de entrada de directorio para ejm. La estructura de un objeto de entrada de directorio está definida en /usr/src/linux-source /include/linux/dcache.h y se llama struct dentry. TIPO CAMPO DESCRIPCION atomic_t d_count Contador de uso del objeto. struct inode *d_inode Es un puntero a la instancia del inodo. struct hlist_node d_hash Puntero a la lista en entrada tabla hash. struct dentry *d_parent Puntero al directorio padre. struct qstr d_name Especifica el nombre del archivo, no se guarda la ruta, solamente el nombre de archivo. struct list_head struct rcu_head d_child Hijos del directorio padre. struct dentry_operations *d_op Puntero a las operaciones de objeto de entrada de directorio. struct super_block *d_sb Puntero al superbloque del sistema de archivos al cual el objeto de entrada de directorio pertenece. Tabla 4.12 Descripción de los campos de objeto de entrada de directorio. Para el campo d_flags pueden existir varias banderas definidas en dcache.h; sin embargo, solamente dos de ellas son relevantes: DCACHE_DISCONNECTED la cual especifica que el objeto de entrada de directorio no está actualmente conectado a un superbloque y 185

216 DCACHE_UNASHED la cual especifica que la instancia de un objeto dentry no está contenida en una tabla hash de ningún inodo. Estas banderas son independientes una de otra. Los objetos de entrada de directorio no tienen una imagen correspondiente en disco, por lo que no se incluye un campo en la estructura dentry para especificar que ha sido modificado. Los objetos dentry se almacenan en una cache, denominada dentry_cache Estados del objeto de entrada de directorio Cada objeto de entrada de directorio puede estar en uno de los siguientes estados: Libre (free): El objeto dentry contiene información no válida y no es usado por el VFS. Sin uso (unused): El objeto dentry no está siendo usado actualmente por el kernel. El campo d_count es igual a 0 pero el campo d_inode apunta aún al inodo asociado. Es decir, contiene información válida pero el contenido puede ser descartado si se solicita memoria. En uso (in use): El objeto dentry esta actualmente utilizado por el kernel. El campo d_count es positivo y el campo d_inode apunta al objeto inodo asociado. El objeto dentry contiene información válida que no puede ser descartada. Negativo (negative): El inodo asociado con el objeto dentry ya no existe, porque el correspondiente inodo en disco ha sido borrado. El campo d_inode del objeto dentry apunta a NULL Operaciones asociadas a un objeto de entrada de directorio Los métodos asociados con un objeto de entrada de directorio se denominan operaciones de entrada de directorio (dentry operations), y se describen en la estructura dentry_operations, cuya dirección se almacena en el campo d_op. Si bien algunos sistemas de archivos definen sus propios métodos, los campos suelen estar a NULL y el VFS los sustituye con funciones por defecto. 186

217 Algunos de los métodos son: int (*d_revalidate)(struct dentry *, struct nameidata *):Determina si el objeto dentry es aún válido antes de usarlo para traducir una ruta de nombre. int (*d_compare) (struct dentry *, struct qstr *, struct qstr *): Compara dos nombres de archivos pertenecientes a un directorio. int (*d_delete)(struct dentry *): Es invocada cuando d_count llega a 0. void (*d_release)(struct dentry *):Se invoca cuando se va a liberar al objeto. void (*d_iput)(struct dentry *, struct inode *): Invocada cuando un objeto dentry se transforma en negativo, es decir, pierde el inodo. La cache de entradas de directorios (directory entry cache- dentry cache) Dado que leer una entrada de directorio de disco y construir el correspondiente objeto de entrada de directorio requiere de un tiempo considerable, incluso si el dispositivo de datos ya se encuentra en cache de página, no es necesario repetir toda la operación de búsqueda cada vez que se vuelva a necesitar estos objetos. Tiene sentido mantener en memoria estos objetos aunque se haya finalizado con ellos, ya que más adelante puede que se vuelvan a utilizar. Por ejemplo, se puede editar un archivo y compilarlo, volver a editarlo, imprimirlo, y hacer una copia de él, etcétera. el punto es que en este caso, el mismo archivo es accedido de forma repetitiva. Una vez que el VFS, junto con la implementación de sistema de archivos, lee los datos de una entrada de directorio o archivo, una instancia de objeto dentry se crea para almacenar en cache los datos encontrados. Una instancia de objeto dentry forma un mapa de red de la estructura del sistema de archivos. Todos los archivos y subdirectorios asociados con la instancia de un objeto dentry de un directorio son incluidos en la lista d_subdirs (así como las instancias dentry asociadas). d_child es el vínculo a los elementos hijos de las instancias. Para maximizar la eficiencia en la gestión de entradas de directorio, Linux utiliza una cache de entrada de directorio la cual se forma a partir de dos clases de estructuras de datos: 1. Un conjunto de objetos de entrada de directorio en los estados: en uso (in use), no utilizado (unused) o negativo (negative). 187

218 9. Una tabla hash (hash table) para obtener el objeto de entrada de directorio asociado con el nombre de archivo dado y el directorio de manera rápida. Si el objeto requerido no es incluido en la cache de entrada de directorio, la función hashing retorna un valor NULL. Sin embargo, la topología de los sistemas de archivos no se asigna completamente porque la dentry cache solo puede contener un pequeño extracto de ella. Los archivos y directorios más usados frecuentemente son guardados en memoria. En principio, esto puede ser posible para generar entradas dentry para todos los objetos de los sistemas de archivos, pero el espacio en RAM y el rendimiento son razones para no implementar esto. Como se ha mencionado con frecuencia, el objetivo principal de la estructura dentry es establecer un vínculo entre el nombre de un archivo y su inodo asociado. La cache de entrada de directorio también actúa como un controlador de la cache de inodos. Los inodos en la memoria del kernel que son asociados con una entrada de directorio sin uso no se descartan, ya que el cache de entrada de directorio sigue utilizándolo y por tanto su i_count no es NULL. Así, los objetos inodo se mantienen en RAM y pueden ser referenciados rápidamente por la entrada correspondiente. Existen dos componentes para organizar el objeto de entrada de directorio en memoria: 1. Una lista LRU (Least Recently Used) en la que a los objetos que ya no se utilizan se les concede un último período de gracia antes de ser eliminados de la memoria. Todas las entradas de directorios sin uso son incluidas en una lista doblemente enlazada la Menos Usada Recientemente (Least Recently Used) la cual es ordenada por tiempo de inserción. En otras palabras, el objeto de entrada de directorio que se liberó se pone enfrente de la lista, y objeto de entrada de directorio menos usado recientemente esta al final de la lista. Cuando la cache de entrada de directorio tiene que disminuir, el kernel remueve elementos de la cola de la lista a fin de que el objeto usado más recientemente sea preservado. Cada objeto de entrada de directorio en uso es insertado en una lista doblemente enlazada especificada por el campo i_dentry del correspondiente objeto inodo (ya que cada inodo puede ser asociado con varios enlaces duros, la lista es requerida) el campo d_alias de el objeto de entrada de directorio guarda la dirección de los elementos adyacentes en la lista. Ambos campos son de tipo struct list_head. Un objeto de entrada de directorio puede convertirse en negativo cuando el último enlace duro del correspondiente archivo es eliminado. En este caso, el objeto de entrada de directorio se mueve dentro de la lista LRU de entradas sin uso cada vez que el kernel reduce la dentry cache, las 188

219 entradas negative se mueven hacia la cola de la lista LRU tal que ellos se pueden liberar gradualmente. 2. Una tabla hash que es implementada por un arreglo (array) dentry_hashtable la cual contiene todos los objetos de entrada de directorio. Cada elemento es un puntero a la lista de entradas de directorio que hash al mismo valor de la tabla hash. El tamaño del arreglo depende de la cantidad de RAM disponible en el sistema. El campo d_hash del objeto de entrada de directorio contiene puntero a los elementos adyacentes en la lista asociada a un único valor hash. La función hash produce este valor a partir de la dirección del objeto de entrada de directorio del directorio y del nombre de archivo Los métodos asociados con un objeto de entrada de directorio son llamadas operaciones dentry (dentry operations). Estas son descritas por la estructura dentry_operations, cuya dirección es almacenada en el campo d_op. Aunque algunos sistemas de archivos definen sus propios métodos dentry, los campos son usualmente NULL, y el VFS los reemplaza con funciones por defecto Principales objetos VFS y sus relaciones. Figura 4.16 Interacción entre procesos y objetos VFS 189

220 La figura 4.16 muestra la interacción de procesos y los objetos VFS, así como las estructuras de datos involucradas en esa interacción. Así mismo se muestra la relación entre los distintos componentes VFS Montaje de un sistema de archivos Los sistemas de archivos son implementados de forma modular en el kernel, lo que signifca que ellos pueden ser compilados dentro del kernel como módulos o puede ser totalmente ignorado por la compilación del kernel sin soportar una versión particular de sistema de archivos. Cada sistema de archivos debe registrarse en el kernel antes de ser usado a fin de que Linux tenga una visión general de los sistemas de archivos disponibles y pueda invocar las funciones de montaje requeridas. Cada sistema de archivos tiene su propio directorio raíz. El directorio sobre el que se monta un sistema de archivos se denomina punto de montaje. Un sistema de archivos montado es un hijo del sistema de archivos al que pertenece el directorio punto de montaje. Por ejemplo, /proc es un hijo del sistema de archivos raíz del sistema. El directorio raíz de un sistema de archivos montado oculta los contenidos del subárbol a partir del punto de montaje del sistema de archivos padre Registrando un sistemas de archivos Cuando un sistema de archivos es registrado en el kernel, no hay diferencia si el codigo es compilado como módulo o si esta permanentemente compilado dentro del kernel. Las funciones especiales para ese sistema de archivos son disponibles en el kernel. El enfoque técnico es el mismo en ambos casos, independientemente del momento de registro. Cabe aclarar que los sistemas de archivos compilados permanentemente dentro del kernel son registrados en el momento de arranque; sin embargo, los sistemas de archivos modular se accesan cuando se carga el modulo en el kernel dinamicamente. Algunos campos de la estructura que se utiliza para definir un sistema de archivos es la siguiente: struct file_system_type { const char *name; int fs_flags; int (*get_sb) (struct file_system_type *, int, const char *, void *, struct vfsmount *); void (*kill_sb) (struct super_block *); struct module *owner; 190

221 struct file_system_type * next; struct list_head fs_supers; }; Los campos de la estructura file_system_type se definen a continuación: *name: Guarda el nombre del tipo de sistema de archivos, aparece en el archivo /proc/filesystems y es usado como clave para encontrar un sistema de archivos por su nombre; este mismo nombre es usado por el tipo de sistema de archivos en mount, y debería de ser único; sólo puede haber un sistema de archivos con un nombre dado. fs_flags: Indicadores de montaje. *kill_sb: Cuando un tipo de archivos ya no es necesario se elimina. *owner: Es un puntero a la estructura del modulo que contiene solamente un valor si el sistema de archivos ha sido cargado como modulo (un puntero NULL indica que el sistema de archivos permanece compilado en el kernel). next: Los sistemas de archivos disponibles están vinculados por medio del campo next, el cual no puede utilizar una lista de funciones estandar porque la lista es vinculada en un directorio solamente. (*get_sb) y fs_supers: Una estructura de superbloque es creada en memoria por cada sistema de archivos montado. Esta estructura mantiene información relevante del sistema de archivos y tambien del punto de montaje, esto debido a que varios sistemas de archivos del mismo tipo pueden ser montados (el mejor ejemplo es el mismo sistema de archivos en el directorio home y en la partición root) varias estructuras de superbloque existen por cada tipo de sistemas de archivos y se agrupan juntos en una lista enlazada como se mostró anteriormente cuando se habló del superbloque.fs_supers es la cabeza de esta lista (list head) del superbloque. También es de gran importancia en el proceso de montaje la función de lectura de un superbloque desde un medio de almacenamiento (get_sb), esta función depende del sistema de archivos y no puede ser implementada como una abstracción; esta función no puede ser guardada en la estructura super_operations debido a que el objecto superbloque y el puntero a esta estructura no se crean hasta que get_sb se invoca. Durante la inicialización del sistema, se invoca a la función register_filesystem () para registrar en el VFS los sistemas de archivos especificados en tiempo de compilación en el kernel. La función 191

222 inserta el objeto file_system_type en la lista de tipos de sistemas de archivos. Esta función también se invoca cuando se carga un módulo que implementa un sistema de archivos. En este caso, el sistema de archivos puede des-registrarse cuando se descargue el módulo. La estructura de la función es muy sencilla; register_filesystem de fs/super.c es utilizado para registrar sistemas de archivos en el kernel. Todos los sistemas de archivos son guardados en una simple lista enlazada, y el nombre de cada sistema de archivos es almacenado como una cadena (string) en la lista del objeto. Cuando un nuevo sistema de archivos es registrado en el kernel, esta lista es escaneada elemento por elemento hasta que se alcanza el final de la lista o el sistema de archivos requerido es encontrado. En el último caso, un mensaje de error es retornado (el sistema de archivos no puede ser registrado dos veces) de otro modo, el objeto que describe el nuevo sistema de archivos se encuentra al final de la lista y por lo tanto es registrado en el kernel Montando-desmontando un sistema de archivos Uno de los retos más importantes en diseño de software de Entrada/Salida es emplear las mismas instrucciones para leer/escribir en un archivo sin que importe si éste se encuentra en un disco duro, un CD-ROM, una USB etcétera; es responsabilidad del sistema operativo permitir esta independencia del dispositivo. Como ya se mencionó anteriormente, los archivos en Linux forman una especie de árbol jerárquico; esta abstracción del sistema de archivos como árbol jerárquico es una visión lógica de los archivos, mas no se refiere a la organización física en el dispositivo de almacenamiento. Sin embargo, cabe mencionar que este árbol jerárquico, también esta formado por los sistemas de archivos de distintos discos, particiones, u otro medio de almacenamiento que de alguna forma ha sido montado en el sistema de archivos Linux. Todo lo que respecta a un ensamblado lógico de los distintos sistemas de archivos se realiza por medio de una operación de montaje; esta operación se efectúa generalmente al arrancar el sistema, donde los sistemas de archivos que se encuentran listados en /etc/fstab son montados automáticamente (a no ser que se indique que requieren un montado manual). Los sistemas de archivos montados son inicializados por la llamada al sistema mount Estructura de montaje VFS Linux emplea una sola jerarquía de sistema de archivos en la cual los nuevos sistemas de archivos pueden ser integrados. 192

223 En todo momento el usuario del sistema puede conocer los sistemas de archivos montados por medio del comando mount. Figura 4.17 Consultando sistema de archivos por medio de mount En la figura 4.17, se puede ver un directorio raiz (root directory) global que utiliza el sistema de archivos EXT3, así como el directorio /media/laura G del tipo vfat (este directorio corresponde con una memoria USB de nombre LAURA G), el directorio /media/cdrom1 del tipo iso9660, el directorio /proc de tipo proc y el directorio /media/laurix de tipo vfat (este directorio corresponde con la partición de Windows). Los dos ultimos directorios son conocidos como puntos de montaje debido a que en ese lugar estan los sistemas de archivos montados. Cada sistema de archivos montado tiene un directorio raiz local que contiene la dirección del sistema (y el directorio de librerías en el caso del CD ROM). La plataforma para cada sistema de archivos montado es una instancia de la estructura vfsmount la cual se encuentra en /usr/src/linux-source /include/linux/ mount.h La cual se define a continuación: struct vfsmount { struct list_head mnt_hash; struct vfsmount *mnt_parent; struct dentry *mnt_mountpoint; struct dentry *mnt_root; 193

224 struct super_block *mnt_sb; struct list_head mnt_mounts; struct list_head mnt_child; int mnt_flags; char *mnt_devname; struct list_head mnt_list; struct list_head mnt_expire; struct list_head mnt_share; struct list_head mnt_slave_list; struct list_head mnt_slave; struct vfsmount *mnt_master; struct mnt_namespace *mnt_ns; atomic_t mnt_count; int mnt_expiry_mark; int mnt_pinned; }; Donde: *mnt_mountpoint: Es la estructura dentry del punto de montaje en el directorio padre en el cual el sistema de archivos es montado. *mnt_root: Guarda el directorio raíz de un sistema de archivos. Dos instancias dentry: Representan el mismo directorio (es decir, el punto de montaje) esto significa que no es necesario eliminar la información del anterior punto de montaje de la memoria y hacer que este disponible nuevamente una vez que el sistema de archivos ha sido desmontado. 194

225 El puntero mnt_sb: Crea un enlace con el superbloque asociado (el cual es exactamente una instancia por cada sistema de archivos montado). mnt_parent: Apunta a la estructura vfsmount del sistema de archivos padre. Las relaciones padre e hijo son representadas por una lista vinculada por dos elementos de la estructura. El mnt_mounts es la cabeza de la lista de los sistemas de archivos hijos, los elementos individuales de la lista son enlazados por medio del campo mnt_child. mnt_flags: Puede tomar uno de los valores siguientes: #define MNT_NOSUID 0x01: Prohibe los indicadores setuid y setgid #define MNT_NODEV 0x02: Se establece si el sistema de archivos montado es virtual, es decir no tiene un dispositivo de apoyo físico. #define MNT_NOEXEC 0x04: Rechaza la ejecución de programas. #define MNT_NOATIME 0x08: No modificar el tiempo de acceso del archivo. #define MNT_NODIRATIME 0x10: No actualizar el tiempo de acceso a directorios. #define MNT_RELATIME 0x20 #define MNT_SHRINKABLE 0x100: Se utiliza para marcar submounts. #define MNT_SHARED 0x1000 #define MNT_UNBINDABLE 0x2000 #define MNT_PNODE_MASK 0x3000 Se utiliza una tabla hash llamada mount_hashtable que se define en fs/namespace.c. mnt_hash es el puntero a la lista de tabla hash. La dirección de la instancia vfsmount y de la dirección asociada por medio del objeto de entrada de directorio (dentry object) es utilizada para calcular la suma hash. mnt_namespace: Puntero al espacio de nombres del sistema de archivos que se montó. mnt_slave, mnt_slave_list, and mnt_maste: Sirven para realizar montajes exclavos (slave mount). El master mount mantiene todos los montajes exclavos en una lista enlazada en la cual el campo mnt_slave_list es utilizado como cabeza de esta lista. Todos los exclavos montados accesan a su master a través del campo mnt_master. 195

226 Mnt_expiry_mark: Se utiliza para indicar que el monjate ya no es utilizado. Mnt_expire: Permite la colocación de todos los puntos de montaje que son propensos a caducar en una lista enlazada. mnt_share: Lista circular de los montajes compartidos. mnt_devname: Nombre del dispositivo a montar. struct super_block mnt_sb: Apunta a la estructura superbloque que controla el sistema de archivos. mnt_root: Raíz del árbol del sistema de archivos montado. mnt_ns: Apunta al espacio de nombre al cual el montaje pertenece. mnt_count: Contador de uso. Los sistemas de archivos que se van a montar son inicializados por la llamada al sistema moun (), su rutina de servicio sys_mount () actua sobre los parámetros: 1. La ruta del dispositivo que contiene el sistema de archivos o NULL si éste no es requerido (por ejemplo, cuando se monta un sistema de archivos basados en red) este es el campo dev_name. 2. La ruta del directorio en el cual el sistema de archivos ha sido montado (esto es el punto de montaje) este es el campo dir_name. 3. El tipo de sistema de archivos, el cual debe debe ser un nombre de sistema de archivos previamente registrado.este es el campo type. 4. Los indicadores de montaje (los valores descritos anteriormente en el campo mnt_flag). 5. Un puntero a una estructura de datos dependiendo del sistema de archivos (que puede ser NULL). La función sys_mount () copia los valores de los parámetros en un búffer temporal del kernel, bloquea el semáforo big kernel lock (exclusivo de VFS) el cual indica globalmente que se está en una "región crítica" por lo cual el procesador no puede interrumpirse por nada e invoca a do_mount () que lleva a cabo la operación de montaje del sistema de archivos. La función do_mount () se encuentra en /fs/namespace.c 196

227 En caso contrario, también existe la función umount () que es utilizada para desmontar sistemas de archivos. El sistema de archivos a desmontar debe especificar dando el directorio donde ha sido montado, o bien dando el dispositivo o archivo de dispositivo donde reside. Un sistema de archivos no puede desmontarse cuando está ocupado por ejemplo cuando hay en él archivos abiertos, o cuando algún proceso tiene su directorio de trabajo allí. Figura 4.18 Relación entre las estructuras vfsmount, super_block y file_system_type La figura anterior describe la interacción entre las estructuras vfsmount, super_block y file_system_type.. En este caso, en devname se encuentra el nombre del dispositivo y por medio del campo mnt_sb se accesa a la estructura super_block en cuyo campo s_type se guarda un puntero a file_system_type en donde se especifica el sistema de archivos montado en ese dispositivo. 197

228

229 CAPÍTULO 5: GESTIÓN DE DISPOSITIVOS DE E/S El sistema de Archivos Virtual (VFS) depende de funciones de bajo nivel para llevar a cabo operaciones de lectura, escritura u otra operación de manera apropiada a cada dispositivo. En la primera sección del capítulo, se presenta una breve descripción de la arquitectura de E/S de los microprocesadores intel 80x86. En la segunda sección se ilustra la forma en que el VFS asocia un archivo de dispositivo con cada dispositivo de hardware (de bloque y carácter) para que distintos programas de aplicación puedan utilizar todo tipo de dispositivos de hardware de la misma manera. El objetivo general de de este capítulo es ilustrar la organización general de los controladores de dispositivos en Linux Arquitectura de E/S. El papel que actualmente juegan los dispositivos periféricos en la computadora es fundamental; realmente sin los dispositivos la computadora no sería totalmente útil ya que por medio de ellos se pueden introducir datos a la computadora, y posteriormente se obtienen los resultados de operaciones que se realizan sobre esos datos (información). La comunicación con dispositivos periféricos se hace usualmente por medio de entradas y salidas, abreviadas E/S. Los dispositivos se pueden clasificar en: Dispositivos de Entrada: Los dispositivos de entrada se utilizan para introducir datos a la computadora para ser procesados. Los datos se leen de los dispositivos de entrada. Algunos de ellos son: teclados, lápiz óptico, ratón. Dispositivos de Salida: Estos dispositivos son los que permiten representar los resultados, es decir la salida, del procesamiento de datos (información). Algunos de ellos son impresoras, monitores, bocinas. Dispositivos de E/S: Son aquellos dispositivos que cumplen las características de los tipos de dispositivos descritos anteriormente. Ejemplo de ellos son: módem, tarjeta de red, auriculares con micrófono, impresoras multifuncionales. Dispositivos de almacenamiento: Almacenan datos que pueden ser utilizados por la CPU una vez que han sido eliminados de la memoria principal. Algunos de ellos son: Disco duro, disquete, cintas magnéticas, tarjetas perforadas. 199

230 Para que la computadora trabaje de manera apropiada, la ruta de datos debe ser provista de tal forma que permita que la información fluya entre la CPU, RAM y los dispositivos de E/S que pueden ser conectados actualmente a un computador personal. Esta ruta de datos, la cual es denominada como un bus, actúa como el principal canal de comunicación dentro de la computadora. Varios tipos de buses, como ISA, EISA, PCI y MCA están actualmente en uso. En este capítulo se van a discutir las características comunes de todas las arquitecturas de computadoras sin dar detalles de su específico tipo de bus Tipo de Buses Bus paralelo Por medio de este bus los datos son enviados por bytes al mismo tiempo, por medio de varias líneas con funciones determinadas. Se envían cantidades grandes de datos y lo utilizan buses de discos duros tarjetas de video, impresoras entre otros. Lo que comúnmente es denominado como bus consiste en tres tipos específicos de buses: Bus de datos: Un grupo de líneas que transfiere datos en paralelo. Los Pentium tienen un ancho de bus de 64 bits. Bus de direcciones: Un grupo de líneas que transfieren direcciones en paralelo. Los Pentium tienen un ancho de bus de 32 bits. Este bus es el responsable de establecer la dirección de memoria de datos o dispositivos (es decir, la posición de datos dentro de la memoria principal o en todo caso, el espacio de direcciones de la unidad de E/S). Bus de control: Un grupo de líneas que transmite información del control de los circuitos conectados. Los Pentium hacen uso de estas líneas de control para especificar, por ejemplo, si el bus es utilizado para permitir la transferencia de datos entre el procesador y la RAM o bien entre el procesador y un dispositivo de E/S. Las líneas de control también determinan si una transferencia de lectura o escritura puede ser realizada. Bus serial Por medio de este bus, los datos son enviados bit por bit a través de una línea. Algunos dispositivos como mouse o teclados utilizan este bus, sin embargo, es lento comparado con un bus paralelo. 200

231 El acceso a la memoria y a los dispositivos de E/S se realiza mediante los buses de datos, direcciones y control; Cuando el bus conecta la CPU con un dispositivo de E/S este es llamado bus de E/S (I/O bus). En el caso de los Microprocesadores Intel 80x86 utilizan 16 de las 32 líneas del bus de direcciones para manejar dispositivos de E/S y 8,16 o 32 líneas de las 64 líneas de datos para transferencia de datos. El bus de E/S es conectado a cada dispositivo de E/S creando una jerarquía de componentes de hardware que incluye tres elementos: puertos de E/S, interfaces y controladores de dispositivos. Figura 5.1 Arquitectura de E/S La figura 5.1 muestra los componentes de una arquitectura de E/S Puertos de E/S Cada dispositivo conectado al bus de E/S tiene su propio conjunto de direcciones de E/S las cuales son usualmente llamadas puertos de E/S. Los puertos E/S son direcciones de memoria que son utilizadas por el microprocesador para la comunicación directa con un dispositivo que ha generado una interrupción y es atendida por el microprocesador. Ya que los dispositivos conectados al bus de E/S están escuchando los datos que circulan por dicho bus, los dispositivos solo reaccionan cuando la dirección existente en determinado momento 201

232 en el bus de direcciones tiene un valor específico. Este valor, que generalmente concuerda con un rango de valores contiguos hexadecimales, es único para cada dispositivo y dependiendo de la dirección dada el dispositivo (que tiene asociado esa dirección) puede escribir un dato en el bus de datos o leer el dato que existe en ese momento en el bus; si esa dirección no corresponde con un determinado dispositivo, simplemente éste ignora lo que está sucediendo en el bus de E/S. Estas direcciones son los puertos de E/S y en realidad son direcciones de dispositivos. El funcionamiento básico de un dispositivo es que cuando ve que un dato en el bus de direcciones se refiere a él, realiza una acción determinada de lectura o escritura de datos en el bus de datos (también depende de otras líneas de control que se mencionan más adelante). Desde el punto de vista del software, un puerto es una interfaz con ciertas características; se trata por tanto de una abstracción (no del conector físico de un dispositivo al sistema), aunque desde el punto de vista del hardware, esta abstracción se corresponde con un dispositivo físico capaz de intercambiar información de entrada y salida con el bus. Para intercambiar datos entre el procesador y la memoria, el procesador dispone de varias instrucciones para realizar operaciones entre registros de la CPU y direcciones de memoria (MOV, AND, OR); de forma análoga, también se cuentan con ciertas instrucciones para realizar operaciones entre registros de la CPU y direcciones de puertos (como instrucciones IN, OUT). Estas últimas instrucciones se definen como: IN: Lee un byte del registro de entrada de la interfaz de E/S y lo sitúa en un registro del microprocesador. OUT: Escribe el contenido de un registro del microprocesador en el registro salida (de la interfaz de ES). Sintaxis: IN IN OUT OUT registro acumulador AX o AL (destino), dirección-de-puerto (origen) registro acumulador AX o AL (destino), DX (origen) dirección-de-puerto(destino), registro acumulador AX o AL (origen) DX(destino), registro acumulador AX o AL (origen) Estas instrucciones permiten a la CPU leer y escribir en un puerto de E/S. Cada vez que se ejecuta una de estas instrucciones, la CPU hace uso del bus de direcciones para seleccionar el puerto de E/S requerido y del bus de datos para trasferir datos entre el registro de la CPU y el puerto de E/S. 202

233 Básicamente, estas instrucciones se utilizan para direccionar los registros de los controladores de dispositivos. Los puertos de E/S también pueden ser mapeados dentro de espacios de direcciones físicas en memoria; esto debido a que algunos dispositivos (como adaptadores gráficos) requieren mover grandes cantidades de datos de manera rápida. Básicamente, se construyen dispositivos periféricos de forma que sus registros internos respondan como direcciones de memoria y a la vez, estos registros deben ser lo suficientemente rápidos para que puedan ser equiparados a la velocidad de acceso a RAM. El procesador también es capaz de comunicarse con dispositivos de E/S mediante instrucciones en lenguaje ensamblador que operan directamente en memoria (algunas instrucciones como MOV, AND, OR). Debe notarse que en este caso, el microprocesador no hace diferencia alguna entre direcciones de memoria RAM y las direcciones que corresponden a un dispositivo periférico. La ventaja de que las direcciones sean mapeadas en la memoria física es que los dispositivos se manejan de manera uniforme dentro del espacio de direcciones físicas de la memoria y por lo tanto se dispone de los recursos propios del manejo de memoria como por ejemplo el DMA. Dispositivos de hardware modernos prefieren mapas de E/S ya que es más rápido y puede combinarse con DMA. Accediendo a puertos de E/S Las instrucciones de ensamblador IN, OUT, INS Y OUTS se utilizan para accesar a puertos de E/S las siguientes funciones auxiliares son incluidas en el kernel para simplificar estos accesos: inb (), inw () e inl (). Leen 1,2 o 4 bytes consecutivos, respectivamente, desde cada puerto de E/S. El sufijo b, w o l especifica, respectivamente, si es un byte (8 bits) una palabra (16 bits) o 32 bits. inb_p ( ), inw_p( ), inl_p( ). Lee 1,2 o 4 bytes consecutivos, respectivamente, desde un Puerto de E/S y después ejecuta una instrucción para introducir una pausa. outb ( ), outw ( ), outl ( ).Escribe 1,2 o 4 bytes consecutivos, respectivamente, en cada puerto de E/S. outb_p ( ), outw_p ( ), outl_p ( ).Escribe 1,2 o 4 bytes consecutivos, respectivamente, en un Puerto de E/S y luego ejecuta una instrucción para introducir una pausa. 203

234 insb ( ), insw ( ), insl ( ).Leen secuencia de bytes consecutivos de un puerto de E/S, en grupos de 1,2 o 4 bytes. La longitud de la secuencia es especificada como parámetro de la función. outsb ( ), outsw ( ), outsl ( ). Escribe secuencias de bytes consecutivos en grupos de 1, 2, 4 bytes, respectivamente, en un puerto de E/S. Mientras accesar a puertos de E/S es simple, detectar cuál puerto de E/S ha sido asignado a un dispositivo de E/S no lo es, en particular, para un sistema basado en bus ISA. Frecuentemente, un controlador de dispositivo debe escribir a ciegas en algún puerto de E/S con el fin de examinar el dispositivo de hardware; sin embargo, si este puerto de E/S ya es utilizado por algún otro dispositivo de hardware podría producir que el sistema quede colgado. Para prevenir estas situaciones, el kernel mantiene una pista de los puertos de E/S asignados a cada dispositivo de hardware por medio de una tabla llamada iotable. Cualquier controlador (driver) de dispositivo puede utilizar las siguientes funciones: request_region( ) Asigna un determinado intervalo de puertos de E/S a un dispositivo de E/S. check_region( ) Comprueba si un determinado intervalo de puertos de E/S esta libre o si algunos de ellos ya han sido asignados a algunos dispositivos de E/S. release_region( ) Libera un intervalo de puertos de E/S previamente asignados a dispositivos de E/S. Las direcciones de E/S actualmente asignadas a dispositivos de E/S pueden ser obtenidas del archivo /proc/ioports Interfaz de E/S Una interfaz de E/S es un circuito de hardware insertado entre un grupo de puertos de E/S y el correspondiente controlador de dispositivo. Este actúa como un intérprete que traduce los valores de los puertos de E/S en comandos y datos para cada dispositivo. En dirección opuesta, detecta todos los cambios en el estado del dispositivo y se actualiza el puerto de E/S correspondiente que desempeña algunas veces la función de registro de estado. 204

235 Este circuito también se puede conectar a través de una línea IRQ a un Controlador de interrupciones programables (Programmable Interrupt Controller) a fin de que pueda atender las peticiones por medio de interrupciones en nombre del dispositivo. Las funciones principales de la interfaz de E/S se resumen en: Interpretar las órdenes que recibe de la CPU y transmitirlas al dispositivo periférico. Controlar la transferencia de datos entre la CPU y el dispositivo periférico. Conversión de formatos y/o niveles eléctricos. Adaptar la diferencia de velocidades entre la CPU y el periférico (por medio de buffers de almacenamiento). Informar a la CPU el estado del periférico. Figura 5.2 Interfaz de E/S Una interfaz de E/S está compuesta de tres elementos básicos: Registros de interfaz de E/S: Permiten a la CPU programar la interfaz de E/S, consultar el estado del dispositivo y enviar o recibir datos hacia/desde el periférico. 205

236 Estos registros son: Registro de salida: Cuando la CPU envía datos al periférico los escribe en el registro de datos de salida. Registro de entrada: Cuando la CPU recibe datos del periférico los lee desde el registro de entrada. Registro de estado: Cuando la CPU desea conocer el estado del periférico, lo hace leyendo el registro de estado. Registro de control: Cuando la CPU desea transmitir una orden al periférico lo hace escribiendo en el registro de control. Líneas de comunicación con la CPU: Las cuales permiten a la CPU comunicarse con la interfaz de E/S. Las líneas de comunicación son: Líneas de bus de datos: Se utilizan para la transferencia de datos entre la CPU y la interfaz de E/S así como para la programación del registro de control y lectura del registro de estado. Línea de R/W*: Se utiliza para especificar el tipo de operación a realizar (lectura o escritura). Línea CS* (Chip Select) y RS (Register Select): Se utiliza para seleccionar la interfaz de E/S y acceder a un registro particular del mismo. Normalmente se conectan al bus de direcciones mediante un decodificador (en el caso del CS*) Línea IRQ*: Para peticiones de interrupción cuando el periférico esta listo para transmitir/recibir. Líneas de comunicación con el dispositivo periférico: Permiten a la interfaz de E/S comunicarse con el dispositivo periférico. Líneas de datos de entrada y salida: Se utiliza para transferencia de datos entre la interfaz de E/S y periférico. Líneas de control de entrada y salida: Se utiliza para la transferencia de órdenes al periférico (control de salida) y consultar su estado (control de entrada). 206

237 Un importante objetivo de los diseñadores del sistema operativo es ofrecer un enfoque unificado de la programación de E/S sin sacrificar rendimiento; con este fin, los puertos de E/S de cada dispositivo se estructuran en un conjunto de registros especializados como se muestra en la figura. Funcionamiento de los registros de interfaz de E/S Registro de Control CPU Registro de Estado Registro de Entrada Interfaz de E/S Registro de Salida Figura 5.3 Puertos de E/S especializados La CPU escribe dentro del registro de control los comandos que se enviarán al dispositivo y lee del registro de estado el valor que representa el estado interno del dispositivo. La CPU también obtiene datos del dispositivo leyendo bytes del registro de entrada y empuja datos para el dispositivo escribiendo bytes en el registro de salida. Para disminuir costos, el mismo puerto de E/S es utilizado para diferentes propósitos. Por ejemplo, algunos bits describen el estado del dispositivo, mientras que otros especifican el comando que puede ser usado por el dispositivo. Similarmente, el mismo puerto de E/S puede ser usado como un registro de entrada o salida. Existen dos tipos de interfaces de E/S: Interfaces de E/S personalizadas: Dedicadas a conectar un determinado dispositivo de hardware. En algunos casos, el controlador de dispositivo es localizado en la misma tarjeta que contiene la interfaz de E/S. Los dispositivos conectados a una interfaz de E/S personalizada puede ser tanto interna (dispositivos dentro de la computadora) o externa (dispositivos fuera de la computadora). Algunos de los dispositivos que utilizan una interfaz de E/S personalizada son: Interfaz de teclado: Conectado a un controlador de teclado que incluye un microprocesador dedicado. Este microprocesador decodifica la combinación de teclas pulsadas, genera una interrupción y pone el código de escaneo correspondiente en un registro de salida. 207

238 Interfaz gráfica: Cada controlador de tarjeta gráfica tiene su propio buffer de marcos (frame buffer), así como un procesador especializado y código almacenado en una memoria de lectura (ROM). El buffer de marcos es una memoria a bordo que contiene descripciones gráficas del contenido de la pantalla actual. Interfaz de disco: Conectado por un cable al controlador de disco el cual suele ser integrado con el disco. Por ejemplo la interfaz IDE esta conectada por un cable plano de 40 conductores a un controlador de disco inteligente que se puede encontrar en el propio disco. Interfaz de Mouse: El correspondiente controlador se incluye en el ratón, que esta conectado a través de un cable a la interfaz. Interfaces de E/S para propósito general: Es utilizada para conectar varios dispositivos de hardware diferentes. Los dispositivos dentro de esta clase son siempre externos. Las computadoras modernas incluyen varias interfaces de E/S para propósito general que se utilizan para conectar una amplia gama de dispositivos externos. La mayoría de interfaces comunes son: Puerto paralelo: Es una interfaz entre la computadora y periféricos los cuales tienen la característica que transfieren la información en bytes a la vez. Se utiliza para conectar impresoras, escáner, discos extraíbles, copias de seguridad. Puerto serial: Los datos se transfieren bit por bit a la vez. Incluyen un chip Transmisor y Receptor Asíncrono Universal (Universal Asynchronous Receiver and Transmitter UART) La función principal del chip UART es manejar las interrupciones de los dispositivos conectados al puerto serial y de convertir los datos en formato paralelo, transmitidos al bus de sistema, a datos en formato serie, para que puedan ser transmitidos a través de los puertos y hace la función inversa al ser recibidos. Bus universal en serie (Universal serial bus USB): Es un puerto útil para conectar periféricos a una computadora. Este puerto opera a alta velocidad y puede ser utilizado para dispositivos externos tradicionalmente conectados al puerto paralelo y serial Controladores de dispositivos Un dispositivo complejo puede requerir un controlador de dispositivo que lo maneje. 208

239 El controlador tiene dos roles importantes: Interpreta los comandos de alto nivel recibidos de la interfaz de E/S y obliga al dispositivo a ejecutar acciones específicas mediante el envío de una secuencia de señales eléctricas. Convierte e interpreta las señales eléctricas recibidas del dispositivo y modifica el valor del registro de estado. Un típico controlador de dispositivo es el controlador de disco, el cual recibe comando de alto nivel tales como escribir este bloque de datos desde el microprocesador (a través de interfaz de E/S) y la convierte en operaciones de bajo nivel de disco, como la posición de la cabeza del disco en el camino correcto y escribir los datos en el interior de la pista. Los controladores de disco modernos son muy sofisticados, ya que ellos pueden guardar los datos de disco en memoria cache y pueden reordenar las solicitudes de alto nivel optimizadas según la geometría del disco. Algunos dispositivos no tienen un controlador, por ejemplo el Controlador de Interrupciones Programable (programable Interrupt Controller PIC). Acceso directo a memoria (Direct Memory Access DMA) El controlador de dispositivo se comunica directamente con la memoria principal del computador, por lo que la CPU no realiza ninguna tarea ya que la inicialización y la transferencia de datos es realizada por el dispositivo periférico independientemente de la CPU. Esto es posible gracias a la implementación de un procesador auxiliar denominado Controlador de acceso directo a memoria (Direct Memory Access Controller) o DMAC el cual es utilizado para transferir datos entre la RAM y dispositivos de E/S. Una vez que se activa por la CPU, el DMAC es capaz de realizar la transferencia de datos por su propia cuenta; cuando esta transferencia de datos es completada, el DMAC realiza una interrupción. El DMA cuenta con ciertos elementos de hardware auxiliares que lo convierten en un subsistema autónomo dentro del bus. Estos elementos son: Líneas dedicadas en el bus de control. El bus de control tiene líneas específicas para este tipo de transferencia de datos las cuales son: Líneas DREQ0 a DREQ3 ("DMA request").entradas de petición de DMA por parte de los periféricos. 209

240 Líneas DACK0 a DACK3 ("DMA acknowledge"). Son salidas que se utilizan para acusar recibo de la petición DREQ correspondiente. Avisa a los dispositivos periféricos que se ha atendido su petición. AEN ("Address Enable"). Cuando esta señal está alta, el controlador DMA tiene control sobre ciertas líneas del bus; precisamente las que gobiernan los intercambios con memoria y puertos (MEMR, MEMW, IOR, IOW). Ademas, habilita el latch de 8 bits que guarda la parte alta de la dirección. MEMR ("Memory Read"). Es una salida que indica que se lean datos de la memoria durante un ciclo de lectura de DMA. MEMW ("Memory Write"). Es una salida que indica que se escriban datos en la memoria durante un ciclo de escritura de DMA. Un procesador específico, el DMAC (Direct Memory Access Controller) El mecanismo de acceso directo a memoria es controlado por el chip DMAC el cual permite que puedan realizarse transferencia de datos entre la RAM y dispositivos de E/S sin intervención del procesador. Definiciones básicas: Una lectura de DMA transfiere datos de la memoria hacia el dispositivo de E/S; en la escritura de DMA, transfiere datos de un dispositivo de E/S hacia la memoria. En las dos operaciones, la memoria y el dispositivo de E/S se controlan de manera simultánea ya que el sistema contiene señales para el control de memoria y de dispositivos de E/S. [Brey, 2000: p.498] Estas señales de control separadas del microprocesador permite la transferencia de DMA. Una lectura de DMA hace que MRDC (MEMR) e IOWC (IOW) se activen y transfieran datos desde la memoria hacia el dispositivo de E/S.Una escritura de DMA hace que las señales MWTC (MEMW) e IORC (IOR) se activen. Estas señales las emite un circuito cuyo diagrama se muestra a continuación: 210

241 Figura 5.4 Circuito que se utiliza para producir las señales del canal de control en un El controlador 8237 de DMA sistema en el que se emplea DMA. [Brey, 2000: p.499] Figura 5.5 conexiones con terminales del circuito integrado 8237 [Brey, 2000: p.500] El controlador de DMA 8237 suministra a la memoria y al dispositivo de E/S señales de control e información de direccionamiento de memoria durante la transferencia de DMA. En realidad, el 8237 es un microprocesador de propósito especial cuya labor principal es la transferencia de datos a alta velocidad entre la memoria y los dispositivos de E/S. 211

242 Este controlador se emplea en un conjunto de controladores integrados (chipset) que existen en los sistemas más nuevos. El 8237 tiene cuatro canales de 8 bits (es decir puede mover solo un byte a la vez) y sus direcciones de puerto son FH; además es compatible con los microprocesadores 8086 y El 8237 puede expandirse para incluir cualquier número de entradas de canal de DMA. A partir de 1996, integran en el chipset dos de estos integrados y las correspondientes líneas auxiliares en el bus de control. Estos integrados se conectan en cascada por medio de las líneas DREQ0 y DACK0 del primer integrado. Los canales del segundo DMAC están asignados a las direcciones 0C0-0DFH y son de 16 bits; esto permite mover 2 bytes de posiciones contiguas cada vez. El 8237 puede expandirse para incluir cualquier número de entradas de canal de DMA y puede realizar transferencias a velocidades de hasta 1.6 MB por segundo. Cada canal puede direccionar a una sección completa de 64 KB de la memoria y puede transferir hasta 64 KB. También soporta transferencias memoria a memoria incluyendo la posibilidad de rellenar un área de memoria con cierto dato. Figura 5.6 Conexión en cascada de varios

243 Descripción del integrado CLK (Clock imput): Señal de reloj. CS (Chip Select): Línea de habilitación del circuito integrado. Por lo general esta conectada a la salida de un decodificador. RESET: Reinicia el READY: Señal que puede ser empleada para extender los pulsos de lectura y escritura en memoria del 8237 para trabajar con memorias lentas. HLDA (Hold Acknowledge): Línea por la que la CPU indica que ha liberado los buses. HOLD (Hold Request): Línea por la cual se realiza la petición de los buses a la CPU. DREQ0 -DREQ3 (DMA Request): Entradas de solicitud de DMA por parte de los periféricos (4 canales). Debido a que la polaridad de estas entradas es programable, son entradas activas en alto y bajo. DB0 - DB7 (Data bus) :Son las conexiones al bus de datos del microprocesador y se emplean durante la programación del controlador de DMA Durante los ciclos de DMA, los 8 bits más significativos de la dirección son colocados en el bus de datos con objeto de ser almacenados en un circuito integrado latch exterior controlado por ADSTB. En las operaciones memoria-memoria, el bus de datos recibe y envía los bytes a transferir. IOR (I/O Read): Línea bidireccional. Como entrada para leer los registros de control (internos). Como salida controla la lectura de datos de los dispositivos periféricos. IOW (I/O write): Línea bidireccional. Como entrada escribe en los registros internos del 8237 como salida controla la escritura de datos en los dispositivos periféricos. EOP (end of process): Es una señal bidireccional que se emplea como entrada para terminar un proceso de DMA o como salida para indicar el final de una transferencia de DMA. A3 - A0 (address bus): Líneas bidireccionales. Como entrada se emplean para direccionar los registros internos en lectura o escritura. Como salida proveen los 4 bits menos significativos de la dirección DMA. 213

244 A4 - A7 (address bus): Líneas de Salida. Proveen los 4 bits más significativos de la dirección DMA. HRQ (HOLD Request): Línea de salida que se conecta a la entrada HOLD del microprocesador a fin de solicitar una transferencia de DMA. Descripción interna del controlador Figura 5.7 Diagrama de bloque interno del controlador 8237 [Brey, 2000: p.500] El controlador de DMA es realmente un circuito secuencial generador de señales de control y direcciones que permite la transferencia directa de los datos sin necesidad de registros temporales intermedios, lo que incrementa drásticamente la tasa de transferencia de datos y libera la CPU para otras tareas. Las operaciones memoria-memoria precisan de un registro temporal intermedio, por lo que son al menos dos veces más lentas que las de E/S, aunque en algunos casos aún más veloces que la propia CPU (no es el caso de los ordenadores compatibles). El 8237 consta internamente de varios bloques: un bloque de control de tiempos que genera las señales de tiempo internas y las señales de control externas; un bloque de gestión de prioridades, que resuelve los conflictos de prioridad cuando varios canales de DMA son accedidos a la vez; también posee un elevado número de registros para gestionar el funcionamiento. Los registros internos del 8237 están resumidos en la siguiente tabla. 214

245 TIPO DE REGISTRO TAMAÑO N REGISTRO Decrementor temp. word count reg. 16 bits 4 Decrementor temp address 16 bits 4 Base address 16 bits 4 Base word count 16 bits 4 Curremt address 16 bits 1 Current word count 16 bits 1 Command 8 bits 1 Status 8 bits 1 Tempory 8 bits 1 Read White mode 6 bits 4 Request 4 bits 1 Mask 4 bits 1 Funcionamiento de un DMA Tabla 5.1 Los registros internos del 8237 El movimiento de grandes volúmenes de datos entre memoria y un dispositivo de E/S requiere cierta intervención del microprocesador. El movimiento se hace a gran velocidad, y cada transferencia se inicia con una interrupción que obliga al procesador a suspender su tarea para permitir un nuevo intercambio de información. A continuación se mostrara una visión general de este proceso. Cada transferencia DMA requiere cierta preparación previa; conocer el volumen de datos a transferir (la más simple es de 1 byte) y la dirección de inicio del bufer de memoria involucrado (del que se leerán los datos o donde se escribirán). Cuando la transferencia ha concluido, el DMAC pone en nivel alto la línea T/C ("Terminal Count") en el bus de control (la cual sirve para señalar que el controlador DMA ha alcanzado el final de una transferencia) y procede a enviar al procesador la señal EOP ("End of Process"). A partir de este 215

246 momento el controlador no puede realizar otra transferencia hasta que sea programado de nuevo por la CPU. Aunque existen varios canales, el sistema de prioridades garantiza que solo uno de ellos puede estar en funcionamiento cada vez, de forma que sus funcionamientos no pueden enmascararse, y la señal EOP se refiere forzosamente al canal activo en ese momento. El sistema DMA tiene distintas formas de operación pero en el caso más simple, el proceso es el siguiente: cuando un dispositivo, que tiene asignado el canal x DMA, solicita una transferencia, activa la línea DREQx, avisando así al controlador DMAC de la petición. Al recibir la señal, el controlador DMAC comprueba que dicha línea está programada y activada, y que no existe ninguna petición en otra línea DREQ de prioridad más alta. A continuación solicita a la CPU que le ceda el control del bus enviándole una señal HRQ (Hold Request). Cuando el procesador recibe la petición HRQ, termina la instrucción en curso, y una vez que está en condiciones de ceder el control del bus, envía las señales relacionadas con su dominio (MEMR, MEMW, IOR, IOW). A continuación envía una señal HLDA (Hold Acknowledge) al DMAC por la patilla correspondiente indicándole que éste debe tomar el control. Cabe aclarar: Después de ceder el control del bus, la CPU puede seguir su proceso, pero en cuanto llega a una instrucción que necesita un dato de memoria que no esté previamente en su cache (si es un procesador moderno), debe detenerse y permanecer a la espera de ganar el control de nuevo. Esta circunstancia es totalmente transparente para el sistema operativo (y desde luego para las aplicaciones que corren sobre él). A partir de este momento, el MDAC toma control del bus y monitoriza las señales que dejó enviar la CPU; activa las líneas MEMR, MEMW, IOR e IOW. Si por ejemplo, la transferencia DMA programada consiste en transferir 1 byte a la dirección XXXXH (de memoria), el DMAC coloca la dirección en el bus de direcciones y envía una señal al dispositivo que solicitó la transferencia activando la línea DACKx correspondiente, lo que indica al dispositivo que debe depositar el dato en el bus de datos. A continuación de la señal DACKx el controlador DMA espera un ciclo y, suponiendo que el dispositivo no haya solicitado tiempo adicional mediante la señal I/O CHRDY del bus de control (esta señal sirve para avisar al procesador o al DMAC que un dispositivo lento necesita tiempo extra para estar preparado) desactiva las líneas MEMW e IOR, lo que permite que se complete la operación ("Latch"). Los datos del bus de datos son escritos en la dirección contenida en el bus de direcciones. Se supone que se trata de un dispositivo lento, por lo que desactiva la señal DREQx anunciando al DMAC que no lo necesita por el momento. El DMAC responde desactivando la señal DACKx que recuerda al dispositivo que no puede transmitir más, y comprueba si existen otras peticiones que atender. A continuación coloca en tri-estado las señales del bus y envía al procesador la señal 216

247 EOP. El procesador contesta desactivando a su vez la señal HLDA; deja de enviar las líneas de bus y continua con el proceso que venía ejecutando. La figura ilustra el funcionamiento del DMAC. Figura 5.8 Funcionamiento a nivel de hardware de un DMA 1. Zonas auxiliares de memoria, conocidas como Registros de página Los registros de direcciones del DMA son de 16 bits, mientras que los microprocesadores 80x86 pueden direccionar entre 1 MB y 4 GB de memoria. Cómo es posible que el DMA accesa a la memoria de la computadora con direcciones de 20 y 32 bits? La solución técnica soportada por los diseñadores de PC fue añadir registros externos, ubicados fuera del chip 8237 que se encargan de suministrar los bits de direcciones que faltan: estos registros internos son los llamados registros de página de DMA. Estos registros controlan las líneas más altas del bus de direcciones DMA y Se cargan a través de puertos de E/S.Existe uno por cada canal. 217

248 Los registros de página de DMA poseen 8 bits significativos que generan la parte alta de la dirección de memoria. Los restantes bits del espacio de direcciones (A24-A31 del 386) no se pueden emplear. Figura 5.9 Representación gráfica de un registro de página La figura anterior representa un registro de página. Nótese que se utilizan las líneas de A16-A23 Figura 5.10 Esquema general de la relación entre los registros de página, buffer y bus de direcciones. 218

249 Modos de transferencia DMA Aunque el mecanismo de transferencia se ajusta en lo básico al proceso anteriormente descrito, el controlador MDAC permite varios modos de operación: Modo de transferencia única (Single Transfer Mode): Este modo transfiere solo un byte cada vez. Después de cada transferencia el sistema cede el control del bus y debe adquirirlo de nuevo para transmitir el siguiente. Es utilizada por dispositivos que solo pueden transmitir 1 byte cada vez a intervalos muy largos (periféricos lentos). Por ejemplo, el primitivo controlador de disquete utilizaba este modo porque su buffer era de un byte. El ciclo solicitud adquisición del bus transferencia sesión del bus, se repite cuantas veces que sean necesarias. Modo de transferencia de bloque (Block Transfer Mode) Las transferencias se realizan en bloques (un máximo de 64 KB). Se supone que el dispositivo periférico es capaz de escribir/leer datos a una velocidad soportada, ya que una vez iniciada la transferencia, continúa hasta que se completa. En caso necesario el dispositivo periférico puede solicitar una pausa momentánea mediante la línea CHRDY del bus, pero en general, los dispositivos lentos utilizan el modo anterior. La transferencia puede interrumpirse irse si se activa la señal EOP. Modo de transferencia por demanda (Demand Transfer Mode). Se diferencia del anterior en que la transferencia se realiza sólo mientras DREQ permanece activo. Esto significa que se pueden transferir datos hasta agotar las posibilidades del dispositivo; cuando el dispositivo tenga más datos listos puede volver a activar DREQ para continuar donde lo dejó. Esta modalidad permite dejar ciclos a la CPU cuando no es realmente necesario que el DMA opere. Además, en los períodos de inactividad, los valores de dirección en curso y contador de palabras son almacenados en el Registro de direcciones en curso y en el Registro contador de palabras en curso correspondientes al canal implicado; mientras tanto, otros canales de mayor prioridad pueden ser atendidos por el Conexión en cascada de varios 8237 Esta conexión es empleada para conectar más de un 8237 en el sistema. La línea HRQ de los 8237 hijo es conectada a la DREQ del 8237 padre; la HLDA lo es a la DACK. Esto permite que las peticiones en los diversos 8237 se propaguen de uno a otro a través de la escala de prioridades del 8237 del que dependen. La estructura de prioridades es por tanto preservada. Teniendo en cuenta que el canal del 8237 padre es empleado sólo para priorizar el 8237 adicional que cuelga 219

250 (hijo), no puede emitir direcciones ni señales de control por sí mismo: esto podría causar conflictos con las salidas del canal activo en el 8237 hijo. Por tanto, el 8237 padre se limita en el canal del que cuelga el 8237 hijo a controlar DREQ, DACK y HRQ, dejando inhibidas las demás señales. El EOP externo será ignorado por el 8237 padre, pero sí tendrá efecto en el 8237 hijo correspondiente. Autoinicialización ("Autoinicialize") En esta forma las transferencias se realizan al modo Único o Demanda, pero cuando la CPU vuelve a tomar el control y el dispositivo está listo para enviar o recibir nuevos datos, no es necesario reprogramar la siguiente transferencia. Si se estaban transfiriendo datos desde el dispositivo a un buffer de memoria, la CPU puede seguir añadiendo datos al buffer a continuación de los últimos transmitidos. Si era una transferencia de de datos desde un buffer hacia el dispositivo, la CPU puede seguir leyendo datos desde la última posición de leída y escribiéndolos en el dispositivo. Esta técnica se utiliza con dispositivos que tienen buffers pequeños. Por ejemplo, dispositivos de audio. Supone cierta sobrecarga para la CPU, pero es la única forma de eliminar el retardo existente entre el momento en que termina una transferencia y se reprograma la siguiente. Poniendo el DMA a trabajar Hasta ahora se han distinguido tres tipos de direcciones de memoria: Direcciones lógicas, direcciones lineales (las cuales son utilizadas internamente por la CPU) y además direcciones físicas las cuales son direcciones de memoria utilizadas por la CPU para manejar físicamente el bus de datos. Sin embargo, hay un cuarto tipo de direcciones de memoria, llamado bus de direcciones el cual corresponde con la dirección de memoria utilizada por todos los dispositivos de hardware (excepto la CPU) para manejar el bus de datos. En la arquitectura de las computadoras, el bus de direcciones coincide con la dirección física. Por qué debería el kernel estar interesado en el bus de direcciones? Bueno, en las operaciones del DMA, la transferencia de datos toma lugar sin la intervención de la CPU: El bus de datos es directamente manejado por el dispositivo de E/S y el DMAC. Por lo tanto, cuando el kernel establece una operación al DMA, éste debe escribir en el bus de direcciones de la memoria del buffer involucrada en el puerto de E/S del DMAC o del dispositivo de E/S. El controlador DMA permite a dispositivos transferir datos hacia o desde la memoria del sistema sin la intervención directa del procesador. Como ya se mencionó anteriormente, varios dispositivos de E/S hacen uso de un Controlador de Acceso Directo a Memoria (DMAC) para acelerar operaciones. El DMAC interactúa con el 220

251 controlador (controller) de E/S del dispositivo para realizar una transferencia de datos; el kernel incluye un conjunto de rutinas sencillas para programar el DMAC. El controlador de E/S señala a la CPU, por medio de la línea IRQ, cuando la transferencia de datos ha terminado. Cuando un controlador de dispositivo establece una operación DMA para algún dispositivo de E/S, éste debe especificar la memoria del buffer involucrada por medio del bus de direcciones. El kernel provee dos macros, la virt_to_bus y bus_to_virt que se utilizan para traducir una dirección lineal en un bus de direcciones y viceversa. Al igual que con las líneas IRQ, el DMAC es un recurso que se debe asignar dinámicamente a los controladores (driver) que los necesitan. La forma en que el controlador (driver) comienza y termina las operaciones DMA depende del tipo de bus. El conflicto ocurre cuando el CPU y el DMAC necesitan accesar a la misma localidad de memoria al mismo tiempo. Este conflicto se resuelve mediante un circuito de hardware llamado memoria árbitro (memory arbiter). El DMAC es utilizado por controladores de disco y otros dispositivos de transferencia lenta los cuales necesitan transferir un número largo de bytes a la vez. DMA para bus ISA Los CD-ROM, módems, tarjetas de sonido y otros dispositivos tienen algo en común, no interactúan directamente con el CPU, en cambio, ellos son conectados por medio de un bus que es el responsable de la comunicación entre dispositivos y el CPU y también entre dispositivos individuales. Algunos buses son: Arquitectura Estandar Industrial (Industrial Standard Architecture ISA): Este tipo de bus fue diseñado por IBM PC en 1980 para conectar tarjetas de ampliación a la tarjeta madre (motherboard). Es un antiguo tipo de bus que aún se encuentra en uso. Porque es simple en términos de la electrónica. Sin embargo tiene algunos problemas: los usuarios de computadoras basadas en buses ISA tenían que disponer de información adicional sobre el hardware que se iba a conectar al sistema, había que configurar cosas como la IRQ, las direcciones de E/S o el canal DMA ( a pesar que la mayoría de tarjetas tenían la tecnología de conectar y usar (plug-and-play) que permite a un dispositivo ser conectado a una computadora sin tener que configurar nada a nivel de hardware o software (no controladores). 221

252 Interconexión de Componentes Periféricos (Periphecal component interconnect PCI): El principal sistema de buses usado en la mayoría de arquitecturas. Los dispositivos PCI se colocan en las ranuras (slots) de la tarjeta madre (motherboard). El bus PCI permite configurar de manera dinámica un dispositivo periférico. Se asignan IRQs y direcciones de puerto de forma dinámica a diferencia de los buses ISA. Las versiones modernas de buses también soportan hotplugging lo que significa que los dispositivos pueden ser conectados y desconectados en caliente mientras el sistema sigue corriendo y al ser conectados puedan ser utilizados (el código del kernel de Linux permite esto). PCI logra una máxima velocidad de transferencia de unos pocos cientos de megabytes por segundo y así cubre una amplia gama de aplicaciones. El trabajo de la investigación de PCI comenzó en los laboratorios de intel en 1900 pero fue lanzada su primera versión en 1992 bajo el nombre de EL PCI 1.0 DMAC ISA Cada DMAC ISA puede controlar un número limitado de canales. Cada canal incluye un conjunto independiente de registros internos a fin de que el DMAC puede controlar las transferencias de datos al mismo tiempo. Los controladores de dispositivos reservan y liberan el DMAC ISA. Como es usual, el controlador (driver) de dispositivo se basa en un contador de uso para detectar cuando un archivo de dispositivo no está siendo accedido por un proceso por un periodo largo de tiempo. El controlador (driver) realiza las siguientes operaciones: En el método open () de un archivo de dispositivo, incrementa el contador de uso de dispositivo. Si el valor previo es cero, el controlador (driver) realiza las operaciones siguientes: o o o o Invoca request_irq() para asignar la línea IRQ usada por el DMAC ISA Invoca request_dma() para asignar el canal DMA Notifica al dispositivo de hardware que debe utilizar el DMA y la interrupción en cuestión. Asigna, si es necesario, un área de almacenamiento para el buffer de DMA. Cuando una operación de DMA debe comenzar, realiza las siguientes operaciones en el método propio del archivo de dispositivo (comúnmente leer o escribir): 222

253 o o o o o o o Invoca el set_dma_mode () para establecer el canal en modo de lectura o escritura. Invoca el set_dma_addr () para establecer el bus de direcciones del buffer DMA (solo los 24 bits menos significativos de la dirección son enviados al DMAC, por lo que el buffer deben ser incluidos en los primeros 16 MB de RAM). Invoca el set_dma_count () para establecer el número de bytes a ser transferidos. Invoca a enable_dma () para habilitar el canal DMA. Pone el actual proceso en el dispositivo de cola de espera y lo suspende. Cuando el DMAC termina la operación de transferencia, controlador (controller) del dispositivo de E/S genera una interrupción y el correspondiente manejador de interrupciones despierta el proceso dormido. Una vez despierto, invoca el disable_dma () para deshabilitar el canal DMA. Invoca el get_dma_residue () para comprobar si todos los bytes han sido transferidos. En el método de liberación (release) de un archivo de dispositivo, se decrementa el contador de uso del dispositivo. Si éste se convierte en cero, se ejecuta las siguientes operaciones: o o o Se deshabilita el DMA y la correspondiente interrupción en el dispositivo de hardware. Se invoca free_dma() para liberar el canal DMA Se invoca free_irq () para liberar la línea de IRQ usado por el DMA. DMAC PCI Hacer uso de DMA para bus PCI es muy simple ya que el DMAC es integrado en la interfaz de E/S. Usualmente, en el método open () el controlador (driver) del dispositivo debe asignar la línea de IRQ usada para señalizar la terminación de una operación DMA. Sin embargo, éste no necesita asignar un canal DMA ya que cada dispositivo de hardware controla directamente las señales eléctricas de bus PCI. Para comenzar la operación DMA, el controlador del dispositivo simplemente escribe en algún puerto de E/S del dispositivo de hardware el bus de direcciones del buffer DMA, la dirección de transferencia, y el tamaño de los datos; el controlador entonces 223

254 suspende el proceso actual. El método de liberación release () libera la línea IRQ cuando el objeto archivo es cerrado por el ultimo proceso Asociando archivos con dispositivos de E/S Como se ha mencionado en varias ocasiones, un concepto fundamental de Linux es que todo puede ser representado como un archivo. Según este enfoque, los dispositivos de E/S son tratados como archivo y por lo tanto, las mismas llamadas al sistema que se utilizan para interactuar con archivos en el disco se pueden utilizar para interactuar directamente con dispositivos de E/S. [Bovet y Cesati, 2000: p.378] Archivos de dispositivo: Los archivos de dispositivo (device file) se utilizan para representar la mayoría de dispositivos de E/S soportados por Linux. Es un archivo especial que se define en el directorio /dev y los subdirectorios dentro de él establecen un vínculo con un controlador del dispositivo con el fin de apoyar la comunicación con dispositivos de la computadora (dispositivos reales o virtuales). Prácticamente permite abstraer y representar el dispositivo y permite a los programas de usuario acceder a los controladores de dispositivos. Además del nombre del dispositivo, existen tres atributos principales los cuales son: Tipo: Si es dispositivo de bloque o de carácter. Número mayor: Identifica el controlador con el cual está asociado el archivo de dispositivo. Es un número entre 1 y 255 que identifica el tipo de dispositivo. Usualmente, todos los dispositivos de archivos importantes que tienen el mismo número mayor y el mismo tipo comparten el mismo conjunto de operaciones de archivos ya que son manejados por el mismo controlador de dispositivo. Número menor: Un número que identifica un dispositivo específico entre un grupo de dispositivos que comparten el mismo número mayor. Es utilizado por el kernel para determinar exactamente el dispositivo que está siendo referenciado. Algunos archivos de dispositivo conocidos se ilustran en la tabla: NOMBRE TIPO NUMERO MAYOR NUMERO MENOR DESCRIPCION /dev/fd0 Bloque 2 0 Unidad de disquete. 224

255 /dev/hda Bloque 3 0 Primera particion disco duro IDE (primary master). /dev/hda2 Bloque 3 2 Segunda partición primaria del disco duro IDE (primary slave). /dev/sda Bloque 8 0 Primer disco duro SCSI. /dev/sda1 Bloque 8 1 Primera partición del primer disco SCSI. /dev/ttyp0 Caracter 3 0 Terminal. /dev/console Caracter 5 1 Consola. /dev/lp1 Caracter 6 1 Impresora paralelo. /dev/ttys0 Caracter 4 64 Primer puerto serial. /dev/rtc Caracter Reloj en tiempo real. /dev/null Character 1 3 Dispositivo nulo. Tabla 5.2 Algunos archivos de dispositivo Para consultar estos archivos de dispositivo, basta con digitar desde consol:a $ ls l /dev/(archivo de dispositivo) Algunos de los archivos de dispositivos consultados, se muestran en la figura 5.11: Figura 5.11 Archivos de dispositivos en Linux Nótese que el mismo número mayor puede ser usado para identificar dispositivos de carácter y de bloque. 225

256 Los archivos de dispositivo se mapean en los correspondientes dispositivos a través de números de dispositivo mayor y menor ("major and minor device numbers") Usualmente, un archivo de dispositivo es asociado con un dispositivo de hardware como un disco duro o con alguna parte física o lógica de un dispositivo de hardware, como una partición; sin embargo, en algunos casos, un archivo de dispositivo no es asociado con ningún dispositivo de hardware real, pero representa un dispositivo lógico ficticio, por ejemplo /dev/null es un archivo de dispositivo que corresponde con un hoyo negro todos los datos escritos dentro de el simplemente son descartados y el archivo aparece siempre vacío Dispositivos de bloque VS dispositivos de carácter Los dispositivos de bloque y de carácter se describieron en el capítulo llamado archivos en Linux de este documento. Los dispositivos de bloque tienen las siguientes características: Son capaces de transferir un bloque de datos de tamaño fijo en una sola operación de E/S. Los bloques guardados en estos dispositivos pueden ser direccionados de manera aleatoria. El tiempo necesario para transferir un bloque de datos puede ser independiente de la dirección dentro del bloque.ejemplos de estos dispositivos son discos duros, CD- ROM. Dispositivos de carácter tienen las siguientes características: Son capaces de transferir datos de tamaño arbitrario en una sola operación de E/S. En realidad, algunos dispositivos de carácter como impresoras, transfieren un byte a la vez, mientras otros como unidades de cinta, transfieren bloques de longitud variable de datos. Usualmente, tienen direcciones de caracteres secuencialmente. Cómo el VFS maneja archivos de dispositivo? Los archivos de dispositivo se encuentran en el árbol del sistema de directorios pero son intrínsecamente diferentes a los archivos regulares y directorios. Cuando un proceso accesa a un archivo regular, éste accesa a algunos bloques de datos en alguna partición del disco a través de un sistema de archivos; pero cuando un proceso accede a un archivo de dispositivo, éste es manejado por un dispositivo de hardware. 226

257 Por ejemplo, un proceso puede acceder a un archivo de dispositivo para leer la temperatura de un termómetro digital conectado a la computadora. El VFS es el responsable de esconder las diferencias entre archivos de dispositivo y archivos regulares de los programas de aplicación. Para poder hacer esto, el VFS cambia las operaciones por defecto de archivos de un archivo abierto; como resultado, cualquier llamada al sistema en un archivo de dispositivo puede ser traducido como una invocación de una función relacionada con el dispositivo en vez de una función correspondiente del sistema de archivos principal. La función relacionada con el dispositivo actúa en el dispositivo de hardware para mejorar las operaciones requeridas por los procesos. Cuando un programa realiza una operación sobre un archivo de dispositivo, el kernel intercepta la referencia y busca la función adecuada y le transfiere el control. El conjunto de funciones relacionadas con dispositivos que controlan dispositivos de E/S son llamadas controladores (drivers) de dispositivos y estos controladores pueden ser de caracteres o de bloques. Ya que cada dispositivo tiene un controlador de E/S único, también tiene sus propios comandos y sus propios estados de información. Figura 5.12 Modelo de capa de direccionamiento de periféricos. [Wolfgang, 2008: p.392] La comunicación entre los programas de aplicación se muestra en la figura Los programas acceden a un archivo de dispositivo el cual interactúa con la capa de abstracción, la cual a su vez implementa las funciones necesarias para interactuar con el controlador (driver) asociado con cada dispositivo de hardware y realizar la acción requerida (escritura, lectura, apertura). 227

258 5.3. Controlador de dispositivo (device driver) Como se ha visto, el VFS usa un conjunto de funciones (open, read, lseek, etc) para controlar el dispositivo. La actual implementación de todas estas funciones es delegada al controlador (driver) de dispositivo. Ya que cada dispositivo tiene un único controlador de E/S (I/O controller) y por lo tanto sus propios comandos y estados de información, más dispositivos de E/S tienen sus propios controladores. El controlador de dispositivo es un programa que se agrega al kernel el cual le permite al sistema operativo gestionar e interactuar con un dispositivo de hardware haciendo una abstracción del hardware y provee una interfaz para poder utilizar el dispositivo Nivel de soporte de kernel El kernel soporta acceso a dispositivos de hardware en tres posibles formas: No soporta a todos. Los programas de aplicación interactúan directamente con el dispositivo a través de puertos E/S mediante instrucciones de lenguaje ensamblador IN y OUT. Soporte mínimo. El kernel no reconoce el dispositivo de hardware, solamente reconoce su interfaz de E/S. Programas de usuarios son capaces de tratar la interfaz como un dispositivo secuencial capaz de leer y/o escribir secuencia de caracteres. Soporte extendido. El kernel reconoce el dispositivo de hardware y maneja la interfaz de E/S en sí, (que de hecho podría incluso no ser un archivo de dispositivo para el dispositivo). El común ejemplo del primer enfoque no tiene que ver con el controlador (driver) del dispositivo del kernel, sino como el sistema operativo Windows maneja la pantalla gráfica. Este enfoque es muy eficiente. Si bien restringe el servidor X de hacer uso de las interrupciones de hardware utilizadas por dispositivos de E/S este enfoque también requiere algún esfuerzo adicional a fin de permitir que el servidor X pueda acceder al puerto de E/S. El soporte mínimo es usado para manejar los dispositivos de hardware externos conectados a la interfaz de E/S de propósito general. El kernel se encarga de la interfaz de E/S ofreciendo un archivo de dispositivo y por tanto un controlador (driver) de dispositivo; el programa de aplicación maneja el dispositivo de hardware externo leyendo y escribiendo el archivo de dispositivo. El soporte mínimo es preferible al soporte extendido porque mantiene el kernel de tamaño pequeño, sin embargo, en medio de las interfaces de E/S de propósito general que comúnmente 228

259 se encuentran en una PC, solamente el puerto serial es manejado por este enfoque. Por tanto, un ratón serial es directamente controlado por un programa de aplicación. El soporte mínimo tiene un rango limitado de aplicaciones debido a que no es utilizado cuando un dispositivo externo debe interactuar fuertemente con una estructura de datos interna del kernel. Como ejemplo, considere un disco duro removible que es conectado a una interfaz de E/S de propósito general. Un programa de aplicación no puede interactuar con todas las estructuras de datos del kernel y funciones necesarias para reconocer el disco y montar su sistema de archivos. Por lo que el soporte extendido es obligatorio en este caso. En general, cualquier dispositivo de hardware directamente conectado a un bus de E/S, como por ejemplo un disco duro interno, es manejado de acuerdo con el enfoque de soporte extendido. El kernel debe proveer un controlador (driver) de dispositivo por cada dispositivo. Dispositivos externos junto con puerto paralelo, el bus serial universal USB (Universal Serial Bus) el puerto PCMCIA se encuentra en muchas computadoras portátiles, o cualquier interfaz de E/S de propósito general exceptuando el puerto serial, requiere también soporte extendido Monitoreando operaciones de E/S La duración de una operación de E/S es frecuentemente impredecible. Esta puede depender de consideraciones mecánicas (la actual posición de la cabeza del disco con respecto al bloque que va a ser transferido), o de eventos aleatorios (cuando un paquete de datos llega a la tarjeta de red) o por factores humanos (cuando un usuario presiona una tecla del teclado o cuando hay atasco de papel en una impresora); en cada caso, el controlador (driver) de dispositivo que comienza con la operación de E/S debe contar con una técnica de monitoreo de señales que indique el final de una operación de E/S o si el tiempo ha finalizado. En caso de que la operación termine, el controlador (driver) de dispositivo lee el registro de estado de la interfaz de E/S para determinar si la operación de E/S fue llevada con éxito. En el caso que el tiempo termine, el controlador sabe qué fue lo que salió mal ya que que intervalo de tiempo máximo permitido para completar la operación ha transcurrido y nada sucede. Las dos técnicas permitidas para este monitoreo son: Modo polling. Según esta técnica, la CPU comprueba el registro de estado de los dispositivos repetidamente hasta que su valor diga que la operación de E/S ha sido completada. Mode de interrupciones. Puede ser utilizado si el controlador (controller) de E/S es capaz de señalar, por medio de la línea IRQ, el final de una operación de E/S. El controlador 229

260 (driver) del dispositivo comienza la operación de E/S e invoca interruptible_sleep_on () o sleep_on () pasando como parámetro un puntero al dispositivo de E/S que espera en la cola.cuando una interrupción ocurre, el manejador de interrupciones invoca a wake_up () para despertar todos los procesos dormidos en el dispositivo que espera en la cola.el controlador de dispositivo que se despierta puede comprobar el resultado de la operación de E/S Solicitando un IRQ La asignación de un IRQ a dispositivos generalmente se hace dinámicamente, justo antes de usarlos, ya que varios dispositivos pueden compartir la misma línea IRQ. Para asegurarse que la IRQ es obtenida cuando se necesita pero no es requerida de manera redundante si ya está actualmente en uso, los controladores (driver) de dispositivos usualmente adoptan el siguiente esquema: Un contador de uso que mantiene la pista del número de procesos que actualmente tienen acceso al archivo de dispositivo. El contador es incrementado en el método open del archivo del dispositivo y decrementa por el método de liberación (release). Mas presiso, el contador de uso mantiene la pista del número de objeto archivo (file object) que se refieren al archivo de dispositivo ya que los procesos clonados pueden compartir el mismo objeto archivo. El método open comprueba el valor del contador de uso antes de ser incrementado. Si el contador es nulo, el controlador de dispositivo debe asignar el IRQ y permitir interrupciones en un dispositivo de hardware. Por lo tanto, este invoca a request_irq () y configura el controlador de E/S correctamente. El método de liberación (release) comprueba el valor del contador de uso después de cada decremento. Si el contador es nulo, significa que no más procesos son utilizados por el dispositivo de hardware. Si es asi, el método invoca a free_irq () libera la línea IRQ y deshabilita interrupciones en el controlador de E/S El controlador de dispositivo de Memoria Local Varios dispositivos de hardware incluyen su propia memoria, la cual es llamada memoria compartida de E/S. Por ejemplo, todas las tarjetas gráficas recienten incluyen megabytes de RAM llamada marco de buffer (frame buffer) la cual es usada para guardar la imagen de la pantalla que se mostrará en el monitor. 230

261 Mapeando direcciones Dependiendo del dispositivo y del tipo de bus, la memoria compartida de E/S en la arquitectura de las PC puede ser mapeada en tres diferentes rangos de direcciones físicas: Para la mayoría de dispositivos conectados al bus ISA. La memoria compartida de E/S es usualmente mapeada dentro de direcciones físicas en el rango de 0xa0000 a 0xfffff; esto da lugar a un espacio entre 640 KB y 1 MB. Para algunos dispositivos viejos que usan bus local VESA (VLB). Este es un bus especializado principalmente usado por tarjetas gráficas: la memoria compartida de E/S es mapeada dentro de direcciones físicas en un rango de 0xe00000 a 0xffffff que es entre 14 MB y 16 MB. Estos dispositivos que complican mucho la inicialización de las tablas de paginación, están fuera de producción. Para dispositivos conectados a un bus PCI La memoria compartida de E/S es mapeada dentro de una dirección física muy grande, por encima del final de las direcciones de memoria RAM físicas. Este tipo de dispositivo es mucho más sencillo de manejar. Accediendo a la memoria compartida de E/S Cómo accesa el kernel a la memoria compartida de E/S? se debe comenzar por la arquitectura de la PC la cual es relativamente sencilla de manejar y después se hará una extensión a otras arquitecturas. Se debe recordar que los programas del kernel actúan en direcciones lineales, entonces la memoria compartida de E/S debe expresarse como direcciones superiores a PAGE_OFFSET que es igual a 0xc lo que significa que las direcciones lineales del kernel son de 4 GB. Los controladores (driver) del kernel deben traducir direcciones físicas de la memoria compartida de E/S en direcciones lineales en el espacio del kernel. En la arquitectura de la PC, esto puede lograrse por ORing la dirección física de 32 bits con una constante 0xc Durante la fase de inicialización, el kernel mapea la direcciones físicas de RAM disponible en una porción inicial de 4 GB del espacio de direcciones lineal. Por tanto, la unidad de paginación mapea 0xc00b0fe4 direcciones lineales que aparecen en el primera declaración de la dirección física de E/S 0xc00b0fe4. 231

262 Esto predice un problema, para la segunda declaración porque la dirección física de E/S es superior a la última dirección física del sistema de la memoria RAM. Por tanto, la dirección lineal 0xfc no necesariamente corresponde con la dirección física 0xfc Como en algunos casos las tablas de página del kernel deben ser modificadas para incluir una dirección lineal que mapea direcciones físicas de E/S: Esto es posible por medio de la invocación de la función ioremap (). Esta función, invoca a get_vm_area () para crear un nuevo descriptor vm_struct (capítulo de administración de memoria) para intervalos de direcciones lineales que tienen el mismo tamaño de área de memoria compartida de E/S requerida. La función ioremap () entonces actualiza la correspondiente tabla de pagina de todos los procesos. Ahora, agregar a la dirección física de E/S constante 0xc para obtener la correspondiente dirección lineal no siempre funciona. Linux incluye algunas macros para acceder a la memoria compartida de E/S: readb, readw, readl: Lee 1, 2, 4 bytes (respectivamente) de la memoria compartida de E/S. writeb, writew, writel Escribe 1, 2, 4 bytes (respectivamente) en la memoria compartida de E/S. memcpy_fromio, memcpy_toio Copia un bloque de datos de una memoria compartida de E/S a una memoria dinámica y viceversa. memset_io Llena la memoria compartida de E/S con valores fijos Controladores de dispositivos de carácter Manejando dispositivos de carácter. El manejo de un dispositivo de carácter es relativamente sencillo ya que no se necesita un buffer de datos y la cache de disco no está involucrada. Por supuesto, los dispositivos de carácter tienen diferentes requerimientos: algunos de ellos deben implementar un protocolo de comunicación sofisticado para manejar el dispositivo de hardware. Por ejemplo, el controlador (driver) de dispositivo de tarjeta multipuertos serial es mucho más complicado que el controlador (driver) de un dispositivo de un ratón. 232

263 Un controlador de un dispositivo de carácter es descrito por la estructura cdev la cual se encuentra en /usr/src/linux-source /include/linux/cdev.h struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }; Donde: kobj: Es un objeto del kernel embebido en la estructura.es utilizado para la administración de la estructura de datos. *owner: Apunta al modulo (si lo hay) que provee el controlador. *ops: Apunta a la lista de operaciones de archivos que implementa operaciones específicas para comunicarse con el hardware. list: Permite la implementación de una lista de todos los inodos que representan archivos de dispositivos de cada dispositivo. dev: Especifica el número del dispositivo. count: Denota cuántos números menores están asociados con el dispositivo. Los controladores de dispositivos de caracter trabajan con operaciones simples y concretas sobre dispositivos; por ello, cada controlador se encarga de implementar sus operaciones de forma directa y no existen, como en los dispositivos de bloque, operaciones genéricas de lectura y escritura que deben traducirse a cada controlador. Para conocer los dispositivos que están actualmente en uso, el kernel utiliza una tabla hash que es indexada por el número mayor. El vector de esta tabla hash llamado chrdevs es representado por 233

264 la estructura de datos char_device_struct que se encuentra en /usr/src/linux-source /fs/inode.c /char_dev.c La estructura de datos es: static struct char_device_struct { struct char_device_struct *next; unsigned int major; unsigned int baseminor; int minorct; char name[64]; struct file_operations *fops; struct cdev *cdev; /* will die */ } *chrdevs[chrdev_major_hash_size]; //tabla hash Esta tabla hash incluye 255 elementos, uno por cada número mayor posible. El valor de 255 se encuentra definido en la constante CHRDEV_MAJOR_HASH_SIZE La tabla chrdevs esta inicialmente vacía. La función register_chrdev () es utilizada para insertar una nueva entrada dentro de la tabla, es decir, habilita un determinado controlador en la lista de controladores de dispositivos de carácter. La funcion unregister_bldev () deshabilita un determinado controlador en la lista de controladores de dispositivos de carácter Controladores de dispositivos de bloque La estructura gendisk que se encuentra en /usr/src/linux-source /include/linux/genhd.h se utiliza para gestionar las particiones de dispositivos de bloques y juega un rol similar al de la estructura cdev para dispositivos de carácter. Esto es razonable ya que un dispositivo de bloque sin particiones puede ser visto como un dispositivo de bloque de una sola partición. A continuación se muestra solamente un conjunto de los campos de la estructura: 234

265 struct gendisk { int major; int first_minor; int minors; char disk_name[32]; struct hd_struct **part; int part_uevent_suppress; struct block_device_operations *fops; struct request_queue *queue; sector_t capacity; struct kobject kobj;. }; Donde: Mayor: Representa el número mayor del dispositivo. First_minor y minors: Indica el rango de números menores que pueden ser localizados. Disk_name: Nombre del disco. Part: Es un arreglo que apunta a hd_struct, cuya definición se da a continuación. Es una entrada por cada partición de disco. Fops: Es un puntero a funciones específicas de dispositivos que realizan varias tareas de bajo nivel. Queue: Es necesario para manejar colas de petición. Capacity: Especifica la capacidad del disco en sectores. Kobj: Es una instancia a kobject de cada objeto del kernel. 235

266 Estructura hd_struct Por cada partición, existe una instancia de la estructura hd_struct (que se encuentra en /usr/src/linux-source /include/linux/genhd.h) para describir los principales datos de la partición dentro del dispositivo. Los campos más importantes son: struct hd_struct { sector_t start_sect;/* Sector de inicio */ sector_t nr_sects; /* Tamaño de la partición en el dispositivo de bloque*/ struct kobject kobj; }; Versiones anteriores del kernel 2.6 utilizaban la funcion register_blkdev() para registrar dispositivos de bloque; sin embargo, hoy en dia, no se necesita llamar a esta función (aunque es posible) ya que los dispositivos de bloque aparecen en /proc/devices Elementos de archivos de dispositivos en inodos. Asociando con el sistema de archivos. Con algunas pocas excepciones, los archivos de dispositivo son manejados por funciones estándar en la misma forma que los archivos regulares. Estas funciones a su vez, son manejadas por el Sistema de Archivos Virtual discutido anteriormente. Cada archivo en el VFS es asociado con un inodo que maneja las propiedades del archivo. Para identificar únicamente que el dispositivo se asocia con un archivo de dispositivo, el kernel guarda el tipo de archivo (bloque o carácter) en el campo i_mode de la estructura inodo y el número mayor y menor en i_rdev. Si el inodo representa un dispositivo de carácter, el campo i_cdev apunta a información del dispositivo de carácter; si el inodo representa un dispositivo de bloque, i_bdev apunta a la información de un dispositivo de bloque. El campo i_fop es una colección de las operaciones de archivo (file operations) tales como abrir (open), leer (read) y escribir (write) utilizadas por el sistema de archivos para trabajar con dispositivos de bloque. 236

267 Operaciones de archivos estándar. Cuando un archivo de dispositivo es abierto, varios sistemas de archivos invocan la función init_special_inode para crear el inodo de un dispositivo de carácter o dispositivo de bloque. La función se encuentra en fs/inode.c y se define de la siguiente forma: fs/inode.c void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev) { inode->i_mode = mode; if (S_ISCHR(mode)) { inode->i_fop = &def_chr_fops; inode->i_rdev = rdev; } else if (S_ISBLK(mode)) { inode->i_fop = &def_blk_fops; inode->i_rdev = rdev; } else printk(kern_debug "init_special_inode: bogus i_mode (%o)\n",mode); } Operaciones estándar para archivos de dispositivo de caracter El kernel al principio no puede proporcionar más que una operación a cada archivo de dispositivo ya que cada uno requiere personalizar un conjunto de operaciones separadas. fs/char_dev.c struct file_operations def_chr_fops = {. open = chrdev_open, 237

268 }; Operaciones estándar para archivos de dispositivos de bloque Los dispositivos de bloque proveen un conjunto más uniforme de operaciones que le permite al kernel seleccionar operaciones directamente desde el principio. Estas operaciones son agrupadas en la estructura llamada def_blk_fops que se encuentra en fs/block_dev.c const struct file_operations def_blk_fops = {.open = blkdev_open,.release = blkdev_close,.llseek = block_llseek,.read = do_sync_read,.write = do_sync_write,.aio_read = generic_file_aio_read,.aio_write = generic_file_aio_write_nolock,.mmap = generic_file_mmap,.fsync = block_fsync,.unlocked_ioctl = block_ioctl,.splice_read = generic_file_splice_read,.splice_write = generic_file_splice_write, }; En contraste con los dispositivos de carácter, los dispositivos de bloque no son descritos completamente por sus mencionadas estructuras de datos, ya que el acceso a dispositivos de bloques no es realizada en respuesta a cada petición individual porque es manejado por un complejo sistema de caches y listas de peticiones de las cuales se habla más adelante.las caches son operadas por código del kernel pero las listas de peticiones son manejadas por la capa de dispositivos de bloque. [Mauerer, 2008: p.408] 238

269 Representando archivos de caracter. Abriendo un archivo de dispositivo. Figura 5.13 Diagrama de flujo de código de chrdev_open [Wolfgang, 2008: p.409] Chrdev_open de fs/char_dev.c es una función genérica para abrir dispositivos de carácter. El kernel accesa a las operaciones de archivo (file_operations) por medio de cdev->ops. Varias conexiones entre estructura de datos se establecen como se muestra a continuación: Figura 5.14 Acceso de la estructura inodo a la estructura cdev [Wolfgang, 2008: p.410] La estructura inode-> i_cdev apunta al la instancia cdev. El inodo es agregado a la lista cdev->list (i_devices es utilizado como lista de inodos de archivos de dispositivo). File->f_ops es seteado para apuntar a la instancia de la estructura file_operations. Estas funciones hacen básicamente tres operaciones: Obtiene el número mayor del controlador de dispositivo (device driver) del campo i_rdev del objeto inodo. Major= MAJOR (inode->i_rdev); Instala la operación de archivo del archivo de dispositivo. Filp->f-op=chrdevs [major].fops; 239

270 Invoca el método open de la tabla de operaciones de archivo. if (filp->f_op!= NULL && filp->f_op->open!= NULL) return filp->f_op->open(inode, filp); Manejando dispositivos de bloque Dispositivos de bloque típicos como disco duro tienen un alto promedio de tiempo de acceso. Cada operación requiere varios milisegundos para ser completada, principalmente porque el controlador del disco duro debe mover la cabeza en la superficie del disco para llegar a la posición exacta en donde se registran los datos. Sin embargo, cuando las cabezas están colocadas correctamente, la transferencia de datos puede considerar decenas de megabytes por segundo. Para lograr una mejora considerable, los discos duros y dispositivos similares transfieren varios bytes adyacentes a la vez; grupos de bytes son adyacentes cuando ellos son registrados sobre la superficie del disco de tal forma que una simple operación de búsqueda puede accesar a ellos. La organización de los manejadores de dispositivos de bloques es complicada. En este capítulo no se va a discutir en detalle todas las funciones involucradas en el kernel para apoyo a estos manejadores, pero se va a tratar de esbozar la arquitectura de software y de introducir las principales estructuras de datos que se utilizan. El kernel soporta manejadores de dispositivos de bloques que incluyen las siguientes características: Ofrecer una interfaz uniforme a través de la VFS. Aplicar de manera eficiente la lectura anticipada de datos en disco. Proporcionar almacenamiento en cache de disco para los datos. El kernel básicamente distingue dos tipos de transferencia de datos de E/S: Operaciones de E/S de buffer. Los datos transferidos se mantienen en los buffers. Cada buffer está asociado con un bloque específico identificado por un número de dispositivo y de bloque. Linux llama estas operaciones como operaciones de E/S síncronas el término síncronas no es el más apropiado en este contexto ya que la operación de E/S de buffer es realmente asíncrona; en otras palabras, la ruta del control del kernel que inicializa la operación puede continuar con su ejecución, sin esperar a que la operación termine. El término es probablemente heredado de versiones anteriores de Linux. 240

271 Operaciones de E/S de página. Los datos transferidos se mantienen en marcos de página. Cada marco de página contiene datos que pertenecen a un archivo regular. Ya que estos datos no están necesariamente guardados en bloques de disco contiguos, este archivo es identificado por el inodo del archivo y por el desplazamiento dentro del archivo. Linux llama de manera indebida estas operaciones como operaciones de E/S asíncronas. Las operaciones de E/S de buffer son más utilizadas ya sea cuando un proceso lee directamente de un archivo de dispositivo de bloque o cuando el kernel lee un particular tipo de bloque en un sistema de archivos (como un bloque que contiene inodos o superbloques). Las operaciones de E/S de página se utilizan principalmente para leer archivos regulares, archivos mapeados en memoria e intercambio (swapping). Ambos tipos de transferencia de datos de E/S se basan en el mismo controlador para acceder a dispositivos de bloque, pero el kernel usa diferentes algoritmos y técnicas de buffering (almacenamiento) con ellos. En este trabajo de graduación, no se describen las operaciones de E/S de página, solamente se explica las operaciones de E/S de buffer Operaciones de E/S de buffer Conceptos importantes Sectores, bloques y buffer Cada operación de transferencia de datos de un dispositivo de bloque actúa en un grupo de bytes adyacentes llamado sector. En la mayoría de los dispositivos de disco, el tamaño de un sector es de 512 bytes, aunque algunos dispositivos más recientes hacen uso de grandes sectores (1024 y 2048 bytes). Nótese que el sector viene a considerarse como la unidad básica de transferencia de datos ya que no es posible transferir menos de un sector aunque la mayoría de dispositivos de disco son capaces de transferir varios sectores adyacentes a la vez. El kernel guarda el tamaño del sector físico con el cual el dispositivo de bloque opera en una tabla llamada hardsect_size. Cada elemento en la tabla es indexada por medio del número mayor y el número menor del correspondiente archivo de dispositivo de bloque. Por ejemplo, hardsect_size [3][2] representa el tamaño del sector de /dev/hda2, la partición primaria secundaria del primer disco IDE. Si hardsect_size [M] es nulo, todos los dispositivos de bloque que comparten el número mayor M tienen un sector estándar de 512 bytes. 241

272 Los controladores de dispositivos de bloque transfieren un número grande de bytes adyacentes llamados bloque en una sola operación. El bloque no se debe confundir con el sector; el sector es la unidad básica de transferencia de datos de un dispositivo de hardware, mientras que el bloque es simplemente un grupo de bytes adyacentes involucrados en una operación de E/S requerida por un controlador de un dispositivo. En Linux, el tamaño del bloque debe ser una potencia de dos y no puede ser más grande que un marco de página. Además, éste debe ser un múltiplo del tamaño del sector, ya que cada bloque debe incluir un número integral de sectores. Por lo tanto, sobre la arquitectura de PC los tamaños de bloques permitidos son 512, 1024,2048 y 4096 bytes. El mismo controlador de dispositivos de bloque puede operar con varios tamaños de bloque de archivos de dispositivo ya que el maneja un conjunto de archivos de dispositivo que comparten el mismo número mayor, mientras que cada archivo de dispositivo de bloque tiene su propio bloque de tamaño predefinido. Por ejemplo, un controlador de dispositivo de bloque puede manejar un disco duro con dos particiones que contengan un sistema de archivos EXT2 y un área de intercambio (swap). En este caso, el controlador de dispositivo hace uso de dos bloques de distinto tamaño: 1024 bytes para la partición EXT2 (ya que 1024 es el tamaño estándar de bloque aunque se le permite otros tamaños de bloque) y 4096 bytes para la partición de intercambio. El kernel guarda el tamaño de bloque en una tabla llamada blksize_size; cada elemento de la tabla es indexado por el número mayor y el número menor del correspondiente archivo de dispositivo de bloque. Si el blksize_size [M] es nulo, todos los dispositivos de bloque que comparten el número mayor M tienen un tamaño estándar de bloque de 1024 bytes. Cada bloque requiere de su propio buffer, que es un área de memoria RAM usada por el kernel para almacenar el contenido de bloques. Cuando un controlador de dispositivo lee un bloque de disco, éste llena el correspondiente buffer con los valores obtenidos desde el dispositivo de hardware, al igual que cuando un controlador de dispositivo escribe un bloque en disco se actualiza el correspondiente grupo de bytes adyacentes en el dispositivo de hardware con el actual valor del buffer asociado. El tamaño del buffer siempre coincide con el tamaño del bloque correspondiente. La figura 5.15 la arquitectura de un controlador de dispositivo de bloque y los principales componentes que interactúan con él cuando se realiza una operación de E/S de buffer 242

273 Panorama de una operación de E/S de buffer Figura 5.15 manejador de dispositivos de bloque para una arquitectura de operaciones de E/S de buffer. [Bovet y Cesati, 2000: p.395] Un controlador (driver) de un dispositivo de bloque es usualmente dividido en dos partes: manejador de dispositivo de alto nivel que es la interfaz con la capa de VFS, y un manejador de dispositivo de bajo nivel el cual maneja el dispositivo de hardware. Suponga que un proceso realiza una llamada al sistema de lectura read () o escritura write () en un archivo de dispositivo. La VFS ejecuta el método read o write del correspondiente objeto archivo y también invoca un procedimiento dentro de un manejador de un dispositivo de bloque de alto nivel. Este procedimiento lleva a cabo todas las acciones relacionadas con la solicitud de lectura y escritura que son especificados en el dispositivo de hardware. El kernel ofrece dos funciones generales llamadas block_read () y block_write () que se ocupa de casi todo como se documenta más adelante en este capítulo. Por lo tanto, en la mayoría de los casos, el manejador de dispositivos de hardware de alto nivel no debe realizar ninguna acción y los métodos de lectura y escritura de los archivos de dispositivos apuntan, a block_read () y block_write (). Sin embargo, algunos manejadores de dispositivos de bloques requieren su propio controlador (driver) de dispositivo de alto nivel personalizado. Un ejemplo significativo es el controlador (driver) de dispositivo de la disquetera; éste debe comprobar que el disco en la unidad no ha sido modificado por el usuario desde el ultimo acceso al disquete; si un disquete nuevo ha sido insertado, el controlador del dispositivo debe invalidar todos los buffer llenos con los datos de el anterior disquete. 243

274 Cuando un manejador de dispositivo de alto nivel incluye su propio método de lectura y escritura, usualmente terminan invocando a block_read () y block_write (). Estas funciones traducen la solicitud de acceso que implica un archivo de dispositivo de E/S en una petición de algunos bloques del correspondiente dispositivo de hardware. Como se verá más adelante en este capítulo, los bloques requeridos pueden ya estar en memoria principal, por lo que block_read() y block_write() invoca la función getblk() para comprobar la cache primero en el caso que el bloque ha cambiado o se ha mantenido sin cambios desde el ultimo acceso. Si el bloque no está en cache, getblk() debe proceder a la solicitud de disco mediante la invocación de ll_rw_blk(). Esta última función activa el manejador de bajo nivel que maneja el controlador (controller) de dispositivo para realizar la operación solicitada en el dispositivo de bloque. Las operaciones de E/S de buffer también se activan cuando la VFS accede a algunos bloques de un dispositivo de bloque directamente. Por ejemplo, si el kernel debe leer un inodo desde un sistema de archivos en disco, éste debe transferir el dato desde bloques de la correspondiente partición de disco. El acceso directo a bloques específicos son realizados por funciones bread () y breada () las cuales a su vez invocan a las funciones getblk() y ll_rw_blk() Ya que los dispositivos de bloque son lentos, el buffer de E/S transfiere datos de manera asíncrona: el manejador de dispositivos de bajo nivel programa la DMAC y el controlador (controller) de disco y entonces termina. Cuando la transferencia se completa, una interrupción se lleva a cabo y el manejador de dispositivo de bajo nivel es activado por segunda vez para limpiar las estructuras de datos involucradas en las operaciones de E/S. De este modo, ninguna ruta de control del kernel debe ser suspendida hasta que la transferencia de datos sea completada (a no ser que la ruta de control del kernel explícitamente tenga que esperar por algún bloque de datos). El papel de la lectura anticipada. Muchos accesos a disco son secuenciales. Como se mostró en el capítulo de sistema de archivos EXT2, los archivos se almacenan en el disco en grupos grandes de sectores adyacentes a fin de que se puedan recuperar rápidamente con pocos movimientos en la cabeza del disco. Cuando un programa lee o copia un archivo, usualmente se hace de forma secuencial desde el primer byte hasta el último. Por tanto, sectores contiguos sobre el disco probablemente son traídos en varias operaciones de E/S. La lectura anticipada es una técnica que consiste en leer varios bloques adyacentes de un dispositivo de bloque por adelantado, antes de que ellos en realidad sean solicitados. En muchos casos, la lectura anticipada mejora considerablemente el funcionamiento del disco ya que permite que el controlador de disco maneje menos comandos que se refieren a los grandes grupos de sectores adyacentes. Por tanto mejora el tiempo de respuesta del sistema. Un proceso que lee 244

275 secuencialmente dispositivos de bloques puede obtener los datos solicitados más rápidamente porque el controlador (driver) realiza un menor acceso a disco. Sin embargo, la lectura anticipada no se usa para accesos aleatorios a dispositivos de bloque; en este caso, es realmente perjudicial ya que tiende a perder espacio en la cache de disco con información que no es útil. Por lo tanto, el kernel deja de hacer lectura anticipada cuando determina que los más recientes accesos de E/S no son secuenciales como el anterior. El campo f_ra del objeto archivo es una bandera que se setea cuando la lectura anticipada esta habilitada por el correspondiente archivo (o archivo de dispositivo de bloque) y se limpia en caso contrario. El kernel guarda en una tabla llamada read_ahead el número de bytes a ser leídos en promedio (por adelantado) cuando un archivo de dispositivo comienza a ser leído secuencialmente; si el valor es cero especifica un número por defecto de 8 sectores de 512 bytes esto es 4 KB. Todos los archivos de dispositivos de bloque que tienen el mismo número mayor comparten el mismo número de sectores predefinido de 512 bytes para ser leídos en promedio (por adelantado); por tanto, cada elemento de read_ahead es indexado por medio de dispositivo con número mayor. Funciones involucradas en las operaciones de E/S de buffer Las funciones de block_read() y block_write() Las funciones block_read () y block_write () son invocadas por manejadores de dispositivos de alto nivel cada vez que un proceso emite una operación de lectura y escritura en un archivo de dispositivo. Las funciones reciben los siguientes parámetros: Filp: Dirección del objeto archivo asociado con el archivo de dispositivo. Buf: Dirección del área de memoria en el espacio de direcciones de modo usuario. block_read () escribe datos traídos del dispositivo de bloque en esta área de memoria; de forma inversa, block_write () lee los datos que van a ser escritos en el dispositivo de bloque desde esta área de memoria. Count: Número de bytes a ser transferidos. Ppos: Dirección de una variable que contiene un desplazamiento en el archivo de dispositivo; usualmente, este parámetro (de tipo struct file) apunta a filp-> f_pos es decir, al puntero del archivo del archivo de dispositivo. La función block_read () realiza las siguientes operaciones: 1. Obtiene el número mayor y el número menor de un dispositivo de bloque por medio de 245

276 i_rdev. 2. Obtiene el tamaño del bloque de un archivo de dispositivo. 3. Calcula a partir de *ppos y del tamaño del bloque el número secuencial del primer bloque a ser leído por el dispositivo. También calcula el desplazamiento del primer byte a ser leído dentro del bloque. 4. Obtiene el tamaño del hardware del dispositivo de bloque. Este valor es guardado en una tabla llamada blk_size. Cada elemento es indexado por el número mayor o el número menor del correspondiente archivo de dispositivo y representa el tamaño de un dispositivo de bloque en unidades de 1024 bytes. Si es necesario, modifica el contador (count) para prevenir cualquier operación de lectura más allá del fin del dispositivo. 5. Obtiene el número de bloques a ser leídos desde dispositivos a partir de la combinación con el contador (count), el tamaño del bloque y el desplazamiento dentro del primer bloque. Si f_ra es establecido (seteado); también toma en consideración el número de bloques a ser leídos de forma anticipada, la cual es especificada en la tabla read_ahead. 6. Para cualquier bloque a ser leído, se realizan las siguientes operaciones: a. Se busca el bloque en la buffer cache usando la función getblk().si este no se encuentra, un nuevo buffer es asignado y se inserta en la cache. b. Si el buffer no contiene datos válidos (por ejemplo, porque acaba de ser asignado) comienza con la operación de lectura utilizando la función ll_rw_blk () y suspende el actual proceso hasta que los datos han sido transferidos al buffer. c. Si el bloque ha sido solicitado por un proceso, esto es, si no ha sido leído por adelantado, copia el contenido del buffer dentro del área de memoria del usuario apuntado por buf. Actualmente, el algoritmo es mucho más elaborado que lo que se explica en este capítulo, ya que éste es optimizado para hacer uso máximo del buffer cache. La función opera por peticiones de grupos grandes de bloques desde un manejador de bajo nivel a la vez; éste no espera hasta que todos los bloques han sido transferidos, antes busca el siguiente grupo de bloques en la buffer cache. Sin embargo, el resultado final es el mismo; después de este paso, todos los buffers del bloque involucrados contienen datos válidos, y los bytes solicitados por el proceso de usuario son copiados dentro de área de memoria del usuario. 7. Agrega a *ppos el número de bytes copiados dentro del área de memoria del usuario. 246

277 8. Setea la bandera f_ra, de modo que el mecanismo de lectura anticipada será utilizado posteriormente (a menos que un proceso modifique el puntero al archivo, en cuyo caso la bandera sea limpiada). 9. Retorna el número de bytes copiados en el área de memoria del usuario. La función block_write () es similar a block_read () sin embargo, no será descrita en este capítulo. No obstante, se pueden identificar algunas diferencias entre ellas: 1. Antes de comenzar con la operación de escritura, la función block_write () debe comprobar si el bloque de dispositivo de hardware es solo de lectura y en ese caso retornar un código de error. Esto sucede por ejemplo, cuando se intenta escribir en un archivo de dispositivo de bloque (block device file) asociado con un CD-ROM. La tabla ro_bits incluye un bit por cada bloque de dispositivo de hardware (block hardware device); el bit es seteado si el correspondiente dispositivo no puede ser escrito y borrado, si este puede ser escrito. 2. La funcion block_write () debe verificar la posición del primer byte a ser escrito dentro del primer bloque. Si el desplazamiento (offset) no es nulo y la buffer cache ya no contiene datos válidos del primer bloque, la función debe leer el bloque desde disco antes de reescribirlo. De hecho, ya que el controlador de dispositivo de bloque (block device driver) opera sobre todos los bloques, la porción del primer bloque que precede los bytes a ser escritos debe ser preservado por la operación de escritura. Del mismo modo, la función debe también leer de disco el último bloque a ser escrito antes de reescribirlo, a menos que el último byte falle en la última posición del último bloque. 3. La función block_write () no necesariamente invoca ll_rw_blk () para forzar a escribir en disco. Por lo general, solamente marca el buffer de los bloques para ser escritos como sucios (dirty) por lo tanto, pospone la actualización de los sectores correspondientes en el disco. Sin embargo, la función invoca a ll_rw_block () si la llamada abre el archivo de dispositivo de bloque (block device file) que ha sido especificado por la bandera O_SYNC. En este caso, el proceso de llamada quiere esperar (sleep) hasta que los datos han sido físicamente escritos en el dispositivo de hardware, de modo que el disco siempre refleja que el proceso piensa que lo hace. La función getblk() La función getblk() es la rutina de servicio principal para la buffer cache. Cuando el kernel necesita leer o escribir los contenidos de algún bloque de un dispositivo físico, debe comprobar si la cabeza de buffer para el buffer requerido ya ha sido incluida en la buffer cache. Si el buffer no está allí, el kernel debe crear una nueva entrada en la cache. En vías de hacer esto, el kernel 247

278 invoca a la función getblk (), especificando como parámetro el identificador del dispositivo, el número de bloque y el tamaño de bloque. Está función retorna la dirección de la cabeza de buffer relacionada con el buffer. Se debe recordar que tener una cabeza de buffer en la cache no implica que los datos del buffer sean válidos. (Por ejemplo, el buffer ya fue leído desde disco). Cualquier función que lea bloques, como block_read(), La función getblk() realiza las siguientes operaciones: 1. Invoca a find_buffer (), la cual hace uso de la tabla hash para verificar si la cabeza de buffer requerida ya está en la cache. 2. Si la cabeza de buffer ha sido encontrada, incrementa su contador de uso (campo b_count) y retorna su dirección. 3. Si la cabeza de buffer no está en la cache, un nuevo buffer y una nueva cabeza buffer deben ser asignados. Deriva del tamaño de bloque un índice en el arreglo free_list y comprueba si la lista correspondiente está vacía. 4. Si la lista disponible no está vacía, realiza las siguientes operaciones: a. Remueve la primera cabeza de buffer de la lista. b. Inicializa la cabeza de buffer con el identificador de dispositivo, el número de bloque y el tamaño de bloque; almacena en el campo b_end_io un puntero a la función end_buffer_io_sync (); y pone en 1 el contador de uso b_count. c. Invoca insert_into_queues () para insertar la cabeza de buffer dentro de la tabla hash y de la lista lru_list [BUF_CLEAN]. d. Retorna la dirección de la cabeza de buffer. 5. Si la lista disponible está vacía, invoca la función refill_freelist () para rellenarla. 6. Invoca a find_buffer () para comprobar una vez más si algunos otros procesos han puesto el buffer en la cache mientras el control de ruta del kernel está esperando por la finalización del paso anterior. Si es así, va al paso 2; de lo contrario, va al paso 3. Las funciones bread () y breada () La función bread () comprueba si un determinado bloque ya está incluido en la buffer cache, sino, la función lee el bloque desde el dispositivo de bloque. La función bread () es ampliamente 248

279 utilizado por sistema de archivos para leer desde disco mapas de bits, inodos y otras estructura de datos basadas en disco (block_read () se utiliza en vez de bread () cuando un proceso quiere leer un archivo de dispositivo de bloque). La función recibe como parámetro el identificador del dispositivo, el número del bloque, el tamaño de bloque y realiza las siguientes operaciones: 1. Invoca la función getblk () para buscar el bloque en el buffer cache, si el bloque no está incluido en la cache, getbl k() asigna un nuevo buffer para la misma. 2. Si el buffer ya contiene actualizados los datos, finaliza. 3. Invoca a ll_rw_blk () para iniciar la operación de lectura. 4. Espera hasta que la transferencia de datos es completada. Esto lo hace a través de la función wait_on_buffer () que introduce el actual proceso en la cola de espera b_wait y suspende el proceso hasta que el buffer este desbloqueado. La función breada () es muy similar a la función bread() pero es incluye también lectura adelantada de algunos bloques adicionales. Nótese que no hay una función que escriba directamente en bloques de disco Solicitud de dispositivos de bloque Aunque los controladores de dispositivos de bloque (block device drivers) son capaces de transferir un solo bloque a la vez, el kernel no realiza una operación de E/S individual por cada bloque a ser accedido en el disco; esto llevaría a un pobre rendimiento del disco, ya que la localización de la posición física del bloque en la superficie del disco consume tiempo considerable. El kernel intenta, siempre que sea posible, hacer grupos de varios bloques y manejarlos como un todo, lo que reduce en promedio el número de movimientos de la cabeza del disco duro. Cuando un proceso, la capa de VFS, o cualquier componente del kernel quiere leer o escribir en un bloque de disco, se crea una solicitud de dispositivos de bloque ; esta solicitud esencialmente describe el bloque solicitado y el tipo de operación a ser realizada en el bloque (lectura o escritura); sin embargo, el kernel no satisface la petición tan pronto como se crea: La operación de E/S debe ser programada y debe ser realizada posteriormente. Este retraso artificial es el mecanismo crucial para impulsar el rendimiento del dispositivo de bloque. Cuando una nueva transferencia de bloque de datos es solicitada, el kernel comprueba si puede ser satisfecha ampliando ligeramente una petición anterior que todavía espera, es decir que si la nueva operación puede ser satisfecha sin lejanas operaciones de búsqueda. Ya que los discos tienden a ser accedidos secuencialmente, este mecanismo es bien efectivo. 249

280 Los aplazamientos de peticiones complican el manejo de los dispositivos de bloque. Por ejemplo, suponga que un proceso abre un archivo regular, y por consecuencia, el controlador (driver) de sistema de archivos quiere leer el correspondiente inodo del disco. El manejador de dispositivo de bloque de alto nivel pone la petición en una cola y el proceso es suspendido hasta que el bloque guarde el inodo a ser transferido. Sin embargo, el manejador de dispositivo de alto nivel no puede ser bloqueado, porque si otro proceso trata de acceder al mismo disco seria bloqueado también. Para mantener el controlador (driver) del dispositivo de bloque suspendido, cada operación de E/S es procesada asíncronamente como ya se mencionó anteriormente; así, la ruta de control del kernel es forzada a esperar hasta que la transferencia de datos es completada. En particular, los controladores (driver) de dispositivos de bloque funcionan por interrupciones, por lo que el manejador de dispositivo de alto nivel puede terminar esta ejecución tan pronto como se haya emitido la solicitud de bloque. El manejador de bajo nivel el cual es activado en un momento posterior, invoca una llamada a rutina de estrategia (strategy routine) la cual tiene la cola de peticiones y se cumple mediante comandos del controlador de disco. Cuando una operación de E/S termina, el controlador de disco ejecuta una interrupción y el correspondiente manejador invoca una rutina de estrategia de nuevo, si es necesario, para procesar otra solicitud en la cola. Cada controlador de dispositivo de bloque mantiene su propia cola de peticiones (request queues); ésta debe ser una cola de petición por cada dispositivo de bloque físico, por lo que esta solicitud puede ser ordenada de tal forma que se incremente el rendimiento del disco. La rutina de estrategia puede escanear secuencialmente la cola y atender a todas las solicitudes con un número mínimo de movimientos de cabeza. Cada solicitud de dispositivo de bloque es representada por un descriptor de peticiones (request descriptor) el cual es almacenado en la estructura de datos request la cual se encuentra en /usr/src/linux-source /include/linux/blkdev.h y cuyos campos se muestran a continuación: struct request: char *buffer: Es un descriptor que apunta al area de memoria utilizada para la transferencia de datos actual sector_t sector: Especifica el sector inicial en el cual comienza la transferencia de datos. unsigned long nr_sectors: Especifica el número de sector solicitado todavía está pendiente. unsigned int current_nr_sectors: Indica el número de sectores a trasferir de la solicitud actual. 250

281 hard_sector, hard_cur_sectors, and hard_nr_sectors tienen el mismo significado pero se relaciona con el hardware actual y no con dispositivos virtuales. struct bio *bio: Puntero al primer elemento de la cabeza de buffer. struct bio *biotail: Puntero al último elemento de la cabeza de buffer Int errors: Código de error struct request *next_rq: Puntero a la siguiente solicitud. Figura 5.16 El descriptor de peticiones, sectores y buffers. [Bovet y Cesati, 2000: p.404] La figura 5.16 ilustra el descriptor de peticiones. Dos de los buffer están de manera consecutiva en la RAM mientras que el tercer buffer esta aparte. La cabeza de buffer identifica los bloques lógicos del dispositivo de bloque; los bloques deben estar necesariamente de manera adyacente. Cada bloque lógico incluye dos sectores. El campo sector del descriptor de peticiones apunta al primer sector del primer bloque en disco. Manejo de solicitudes de bajo nivel Se ha llegado al nivel más bajo de dispositivos de bloque en Linux; este nivel es ejecutado por la rutina de estrategia la cual interactúa con el dispositivo de bloque físico con el fin de satisfacer todas las peticiones recogidas en la cola de solicitudes. La rutina de estrategia se inicia después 251

Software libre. El software libre provee la libertad de: Documentación (guías, wikis, faqs, etc.). Programa ejecutable. Código fuente del programa.

Software libre. El software libre provee la libertad de: Documentación (guías, wikis, faqs, etc.). Programa ejecutable. Código fuente del programa. GNU / Linux Software libre Es una forma ética de entender el software (en su desarrollo, comercialización, distribución y uso). Con el software libre se distribuye: Documentación (guías, wikis, faqs, etc.).

Más detalles

TEMA 3: INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS.

TEMA 3: INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS. TEMA 3: INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS. 1. DEFINICIÓN DE SISTEMA OPERATIVO.... 2 2. FUNCIONES DE LOS SISTEMAS OPERATIVOS.... 2 3. CLASIFICACIÓN DE LOS SISTEMAS OPERATIVOS.... 4 4. MODOS DE EXPLOTACIÓN

Más detalles

Como instalar Ubuntu 9.04

Como instalar Ubuntu 9.04 Como instalar Ubuntu 9.04 Hola a todos, pues como lo prometido es deuda antes del día lunes les traemos este tutorial para que las personas que deseen conocer la nueva versión de este magnífico sistema

Más detalles

Sistema Operativo Linux

Sistema Operativo Linux Fundación Colegio Aplicación Toico Palo Gordo. Municipio Cárdenas. Cátedra: Informática Objetivo N. 2 (SISTEMA OPERATIVO LINUX) Segundo Año. Secciones: A y B. Prof. Dayana Meléndez Sistema Operativo Linux

Más detalles

LINUX. GNU/Linux. Cuatro características muy peculiares lo diferencian del resto de los sistemas que podemos encontrar en el mercado:

LINUX. GNU/Linux. Cuatro características muy peculiares lo diferencian del resto de los sistemas que podemos encontrar en el mercado: LINUX GNU/Linux GNU/Linux es un sistema operativo de libre distribución, basado en el kernel Linux creado por Linus Torvalds y los desarrolladores del grupo GNU (Fundación para el software libre encabezada

Más detalles

ESTUDIO DE CASOS: LINUX

ESTUDIO DE CASOS: LINUX ESTUDIO DE CASOS: LINUX En este capítulo se estudia el sistema operativo Linux. Se trata de un sistema operativo de libre distribución que proporciona una interfaz POSIX. Actualmente Linux es ampliamente

Más detalles

IES Abyla. Departamento de Informática. Sistemas Operativos

IES Abyla. Departamento de Informática. Sistemas Operativos Sistemas Operativos Definición y funciones básicas El Sistema Operativo es el software que permite y simplifica el uso del ordenador (hardware). Sus funciones principales son: Arrancar el ordenador y controlar

Más detalles

2º CURSO INGENIERÍA TÉCNICA EN INFORMÁTICA DE GESTIÓN TEMA 5 ENTRADA/SALIDA. JOSÉ GARCÍA RODRÍGUEZ JOSÉ ANTONIO SERRA PÉREZ Tema 5.

2º CURSO INGENIERÍA TÉCNICA EN INFORMÁTICA DE GESTIÓN TEMA 5 ENTRADA/SALIDA. JOSÉ GARCÍA RODRÍGUEZ JOSÉ ANTONIO SERRA PÉREZ Tema 5. ARQUITECTURAS DE COMPUTADORES 2º CURSO INGENIERÍA TÉCNICA EN INFORMÁTICA DE GESTIÓN TEMA 5 ENTRADA/SALIDA JOSÉ GARCÍA RODRÍGUEZ JOSÉ ANTONIO SERRA PÉREZ Tema 5. Unidad de E/S 1 Unidad de E/S Indice Introducción.

Más detalles

Tema 1: Introducción. Generador del proyecto GNU, Richard Stallman es principalmente conocido por el establecimiento de un.

Tema 1: Introducción. Generador del proyecto GNU, Richard Stallman es principalmente conocido por el establecimiento de un. Tema 1: Introducción Objetivos: Conocimiento de la historia y filosofía de GNU/LINUX para que el estudiante entienda cual es el propósito de la utilización de un sistema operativo libre de licenciamiento.

Más detalles

2. Sistema Operativo Windows

2. Sistema Operativo Windows 2. Sistema Operativo Windows 2.1 Introducción al S.O. Windows NT y Windows 2000 2.2 Subsistema de Archivos 2.3 Subsistema de Procesos 2.4 Gestión de Memoria Dpto. Lenguajes Tema y 2: Sistemas 2. Sistema

Más detalles

COMPILACIÓN BIBLIOGRÁFICA RESUMEN: Sistemas Operativos Linux y las diferentes distribuciones (Detallar Red Hat, Fedora, Ubuntu, etc) y Chrome OS.

COMPILACIÓN BIBLIOGRÁFICA RESUMEN: Sistemas Operativos Linux y las diferentes distribuciones (Detallar Red Hat, Fedora, Ubuntu, etc) y Chrome OS. COMPILACIÓN BIBLIOGRÁFICA RESUMEN: Sistemas Operativos Linux y las diferentes distribuciones (Detallar Red Hat, Fedora, Ubuntu, etc) y Chrome OS. Presentado Por: Daniel Montes Agudelo John Elkin Rendón

Más detalles

INDICE. Prefacio Parte 1: sistemas operativos tradicionales

INDICE. Prefacio Parte 1: sistemas operativos tradicionales INDICE Prefacio Parte 1: sistemas operativos tradicionales 1 1 Introducción 1.1 Qué es un sistema operativo? 1.1.1 El sistema operativo como una maquina extendida 3 1.1.2 El sistema operativo como controlador

Más detalles

Sistemas Operativos de red (NOS).

Sistemas Operativos de red (NOS). Sistemas Operativos 4 tareas principales: Proporcionar interfaz: de comando o gráfica. Administrar los dispositivos de hardware en la computadora. Administrar y mantener los sistemas de archivo de disco.

Más detalles

LINUX es un sistema operativo, compatible UNIX. Posee dos características diferenciadoras del resto de SO:

LINUX es un sistema operativo, compatible UNIX. Posee dos características diferenciadoras del resto de SO: Modulo 3. Gestión de Datos Tema 5. Software Libre. Linux Estefanía Teniente LINUX es un sistema operativo, compatible UNIX. Posee dos características diferenciadoras del resto de SO: 1. Es LIBRE, esto

Más detalles

Sistemas operativos: una visión aplicada. Capítulo 12 Estudio de casos: Windows-NT

Sistemas operativos: una visión aplicada. Capítulo 12 Estudio de casos: Windows-NT Sistemas operativos: una visión aplicada Capítulo 12 Estudio de casos: Windows-NT Contenido Introducción Principios de diseño de Windows NT Arquitectura de Windows NT El núcleo de Windows NT Subsistemas

Más detalles

Denominamos Ordenador o Computadora, a una máquina electrónica que es capaz de dar un tratamiento automatizado a la información.

Denominamos Ordenador o Computadora, a una máquina electrónica que es capaz de dar un tratamiento automatizado a la información. INTRODUCCIÓN AL ORDENADOR Denominamos Ordenador o Computadora, a una máquina electrónica que es capaz de dar un tratamiento automatizado a la información. Se compone de dos elementos fundamentales que

Más detalles

Sistemas operativos: una visión aplicada. Capítulo 11 Estudio de casos: Linux

Sistemas operativos: una visión aplicada. Capítulo 11 Estudio de casos: Linux Sistemas operativos: una visión aplicada Capítulo 11 Estudio de casos: Linux Contenido Historia de Linux Características y estructura de Linux Gestión de procesos Gestión de memoria Entrada/salida Sistema

Más detalles

Tema I. Sistemas operativos

Tema I. Sistemas operativos Pag 1 Tema I. Sistemas operativos Un sistema operativo es un programa (software) encargado de poner en funcionamiento el ordenador, puesto que gestiona los procesos básicos del sistema. Así mismo se encarga

Más detalles

Capítulo 5. Sistemas operativos. Autor: Santiago Felici Fundamentos de Telemática (Ingeniería Telemática)

Capítulo 5. Sistemas operativos. Autor: Santiago Felici Fundamentos de Telemática (Ingeniería Telemática) Capítulo 5 Sistemas operativos Autor: Santiago Felici Fundamentos de Telemática (Ingeniería Telemática) 1 Sistemas operativos Definición de Sistema Operativo Partes de un Sistema Operativo Servicios proporcionados:

Más detalles

SENA CEET, Distrito Capital ADSI Instructor: Ing. Espec. Javier V.aquiro

SENA CEET, Distrito Capital ADSI Instructor: Ing. Espec. Javier V.aquiro SENA CEET, Distrito Capital ADSI Instructor: Ing. Espec. Javier V.aquiro Que hace un Sistema Operativo? El SO viste a la máquina desnuda * 4º INFORMÁTICA * La máquina desnuda es el ordenador sin S.O Definición

Más detalles

Sistemas operativos y sistemas de archivos

Sistemas operativos y sistemas de archivos Sistemas operativos y sistemas de archivos Estructura de contenidos Introducción 1.Sistemas Operativos 1.1 Definición 1.2 Componentes 1.2.1 El Núcleo 1.2.2 Interprete de Comandos 1.2.3 Sistema de archivos

Más detalles

Informática y Programación Escuela de Ingenierías Industriales y Civiles Grado en Ingeniería en Ingeniería Química Curso 2010/2011

Informática y Programación Escuela de Ingenierías Industriales y Civiles Grado en Ingeniería en Ingeniería Química Curso 2010/2011 Módulo 1. Fundamentos de Computadores Informática y Programación Escuela de Ingenierías Industriales y Civiles Grado en Ingeniería en Ingeniería Química Curso 2010/2011 1 CONTENIDO Tema 1. Introducción

Más detalles

DISEÑO DE UN SITIO WEB PARA EL CONTROL ACADÉMICO EN INSTITUCIONES PÚBLICAS DE EDUCACIÓN PARVULARIA DEL MUNICIPIO DE SAN SALVADOR

DISEÑO DE UN SITIO WEB PARA EL CONTROL ACADÉMICO EN INSTITUCIONES PÚBLICAS DE EDUCACIÓN PARVULARIA DEL MUNICIPIO DE SAN SALVADOR UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS ESCUELA DE CIENCIAS EMPRESARIALES Tecnología, Humanismo y Calidad TRABAJO DE GRADUACIÓN DISEÑO DE UN SITIO WEB PARA EL CONTROL ACADÉMICO EN

Más detalles

Sistema Operativo MAC. Francisco Jesús Delgado Almirón fjdelg@correo.ugr.es Diseño de Sistemas Operativos 5º Ingeniería Informática

Sistema Operativo MAC. Francisco Jesús Delgado Almirón fjdelg@correo.ugr.es Diseño de Sistemas Operativos 5º Ingeniería Informática Sistema Operativo MAC Francisco Jesús Delgado Almirón fjdelg@correo.ugr.es Diseño de Sistemas Operativos 5º Ingeniería Informática Introducción Mac OS (Macintosh Operating Systems) es un sistema operativo

Más detalles

Fundamentos de Sistemas Operativos

Fundamentos de Sistemas Operativos Fundamentos de Sistemas Operativos Sistemas Informáticos Fede Pérez Índice TEMA Fundamentos de Sistemas Operativos 1. - Introducción 2. - El Sistema Operativo como parte de un Sistema de Computación 2.1

Más detalles

! " # $!% & % '" ()!*++,

!  # $!% & % ' ()!*++, !" # $!%&%'" ()!*++, Qué es Linux? Antecedentes. Licencia. Características. Entorno de Trabajo. Estructura General. Sistema de Ficheros. Tipos. Path. Permisos de Acceso. Distribuciones Comerciales. Elementos

Más detalles

UNIVERSIDAD NACIONAL DE INGENIERÍA

UNIVERSIDAD NACIONAL DE INGENIERÍA UNIVERSIDAD NACIONAL DE INGENIERÍA Facultad de Ingeniería Industrial y de Sistemas Escuela Profesional de Ingeniería de Sistemas SÍLABO CURSO: SISTEMAS OPERATIVOS I. INFORMACIÓN GENERAL CODIGO : ST-324

Más detalles

Contenidos. Sistemas operativos Tema 3: Estructura del sistema operativo. Componentes típicos de un SO. Gestión de procesos.

Contenidos. Sistemas operativos Tema 3: Estructura del sistema operativo. Componentes típicos de un SO. Gestión de procesos. Contenidos Sistemas operativos Tema 3: Estructura del sistema operativo Componentes típicos del SO Servicios del SO Llamadas al sistema Programas del sistema El núcleo o kernel Modelos de diseño del SO

Más detalles

LISTA DE DEDICTORIAS. * No se hacen leyendas especiales.

LISTA DE DEDICTORIAS. * No se hacen leyendas especiales. LISTA DE DEDICTORIAS * No se hacen leyendas especiales. * Puedes dedicar tu agradecimiento a quien tú quieras, ejemplos : A mis Padres, A mis Padres y Hermanos, A Carlos, Al Maestro José Pablo Vázquez

Más detalles

Introducción a los Sistemas Operativos

Introducción a los Sistemas Operativos Introducción a los Sistemas Operativos 2º Ingeniero de Telecomunicación (Sonido e Imagen) Departamento de Ingeniería Telemática Universidad Carlos III de Madrid 2 Qué vamos a ver hoy? Qué es un sistema

Más detalles

Contenido. Sistemas de Entrada/Salida. Categorias de los Dispositivos de Entrada/Salida. Categorias de los Dispositivos de Entrada/Salida

Contenido. Sistemas de Entrada/Salida. Categorias de los Dispositivos de Entrada/Salida. Categorias de los Dispositivos de Entrada/Salida Contenido Sistemas de Categorias de los Dispositivos de En qué se diferencian los dispositivos de? Técnicas para realizar la E/S Interrupciones Interfaces involucradas en E/S Buffering Categorias de los

Más detalles

2.2. Principales características de los sistemas operativos. UNIDAD 2

2.2. Principales características de los sistemas operativos. UNIDAD 2 2.2. Principales características de los sistemas operativos. UNIDAD 2 Mac OS X es un sistema operativo desarrollado y comercializado por Apple Inc. Ha sido incluido en su gama de computadoras Macintosh

Más detalles

Software Libre / Código Abierto Programa de contenidos

Software Libre / Código Abierto Programa de contenidos Software Libre / Código Abierto Programa de contenidos Resumen Se presenta a continuación la organización de un curso de cincuenta horas cuyo fin es dar a conocer la base ideológica que sostiene a los

Más detalles

MANUAL DE CONFIGURACION RED SISTEMAS SIPNET CIBERWIN

MANUAL DE CONFIGURACION RED SISTEMAS SIPNET CIBERWIN MANUAL DE CONFIGURACION RED SISTEMAS SIPNET CIBERWIN 1 INDICE Introducción.. 3 Configuración de Servidor Windows XP..... 6 Configuración de controladores para ejecutar el sistema en Windows XP...18 Configuración

Más detalles

Tema 2: Implementación del núcleo de un Sistema Operativo

Tema 2: Implementación del núcleo de un Sistema Operativo Tema 2: Implementación del núcleo de un Sistema Operativo 1. Sistema Operativo Unix 2. Sistema Operativo Windows (a partir de NT) Dpto. Lenguajes y Sistemas Informáticos. Universidad de Granada 1 1. Sistema

Más detalles

Unidad 1: Conceptos generales de Sistemas Operativos.

Unidad 1: Conceptos generales de Sistemas Operativos. Unidad 1: Conceptos generales de Sistemas Operativos. Tema 3: Estructura del sistema operativo. 3.1 Componentes del sistema. 3.2 Servicios del sistema operativo. 3.3 Llamadas al sistema. 3.4 Programas

Más detalles

El tema de esta presentación es los conceptos básicos relacionados con Sistemas Operativos.

El tema de esta presentación es los conceptos básicos relacionados con Sistemas Operativos. 1 El tema de esta presentación es los conceptos básicos relacionados con Sistemas Operativos. 2 Qué es un sistema operativo Un sistema operativo es un programa que tiene encomendadas una serie de funciones

Más detalles

UNIVERSIDAD DE LOS ANDES FACULTAD DE CIENCIAS ECONOMICAS Y SOCIALES. PROF. ISRAEL J. RAMIREZ israel@ula.ve

UNIVERSIDAD DE LOS ANDES FACULTAD DE CIENCIAS ECONOMICAS Y SOCIALES. PROF. ISRAEL J. RAMIREZ israel@ula.ve UNIVERSIDAD DE LOS ANDES FACULTAD DE CIENCIAS ECONOMICAS Y SOCIALES PROF. ISRAEL J. RAMIREZ israel@ula.ve UNIVERSIDAD DE LOS ANDES FACULTAD DE CIENCIAS ECONOMICAS Y SOCIALES LOS SISTEMAS OPERATIVOS 1.-

Más detalles

Unidad 2: Gestión de Memoria

Unidad 2: Gestión de Memoria Unidad 2: Gestión de Memoria Tema 3, Gestión de Memoria: 3.1 Definiciones y técnicas básicas. 3.2 Gestión de memoria contigua: Partición, fragmentación, algoritmos de ubicación... 3.3 Paginación: Estructura

Más detalles

Organización del libro 2 Orden de presentación de los temas 3 Recursos en Internet y en la Web 4

Organización del libro 2 Orden de presentación de los temas 3 Recursos en Internet y en la Web 4 Prólogo xvii Capítulo O 0.1 0.2 0.3 Guía del lector 1 Organización del libro 2 Orden de presentación de los temas 3 Recursos en Internet y en la Web 4 PRIMERA PARTE:ANTECECENTES 7 Capítulo 1 1.1 1.2 1.3

Más detalles

Braulio Ricardo Alvarez Gonzaga INTERNET INFORMATION SERVER (IIS) WINDOWS SERVER 2003

Braulio Ricardo Alvarez Gonzaga INTERNET INFORMATION SERVER (IIS) WINDOWS SERVER 2003 INTERNET INFORMATION SERVER (IIS) WINDOWS SERVER 2003 1 INTRODUCCIÓN Cuando nosotros ingresamos a una página web, en busca de información no somos conscientes de los muchos procesos que se realizan entre

Más detalles

Al concluir la práctica el alumno conocerá las definiciones, los elementos y funciones de los Sistemas Operativos.

Al concluir la práctica el alumno conocerá las definiciones, los elementos y funciones de los Sistemas Operativos. Sistemas Operativos Práctica 2 Alumno: Grupo: Fecha: Instituto Politécnico Nacional Secretaria Académica Dirección de Educación Superior ESIME Culhuacan Ingeniería en Computación Fundamentos de Programación

Más detalles

Definición Kernel Procesos Memoria Sistema de Archivos Seguridad y Protección Interfaz con el usuario GNU/Linux. Sistemas Operativos

Definición Kernel Procesos Memoria Sistema de Archivos Seguridad y Protección Interfaz con el usuario GNU/Linux. Sistemas Operativos Sistemas Operativos Colaboratorio de Computación Avanzada (CNCA) 2015 Contenidos 1 Definición 2 Kernel 3 Procesos 4 Memoria 5 Sistema de Archivos 6 Seguridad y Protección 7 Interfaz con el usuario 8 GNU/Linux

Más detalles

LABORATORIO Nro. 1. Linux: Instalación.

LABORATORIO Nro. 1. Linux: Instalación. LABORATORIO Nro. 1. Linux: Instalación. Oscar Mauricio Gallego Alzate Dayan Alfredo Barrientos Delgado Informática I, código 2547100, Grupo 10 Profesor: Henry Alberto Arcila Universidad de Antioquia Facultad

Más detalles

Introducción a la Entrada/Salida

Introducción a la Entrada/Salida Introducción a la Entrada/Salida Organización de entrada/salida La familia de procesadores 80x86, presente en el IBM PC, utiliza la arquitectura Von Neumann, que puede verse en la figura 1. El denominado

Más detalles

INDICE Prologo Capitulo 0. Guía del lector Primera parte: antecedentes Capitulo 1. Introducción a los computadores

INDICE Prologo Capitulo 0. Guía del lector Primera parte: antecedentes Capitulo 1. Introducción a los computadores INDICE Prologo XVII Capitulo 0. Guía del lector 1 0.1. organización del libro 2 0.2. orden de presentación de los temas 3 0.3. recursos en Internet y en la Web 4 Primera parte: antecedentes 7 Capitulo

Más detalles

c) Porqué comprar software comercial si puede conseguir mejor software gratis?

c) Porqué comprar software comercial si puede conseguir mejor software gratis? Indice. Resumen Ejecutivo..2 Indice...3 Introducción4 Cuerpo.5 Conclusión...9 Bibliografía y Referencias.10 Resumen Ejecutivo Este informe lo único que busca es persuadir al lector (cualquiera que este

Más detalles

Instituto Laboral Andino Curso básico de informática

Instituto Laboral Andino Curso básico de informática Instituto Laboral Andino Curso básico de informática MÓDULO I MANEJO BÁSICO DE LA COMPUTADORA ADVERTENCIA La manera de usar un lenguaje que no discrimine entre hombres y mujeres aún no ha conseguido un

Más detalles

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS TRABAJO DE GRADO:

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS TRABAJO DE GRADO: UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS TRABAJO DE GRADO: DISEÑO DE UN PLAN DE MERCADEO PARA INCREMENTAR LA DEMANDA DE LOS PRODUCTOS EMBUTIDOS QUE COMERCIALIZAN LAS MEDIANAS EMPRESAS

Más detalles

Introducción al software libre

Introducción al software libre Introducción al software libre BLOQUE 2: GNU/Linux UPV/EHU - SAE 31 de enero, Vitoria-Gasteiz De qué vamos a hablar? Qué es GNU/Linux? GNU/Linux frente a otros sistemas. Distribuciones de GNU/Linux. :

Más detalles

COLEGIO COMPUESTUDIO

COLEGIO COMPUESTUDIO COLEGIO COMPUESTUDIO ÁREA: TECNOLOGIA E INFORMATICA DOCENTE: WILLY VIVAS LLOREDA ESTUDIANTE: CLEI: III GUIA N 5 N SESIONES: NUCLEO TEMÁTICO: UNIDAD: 2 Sistema operativo (Windows) OBJETIVO: Comprender el

Más detalles

INDICE DE CONTENIDOS. DEDICATORIA... vii. AGRADECIMIENTO... vii

INDICE DE CONTENIDOS. DEDICATORIA... vii. AGRADECIMIENTO... vii INDICE DE CONTENIDOS DEDICATORIA... vii AGRADECIMIENTO... vii CAPITULO 1: ESTUDIO SOBRE REDES EN ENTORNO DE AREA LOCAL (LAN) Error! Marcador no definido. INTRODUCCIÓN... Error! Marcador no definido. 1.1.

Más detalles

UNIVERSIDAD DE LOS ANDES NÚCLEO UNIVERSITARIO RAFAEL RANGEL

UNIVERSIDAD DE LOS ANDES NÚCLEO UNIVERSITARIO RAFAEL RANGEL UNIVERSIDAD DE LOS ANDES NÚCLEO UNIVERSITARIO RAFAEL RANGEL CARRERAS: Comunicación Social - Contaduría Publica Administración -Educación MATERIA: Int. a la Computación - Computación I-Introducción a la

Más detalles

Software Libre. Guillermo Valdés Lozano. 28 de noviembre de 2007

Software Libre. Guillermo Valdés Lozano. 28 de noviembre de 2007 28 de noviembre de 2007 Documento protegido por GFDL Copyright (c) 2008. e-mail: guillermo(en)movimientolibre.com http://www.movimientolibre.com/ Se otorga permiso para copiar, distribuir y/o modificar

Más detalles

TECNOLOGÍAS DE LA INFORMACIÓN Y COMUNICACIÓN PROFESOR: MSC. P. Norma Maya Pérez SISTEMAS OPERATIVOS

TECNOLOGÍAS DE LA INFORMACIÓN Y COMUNICACIÓN PROFESOR: MSC. P. Norma Maya Pérez SISTEMAS OPERATIVOS TECNOLOGÍAS DE LA INFORMACIÓN Y COMUNICACIÓN PROFESOR: MSC. P. Norma Maya Pérez SISTEMAS OPERATIVOS I. Fundamentos del sistema operativo. OBJETIVO: El alumno seleccionará un sistema operativo de acuerdo

Más detalles

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS TRABAJO DE GRADO: DISEÑO DE UN PLAN DE MARKETING PARA PROMOVER EN LAS EMPRESAS FARMACÉUTICAS DEL ÁREA METROPOLITANA DE SAN SALVADOR, EL SERVICIO

Más detalles

Sistemas Operativos de Red

Sistemas Operativos de Red Sistemas Operativos de Red Como ya se sabe las computadoras están compuestas físicamente por diversos componentes que les permiten interactuar mas fácilmente con sus operarios y hasta comunicarse con otras

Más detalles

Curso de Administración de Servidores GNU/Linux

Curso de Administración de Servidores GNU/Linux Curso de Administración de Servidores GNU/Linux Centro de Formación Permanente Universidad de Sevilla Jorge Juan . Abril, 2014 Usted es libre de copiar, distribuir y comunicar públicamente

Más detalles

UNIVERSIDAD DE GUAYAQUIL

UNIVERSIDAD DE GUAYAQUIL II UNIVERSIDAD DE GUAYAQUIL Facultad de Ciencias Matemáticas y Físicas Carrera de Ingeniería en Sistemas Computacionales Desarrollo de una VPN / Firewall de Software con Administración Vía Web TESIS DE

Más detalles

Conceptos Básicos de Software. Clase III

Conceptos Básicos de Software. Clase III Clase III Definición de Sistema Operativo El sistema operativo es el programa (o software) más importante de una computadora. Para que funcionen los otros programas, cada computadora de uso general debe

Más detalles

Sistemas Operativos. Tema 1. Arquitectura Básica de los Computadores

Sistemas Operativos. Tema 1. Arquitectura Básica de los Computadores Sistemas Operativos. Tema 1 Arquitectura Básica de los Computadores http://www.ditec.um.es/so Departamento de Ingeniería y Tecnología de Computadores Universidad de Murcia Sistemas Operativos. Tema 1 Arquitectura

Más detalles

Laboratorio de Optimización

Laboratorio de Optimización Laboratorio de Optimización Sistema Operativo Linux Oscar Alvarado Nava oan@correo.azc.uam.mx Departamento de Electrónica División de Ciencias Básicas e Ingeniería Universidad Autónoma Metropolitana, Azcapotzalco

Más detalles

Curso de Informática básica

Curso de Informática básica Universidad Rey Juan Carlos Curso de Informática básica Estefanía Martín Barroso Liliana Patricia Santacruz Valencia Laboratorio de Tecnologías de la Información en la Educación Contenidos 2 Bloque 1:

Más detalles

Tema 11. Soporte del Sistema Operativo 11.1. REQUERIMIENTOS DE LOS SISTEMAS OPERATIVOS. 11.1.1. MULTIPROGRAMACIÓN.

Tema 11. Soporte del Sistema Operativo 11.1. REQUERIMIENTOS DE LOS SISTEMAS OPERATIVOS. 11.1.1. MULTIPROGRAMACIÓN. Tema 11 Soporte del Sistema Operativo 11.1. REQUERIMIENTOS DE LOS SISTEMAS OPERATIVOS. El sistema operativo es básicamente un programa que controla los recursos del computador, proporciona servicios a

Más detalles

Tema 1. Hardware. Fundamentos de Informática Grado en Ingeniería Mecánica

Tema 1. Hardware. Fundamentos de Informática Grado en Ingeniería Mecánica Tema 1. Hardware. Fundamentos de Informática Grado en Ingeniería Mecánica Definición de computador Máquina electrónica de propósito general utilizada para procesar información El computador moderno se

Más detalles

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS SOCIALES

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS SOCIALES UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS SOCIALES PROGRAMA DE ESCUELA DE PADRES Y MADRES COMO ESTRATEGIA PARA PROMOVER EL ACERCAMIENTO DE LOS PADRES Y MADRES A LA ESCUELA DE EDUCACIÓN PARVULARIA,

Más detalles

IN ST IT UT O POLIT ÉCN ICO N A CION A L SECRETARÍA ACADÉMICA DIRECCIÓN DE ESTUDIOS PROFESIONALES EN INGENIERÍA Y CIENCIAS FÍSICO MATEMÁTICAS

IN ST IT UT O POLIT ÉCN ICO N A CION A L SECRETARÍA ACADÉMICA DIRECCIÓN DE ESTUDIOS PROFESIONALES EN INGENIERÍA Y CIENCIAS FÍSICO MATEMÁTICAS PROGRAMA SINTÉTICO CARRERA: Ingeniería en Sistemas Computacionales ASIGNATURA: Sistemas Operativos I SEMESTRE: Cuarto OBJETIVO GENERAL: El alumno analizará los algoritmos, relaciones hardware-software,

Más detalles

UNIVERSIDAD DE ORIENTE FACULTAD DE CIENCIAS ECONOMICAS CARRERA: LICENCIATURA EN CIENCIAS DE LA COMPUTACION.

UNIVERSIDAD DE ORIENTE FACULTAD DE CIENCIAS ECONOMICAS CARRERA: LICENCIATURA EN CIENCIAS DE LA COMPUTACION. UNIVERSIDAD DE ORIENTE FACULTAD DE CIENCIAS ECONOMICAS CARRERA: LICENCIATURA EN CIENCIAS DE LA COMPUTACION. TEMA: ESTUDIO DE FACTIBILIDAD SOBRE INTEGRACIÓN DE VOZ Y DATOS PARA UNA LAN EN EL INSTITUTO NACIONAL

Más detalles

Oracle VM VirtualBox.

Oracle VM VirtualBox. UNIVERSIDAD DON BOSCO FACULTAD DE ESTUDIOS TECNOLÓGICOS ESCUELA DE COMPUTACIÓN CICLO: I-2015 GUIA DE LABORATORIO #1 Nombre de la Practica: VirtualBox y CentOS Lugar: Laboratorio de Redes Tiempo Estimado:

Más detalles

Por qué utilizar software libre en las organizaciones y movimientos sociales centroamericanos?

Por qué utilizar software libre en las organizaciones y movimientos sociales centroamericanos? Por qué utilizar software libre en las organizaciones y movimientos sociales centroamericanos? Elaborado por: Lizeth Ramírez Camacho (con la retroalimentación del Equipo de trabajo de ) Diciembre, 2009

Más detalles

Sistemas Operativos Windows. JORGE DE NOVA SEGUNDO

Sistemas Operativos Windows. JORGE DE NOVA SEGUNDO Sistemas Operativos Windows. JORGE DE NOVA SEGUNDO Microsoft Windows es el nombre de una familia de sistemas operativos de software propietario desarrollados y vendidos por Microsoft. Microsoft introdujo

Más detalles

Introducción al Mundo GNU/Linux

Introducción al Mundo GNU/Linux Pequeña Introducción a algunas características del Mundo GNU/Linux y el Software Libre Slide 1 Nota de Copyright 2005 Diego Chaparro. Algunos derechos reservados. Este trabajo se distribuye bajo la licencia

Más detalles

ACTIVIDADES TEMA 1. EL LENGUAJE DE LOS ORDENADORES. 4º E.S.O- SOLUCIONES.

ACTIVIDADES TEMA 1. EL LENGUAJE DE LOS ORDENADORES. 4º E.S.O- SOLUCIONES. 1.- a) Explica qué es un bit de información. Qué es el lenguaje binario? Bit es la abreviatura de Binary digit. (Dígito binario). Un bit es un dígito del lenguaje binario que es el lenguaje universal usado

Más detalles

Sistemas operativos TEMA 2 de tico

Sistemas operativos TEMA 2 de tico 2012 Sistemas operativos TEMA 2 de tico MARILO GARCÍA MARTÍNEZ. MARILÓ GARCÍA MARTÍNEZ para RAIMUNDO RODRÍGUEZ CAMPOS TEMA 2. SISTEMAS OPERATIVOS. INDICE DE CONTENIDOS 1. CONCEPTO DE SISTEMA OPERATIVO

Más detalles

Introducción CAPÍTULO 1. Características del sistema

Introducción CAPÍTULO 1. Características del sistema CAPÍTULO 1 Introducción Los sistemas Dell PowerEdge 300 son servidores de alta velocidad con diseño basado en los microprocesadores Intel Pentium III. Estos sistemas soportan el bus PCI (Peripheral Component

Más detalles

TEMA 6: GESTIÓN DE ENTRADA/SALIDA

TEMA 6: GESTIÓN DE ENTRADA/SALIDA 1. Introducción TEMA 6: GESTIÓN DE ENTRADA/SALIDA Función principal de un S.O.: controlar todos los dispositivos de E/S de la computadora. El Subsistema de E/S se encarga de Emitir órdenes a los dispositivos

Más detalles

Índice. agradecimientos...19

Índice. agradecimientos...19 Índice agradecimientos...19 CAPÍTULO 1. CARACTERIZACIÓN DE SISTEMAS OPERATIVOS...21 1.1 El sistema informático...22 1.1.1 Clasificación de los sistemas informáticos...24 1.2 El sistema operativo... 26

Más detalles

ESCUELA DE CIENCIAS BASICAS TECNOLOGIA E INGENIERIA 208006 Sistemas Embebidos Act 11: Reconocimiento Unidad 3 LECTURA 1

ESCUELA DE CIENCIAS BASICAS TECNOLOGIA E INGENIERIA 208006 Sistemas Embebidos Act 11: Reconocimiento Unidad 3 LECTURA 1 LECTURA 1 Qué diferencias hay entre aplicaciones para PC convencional o para sistemas embebidos? No es lo mismo desarrollar aplicaciones para un PC convencional que para un sistema embebido. El desarrollo

Más detalles

Hardware y Estructuras de Control. Memoria Virtual. Ejecución de un Programa. Ejecución de un Programa

Hardware y Estructuras de Control. Memoria Virtual. Ejecución de un Programa. Ejecución de un Programa Memoria Virtual Capítulo 8 Hardware y Estructuras de Control Las referencias de memoria se traducen a direcciones físicas dinámicamente en tiempo de ejecución Un proceso puede ser intercambiado hacia dentro

Más detalles

Taxonomía de los sistemas operativos. Programación de Sistemas. Características en Win3.1 (1/3) Características en Win3.1 (3/3)

Taxonomía de los sistemas operativos. Programación de Sistemas. Características en Win3.1 (1/3) Características en Win3.1 (3/3) Programación de Sistemas Taxonomía de los sistemas operativos Mtro. en IA José Rafael Rojano Cáceres tareasrojano@gmail.com http://www.uv.mx/rrojano Referencia [Oney 96] Características en Win3.1 (1/3)

Más detalles

Unidad I. 1. Introducción. Equipo (PC) Sistema Operativo. Red de PC s. Sistema Operativo de Red. Compartir Recursos Habilitar Usuarios.

Unidad I. 1. Introducción. Equipo (PC) Sistema Operativo. Red de PC s. Sistema Operativo de Red. Compartir Recursos Habilitar Usuarios. Unidad I 1. Introducción. Equipo (PC) Sistema Operativo necesitan Red de PC s Sistema Operativo de Red. para Compartir Recursos Habilitar Usuarios. Niveles de Integración: Añadido al S.O (Novell, Lantastic).

Más detalles

Tipos de comunicación La comunicación puede ser:

Tipos de comunicación La comunicación puede ser: Unidad 3. Procesos concurrentes 3.3 Semáforos (informática) Un semáforo es una variable especial (o tipo abstracto de datos) que constituye el método clásico para restringir o permitir el acceso a recursos

Más detalles

MANUAL PARA USO DEL COMPUTADOR NETBOOK

MANUAL PARA USO DEL COMPUTADOR NETBOOK MANUAL PARA USO DEL COMPUTADOR NETBOOK Secretaría Informática Página 1 Índice PRESENTACIÓN... 2 Objetivos... 2 Competencias a lograr... 2 LA COMPUTADORA... 3 PARTES DE UNA COMPUTADORA... 3 El equipo (hardware)...

Más detalles

INFORMÁTICA BÁSICA. Este tipo de memoria se define como volátil que significa que se pierde su contenido cuando se apaga el ordenador.

INFORMÁTICA BÁSICA. Este tipo de memoria se define como volátil que significa que se pierde su contenido cuando se apaga el ordenador. INFORMÁTICA BÁSICA MEMORIA RAM La memoria de un ordenador hace referencia al dispositivo dónde se almacenan los datos y los programas para que se pueda trabajar con ellos. Cuando hablamos de memoria de

Más detalles

Contenido. Sistema de archivos. Operaciones sobre archivos. Métodos de acceso a archivos. Directorio. Sistema de archivos por capas.

Contenido. Sistema de archivos. Operaciones sobre archivos. Métodos de acceso a archivos. Directorio. Sistema de archivos por capas. Contenido Sistema de archivos Operaciones sobre archivos Métodos de acceso a archivos Directorio Sistema de archivos por capas Espacio libre Sistema de archivos Proporciona el mecanismo para el almacenamiento

Más detalles

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS TRABAJO DE GRADO:

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS TRABAJO DE GRADO: UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONÓMICAS TRABAJO DE GRADO: PROPUESTA DE UN PLAN DE PUBLICIDAD CORPORATIVA PARA AUMENTAR LA COMPETITIVIDAD DE LA EMPRESA DISTRIBUIDORA LOURDES S.A. DE

Más detalles

Unidad 1: Conceptos generales de Sistemas Operativos.

Unidad 1: Conceptos generales de Sistemas Operativos. Unidad 1: Conceptos generales de Sistemas Operativos. Tema 2: Estructura de los sistemas de computación. 2.1 Funcionamiento de los sistemas de computación. 2.2 Ejecución de instrucciones e interrupciones

Más detalles

Unidad 1: Conceptos generales de Sistemas Operativos.

Unidad 1: Conceptos generales de Sistemas Operativos. Unidad 1: Conceptos generales de Sistemas Operativos. Tema 1: Introducción: 1.1 Introducción: Qué es un sistema operativo?. 1.2 Conceptos clave de un sistema operativo. 1.3 El sistema operativo como administrador

Más detalles

Distribuciones Linux

Distribuciones Linux Distribuciones Linux Linux es un núcleo libre de sistema operativo basado en Unix. Es uno de los principales ejemplos de software libre. Linux está licenciado bajo la GPL v2 y está desarrollado por colaboradores

Más detalles

Aplicaciones Informáticas

Aplicaciones Informáticas Aplicaciones Informáticas Profesor: Eduardo Zúñiga Sistema de aprobación: 2 parciales y recuperatorio Promoción: Sumar 14 o más puntos entre los dos parciales y no sacar menos de 6 en ninguno de los dos

Más detalles

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONOMICAS

UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONOMICAS UNIVERSIDAD FRANCISCO GAVIDIA FACULTAD DE CIENCIAS ECONOMICAS TRABAJO DE GRADO PROPUESTA DE UN PLAN PROMOCIONAL PARA POTENCIAR LA VENTA AL DETALLE DE PISOS Y AZULEJOS CERÁMICOS QUE COMERCIALIZA LA EMPRESA

Más detalles

SISTEMA OPERATIVO FUNCIONES DEL SISTEMA OPERATIVO

SISTEMA OPERATIVO FUNCIONES DEL SISTEMA OPERATIVO SISTEMA OPERATIVO El sistema operativo es el software destinado a administrar los recursos de un ordenador, actuando intermediario entre el hardware, los programas y los usuarios. Se inicia al encender

Más detalles

Adopción de Ubuntu en una dependencia gubernamental, caso del Instituto

Adopción de Ubuntu en una dependencia gubernamental, caso del Instituto Adopción de Ubuntu en una dependencia gubernamental, caso del Mtro. Alejandro Escalante Enrique Martínez Fredy Alvarado Abril 2008 Introducción a Ubuntu Existen mas de 1000 diferentes versiones de distribuciones

Más detalles

GUÍA DE INSTALACIÓN DE SLACKWARE LINUX 11.0 v. 1.0

GUÍA DE INSTALACIÓN DE SLACKWARE LINUX 11.0 v. 1.0 GUÍA DE INSTALACIÓN DE SLACKWARE LINUX 11.0 v. 1.0 Autor: eli@s (Elías Cuellar Rodríguez) Licencia: GPL Fecha: 20 de Abril de 2007 UNIVERSIDAD PERUANA UNIÓN FACULTAD DE INGENIERÍA EAP de Ingeniería de

Más detalles

TEMA: DESCARGA DE DRIVERS DE HARDWARE Y APLICACIONES UTILITARIAS.

TEMA: DESCARGA DE DRIVERS DE HARDWARE Y APLICACIONES UTILITARIAS. Empremática, Guía 2 1 TEMA: DESCARGA DE DRIVERS DE HARDWARE Y APLICACIONES UTILITARIAS. Objetivos Conocer los diferentes drivers que se utilizan en una computadora. Aprender a descargar las aplicaciones

Más detalles

Tema 1 Fundamentos de Computación

Tema 1 Fundamentos de Computación Tema 1 Fundamentos de Computación Clase 2 Prof. María Alejandra Quintero Asignatura: Informática Escuela de Ingeniería Forestal Puntos a tratar Continuación hardware Memoria principal Dispositivos de almacenamiento

Más detalles

Sistemas de archivos distribuidos. Alvaro Ospina Sanjuan alvaro.ospina@correo.upb.edu.co

Sistemas de archivos distribuidos. Alvaro Ospina Sanjuan alvaro.ospina@correo.upb.edu.co Sistemas de archivos distribuidos Alvaro Ospina Sanjuan alvaro.ospina@correo.upb.edu.co >Abstracción del sistema operativo para representar y organizar los recursos de almacenamiento >Se debe hacer la

Más detalles

TEMA: LOS SISTEMAS OPERATIVOS

TEMA: LOS SISTEMAS OPERATIVOS TEMA 1. LOS SISTEMAS OPERATIVOS 1 Introducción En este tema, estudiaremos los sistemas operativos como el primer software que necesita cargar el ordenador en el arranque, y que tiene la responsabilidad

Más detalles

Básico de Arquitectura del Computador. Ing. Irvin Cuervo

Básico de Arquitectura del Computador. Ing. Irvin Cuervo Básico de Arquitectura del Computador El Computador Hardware Software El Computador Qué es y qué hace un computador? Un computador es básicamente una máquina cuya función principal es procesar información.

Más detalles