<font size=+3>El núcleo Linux

El núcleo Linux

David A Rusling
david.rusling@digital.com

January 1998

Esta página se ha dejado en blanco intencionalmente.

Dedicado a Gill, Esther y Stephen

Notificaciones legales


UNIX es marca registrada de Univel.
Linux es marca registrada de Linus Torvalds, y no tiene ninguna relación con UNIX or Univel.
Copyright ©  1996,1997,1998  David A Rusling
3 Foxglove Close, Wokingham, Berkshire RG41 3NF, UK
david.rusling@digital.com ``El núcleo Linux'' Puede reproducirse y distribuirse en un todo o en parte, siempre que se cumplan las siguientes condiciones:

  1. La declaración de "copyright" precedente y esta declaración de permiso se deben preservar completas en todas las copias totales o parciales.
  2. Cualquier traducción o trabajo derivado de ``El núcleo Linux'' debe ser aprobada por escrito por el autor antes de su distribución.
  3. Si Ud. distribuye ``El núcleo Linux'' en parte, deberá incluir instrucciones para obtener la versión completa, y deberá proveer un medio para obtener la versión comkpleta.
  4. Puede reproducirse a modo de ilustración para revisiones o citas en otros trabajos, sin necesidad de esta declaración de permiso, siempre que se dé una cita apropiada.
  5. Si Ud. imprime y distribuye ``El núcleo Linux'', Ud. no podrá referirse a él como la "<Versión oficial impresa">.
  6. La Licencia pública general GNU ("<GNU General Public License">) que se cita más abajo, puede reproducirse bajo las condiciones que le son propias.
Hay excepciones a las reglas previamente mencionadas que pueden autorizarse con propósitos académicos: escriba a David Rusling a la dirección postal anterior, o envíe correo electrónico a david.rusling@digital.com, y consulte. Estas restricciones están para protegernos a nosotros como los autores, y no para restringir a los educadores y alumnos.

Todo el código fuente en ``El núcleo Linux'' está cubierto por la "<Licencia pública general GNU">. Si desea una copia de la "<GPL">, vea en el apéndice  gnu-license.

El autor no es responsable judicialmente por ningún daño, directo o indirecto, que resulte de la información provista en este documento.

Capítulo 1
Prefacio

 es un fenómeno producto de Internet. Concebido como un proyecto para el entretenimiento de un estudiante, ha crecido hasta sobrepasar en popularidad a todos los demás sistemas operativos de distribución libre. Para muchos Linux es un enigma. ¿Cómo puede valer la pena algo que es gratis? En un mondo dominado por un puñado de enormes corporaciones de software, ¿cómo puede ser que algo escrito por una manada de «hackers» (sic) tenga la esperanza de competir?. ¿Cómo puede ser que las contribuciones de software realizadas por tantas personas distintas, con tan diferentes países de origen de todo el mundo, tengan la esperanza de ser estables y efectivas? Y sin embargo, es estable y efectivo, y también compite. Muchas universidades y establecimientos de investigación lo utilizan para satisfacer sus necesidades de cómputo cotidianas. La gente lo hace funcionar en las PCs de sus casas, y yo puedo apostar a que la mayoría de las compañías lo están utilizando en algún lugar, aún cuando puede que no estén al tanto de ello.  se utiliza para navegar en la «web», para hospedar sitios «web», escribir tesis, enviar correo electrónico y, como desde siempre se ha hecho con las computadoras, para divertirse con juegos. Debo expresar enfáticamente que   no es un juguete; es un sistema operativo completamente desarrollado, y escrito de manera profesional, y que utilizan los entusiastas por todo el mundo.

Las raíces de  pueden rastrearse hasta los orígenes de Unix\ . En 1969, Ken Thompson perteneciente al «Grupo de investigación» (Research Group) de los «Laboratorios Bell», comenzó a experimentar con un sistema operativo multiusuario, multiprogramado1 trabajando sobre una PDP-7 que nadie utilizaba. Pronto se le unió Dennis Richie y entre ambos, junto con otros miembros del Grupo de Investigación, produjeron las primeras versiones de Unix. Richie estab fuertemente influído por un proyecto anterior denominado MULTICS, y el nombre Unix es en sí mismo un gracioso juego de palabras derivado de MULTICS. Las primeras versiones se escribieron en lenguaje ensamblador, pero la tercer versión se escribió en el nuevo lenguaje C. Richie diseñó y escribió el lenguaje de programación C expresamente para que sea utilizado en la escritura de sistemas operativos. Al rescribir Unix en C, fue posible llevarlo a las computadoras más poderosas PDP-11/45 y 11/70 que por ese entonces fabricaba DIGITAL. El resto, como se suele decir, es historia. Unix salió de los laboratorios y se introdujo en la corriente principal de los sistemas de cómputo, y muy pronto los más importantes fabricantes de computadoras estaban produciendo sus propias versiones.

 fue en un principio la solución a una simple necesidad. Al único software que Linus Torvalds -autor y principal encargado de mantenimiento de - podía acceder era Minix. Minix es un sistema operativo sencillo, del estilo Unix que se utiliza ampliamente como herramienta didáctica. Linus no estaba muy impresionado por sus características, y para solucionarlo se decidió a escribir su propio software. Como modelo, eligió a Unix pues ése era el sistema operativo con el cual estaba familiarizado en su vida estudiantil. Con una PC basada en el Intel 386 comenzó a escribir. El progreso fue rápido y esto lo entusiasmó, decidió entonces ofrecer el resultado de sus esfuerzos a otros estudiantes a través de la emergente red mundial de computadoras, que por ese entonces era utilizada principalmente por la comunidad académica. Otros vieron así el software y comenzaron a contribuir. La mayoría del nuevo software que se agregaba a través de las contribuciones tenía su origen en la solución a un problema que tenía quien contribuía. Poco tiempo después,  se había transformado en un sistema operativo. Es importante destacar que  no contiene código Unix pues se trata de una rescritura basada en los estándares POSIX publicados.  se construyó con el software GNU (GNU's Not Unix) producido por la Free Software Foundation (de Cambridge, Massachusetts en los Estados Unidos de América) y utiliza muchos de los productos de software de GNU.

La mayoría de las personas utilizan  simplemente como una herramienta, y con frecuencia lo instalan a partir de una de las buenas distribuciones basadas en CD-ROM. Un montón de usuarios de  lo utilizan para escribir aplicaciones, o para correr aplicaciones escritas por otros. Muchos leen ávidamente los COMOs2 y sienten la emoción del éxito cuando configuran correctamente alguna parte del sistema, y la frustración del fracaso cuando no pueden hacerlo. Una minoría es lo suficientemente avezada como para escribir controladores de dispositivo, y para ofrecer parches para el núcleo a Linus Torvalds, el creador y responsable del mantenimiento del núcleo de . Linus acepta las adiciones y modificaciones a los fuentes del núcleo, no importa de donde provengan o quien las envíe. Esto puede sonar como una invitación a la anarquía, pero Linus ejerce un estricto control de calidad e introduce el código nuevo por sí mismo. En un momento dado, sólo un puñado de gente contribuye fuentes al núcleo de .

La mayoría de los usuarios de  no se fijan en cómo funciona el sistema operativo, o cómo es que forma una unidad. Es una lástima que así suceda, porque una muy buena forma de aprender el funcionamiento de un sistema operativo es mirar como lo hace . Se trata de un sistema bien escrito, y no sólo eso, sino que sus fuentes están disponibles libremente (y gratis) para que les eche una ojeada. Las cosas están así dispuestas porque si bien los autores retienen los derechos («copyright») que les son propios sobre su software, permiten la libre distribución de los fuentes bajo la Licencia Pública de GNU, un proyecto de la Free Software Foundation. Sin embargo, a primera vista los fuentes pueden confundirnos un poco; verá usted directorios con nombres como kernel, mm y net, pero ¿que contienen? y ¿cómo funciona todo ese código? Lo que se necesita entonces es un entendimiento más amplio de la estructura general y propósito de . Para alcanzar esta finalidad, se ha escrito este libro: para promover una clara comprensión de como trabaja el sistema operativo . Deseo proporcionarle un modelo mental que le permita imaginarse que es lo que sucede en el sistema cuando usted copia un fichero de un lado a otro, o cuando lee su correo electrónico. Recuerdo muy bien el entusiasmo que sentí cuando me di cuenta por primera vez de la manera que el sistema funcionaba. Ese entusiasmo es lo que quiero transmitir a los lectores de este libro.

Mi compromiso con  comienza a finales de 1994 cuando visité a Jim Paradis que estaba trabajando para transportar  a sistemas basados en el procesador Alpha AXP. He trabajado para Digital Equipment Co. Limited desde 1984, la mayor parte en redes y comunicaciones, y en 1992 comencé a trabajar en la recientemente formada división Digital Semiconductor. La meta de esta división era meterse completamente en el mercado de fabricantes de chips, en particular el rango de microprocesadores Alpha AXP y sus placas de sistema, y vender esos productos fuera de Digital. Cuando escuché acerca de  por primera vez, inmediatamente vi una oportunidad para vender más hardware Alpha AXP. El entusiasmo de Jim era atrapante, y comencé a ayudarlo en el transporte del código. A medida que trabajaba en esto, empecé a apreciar cada vez más el sistema operativo, pero sobre todo a la comunidad de ingenieros que lo produce. Se trata de un grupo de gente destacable, con cualquier parámtero que se los mida, y al involucrarme con ellos y con el núcleo de  viví tal vez el período más satisfactorio del tiempo que dediqué al desarrollo de software. La gente muchas veces me pregunta acerca de  en el trabajo y en casa, y me hace muy feliz complacerles. Cuanto más uso  tanto en mi vida profesional como personal, más me transformo en un «celote» de . Debe advertir que utilizo el término «entusiasta», y no «fanático»; mi definición de entusiasta es una persona que reconoce la existencia de otros sistemas operativos, pero prefiere no utilizarlos. Como mi esposa, Gill, que utiliza Windows 95, cierta vez comentó: «Nunca hubiera pensado que tuviéramos el sistema operativo `de él' y el `de ella'». Para mí, como ingeniero,  satisface plenamente mis necesidades. Es una herramienta de ingeniería súper, flexible y adaptable. La mayoría del software disponible en forma libre puede compilarse fácilmente para , y con frecuencia sólo necesito obtener los binarios ya listos para ejecutar, o los instalo desde un CD ROM. ¿Qué otra cosa podría utilizar para aprender a programar en C++, Perl, o Java, y encima gratis?

Alpha AXP es sólo una de las varias plataformas sobre las cuales corre . La mayoría de los núcleos de  están funcionando sobre sistemas basados en procesadores Intel, pero los sistemas  que corren en plataformas de hardware distintas de Intel están creciendo en número, y están disponibles de manera más rutinaria. Entre ellos están Alpha AXP, MIPS, Sparc y PowerPC. Podría haber escrito este libro utilizando cualquiera de esas plataformas, pero mi conocimiento previo y experiencias técnicas con  se han dado con  en el Alpha AXP, y ése es el motivo por el cual en este libro se usa dicho hardware para ilustrar algunos puntos clave. Debe destacarse que cerca del 95% de los fuentes del núcleo de  es común a todas las plataformas de hadrware en las que corre. En el mismo sentido, alrededor del 95% de este libro trata de las partes del núcleo de  independientes de la máquina.

Perfil del lector

Este libro no hace ninguna presunción acerca del conocimiento o experiencia del lector. Creo que el interés en el tema en cuestión fomentará el proceso de autoaprendizaje cuando sea requerido. Dicho esto, debo agregar que para que el lector obtenga un beneficio real a partir de este material, ayudará un cierto grado de familiaridad con las computadoras, preferiblemente del tipo PC, y algún conocimiento del lenguaje de programación C.

Organización de este libro

Este libro no está pensado para usarse como un manual de los aspectos internos de . En lugar de ello, se trata de una introducción a los sistemas operativos en general, y a linux en particular. Cada uno de los capítulos siguen mi regla de «trabajar desde lo general a lo particular». Primero se presenta un panorama del subsistema en cuestión, antes de lanzarse sobre los detalles específicos.

En forma deliberada, no se describen los algoritmos del núcleo, que son los métodos que utiliza para hacer las tareas, en términos de la rutina_X llama a la rutina_Y, la cual incrementa el campo foo en la estructura de datos bar. Si desea ver eso, no tiene más que mirar el código. Cada vez que necesito comprender una porción de código, o tengo de explicársela a otra persona, con frecuencia comienzo por dibujar sus estructuras de datos en una pizarra. Por ello, he descripto con cierto detalle muchas de las estructuras de datos relevantes del núcleo.

Cada capítulo es relativamente independiente, como los subsistemas del núcleo de  que desribe cada uno de ellos. algunas veces, sin embargo, hay conexiones; por ejemplo, no se puede describir un proceso sin entender como funciona la memoria virtual.

El capítulo «Aspectos básicos del hardware» (Capítulo  hw-basics-chapter) nos da una breve introducción a la PC moderna. Un sistema operativo debe trabajar íntimamente asociado con el hardware del sistema que funciona como su cimiento. El sistema operativo necesita ciertos servicios que sólo pueden ser provistos por el hardware. Para entender completamente el sistema operativo , deberá usted entender los aspectos básicos del hardware subyacente.

El capítulo «Aspectos básicos del software» (Capítulo  sw-basics-chapter) introduce principios básicos de software y pone una mirada en los lenguajes ensamblador y C. Aquí se aprecian las herramientas que se utilizan para construir un sistema como  y se da un panorama de la finalidad y funciones de un sistema operativo.

El capítulo «Gestión de memoria» (Capítulo  mm-chapter) describe la forma en la cual  administra las memorias física y virtual en el sistema.

El capítulo «Procesos» (Capítulo  processes-chapter) describe que es un proceso, y como hace el núcleo para crear, gestionar y eliminar los procesos en el sistema.

Los procesos se comunican entre sí y con el núcleo a los fines de coordinar sus actividades. En  hay disponibles cierto número de mecanismos de comunicación inter-proceso (Inter-Process Communication: IPC) mechanisms. Dos de ellos son las señales y las tuberías; también se dispone del mecanismo de IPC conocido como tipo System V, y que toma ese nombre a partir de su entrega con el sistema Unix, en el cual apareció por vez primera. Estos mecanismos de comunicación entre procesos se describen en el Capítulo  IPC-chapter.

El estándar Peripheral Component Interconnect (PCI) se ha establecido firmemente en la actualidad como el bus de alta perfomance y bajo costo para las PCs. El capítulo sobre «PCI» (Capítulo  PCI-chapter) describe como hace el núcleo  para inicializar y utilizar los buses y dispositivos PCI existentes en el sistema.

El capítulo sobre «Interrupciones y gestión de interrupciones» (Capítulo  interrupt-chapter) nos muestra como maneja las interrupciones el núcleo . Si bien el núcleo tiene mecanismos e interfaces genéricos para la gestión de las interrupciones, parte de los detalles de esta gestión es dependiente del hardware y de las cuestiones específicas de la arquitectura.

Uno de los puntos fuertes de  es su capacidad de funcionar con muchos de los dispositivos de hardware disponibles para la PC moderna. El capítulo sobre «Controladores de dispositivos» (Capítulo  dd-chapter) describe como se controlan los dispositivos físicamente conectados al sistema.

El capítulo sobre el «Sistema de ficheros» (Capítulo  filesystem-chapter) describe como se mantienen los ficheros en el sistema de ficheros que les da sostén. Se describe el «Sistema de ficheros virtual» (Virtual File System: VFS) y como se gestionan los sistemas de ficheros reales desde el núcleo .

Redes y  son casi sinónimos. En cierta forma (muy real por cierto)  es un producto de la Internet o de la World Wide Web (WWW). Sus desarrolladores y usuarios utilizan la web para intercambiar información, ideas, código, y \ en sí mismo con frecuencia se utiliza para sustentar las necesidades en el tema redes que tienen las organizaciones. El Capítulo  networks-chapter describe como funciona  con los protocolos conocidos colectivamente como TCP/IP.

El capítulo «Mecanismos del núcleo» (Capítulo  kernel-chapter) nos muestra algunas de las tareas y mecanismos generales que el núcleo  tiene que proveer, de manera que las otras secciones del núcleo puedan trabajar efectivamente juntas.

El capítulo «Módulos» (Capítulo  modules-chapter) describe la manera en la cual el núcleo  puede cargar funciones dinámicamente , por ejemplo un sistema de ficheros, sólo cuando se necesitan.

El capítulo «Fuentes» (Capítulo  sources-chapter) describe dónde buscar una función en particular, entre todos los fuentes del núcleo.

Convenciones utilizadas en el libro

La siguiente es una lista de convenciones tipográficas que se utilizan a lo largo del libro.

tipo lineal identifica órdenes, u otro texto que deba
ser ingresado literalmente por el usuario.
tipo monoespaciado para referirnos a estructuras de datos,
o campos dentro de las estructuras de datos.

A lo largo del texto existen referencias a ciertas porciones de código dentro del árbol de directorios del núcleo de  (por ejemplo, la nota al márgen encerrada en un recuadro y adyacente a este texto). Si usted desea echar una ojeada al código por sí mismo, encontrará de utilidad estas referencias; todas las rferencias a los ficheros son relativas a /usr/src/linux. Tomemos foo/bar.c como ejemplo, entonces el nombre completo será /usr/src/linux/foo/bar.c. Si usted está utilizando  (y debería), entonces puede obtener una provechosa experiencia al mirar el código, y puede utilizar este libro como ayuda para entender tanto el código como sus múltiples estructuras de datos.

Marcas registradas

Caldera, OpenLinux y el logotipo ``C'' son marcas registradas de Caldera, Inc.

Caldera OpenDOS 1997 es una marca registrada de Caldera, Inc.

DEC es una marca registrada de Digital Equipment Corporation.

DIGITAL es una marca registrada de Digital Equipment Corporation.

Linux es una marca registrada de Linus Torvalds.

Motif es una marca registrada de The Open System Foundation, Inc.

MSDOS es una marca registrada de Microsoft Corporation.

Red Hat, glint y el logotipo Red Hat logo son marcas registradas de Red Hat Software, Inc.

UNIX es una marca registrada de X/Open.

XFree86 es una marca registrada de XFree86 Project, Inc.

X Window System es una marca registrada de el X Consortium y del Massachusetts Institute of Technology.

Agradecimientos

Debo agradecer a las muchas personas que han sido tan amables como para tomarse el tiempo para enviarme por correo electrónico sus comentarios acerca de este libro. He intentado incorporar dichos comentarios en cada nueva versión que he producido. Estoy especialmente agradecido a John Rigby y Michael Bauer que me proporcionaron completas y detalladas notas de revisión de todo el libro. Una tarea nada fácil.

Contents

Prefacio
1  Aspectos básicos del hardware
    1.1  La CPU
    1.2  Memoria
    1.3  Buses
    1.4  Controladores y periféricos
    1.5  Espacios de direcciones
    1.6  Cronómetros
2  Aspectos básicos del software
    2.1  Lenguajes de computadora
        2.1.1  Lenguajes ensambladores
        2.1.2  El Lenguaje de Programación C y su Compilador
        2.1.3  Enlazadores
    2.2  Qué es un sistema operativo?
        2.2.1  Gestión de memoria
        2.2.2  Procesos
        2.2.3  Controladores de unidad
        2.2.4  El Sistema de Ficheros
    2.3  Estructuras de datos del núcleo
        2.3.1  Lista Enlazadas
        2.3.2  Tablas Hash
        2.3.3  Interfaces Abstractos
3  Gestión de memoria
    3.1  Modelo Abstracto de Memoria Virtual
        3.1.1  Paginación por Demanda
        3.1.2  Intercambio (swapping)
        3.1.3  Memoria virtual compartida
        3.1.4  Modos de direccionamiento f í sico y virtual
        3.1.5  Control de acceso
    3.2  Caches
    3.3  Tablas de Páginas en Linux
    3.4  Asignación y liberación de páginas
        3.4.1  Asignación de páginas
        3.4.2  Liberación de páginas
    3.5  Proyección de Memoria (Memory Mapping)
    3.6  Paginación por Demanda
    3.7  La Cache de Páginas de Linux
    3.8  Intercambiando y Liberando Páginas
        3.8.1  Reduciendo el tama no de la Cache de Páginas y el Buffer Cache
        3.8.2  Intercambio de Páginas compartidas (System V Shared Memory Pages)
        3.8.3  Intercambiando y Descartando Páginas
    3.9  La cache de Intercambio
    3.10  Cargando Páginas de Intercambio
4  Procesos
    4.1  Procesos de Linux
    4.2  Identificadores
    4.3  Planificación
        4.3.1  Planificación en Sistemas Multiprocesador
    4.4  Ficheros
    4.5  Memoria Virtual
    4.6  Creación de un Proceso
    4.7  Tiempos y Temporizadores
    4.8  Ejecución de Programas
        4.8.1  ELF
            Bibliotecas ELF Compartidas
        4.8.2  Ficheros de Guión
5  Mecanismos de Comunicacion Interprocesos
    5.1  Se nales
    5.2  Tuber í as
    5.3  Enchufes
        5.3.1  Mecanismos IPC System V
        5.3.2  Colas de Mensajes
        5.3.3  Semaforos
        5.3.4  Memoria Compartida
6  PCI
    6.1  PCI Address Spaces
    6.2  PCI Configuration Headers
    6.3  PCI I/O and PCI Memory Addresses
    6.4  PCI-ISA Bridges
    6.5  PCI-PCI Bridges
        6.5.1  PCI-PCI Bridges: PCI I/O and PCI Memory Windows
        6.5.2  PCI-PCI Bridges: PCI Configuration Cycles and PCI Bus Numbering
    6.6  Linux PCI Initialization
        6.6.1  The Linux Kernel PCI Data Structures
        6.6.2  The PCI Device Driver
            Configuring PCI-PCI Bridges - Assigning PCI Bus Numbers
        6.6.3  PCI BIOS Functions
        6.6.4  PCI Fixup
            Finding Out How Much PCI I/O and PCI Memory Space a Device Needs
            Allocating PCI I/O and PCI Memory to PCI-PCI Bridges and Devices
7  Interrupciones y Manejo de Interrupciones
    7.1  Controladores de Interrupciones Programables
    7.2  Inicializando las Estructuras de Datos del Manejo de Interrupciones
    7.3  Manejo de Interrupciones
8  Device Drivers
    8.1  Polling and Interrupts
    8.2  Direct Memory Access (DMA)
    8.3  Memory
    8.4  Interfacing Device Drivers with the Kernel
        8.4.1  Character Devices
        8.4.2  Block Devices
    8.5  Hard Disks
        8.5.1  IDE Disks
        8.5.2  Initializing the IDE Subsystem
        8.5.3  SCSI Disks
            Initializing the SCSI Subsystem
            Delivering Block Device Requests
    8.6  Network Devices
        8.6.1  Initializing Network Devices
9  The File system
    9.1  The Second Extended File system (EXT2)
        9.1.1  The EXT2 Inode
        9.1.2  The EXT2 Superblock
        9.1.3  The EXT2 Group Descriptor
        9.1.4  EXT2 Directories
        9.1.5  Finding a File in an EXT2 File System
        9.1.6  Changing the Size of a File in an EXT2 File System
    9.2  The Virtual File System (VFS)
        9.2.1  The VFS Superblock
        9.2.2  The VFS Inode
        9.2.3  Registering the File Systems
        9.2.4  Mounting a File System
        9.2.5  Finding a File in the Virtual File System
        9.2.6  Creating a File in the Virtual File System
        9.2.7  Unmounting a File System
        9.2.8  The VFS Inode Cache
        9.2.9  The Directory Cache
    9.3  The Buffer Cache
        9.3.1  The bdflush Kernel Daemon
        9.3.2  The update Process
    9.4  The /proc File System
    9.5  Device Special Files
10  Networks
    10.1  An Overview of TCP/IP Networking
    10.2  The Linux TCP/IP Networking Layers
    10.3  The BSD Socket Interface
    10.4  The INET Socket Layer
        10.4.1  Creating a BSD Socket
        10.4.2  Binding an Address to an INET BSD Socket
        10.4.3  Making a Connection on an INET BSD Socket
        10.4.4  Listening on an INET BSD Socket
        10.4.5  Accepting Connection Requests
    10.5  The IP Layer
        10.5.1  Socket Buffers
        10.5.2  Receiving IP Packets
        10.5.3  Sending IP Packets
        10.5.4  Data Fragmentation
    10.6  The Address Resolution Protocol (ARP)
    10.7  IP Routing
        10.7.1  The Route Cache
        10.7.2  The Forwarding Information Database
11  Kernel Mechanisms
    11.1  Bottom Half Handling
    11.2  Task Queues
    11.3  Timers
    11.4  Wait Queues
    11.5  Buzz Locks
    11.6  Semaphores
12  Modules
    12.1  Loading a Module
    12.2  Unloading a Module
13  El código fuente del núcleo de Linux
A  Las estructuras de datos de Linux
B  El procesador AXP de Alpha
C  Lugares útiles en Web y FTP
D  The GNU General Public License
    D.1  Preamble
    D.2  Terms and Conditions
    D.3  How to Apply These Terms

Glosario
Bibliograf í a

List of Figures

    1.1  Una placa madre de PC t í pica.
    3.1  Modelo abstracto de traducción de memoria virtual a f í sica.
    3.2  Entrada de tabla de páginas del Alpha AXP
    3.3  Tablas de páginas de tras niveles
    3.4  La estructura de datos free_area
    3.5  Areas de Memoria Virtual
    3.6  La Cache de Páginas de Linux
    4.1  Los Ficheros de un Proceso
    4.2  La Memoria Virtual de un Proceso
    4.3  Formatos Binarios Registrados
    4.4  El Formato de Ficheros Ejecutable ELF
    5.1  Tuber í as
    5.2  Colas de Mensajes IPC System V
    5.3  Semaforos IPC System V
    5.4  Memoria Compartida IPC System V
    6.1  Example PCI Based System
    6.2  The PCI Configuration Header
    6.3  Type 0 PCI Configuration Cycle
    6.4  Type 1 PCI Configuration Cycle
    6.5  Linux Kernel PCI Data Structures
    6.6  Configuring a PCI System: Part 1
    6.7  Configuring a PCI System: Part 2
    6.8  Configuring a PCI System: Part 3
    6.9  Configuring a PCI System: Part 4
    6.10  PCI Configuration Header: Base Address Registers
    7.1  Un diagrama lógico del rutado de interrupciones
    7.2  Estructuras de Datos del Manejo de Interrupciones en Linux
    8.1  Character Devices
    8.2  Buffer Cache Block Device Requests
    8.3  Linked list of disks
    8.4  SCSI Data Structures
    9.1  Physical Layout of the EXT2 File system
    9.2  EXT2 Inode
    9.3  EXT2 Directory
    9.4  A Logical Diagram of the Virtual File System
    9.5  Registered File Systems
    9.6  A Mounted File System
    9.7  The Buffer Cache
    10.1  TCP/IP Protocol Layers
    10.2  Linux Networking Layers
    10.3  Linux BSD Socket Data Structures
    10.4  The Socket Buffer (sk_buff)
    10.5  The Forwarding Information Database
    11.1  Bottom Half Handling Data Structures
    11.2  A Task Queue
    11.3  System Timers
    11.4  Wait Queue
    12.1  The List of Kernel Modules

Capítulo 2
Aspectos básicos del hardware

Un sistema operativo debe trabajar íntimamente con el hardware que le sirve de cimientos. El sistema operativo necesita ciertos servicios que solo pueden suministrar el hardware. Para entender totalmente el sistema operativo Linux, se necesita entender los aspectos básicos del hardware que hay debajo. Este capítulo muestra una breve introducción a dicho hardware: el PC moderno.

Cuando el número de Enero de 1975 de la revista ``Popular Electronics'' salió impreso con una ilustración del Altair 8080 en su portada, comenzó una revolución. El Altair 8080, llamado así por el lugar de destino de uno de los primeros episodios de Star Trek, podía ser ensamblado por entusiastas de la electrónica doméstica por sólo unos $397. Con su procesador Intel 8080 y 256 bytes de memoria, pero sin pantalla ni teclado, era una baratija según los estándares actuales. Su inventor, Ed Roberts, acuñó el término ``computadora personal'' (personal computer) para describir su nuevo invento, pero el término PC se usa ahora para referirse a casi cualquier ordenador que una persona puede levantar del suelo sin necesidad de ayuda. Siguiendo esta definición, incluso algunos de los muy poderosos sistemas Alpha AXP son PCs.

Los hackers entusiastas vieron el potencial del Altair, y comenzaron a escribir software y construir hardware para él. Para esos primeros pioneros representaba libertad; liberarse de los enormes sistemas ``mainframe'' con sus colas de proceso, administrados y protegidos por una élite clerical. Grandes fortunas fueron hechas de la noche a la mañana por gente que abandonaba los estudios fascinados por este nuevo fenómeno, una computadora que se podía tener en casa sobre la mesa de la cocina. Una gran cantidad de hardware estaba apareciendo, cada uno diferente de los demás hasta cierto punto, y los hackers del software estaban felices de escribir software para esas nuevas máquinas. Paradójicamente, fue IBM quien cinceló el molde del PC moderno anunciando el IBM PC en 1981, y sirviéndoselo a los compradores a principios de 1982. Con su procesador Intel 8088, 64K de memoria (expandible a 256K), dos unidades de discos flexibles y un Adaptador de Gráficos en Color (Colour Graphics Adapter - CGA) de 80 caracteres por 25 líneas, no era muy poderoso según los estándares actuales, pero se vendió bien. Fue seguido, en 1983, por el IBM PC-XT, que contaba con el lujo de un disco duro de 10Mbyte. No pasó demasiado tiempo hasta que los clones del IBM PC comenzaron a ser producidos por una hueste de compañías como por ejemplo Compaq, y la arquitectura del PC se convirtió en un estándar de hecho. Este estándar de hecho ayudó a una multitud de empresas de hardware a competir entre ellas en un mercado creciente que, para alegría de los consumidores, mantuvo los precios bajos. Muchas de las características arquitectónicas del sistema de aquellos primitivos PCs se han mantenido hasta llegar al PC moderno. Por ejemplo, incluso el más poderoso sistema basado en el Pentium Pro de Intel arranca en el modo de direccionamiento del Intel 8086. Cuando Linus Torvalds comenzó a escribir lo que se convertiría en Linux, escogió el hardware más completo y de precio más razonable, un PC Intel 80386.


Figure 2.1: Una placa madre de PC típica.

Mirando un PC desde fuera, los componentes más obvios son una caja de sistema, un teclado, un ratón y un monitor de vídeo. En el frente de la caja del sistema hay algunos botones, un pequeño indicador que muestra algunos números y una disquetera. La mayoría de los sistemas actualmente tienen un CD ROM, y si uno siente que debe proteger sus datos, entonces habrá también una unidad de cinta para copias de seguridad. Esos dispositivos se conocen de modo colectivo como periféricos.

Aunque la CPU controla todo el sistema, no es el único dispositivo inteligente. Todos los controladores de periféricos, por ejemplo el controlador IDE, tienen algún nivel de inteligencia. Dentro del PC (Figura  2.1) se puede ver una placa madre que contienen la CPU o microprocesador, la memoria y un número de conectores (slots) para los controladores de periféricos ISA o PCI. Algunos de los controladores, por ejemplo el controlador de disco IDE, pueden estar integrados en la placa del sistema.

2.1  La CPU

La CPU, o mejor, microprocesador, es el corazón de cualquier computadora. El microprocesador calcula, realiza operaciones lógicas y gestiona flujos de datos leyendo instrucciones desde la memoria y ejecutándolas. En los primeros días de la informática los componentes funcionales del microprocesador eran unidades separadas (y grandes físicamente). Fue entonces cuando se acuñó el término Unidad Central de Proceso (Central Processing Unit). El microprocesador moderno combina esos componentes en un circuito integrado, grabado en una pequeña pieza de silicio. Los términos CPU, microprocesador y procesador se usan de manera intercambiable en este libro.

Los microprocesadores operan sobre datos binarios; estos son datos compuestos de unos y ceros. Estos unos y ceros corresponden a interruptores eléctricos que están encendidos o apagados. Igual que 42 es un número decimal que significa ``4 decenas y 2 unidades'', un número binario es una serie de dígitos binarios, cada uno representando una potencia de 2. En este contexto, una potencia significa el número de veces que un número es multiplicado por si mismo. 10 elevado a 1 ( 101 ) es 10, 10 elevado a 2 ( 102 ) es 10x10, 103 es 10x10x10 y así sucesivamente. 0001 en binario es 1 en decimal, 0010 en binario es 2 en decimal, 0011 en binario es 3, 0100 en binario es 4, y así sucesivamente. Así, 42 en decimal es 101010 en binario ó (2 + 8 + 32 ó 21 + 23+ 25 ). En vez de usar el binario para representar números en los programas de computadora, otra base, la hexadecimal, es la que se usa normalmente. En esta base, cada dígito representa una potencia de 16. Como los números decimales van sólo de 0 a 9, los números del 10 al 15 se representan con un único dígito usando las letras A, B, C, D, E y F. Por ejemplo, E en hexadecimal es 14 en decimal y 2A en hexadecimal es 42 en decimal ((dos veces 16) + 10). Usando la notación del lenguaje de programación C (como hago a lo largo de este libro) los números hexadecimales llevan el prefijo ``0x''; el hexadecimal 2A se escribe como 0x2A .

Los microprocesadores pueden realizar operaciones aritméticas como sumar, multiplicar y dividir, y operaciones lógicas como ``¿es X mayor que Y?''.

La ejecución del procesador viene gobernada por un reloj externo. Este reloj, el reloj del sistema, envía pulsos de reloj regulares al procesador y, a cada pulso de reloj, el procesador hace algún trabajo. Por ejemplo, un procesador podría ejecutar una instrucción a cada pulso de reloj. La velocidad del procesador se describe en términos de la frecuencia de pulsos del reloj del sistema. Un procesador de 100Mhz recibirá 100 000 000 pulsos de reloj cada segundo. Puede llevar a confusión describir la potencia de una CPU por su frecuencia de reloj ya que diferentes procesadores realizan diferentes cantidades de trabajo a cada pulso de reloj. De todos modos, si el resto de las características son iguales, una mayor velocidad de reloj significa un procesador más potente. Las instrucciones ejecutadas por el procesador son muy simples; por ejemplo ``copia en el registro Y el contenido de la posición X de la memoria''. Los registros son el almacén interno del microprocesador, se usan para almacenar datos y realizar operaciones sobre ellos. Las operaciones realizadas pueden hacer que el procesador deje de hacer los que estaba haciendo y salte a otra instrucción en otra parte de la memoria. Estos pequeños ladrillos dan al microprocesador moderno un poder casi ilimitado ya que puede ejecutar millones o incluso miles de millones de instrucciones por segundo.

Las instrucciones deben ser obtenidas de la memoria para irlas ejecutando. Las propias instrucciones pueden hacer referencia a datos en la memoria, y esos datos deben ser obtenidos de la memoria y guardados en ella cuando se requiera.

El tamaño, número y tipo de registros dentro de un microprocesador depende enteramente de su tipo. Un procesador Intel 486 tiene un conjunto diferente de registros que un procesador Alpha AXP; para empezar, los del Intel son de 32 bits de ancho, y los del Alpha AXPson de 64 bits. En general, sin embargo, cualquier procesador tendrá un número de registros de propósito general y un número menor de registros dedicados. La mayoría de los procesadores tiene los siguientes registros de propósito específico o dedicados:

Contador de Programa (Program Counter - PC)
Este registro contiene la dirección de la siguiente instrucción a ejecutar. Los contenidos del PC se incrementan automáticamente cada vez que se obtiene una instrucción.

Puntero de Pila (Stack Pointer - SP)
Los procesadores deben tener acceso a grandes cantidades de memoria de acceso aleatorio (random access memory - RAM) externa para lectura y escritura, que facilite el almacenamiento temporal de datos. La pila es una manera de guardar y recuperar fácilmente valores temporales en la memoria externa. Normalmente, los procesadores tienen instrucciones especiales que permiten introducir (push) valores en la pila y extraerlos (pop) de nuevo más tarde. La pila funciona en un régimen de ``último en entrar, primero en salir'' (last in first out - LIFO). En otras palabras, si uno introduce dos valores, x e y, en una pila, y luego extrae un valor de dicha pila, obtendrá el valor y.

Las pilas de algunos procesadores crecen hacia el final de la memoria mientras que las de otros crecen hacia el principio, o base, de la memoria. Algunos procesadores permiten los dos tipos, como por ejemplo los ARM.

Estado del Procesador (Processor Status - PS)
Las instrucciones pueden dar lugar a resultados; por ejemplo ``es el contenido del registro X mayor que el contenido del registro Y?'' dará como resultado verdadero o falso. El registro PS mantiene esa y otra información sobre el estado del procesador. Por ejemplo, la mayoría de los procesadores tienen al menos dos modos de operación, núcleo (o supervisor) y usuario. El registro PS mantendrá información que identifique el modo en uso.

2.2  Memoria

Todos los sistemas tienen una jerarquía de memoria, con memoria de diferentes velocidades y tamaños en diferentes puntos de la jerarquía. La memoria más rápida se conoce como memoria caché (o tampón) y es una memoria que se usa para almacenar temporalmente contenidos de la memoria principal. Este tipo de memoria es muy rápida pero cara, por tanto la mayoría de los procesadores tienen una pequeña cantidad de memoria caché en el chip y más memoria caché en el sistema (en la placa). Algunos procesadores tienen una caché para contener instrucciones y datos, pero otros tienen dos, una para instrucciones y la otra para datos. El procesador Alpha AXP tiene dos memorias caché internas; una para datos (la Caché-D) y otra para instrucciones (la Caché-I). La caché externa (o Caché-B) mezcla los dos juntos. Finalmente está la memoria principal, que comparada con la memoria caché externa es muy lenta. Comparada con la caché en la CPU, la memoria principal se arrastra.

Las memorias caché y principal deben mantenerse sincronizadas (coherentes). En otras palabras, si una palabra de memoria principal se almacena en una o más posiciones de la caché, el sistema debe asegurarse de que los contenidos de la caché y de la memoria sean los mismos. El trabajo de coherencia de la caché es llevado a cabo parcialmente por el hardware y parcialmente por el sistema operativo. Esto es cierto también para un número de tareas principales del sistema, donde el hardware y el software deben cooperar íntimamente para alcanzar sus objetivos.

2.3  Buses

Los componentes individuales de la placa del sistema están conectados entre sí por sistemas de conexión múltiple conocidos como buses. El bus de sistema está dividido en tres funciones lógicas; el bus de direcciones, el bus de datos y el bus de control. El bus de direcciones especifica las posiciones de memoria (direcciones) para las transferencias de datos. El bus de datos contiene los datos transferidos El bus de datos es bidireccional; permite escribir datos en la CPU y leerlos desde la CPU. El bus de control contiene varias lineas que se usan para dirigir señales de sincronismo y control a través del sistema. Existen muchos tipos de bus, por ejemplo los buses ISA y PCI son formas populares de conectar periféricos al sistema.

2.4  Controladores y periféricos

Los periféricos son dispositivos reales, como tarjetas gráficas o discos controlados por chips controladores que se encuentran en la placa del sistema, o en tarjetas conectadas a ella. Los discos IDE son controlados por el chip controlador IDE, y los discos SCSI por los chips controladores de disco SCSI, y así sucesivamente. Estos controladores están conectados a la CPU y entre ellos por una variedad de buses. La mayoría de los sistemas construidos actualmente usan buses PCI e ISA para conectar entre sí los principales componentes del sistema. Los controladores son procesadores como la propia CPU, se pueden ver como asistentes inteligentes de la CPU. La CPU tiene el control sobre todo el sistema.

Todos los controladores son diferentes, pero usualmente tienen registros que los controlan. El software que se ejecute en la CPU debe ser capaz de leer y escribir en esos registros de control. Un registro puede contener un estado que describa un error. Otro puede ser usado para propósitos de control; cambiando el modo del controlador. Cada controlador en un bus puede ser accedido individualmente por la CPU, esto es así para que el software gestor de dispositivos pueda escribir en sus registros y así controlarlo, La banda IDE es un buen ejemplo, ya que ofrece la posibilidad de acceder a cada unidad en el bus por separado. Otro buen ejemplo es el bus PCI, que permite acceder a cada dispositivo (por ejemplo una tarjeta gráfica) independientemente.

2.5  Espacios de direcciones

El bus del sistema conecta la CPU con la memoria principal y está separado de los buses que conectan la CPU con los periféricos. El espacio de memoria en el que existen los periféricos hardware se conoce colectivamente como espacio de I/O (por Input/Output, Entrada/Salida). El espacio de I/O puede estar subdividido a su vez, pero no nos preocuparemos demasiado sobre eso de momento. La CPU puede acceder a la memoria en el espacio del sistema y a la memoria en el espacio de I/O, mientras que los controladores sólo pueden acceder a la memoria en el espacio del sistema indirectamente, y sólo con la ayuda de la CPU. Desde el punto de vista del dispositivo, digamos un controlador de disquetes, verá sólo el espacio de direcciones en el que se encuentran sus registros de control (ISA) y no la memoria del sistema. Típicamente, una CPU tendrá instrucciones separadas para acceder al espacio de memoria y al espacio de I/O. Por ejemplo, puede haber una instrucción que signifique ``lee un byte de la dirección de I/O 0x3f0 y ponlo en el registro X''. Así es exactamente como controla la CPU a los periféricos hardware del sistema, leyendo y escribiendo en sus registros en el espacio de I/O. En qué lugar del espacio I/O tienen sus registros los periféricos comunes (controlador IDE, puerta serie, controlador de disco flexible, y demás) ha sido definido por convenio a lo largo de los años conforme la arquitectura del PC se ha desarrollado. Sucede que la dirección 0x3f0 del espacio I/O es la dirección de uno de los registros de control de la puerta serie (COM1).

Existen ocasiones en las que los controladores necesitan leer o escribir grandes cantidades de datos directamente desde o a la memoria del sistema. Por ejemplo, cuando se escriben datos del usuario al disco duro. En este caso, se usan controladores de Acceso Directo a Memoria (Direct Memory Access - DMA) para permitir que los periféricos hardware accedan directamente a la memoria del sistema, pero este acceso está bajo el estricto control y supervisión de la CPU.

2.6  Cronómetros

Todos los sistemas operativos necesitan saber la hora, y así, el PC moderno incluye un periférico especial llamado el Reloj de Tiempo Real (Real Time Clock - RTC). Este dispositivo suministra dos cosas: una hora del día fiable y un cronómetro preciso. El RTC tiene su propia batería, de forma que siga funcionado incluso cuando el PC no esté encendido, así es como el PC ``sabe'' siempre la fecha y hora correctas. El cronómetro permite que el sistema operativo planifique de manera precisa el trabajo esencial.

Capítulo 3
Aspectos básicos del software

Un programa es un conjunto de instrucciones de computadora que realizan una tarea particular. Puede estar escrito en ensamblador, un lenguaje de muy bajo nivel, o en un lenguaje de alto nivel, independiente de la máquina, como el lenguaje de programación C. Un sistema operativo es un programa especial que permite al usuario ejecutar aplicaciones como hojas de cálculo y procesadores de texto. Este capítulo introduce los principios básicos de la programación y da una idea general de los objetivos y funciones de un sistema operativo.

3.1  Lenguajes de computadora

3.1.1  Lenguajes ensambladores

Las instrucciones que una CPU lee desde la memoria y después ejecuta no son comprensibles para los seres humanos; son códigos de máquina que dicen al ordenador qué hacer precisamente. El número hexadecimal 0x89E5 es una instrucción de Intel 80486 que copia el contenido del registro ESP al registro EBP. Una de las primeras herramientas de software inventadas para los primeros ordenadores fue un ensamblador, un programa que toma un fichero fuente leíble por los humanos y lo ensambla en código máquina. Los lenguajes ensambladores se ocupan explícitamente de los registros y las operaciones sobre los datos y son específicos de un microprocesador particular. El lenguaje ensamblador para un microprocesador X86 es muy diferente del ensamblador de un microprocesador Alpha AXP. El código ensamblador siguiente, para Alpha AXP, muestra el tipo de operaciones que un programa puede realizar:
    ldr r16, (r15)    ; Línea 1
    ldr r17, 4(r15)   ; Línea 2
    beq r16,r17,100   ; Línea 3
    str r17, (r15)    ; Línea 4
100:                  ; Línea 5

La primera sentencia (en la línea 1) carga el valor del registro 16 de la dirección guardada en el registro 15. La siguiente instrucción carga el valor del registro 17 desde la siguiente posición en la memoria. La línea 3 compara el contenido del registro 16 con el del 17 y, si son iguales, salta a la etiqueta 100. Si los registros no contienen el mismo valor, el programa sigue por la línea 4 donde el contenido de r17 es salvado en la memoria. Si los registros contienen el mismo valor entonces ningún dato necesita ser salvado. Los programas en ensamblador son muy tediosos y difíciles de escribir, y sujetos a errores. Muy poco en el núcleo de Linux está escrito en lenguaje ensamblador, y aquellas partes que lo están han sido escritas sólo por eficiencia y son específicas de un procesador particular.

3.1.2  El Lenguaje de Programación C y su Compilador

Escribir programas grandes en lenguaje ensamblador es una tarea difícil y larga. Es propensa a errores y el programa resultante no es portable, al estar atado a una familia de procesadores particular. Es mucho mejor usar un lenguaje independiente de la máquina, como C. C le permite describir los programas en términos de sus algoritmos lógicos y de los datos con los que operan. Unos programas especiales llamados compiladores leen el programa en C y lo traducen a lenguaje ensamblador, generando el código específico de la máquina a partir de éste. Un buen compilador puede generar instrucciones en ensamblador casi tan eficientes como la escritas por un buen programador de ensamblador. La mayor parte del núcleo de Linux está escrita en lenguaje C. El fragmento de C siguiente
        if (x != y)
                x = y ;

hace exactamente las mismas operaciones que el ejemplo anterior de código ensamblador. Si el contenido de la variable x no es el mismo que el de la variable y el contenido de y es copiado a x. El código en C está organizado en rutinas, de las cuales cada una cumple una función. Las rutinas pueden devolver cualquier valor o tipo de dato soportado por C. Los programas grandes, como el núcleo de Linux, contienen muchos módulos separados de código C, cada uno con sus propias rutinas y estructuras de datos. Estos módulos de código fuente en C se agrupan en funciones lógicas, como el código para el manejo del sistemas de ficheros.

C soporta muchos tipos de variables; una variable es una posición en la memoria que puede ser referenciada por un nombre simbólico. En el fragmento anterior de C, x e y hacen referencia a las posiciones en la memoria. El programador no se preocupa de dónde son guardadas las variables, es el enlazador (véase abajo) quién se preocupa de ésto. Las variables contienen diferentes tipos de datos; enteros o números de coma flotante, así como punteros.

Los punteros son variables que contienen la dirección, es decir la posición en memoria, de otro dato. Considere una variable llamada x que reside en la memoria en la dirección 0x80010000. Podría tener un puntero, llamado px, que apunta a x. px podia residir en la dirección 0x80010030. El valor de px sería 0x80010000: la dirección de la variable x.

C le permite unir variables relacionadas en estructuras de datos. Por ejemplo,

        struct {
                int i ;
                char b ;
        } mi_estruc ;

es una estructura llamada miestruc que contiene dos elementos, un entero (32 bits de almacenamiento de datos) llamado i y un caracter (8 bits de datos) llamado b.

3.1.3  Enlazadores

Los enlazadores son programas que enlazan varios módulos objeto y las bibliotecas para formar un único programa coherente. Los módulos objeto son la sálida en código máquina del ensamblador o del compilador y contienen código máquina ejecutable y datos junto a información que permite al enlazador combinar los módulos para formar un programa. Por ejemplo, un módulo puede contener todas las funciones de base de datos de un programa, y otro las funciones de manejo de los argumentos de la línea de comandos. Los enlazadores arreglan las referencias entre esto módulos objeto, donde una rutina o estructura de datos referenciados por un módulo está realmente en otro. El núcleo de Linux es un programa único y grande, enlazado a partir de sus numerosos módulos objeto constituyentes.

3.2  ¿Qué es un sistema operativo?

Sin el software la computadora es sólo una montaña de componentes electrónicos que disipa calor. Si el hardware es el corazón de una computadora, el software es su alma. Un sistema operativo es una colección de programas del sistema que permiten al usuario ejecutar aplicaciones. El sistema operativo hace abstracción del hardware del sistema y presenta a los usuarios del sistema y a sus aplicaciones una máquina virtual. En un sentido muy auténtico, el software da el carácter del sistema. La mayor parte de los PCs pueden ejecutar uno o varios sistemas operativos y cada uno puede tener una apariencia y comportamiento muy diferentes. Linux está hecho de varias piezas funcionales diferentes que, combinadas, forman el sistema operativo. Una parte obvia del Linux es el núcleo en sí; pero incluso éste sería inútil sin bibliotecas o intérpretes de comandos.

Para empezar a entender qué es un sistema operativo, considere lo que ocurre cuando teclea el comando aparentemente simple:


$ ls
Mail            c               images          perl
docs            tcl
$ 

El $ es un inductor puesto por el shell (en este caso bash). Esto significa que está esperando que usted, el usuario, teclee algún comando. Escribir ls hace que el controlador del teclado reconozca qué teclas han sido pulsadas. El controlador de teclado las pasa al shell que procesa el comando, buscando un ejecutable del mismo nombre. Lo encuentra en /bin/ls. Los servicios del núcleo son usados para cargar la imagen ejecutable de ls en memoria y empezar a ejecutarla. Ésta realiza llamadas al subsistema de ficheros del kernel para ver qué ficheros están disponibles. El sistema de ficheros puede hacer uso de la información retenida en la cache, o usar el controlador de la unidad de disco para leer esta información desde disco. Puede incluso hacer que un controlador de red intercambie información con una máquina remota a la que tiene acceso (los sistemas de ficheros pueden ser montados remotamente con el Networked File System o NFS (Sistema de Ficheros en Red)). Independientemente de dónde esté la información, ls la escribe y el controlador de vídeo la visualiza en la pantalla.

Todo lo anterior parece bastante complicado, pero muestra que incluso los comandos más sencillos revelan que un sistema operativo está de hecho cooperando con un conjunto de funciones que juntas le dan a usted, el usuario, una visión coherente del sistema.

3.2.1  Gestión de memoria

Si los recursos fuesen infinitos, como por ejemplo la memoria, muchas de las cosas que hace un sistema operativo serían redundantes. Uno de los trucos básicos de un sistema operativo es la capacidad de hacer que una memoria física limitada se comporte como más memoria. Esta memoria aparentemente amplia es conocida como memoria virtual. La idea es que el software ejecutándose en el sistema sea inducido a creer que está ejecutándose en un montón de memoria. El sistema divide la memoria en páginas fácilmente gestionadas e intercambia estas páginas con el disco mientras el sistema funciona. El software no lo nota gracias a otro truco, el multi-proceso.

3.2.2  Procesos

Se puede pensar en un proceso como en un programa en acción; cada proceso es una entidad separada que está ejecutando un programa en particular. Si se fija en los procesos de su sistema Linux, verá que son bastantes. Por ejemplo, escribir ps muestra los procesos siguientes en mi sistema:

$ ps
  PID TTY STAT  TIME COMMAND
  158 pRe 1     0:00 -bash
  174 pRe 1     0:00 sh /usr/X11R6/bin/startx
  175 pRe 1     0:00 xinit /usr/X11R6/lib/X11/xinit/xinitrc --
  178 pRe 1 N   0:00 bowman
  182 pRe 1 N   0:01 rxvt -geometry 120x35 -fg white -bg black
  184 pRe 1 <   0:00 xclock -bg grey -geometry -1500-1500 -padding 0
  185 pRe 1 <   0:00 xload -bg grey -geometry -0-0 -label xload
  187 pp6 1     9:26 /bin/bash
  202 pRe 1 N   0:00 rxvt -geometry 120x35 -fg white -bg black
  203 ppc 2     0:00 /bin/bash
 1796 pRe 1 N   0:00 rxvt -geometry 120x35 -fg white -bg black
 1797 v06 1     0:00 /bin/bash
 3056 pp6 3 <   0:02 emacs intro/introduction.tex
 3270 pp6 3     0:00 ps
$     

Si mi equipo tuviera varias CPUs, cada proceso podría (al menos teóricamente) ejecutarse en una CPU distinta. Desafortunadamente, sólo hay una, así que el sistema operativo usa el truco de ejecutar cada proceso en orden durante un corto período de tiempo. Este periodo de tiempo es conocido como fracción de tiempo. Este truco es conocido como multiproceso o planificación y engaña a cada proceso, haciéndole creer que es el único. Los procesos son protegidos el uno del otro para que si uno se cuelga o funciona incorrectamente no afecte a los demás. El sistema operativo consigue esto dando a cada proceso un espacio de direccionamiento único, al que sólo él tiene acceso.

3.2.3  Controladores de unidad

Los controladores de unidad forman la mayor parte del núcleo de Linux. Como otras partes del sistema operativo, operan en un entorno muy privilegiado y pueden causar desastres si hacen las cosas mal. El controlador de la unidad supervisa la interacción entre el sistema operativo y la unidad de hardware que controla. Por ejemplo, el sistema de ficheros usa la interfaz general de unidades por bloques cuando escribe datos a un disco IDE. El controlador se ocupa de los detalles y hace que las cosas específicas de la unidad ocurran. Los controladores de unidad son específicos del chip controlador que están usando; por eso necesita, por ejemplo, un controlador NCR810 SCSI cuando su sistema tiene una controladora NCR810 SCSI.

3.2.4  El Sistema de Ficheros

En Linux, al igual que en Unix , no se accede a los diferentes sistemas de ficheros que el sistema puede usar mediante indentificadores de unidad (como el número de la unidad o su nombre), sino que son combinados en un único árbol jerárquico que representa el sistema de ficheros como una entidad individual. Linux añade cada sistema de ficheros en su árbol único cuando es montado en un directorio, como por ejemplo /mnt/cdrom. Una de las características más importante de Linux es su soporte para muchos sistemas de ficheros diferentes. Ésto lo hace muiy flexible y capaz de coexistir con otros sistemas operativos. El sistema de ficheros más popular de Linux es el EXT2, y éste es el soportado por la mayor parte de las distribuciones de Linux.

Un sistema de ficheros da al usuario una visión ordenada de los ficheros y directorios guardados en los discos duros del sistema, independientemente del tipo de su sistema y de las características de la unidad física subyacente. Linux soporta transparentemente muchos sistemas diferentes (por ejemplo MS-DOS y EXT2) y presenta todos los ficheros y sistemas de ficheros montados como un único árbol virtual. Por esto, los usuarios y los procesos no necesitan generalmente saber en qué tipo de sistema de ficheros está algún fichero, tan sólo lo usan.

Los controladores de unidades por bloques diferencian entre los tipos de unidad física (por ejemplo, IDE y SCSU) y, al menos en lo que concierne al sistema de ficheros, las unidades físicas son sólo colecciones lineales de bloques de datos. El tamaño de bloque varía entre las unidades; por ejemplo, 512 bytes es una medida común en los ``floppy disc'', mientras que 1024 son más corrientes en las unidades IDE, y, en general, esto es ocultado a los usuarios del sistema. Un sistema de ficheros EXT2 tiene el mismo aspecto, independientemente de la unidad que lo contenga.

3.3  Estructuras de datos del núcleo

El sistema operativo debe guardar mucha información referente al estado actual del sistema. A medida que las cosas van pasando dentro del sistema, estas estructuras de datos deben ser modificadas para ajustarse a la realidad. Por ejemplo, un proceso puede ser creado cuando el usuario entra en el sistema. El núcleo debe crear una estructura de datos que represente al nuevo proceso y enlazarlo a las estructuras de datos que representan a los otro procesos del sistema.

Estas estructuras se guardan sobre todo en la memoria física y son accesibles sólo por el núcleo y sus subsistemas. Las estructuras de datos contienen datos y punteros, es decir la dirección de otras estructuras de datos o de rutinas. Tomadas en su conjunto, las estructuras de datos usadas por el núcleo de Linux pueden parecer muy confusas. Cada estructura de datos tiene su interés y a pesar de que algunas son usadas por varios subsistemas del núcleo, son más sencillas de lo que parecen a primera vista.

Comprender el núcleo de Linux se basa en la comprensión de las estructuras de datos y del uso que las distintas funciones del núcleo hacen de ellos. Este libro basa su descripción del núcleo de Linux en sus estructuras de datos. Habla de cada subsistema del núcleo en términos de sus algoritmos, sus métodos, y su uso de las estructuras de datos del núcleo.

3.3.1  Lista Enlazadas

Linux usa cierto número de técnicas de ingeniería de software para enlazar entre sí las estructuras de datos. Si cada estructura describe una única instancia u ocurrencia de algo, por ejemplo un proceso o una unidad de red, el núcleo debe ser capaz de encontrar el resto. En una lista enlazada un puntero raiz contiene la dirección de la primera estructura de datos, o elemento de la lista, y cada estructura de datos contiene un puntero al siguiente elemento de la lista. El puntero del último elemento contiene 0 o NULL para mostrar que es el final de la lista. En una lista doblemente enlazada, cada elemento contiene a la vez un puntero al siguiente elemento y al anterior de la lista. El uso de listas doblemente enlazadas facilita la adición o el borrado de elementos en el medio de la lista, aunque necesita más accesos a memoria. Ésta es una elección a la que se enfrenta a menudo un sistema operativo: accesos a memoria frente a ciclos de CPU.

3.3.2  Tablas Hash

Las listas enlazadas son una manera útil de unir estructuras de datos entre sí, pero recorrerlas puede ser ineficiente. Si estuviese buscando un elemento en particular, podría fácilmente tener que buscar por toda la lista antes de encontrar el que necesita. Linux usa otras técnicas, llamadas técnicas hash, para evitar estos problemas. Una tabla hash es un array o vector de punteros. Un array es simplemente un conjunto de cosas que vienen una detrás de otra en la memoria. Se podría decir que una estantería es un array de libros. Se accede a los arrays por un índice; éste es el desplazamiento (offset) dentro del array. Llevando la analogía de la estantería de biblioteca más lejos, podríamos describir cada libro por su posición en la estantería; se puede pedir el quinto libro.

Una tabla hash es una array de punteros a estructuras de datos cuyo índice deriva de la información contenida en éstas. Si tuviese estructuras de datos sobre la población de un pueblo, podría usar la edad de cada persona como índice. Para encontrar los datos de una persona en particular, podría usar su edad como índice de la tabla hash, para seguir a continuación el puntero a la estructura de datos con los detalles de la persona. Desafortunadamente, mucha gente puede tener la misma edad en un pueblo, y el puntero de la tabla hash se puede transformar en un puntero a una lista de estructuras con los datos de personas de la misma edad. Sin embargo, la búsqueda sigue siendo más rápida en estas listas cortas que mirar por todas las estructuras de datos en orden secuencial.

Una tabla hash acelera el acceso a estructuras accedidas con frecuencia; Linux usa frecuentemente las tablas hash para implementar caches. Los caches son información útil a la que necesita tener un acceso rápido, y son habitualmente una pequeña parte de toda la información disponible. Los datos son almacenados en el cache porque el núcleo accede a ellos con frecuencia. Un inconveniente de los caches es que son más difíciles de usar y de mantener que las listas enlazadas o las tablas hash. Si una estructura de datos puede ser encontrada en el cache (esto es conocido como un acierto de cache), todo va bien. Si no, debe buscarse por todas las estructuras, y si existe, debe ser añadida al cache. Al añadir nuevos datos al cache, otros datos del cache pueden ser descartados. Linux debe decidir cuáles; el peligro es que el descartado sea el siguiente en ser necesitado.

3.3.3  Interfaces Abstractos

El núcleo de Linux hace a menudo abstracción de sus interfaces. Una interfaz es una colección de rutinas y estructuras de datos que operan de una forma determinada. Por ejemplo, todos los controladores de unidades de red deben proporcionar ciertas rutinas que operan sobre las estructuras de datos. De esta forma las partes genéricas del código pueden usar los servicios (interfaces) de las partes espécificas. La parte de red, por ejemplo, es genérica y la soporta el código específico de la unidad conforme a la interfaz estandar.

A menudo estos niveles inferiores se registran en los superiores durante el arranque. Este registro normalmente consiste en añadir una estructura de datos a una lista enlazada. Por ejemplo cada sistema de ficheros del núcleo se registra durante el arranque, o, si usa módulos, cuando es usado por primera vez. Puede ver qué sistemas de ficheros se han registrado viendo el fichero /proc/filesystems. El registro de estructuras de datos incluye a menudo punteros a funciones. Éstos representan las direcciones de las funciones que hacen tareas específicas. Tomando de nuevo el sistema de ficheros como ejemplo, la estructura de datos que cada sistema de ficheros pasa al núcleo de Linux cuando se registra incluye la dirección de una rutina específica que debe ser llamada cada vez que este sistema de ficheros es montado.

Capítulo 4
Gestión de memoria

El subsistema de gestión de memoria es una de las partes más importantes del sistema operativo. Ya desde los tiempos de los primeros ordenadores, existió la necesidad de disponer de más memoria de la que físicamente existía en el sistema. Entre las varias estrategias desarrolladas para resolver este problema, la de más éxito ha sido la memoria virtual. La memoria virtual hace que que sistema parezca disponer de más memoria de la que realmente tiene compartiéndola entre los distintos procesos conforme la necesitan.

La memoria virtual hace más cosas aparte de ampliar la memoria del ordenador. El subsistema de gestión de memoria ofrece:

Espacio de direcciones grande
El sistema operativo hace que el sistema parezca tener una gran cantidad de memoria. La memoria virtual puede ser muchas veces mayor que la memoria física del sistema,

Protección
Cada proceso del sistema tiene su propio espacio de direcciones virtuales. 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 sobre-escritos por aplicaciones perversas.

Proyección de Memoria (Memory Mapping)
La proyección de memoria se utiliza para asignar un fichero sobre el espacio de direcciones de un proceso. En la proyección de memoria, el contenido del fichero se ``engancha'' directamente sobre el espacio de direcciones virtual del proceso.

Asignación Equitativa de Memoria Física
El subsistema de gestión de memoria permite 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 que necesitan.

Memoria virtual compartida
Aunque la memoria virtual permite que cada proceso tenga un espacio de memoria separado (virtual), hay veces que es necesario que varios procesos compartan memoria. Por ejemplo pueden haber varios procesos del sistema ejecutando el interprete de ordenes bash. En lugar de tener varias copias del bash, una en cada memoria virtual de cada proceso, es mejor sólo tener una sola copia en memoria física y que todos los procesos que ejecuten bash la compartan. Las bibliotecas dinámicas son otro ejemplo típico de código ejecutable compartido por varios procesos.

Por otra parte, la memoria compartida se puede utilizar como un mecanismo de comunicación entre procesos (Inter Process Communication IPC), donde dos o más procesos intercambian información vía memoria común a todos ellos. Linux ofrece el sistema de comunicación entre procesos de memoria compartida de Unix  System V.

4.1  Modelo Abstracto de Memoria Virtual


Figure 4.1: Modelo abstracto de traducción de memoria virtual a física.

Antes de considerar los métodos que Linux utiliza para implementar la memoria virtual, es interesante estudiar un modelo abstracto que no esté plagado de pequeños detalles de implementación.

Conforme el procesador va ejecutando un programa lee instrucciones de la memoria y las decodifica. Durante la decodificación de la instrucción puede necesitar cargar o guardar el contenido de una posición de memoria. El procesador ejecuta la instrucción y pasa a la siguiente instrucción del programa. De esta forma el procesador está siempre accediendo a memoria tanto para leer instrucciones como para cargar o guardar datos.

En un sistema con memoria virtual, todas estas direcciones son direcciones virtuales y no direcciones físicas. Estas direcciones virtuales son convertidas en direcciones físicas por el procesador utilizando para ello información guardada en un conjunto de tablas mantenidas por el sistema operativo.

Para hacer la traducción más fácil, tanto la memoria virtual como la física están divididas en trozos de un tamaño manejable llamados páginas. Estas páginas son todas del mismo tamaño, en principio no necesitarían serlo pero de no serlo la administración del sistema se complicaría muchísimo. Linux en un sistema Alpha AXP utiliza páginas de 8 Kbytes, y en un sistema Intel x86 utiliza páginas de 4 Kbytes. Cada una de estas páginas tiene asociado un único número; el número de marco de página (PFN). En este modelo de paginación, una dirección virtual está compuesta de dos partes: un desplazamiento y un número de página virtual. Si el tamaño de página es de 4Kbytes, los bits 11:0 de la dirección de memoria virtual contienen el desplazamiento y los restantes bits desde el bit 12 son el número de marco de página virtual. Cada vez que el procesador encuentra una dirección virtual ha de extraer el desplazamiento y el número de marco de página. El procesador tiene que traducir el número de marco de la página virtual a la física y luego acceder a la posición correcta dentro de la página física. Para hacer todo esto, el procesador utiliza la tabla de páginas.

En la Figura  4.1 se muestra el espacio de direcciones virtuales de dos procesos, proceso X y proceso Y, cada uno con su propia tabla de páginas. Estas tablas de páginas asocian las páginas de memoria virtual de cada proceso sobre páginas físicas. Se puede ver que el número 0 de marco de página virtual del proceso X se asocia al número de marco de página físico 1 y que el número de marco de página virtual 1 de proceso Y está asociado en el número de marco de página físico 4. La tabla de páginas teórica contienen la siguiente información:

A la tabla de páginas se accede utilizando el número de marco de página virtual como desplazamiento. El marco de página virtual 5 será el sexto elemento de la tabla (el primer elemento es el 0).

Para traducir una dirección virtual a una física, el procesador tiene que extraer de la dirección virtual el número de marco de página y el desplazamiento dentro de la página virtual. Si hacemos que el tamaño de página sea potencia de 2, entonces esta operación se puede hacer fácilmente mediante una máscara y un desplazamiento. Volviendo a las Figuras  4.1 y suponiendo un tamaño de página de 0x2000 bytes (lo que en decimal es 8192) y una dirección virtual de 0x2194 del proceso Y, entonces el procesador descompondrá esta dirección en un desplazamiento de 0x194 dentro del número de maco de página virtual 1.

El procesador utiliza el número de marco de página virtual como índice a la tabla de páginas del proceso para obtener la entrada de la tabla de página correspondiente. Si la entrada en la tabla de páginas de ese índice es valida, el procesador coge el número de marco de página físico de esta entrada. Si por el contrario la entrada no es valida, entonces el proceso ha accedido a una área de memoria virtual que no existe. En este caso, el procesador no puede resolver la dirección y deberá pasar el control al sistema operativo para que éste solucione el problema.

La forma en la que el procesador informa al sistema operativo que un proceso concreto ha intentado realizar un acceso a una dirección virtual que no se ha podido traducir, es específico del procesador. Independientemente del mecanismo empleado por el procesador, a esto se le conoce como fallo de página y el sistema operativo es informado de la dirección virtual que lo ha producido y la razón por la que se produjo.

Suponiendo que ésta sea una entrada valida en la tabla de páginas, el procesador toma el número de marco de página físico y lo multiplica por el tamaño página para obtener la dirección base de la página física en memoria. Finalmente, el procesador le suma el desplazamiento a la instrucción o dato que necesita, y ésta es la dirección física con la que accede a memoria.

Siguiendo en el ejemplo anterior, el número de marco de página virtual 1 del proceso Y está asociado al número de marco de página físico 4 que comienza en la dirección 0x8000 (4 x 0x2000). Sumándole el desplazamiento de 0x194 nos da una dirección física de 0x8194.

Asignando direcciones virtuales a direcciones físicas de esta forma, la memoria virtual se puede asociar sobre las páginas físicas en cualquier orden. Por ejemplo, en la Figura  4.1 el número de marco de página 0 del proceso X está asociado sobre el número de marco de página física 1, mientras que el número de marco 7 está asociado con el marco físico número 0, a pesar de ser una dirección de memoria virtual mayor que la del marco de página 0. Ésto demuestra un interesante efecto lateral de la memoria virtual: las páginas de memoria virtual no tienen que estar necesariamente presentes en memoria física en un orden determinado.

4.1.1  Paginación por Demanda

Puesto que hay mucha menos memoria física que memoria virtual, el sistema operativo ha de tener especial cuidado de no hacer un mal uso de la memoria física. Una forma de conservar memoria física es cargar solo las páginas que están siendo utilizadas por un programa. Por ejemplo, un programa de bases de datos puede ser ejecutado para realizar una consulta a una base de datos. En este caso no es necesario cargar en memoria toda la base de datos, sino solo aquellos registros que que son examinados. Si la consulta consiste en realizar una búsqueda, entonces no tiene sentido cargar el fragmento de programa que se ocupa de añadir nuevos registros. Esta técnica de sólo cargar páginas virtuales en memoria conforme son accedidas es conocida como paginación por demanda.

Cuando un proceso intenta acceder a una dirección virtual que no está en esos momentos en memoria, el procesador no puede encontrar la entrada en la tabla de páginas de la página virtual referenciada. Por ejemplo, en la Figura  4.1 no existe una entrada en la tabla de páginas del proceso X para el marco número 2, por lo que si el proceso X intenta leer de una dirección perteneciente al marco de página virtual 2 no podrá traducirla a una dirección física. Es en este momento cuando el procesador informa al sistema operativo que se a producido un fallo de página.

Si dirección virtual que ha fallado es invalida, significa que el proceso ha intentado acceder a una dirección que no debería. Puede ser que la aplicación haya hecho algo erróneo, por ejemplo escribir en una posición aleatoria de memoria. En este caso, el sistema operativo ha de terminarlo, protegiendo así a otros procesos de este ``perverso'' proceso.

Si la dirección virtual que ha producido el fallo era valida pero la página que referencia no está en memoria en ese momento, el sistema operativo tiene que traer la página apropiada a memoria desde el disco. Los accesos a disco requieren mucho tiempo, en términos relativos, y por tanto el proceso tiene que esperar cierto tiempo hasta que la página se haya leído. Si hay otros procesos que pueden ejecutarse entonces el sistema operativo elegirá alguno de estos para ejecutar. La página pedida se escribe en una página física libre y se añade una entrada a la tabla de páginas del proceso para esta página. El proceso en entonces puesto otra vez en ejecución justo en la instrucción donde se produjo el fallo de página. Esta vez sí que se realizará con éxito el acceso a la dirección de memoria virtual, el procesador puede hacer la traducción de dirección virtual a física y el proceso continua normalmente.

Linux utiliza la paginación por demanda para cargar imágenes ejecutables en la memoria virtual de un proceso. Siempre que se ejecuta un proceso, se abre el fichero que la contiene y su contenido se asocia en la memoria virtual del proceso. Esto se hace modificando las estructuras de datos que describen el mapa de memoria del proceso y se conoce como asociación de memoria. Sin embargo, solo la primera parte de la imagen se copia realmente en memoria física. El resto de la imagen se deja en disco. Conforme se va ejecutando, se generan fallos de página y Linux utiliza el mapa de memoria del proceso para determinar qué partes de la imagen ha de traer a memoria para ser ejecutadas.

4.1.2  Intercambio (swapping)

Si un proceso necesita cargar una página de memoria virtual a memoria física y no hay ninguna página de memoria física libre, el sistema operativo tiene que crear espacio para la nueva página eliminando alguna otra página de memoria física.

Si la página que se va a eliminar de memoria física provenía de una fichero imagen o de un fichero de datos sobre el que no se ha realizado ninguna escritura, entonces la página no necesita ser guardada. Tan sólo se tiene que desechar y si el proceso que la estaba utilizando la vuelve a necesitar simplemente se carga nuevamente desde el fichero imagen o de datos.

Por otra parte, si la página había sido modificada, el sistema operativo debe preservar su contenido para que pueda volver a ser accedido. Este tipo de página se conoce como página sucia (dirty page) y para poderla eliminar de memoria se ha de guardar en un fichero especial llamado fichero de intercambio (swap file). El tiempo de acceso al fichero de intercambio es muy grande en relación a la velocidad del procesador y la memoria física y el sistema operativo tiene que conjugar la necesidad de escribir páginas al disco con la necesidad de retenerlas en memoria para ser usadas posteriormente.

Si el algoritmo utilizado para decidir qué páginas se descartan o se envían a disco (el algoritmo de intercambio) no es eficiente, entonces se produce una situación llamada hiper-paginación (thrashing). En este estado, las páginas son continuamente copiadas a disco y luego leídas, con lo que el sistema operativo está demasiado ocupado para hacer trabajo útil. Si, por ejemplo, el número de marco de página 1 de la Figura  4.1 se accede constantemente entonces no es un buen candidato para intercambiarlo a disco. El conjunto de páginas que en el instante actual está siendo utilizado por un proceso se llama Páginas activas (working set). Un algoritmo de intercambio eficiente ha de asegurarse de tener en memoria física las páginas activas de todos los procesos.

Linux utiliza la técnica de paginación por antigüedad (LRU Last Redently Used) para escoger de forma equitativa y justa las páginas a ser intercambiadas o descartadas del sistema. Este esquema implica que cada página del sistema ha de tener una antigüedad que ha de actualizarse conforme la página es accedida. Cuanto más se accede a una página más joven es; por el contrario cuanto menos se utiliza más vieja e inutil. Las páginas viejas son las mejoras candidatas para ser intercambiadas.

4.1.3  Memoria virtual compartida

Gracias a los mecanismos de memoria virtual se puede coseguir fácilmente que varios procesos compartan memoria. Todos los accesos a memoria se realizan a 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 está página ha de aparecer en las dos tablas de página.

La Figura  4.1 muestra dos procesos que comparten el marco de página física número 4. Para el proceso X esta página representa el su marco de página virtual número cuatro, mientras que para el proceso Y es el número 6. Esto ilustra una interesante cuestión que aparece al compartir páginas: la memoria física compartida no tiene porqué estar en las mismas direcciones sobre memoria virtual en todos los procesos que la comparten.

4.1.4  Modos de direccionamiento físico y virtual

No tiene mucho sentido que el propio sistema operativo se ejecute sobre memoria virtual. Sería una verdadera pesadilla que el sistema operativo tuviera que mantener tablas de página para él mismo. La mayoría de los procesadores de propósito general ofrecen la posibilidad del modo de direccionamiento físico junto con direccionamiento virtual. El modo de direccionamiento físico no necesita las tablas de páginas y el procesador no intenta realizar ningún tipo de traduccines en este modo. El núcleo de Linux está preparado para funcionar sobre un espacio de direccionamiento físico.

El procesador Alpha AXP no tiene un modo especial de direccionamiento físico. Lo que hace es dividir el espacio de memoria en varias áreas y designa a dos de ellas como direcciones físicas. A este espacio de direcciones del núcleo se le conoce como espacio de direcciones KSEG y contiene todas las direcciones a partir de la 0xfffffc0000000000. Para poder ejecutar código enlazado en KSEG (por definición, el código del núcleo) o acceder a datos de esta zona, el código debe ejecutarse en modo núcleo. El núcleo de Linux en Alpha está enlazado para ejecutarse a partir de la dirección 0xfffffc0000310000.

4.1.5  Control de acceso

Las entradas de la tabla de páginas también contienen información relativa al control de acceso. Puesto que el procesador tiene que utilizar la información de la tabla de páginas para traducir las direcciones virtuales a direcciones físicas, puede fácilmente utilizar la información de control de acceso para comprobar que el proceso no está accediendo a memoria de forma apropiada.

Hay muchas razones por las que se puede querer restringir el acceso a determinadas áreas de memoria. Hay memoria, como la que contiene el ejecutable, que es claramente memoria de sólo lectura; el sistema operativo no ha de dejar que el proceso escriba sobre su propio código de programa. Por el contrario, páginas de memoria que contengan datos han de poder ser leídas y escritas, pero un intento de ejecutar algo de estas páginas ha fallar. La mayoría de los procesadores tienen al menos dos modos de operación: modo núcleo y modo usuario. No es deseable que un proceso ejecute código del núcleo, o que acceda a datos del núcleo excepto cuando el procesador está funcionando en modo núcleo.


Figure 4.2: Entrada de tabla de páginas del Alpha AXP

La información de control de acceso se guarda en la PTE y es dependiente el procesador; la figura  4.2 muestra el PTE del Alpha AXP. Los bits que aparecen tienen el siguiente significado:

V
Valido, si está activado, la PTE es valida;

FOE
``Fault on Execute'', cuando se intente ejecutar una instrucción de esta página, el procesador informará de un fallo de página y dará el control al sistema operativo;

FOW
``Fault on Write'' como el anterior pero el fallo de página se produce cuando se intenta escribir sobre alguna posición de memoria de la página.

FOS
``Fault on Read'' como el anterior pero el fallo de página se produce cuando se intenta leer de esta página.

ASM
``Address Space Match''. Este se utiliza cuando el sistema operativo quiere eliminar solo algunas de las entradas de la Tabla de Traducción (Translation Table);
KER
Solo el código ejecutado en modo núcleo puede leer esta página;

URE
Código ejecutado en modo usuario puede leer esta página;

GH
``Granularity Hint'' utilizado para asociar un bloque completo con una sola entrada del buffer de traducción en lugar de con varias.

KWE
Código ejecutado en modo núcleo puede escribir en esta página;

UWE
Código ejecutado en modo usuario puede escribir en esta página;

PFN
Para páginas con el bit V activado, este campo contiene la dirección física del número de marco de página para esta PTE. Para PTE invalidas, si el campo es distinto de cero, contiene información a cerca de dónde está la página en memoria secundaria.

Los siguientes dos bits los define y utiliza Linux:

_PAGE_DIRTY
si activado, la página necesita ser copiada a disco;
_PAGE_ACCESSED
Utilizada por Linux para marcar una página como accedida.

4.2  Caches

Si implementáramos un sistema utilizando el modelo teórico que acabamos de describir, obviamente funcionaria, pero no sería particularmente eficiente. Tanto los diseñadores de sistemas operativos como los de procesadores, se esfuerzan al máximo para obtener el mayor rendimiento posible del sistema. Además de hacer los procesadores, la memoria y otros dispositivos más rápidos la mejor forma de obtener un buen rendimiento consiste en mantener en memorias caches la información que se utiliza muy a menudo. Linux emplea unas cuantas caches para la gestión de la memoria:

Buffer Cache
Contiene buffers de datos que son utilizados por los manejadores de dispositivos de bloques. Estos buffers son de tamaño fijo (por ejemplo 512 bytes) y contienen bloques de información que ha sido bien leída de un dispositivo de bloques o que ha de ser escrita. Un dispositivo de bloques es un dispositivo sobre el que sólo se pueden realizar operaciones de lectura o escritura de bloques de tamaño fijo. Todos los discos duros son dispositivos de bloque.

El cache buffer está indexado vía el identificador de dispositivo y el número de bloque deseado, índice que es utilizado para una rápida localización del bloque. Los dispositivos de bloque son exclusivamente accedidos a través del buffer cache. Si un dato (bloque) se puede encontrar en el buffer cache, entonces no es necesario leerlo del dispositivo de bloques físico, por el disco duro, y por tanto el acceso es mucho más rápido.

Cache de Páginas
Este se utiliza para acelerar el acceso a imágenes y datos en disco. Se utiliza para guardar el contenido lógico de un fichero de página en página y se accede vía el fichero y el desplazamiento dentro del fichero. Conforme las páginas se leen en memoria, se almacenan en la page cache.

Cache de Intercambio
Solo las páginas que han sido modificadas (dirty) son guardadas en el fichero de intercambio.

Mientras no vuelvan a ser modificadas después de haber sido guardadas en el fichero de swap, la próxima vez que necesiten ser descartadas (swap out) no será necesario copiarlas al fichero de intercambio pues ya están allí. Simplemente se las elimina. En un sistema con mucho trasiego de páginas, esto evita muchas operaciones de disco innecesarias y costosas.

Caches Hardware
Es una cache normalmente implementada en el propio procesador; la cache de entradas de tabla de página. En este caso, el procesador no necesita siempre leer la tabla de páginas directamente, sino que guarda en esta cache las traducciones de las páginas conforme las va necesitando. Estos son los Translation Look-aside Buffers (TLB) que contienen copias de las entradas de la tabla de páginas de uno o más procesos del sistema.

Cuando se hace la referencia a una dirección virtual, el procesador intenta encontrar en el TLB la entrada para hacer la traducción a memoria física. Si la encuentra, directamente realiza la traducción y lleva a cabo la operación. Si el procesador no puede encontrar la TPE buscada, entonces tiene que pedir ayuda al sistema operativo. Esto lo hace enviando una señal al sistema operativo indicando que se ha producido un fallo de TLB3. Un mecanismo específico al sistema se utiliza para enviar esta excepción al código del sistema operativo que puede arreglar la situación. El sistema operativo genera una nueva entrada de TLB para la dirección que se estaba traduciendo. Cuando la excepción termina, el procesador hace un nuevo intento de traducir la dirección virtual. Esta vez tendrá éxito puesto que ahora ya hay una entrada en la TLB para esa dirección.

El inconveniente de utilizar memorias cache, tanto hardware como de otro tipo, es que para evitar esfuerzos Linux tiene que utilizar más tiempo y espacio para mantenerlas y, si se corrompe su contenido, el sistema dejará de funcionar.

4.3  Tablas de Páginas en Linux


Figure 4.3: Tablas de páginas de tras niveles

Linux supone que hay tres niveles de tablas de páginas. Cada nivel de tablas contiene el número de marco de página del siguiente nivel en la tabla de páginas. La figura  4.3 muestra cómo una dirección virtual se divide en un número de campos, donde cada uno de ellos representa un desplazamiento dentro de una tabla de páginas. Para traducir una dirección virtual a una física, el procesador tiene que tomar el contenido de cada uno de estos campos, convertirlos en desplazamientos de la página física que contiene la tabla de páginas y leer el número de marco de página del siguiente nivel de la tabla de páginas. Esta operación se repite tres veces hasta que se encuentra el número de la página física que contiene la dirección virtual. Ahora el último campo de la dirección virtual se utiliza para encontrar el dato dentro de la página.

Cada plataforma sobre la que funciona Linux tiene que proporcionar las macros que permitan al núcleo atravesar las tablas de página de cada proceso. De esta forma, el núcleo no necesita conocer el formato de las entradas de la tabla de páginas ni cómo éstas se organizan. Esto es tan útil que Linux utiliza el mismo código de gestión de tablas de páginas en un procesador Alpha, que tiene tres niveles de tablas de páginas, que en un Intel x86, que sólo tiene dos niveles de tablas.

4.4  Asignación y liberación de páginas

Se producen muchas peticiones de páginas físicas. Por ejemplo, cuando una imagen se carga a memoria, el sistema operativo necesita asignar páginas. Éstas serán liberadas cuando la imagen concluya su ejecución y se descargue. Otro uso de páginas físicas es para contener estructuras de datos específicas tales como las propias tablas de páginas. Los programas y las estructuras de datos relacionados con la asignación y liberación de páginas son quizás los más críticos para obtener un subsistema de memoria virtual eficiente.

Todas las páginas físicas del sistema están descritas por la estructura mem_map que es una lista de estructuras del tipo mem_map_t, 4, la cual es inicializada al arrancar del sistema. Cada mem_map_t describe una sola página física. Los campos más importantes son (en lo relativo a la gestión de memoria):

count
Contador del número de usuarios de esta página. El valor del contador es mayor que uno cuando la página es compartida por varios procesos.
age
Este campo describe la antigüedad de la página, y se utiliza para decidir si la página es una buena candidata para ser descartada o enviada a disco.
map_nr
Número de marco de página física que describe esta estructura mem_map_t.

El vector free_area es utilizado por el código de asignación de páginas para encontrar páginas libres. Todo el esquema de gestión de buffers está soportado por este mecanismo y desde el punto de vista del código, el tamaño de las páginas y los mecanismos de paginación física utilizados por el procesador son irrelevantes.

Cada elemento de free_area contiene información sobre bloques de páginas. El primer elemento del vector describe páginas simples, el siguiente bloques de 2 páginas, el siguiente bloques de 4 páginas y así creciendo en potencias de 2. El elemento list se utiliza como cabeza de una cola que contiene punteros a la estructura page del vector mem_map. Los bloques de páginas libres se encolan aquí. map es un puntero a un mapa de bits que mantiene un registro de los grupos de páginas de este tamaño. El bit N del mapa de bits está a uno si el ene-avo bloque de páginas está libre.

La Figura  free-area-figure muestra la estructura free_area. El elemento 0 tiene una página libre (número de marco de página cero) y el elemento 2 tiene 2 bloques de 4 páginas libres, el primero comienza en el número de página 4 y el segundo en el número de marco 56.

4.4.1  Asignación de páginas

Linux utiliza el algoritmos Buddy5 para asignar y liberar eficientemente bloques de páginas. El código de asignación intenta asignar un bloque de una o más páginas físicas. Las páginas se asignan en bloques de tamaño potencia de 2. Esto quiere decir que puede asignar bloques de 1, 2, 4, etc páginas. Mientras haya suficientes páginas libres en el sistema para satisfacer esta petición (nr_free_pages > min_free_pages) el código de asignación buscará en el free_area bloques de páginas del tamaño pedido. Así, elemento del free_area tiene un mapa de bloques de páginas asignados y libres para ese tamaño de bloque. Por ejemplo, el elemento 2 del vector tiene un mapa de memoria que describe los bloques de 4 páginas libres y asignados.

El algoritmo de asignación busca primero entre los bloques de páginas de igual tamaño que el pedido. Luego sigue la lista de páginas libres que está encolada en el elemento list de la estructura de datos free_area. Si no encuentra ningún bloque de páginas del tamaño pedido libre, entonces busca en los siguientes (los cuales son del doble del tamaño pedido). Este proceso continua hasta que bien todos los elementos de free_area han sido examinados o bien se he encontrado un bloque de páginas libres. Si el bloque de páginas encontrado es mayor que el pedido, entonces se trocea hasta conseguir un bloque del tamaño deseado. Puesto que el número de páginas de cada bloque es potencia de 2, simplemente dividiendo el bloque en dos tenemos dos bloques con un tamaño de bloque inmediatamente inferior. Los bloques libres se insertan en la cola apropiada y el bloque de páginas asignado se devuelve al que realizó la llamada.


Figure 4.4: La estructura de datos free_area

Por ejemplo, en la figura  4.4 si se pide un bloque de 2 páginas, el primer bloque de 4 páginas (que comienza en el número de marco de página 4) tendrá que ser troceado en 2 bloques de 2 páginas. El primero, que comienza en el número de marco de página 4, será devuelto como las páginas asignadas y el segundo bloque, que comienza en el número de marco de página 6, será insertado en la cola en el elemento 1 del vector free_areacomo un bloque de 2 páginas libres.

4.4.2  Liberación de páginas

Asignar bloques de páginas tiende a fragmentar la memoria al dividir los bloques grandes para conseguir bloques más pequeños. El código de liberación de páginas re-combina páginas en bloques de mayor tamaño siempre que es posible. De hecho, el tamaño de bloque de página es importante pues facilita la recombinación en bloques grandes.

Siempre que se libera un bloque de páginas, se comprueba si está libre el bloque adyacente de igual tamaño. Si es así, se combina con el bloque de páginas recién liberado para formar un bloque nuevo de tamaño doble. Cada vez que dos bloques de páginas se recombinan en uno mayor, el algoritmo de liberación intenta volver a recombinarlo en otro aún mayor. De esta forma, los bloques de páginas libres son tan grandes como la utilización de la memoria permita.

Por ejemplo, en la figura  4.4, si el marco de página número 1 fuera liberado, entonces éste sería combinado con el número de marco de página 0, que ya está libre, e insertado en la cola del elemento 1 de la estructura free_area como un bloque libre con un tamaño de 2 páginas.

4.5  Proyección de Memoria (Memory Mapping)

Cuando se ejecuta programa, el contenido del fichero imagen se ha de copiar al espacio de memoria virtual del proceso. Lo mismo sucede con cualquier biblioteca compartida que el proceso necesite. El fichero ejecutable realmente no se lleva a memoria física, sino que solamente se enlaza en la memoria virtual del proceso. Luego, conforme partes del programa son referenciadas por la ejecución de la aplicación, la imagen es llevada a memoria física desde la imagen del ejecutable. Este tipo de enlazado de una imagen sobre el espacio de direcciones virtuales de un proceso se conoce como "proyección de memoria".


Figure 4.5: Areas de Memoria Virtual

La memoria virtual de cada proceso está representado por la estructura de datos mm_struct. Ésta contiene información sobre la imagen que actualmente se está ejecutando (por ejemplo bash) así como punteros a unas cuantas estructuras vm_area_struct. Cada estructura de datos vm_area_struct describe el inicio y fin de un área de memoria virtual, los permisos del proceso sobre esta memoria y el conjunto de operaciones para gestionar la. Estas operaciones son el conjunto de rutinas que Linux tiene que utilizar para manipular esta área de memoria virtual. Por ejemplo, una de las operaciones de memoria virtual lleva a cabo las acciones necesarias cuando el proceso ha intentado acceder a la memoria virtual pero encuentra (debido a un fallo de página) que la memoria no está realmente en memoria física. Esta es la operación nopage. La operación nopage se emplea cuando Linux pide páginas de un fichero ejecutable a memoria.

Cuando una imagen ejecutable se proyecta sobre las direcciones virtuales de un proceso se generan un conjunto de estructuras de datos vm_area_struct. Cada estructura de éstas representa una parte de la imagen del ejecutable; el código ejecutable, datos inicializados (variables), datos no inicializados y demás. Linux tiene unas cuantas operaciones de memoria virtual estándar, y cuando se crean las estructuras de datos vm_area_struct, el conjunto de operaciones correcto se asocia con ésta.

4.6  Paginación por Demanda

Una vez una imagen ejecutable ha sido proyectada sobre la memoria virtual de un proceso, éste puede comenzar su ejecución. Puesto que solo el principio de la imagen ha sido realmente copiado en memoria física, rápidamente el programa accederá a una área de memoria virtual que todavía no ha sido llevada a memoria física. Cuando un proceso accede a una dirección virtual que no tiene una entrada valida en la tabla de páginas, el procesador informará sobre el fallo de página a Linux. El fallo de de página indica la dirección virtual donde se produjo el fallo de página y el tipo de acceso que lo causó.

Linux debe encontrar la vm_area_struct que representa el área de memoria donde sucedió el fallo de página. Puesto que la búsqueda por las estructuras de datos vm_area_struct es crítica para la gestión eficiente de los fallos de páginas, éstas están organizadas juntas en un estructura de árbol AVL (Adelson-Velskii and Landis). Si no hay ninguna estructura vm_area_struct para la dirección virtual que produjo el fallo de página entonces el proceso ha accedido a una dirección de memoria virtual ilegal. Linux enviará al proceso la señal SIGSEGV, y si el proceso no ha instalado un manejador de señales para esta señal entonces morirá.

Lo siguiente que hace Linux es comprobar el tipo de fallo de página producido y los tipos de acceso permitidos para el área de memoria virtual en cuestión. Si el proceso ha intentado acceder a la memoria de forma ilegal, por ejemplo intentando escribir sobre una área de la que sólo tenía permisos de lectura, también se señala un error de memoria.

Ahora que Linux ha determinado que el fallo de página es legal, tiene que tratarlo. Linux ha de diferenciar entre páginas que están en un fichero de intercambio y aquellas que son parte de una imagen ejecutable localizadas en algún lugar del disco. Esto lo hace utilizando la entrada en la tabla de páginas de la página que causó el fallo.

Si la entrada de la tabla de páginas es invalida pero no está vacía, el fallo de página se debe a que la página está en esos momentos en un fichero de intercambio. Estas entradas se identifican en el Alpha AXP  porque no tienen el bit de valido activado pero tienen un valor distinto de cero en el campo PFN. En este caso, el campo PFN contiene información sobre dónde se encuentra la página: fichero de intercambio y posición dentro de éste. Cómo se gestionan las páginas en un fichero de intercambio se describirá más adelante en este mismo capítulo.

No todas las estructuras vm_area_struct tienen un conjunto de operaciones de memoria virtual e incluso éstas pueden no tener la operación nopage. Esto es debido a que por defecto Linux gestiona los accesos asignando una página de memoria física nueva y creando para ésta la correspondiente entrada en la tabla de páginas. Si no hay operación de nopage para esta área de memoria virtual, Linux hará esto último.

La operación genérica de nopage de Linux se utiliza para proyectar imágenes ejecutables y utiliza la cache de páginas para traer la página imagen requerida a memoria física.

Cuando la página necesitada es cargada en memoria física, las tablas de páginas del proceso son actualizadas. Puede ser necesario realizar determinadas acciones especificas del hardware para actualizar estas entradas, en particular si el procesador utilizar buffers cache TLB (Translatión Look-aside Buffer). Ahora que el problema con la página que produjo el fallo ha sido resuelto, se puede olvidar el percance y el proceso puede continuar su ejecución en la instrucción que produjo el fallo de página.

4.7  La Cache de Páginas de Linux


Figure 4.6: La Cache de Páginas de Linux

El cometido de la cache de páginas en Linux es el de acelerar el acceso a los fichero de disco. Las lecturas sobre los ficheros proyectados en memoria se realizan página a página y estas páginas se guardan en la cache de páginas. La Figura  4.6 muestra que la cache de páginas consta de la page_hash_table y un vector de punteros a estructuras mem_map_t. Cada fichero en Linux se identifica por una estructura inode VFS (descrita en el Capítulo  filesystem-chapter) cada inode VFS es único y describe a un y solo un fichero. El índice en la tabla de páginas se construye a partir del inode VFS del fichero y el desplazamiento dentro de éste.

Siempre que se lee en una página de un fichero proyectado en memoria, por ejemplo cuando se necesita traer a memoria una página desde un fichero de intercambio, la página se lee a través de la cache de páginas. Si la página está presente en la cache, se devuelve un puntero a la estructura de datos mem_map_t a la rutina de tratamiento de fallos de página. En caso contrario, la página se ha de traer a memoria desde el sistema de ficheros que contiene la imagen. Linux asigna una página física y lee la página desde el fichero del disco.

Si es posible, Linux comenzará una lectura de la siguiente página del fichero. Con esta página de adelanto se consigue que si el proceso está accediendo las paginas de forma secuencial, la siguiente página esté lista y esperando en memoria la petición del proceso.

Con el tiempo, la cache de páginas va creciendo conforme las imágenes se leen y ejecutan. Las páginas han de ser eliminadas de la cache cuando dejan de utilizarse, por ejemplo cuando una imagen ya no es utilizada por ningún proceso. Conforme Linux utiliza memoria puede comenzar a escasear las páginas de memoria física. En esta situación Linux reducirá el tamaño de la cache de páginas.

4.8  Intercambiando y Liberando Páginas

Cuando queda poca memoria física, el subsistema de gestión de memoria de Linux tiene que intentar liberar páginas físicas. Este trabajo es realizado por el demonio de intercambio del núcleo (kswapd). El demonio de intercambio del núcleo es un tipo especial de proceso, un hilo de ejecución del núcleo (kernel thread). Los hilos del núcleo son procesos que no tienen memoria virtual, en lugar de ello, se ejecutan en modo núcleo en el espacio de memoria física. Su misión es la de garantizar que haya suficientes páginas libres en el sistema para mantener al sistema de gestión de memoria funcionando eficientemente.

El demonio de intercambio (kswapd) es puesto en marcha por el proceso init del núcleo durante el arranque de Linux y se queda esperando al temporizador del swap del núcleo a que lo despierte de forma periódica. Cada vez que expira el temporizador, el demonio de intercambio comprueba que el número de páginas libres no sea demasiado bajo. Utiliza dos variables, free_pages_high y free_pages_low para decidir si ha de liberar algunas páginas. Mientras el número de páginas libres del sistema se mantenga por encima de free_pages_high, el demonio de intercambio no hace nada; se duerme hasta que vuelva a despertarlo el temporizador. Linux no quiere enviar a disco de intercambio demasiadas páginas a la vez, por lo que en nr_async_pages lleva la cuenta de cuantas páginas están en ese momento siendo copiadas al fichero de intercambio. nr_async_pages es incrementado cada vez que se encola una página para ser escrita en el fichero de intercambio, y decrementado cuando ha concluido la escritura.

Los valores defree_pages_low y free_pages_high se asignan al arrancar el sistema en función del número de páginas de memoria física del sistema.

Si hay suficientes páginas libres, el demonio de intercambio se vuelve a dormir hasta que el temporizador vuelva a expirar otra vez, en caso contrario,el demonio intenta de tres formas distintas reducir el número de páginas físicas ocupadas:

Reduciendo el tamaño de la cache de páginas y el buffer cache
Enviando a disco páginas compartidas,
Enviando a disco o descartando páginas.

Si el número de páginas libres ha caído por debajo de free_pages_low, el demonio de intercambio intentará liberar 6 páginas antes de su próxima ejecución. En caso contrario, intentará liberar 3 páginas. Los métodos anteriores se intentan uno tras otro de hasta que se consiguen liberar suficientes páginas. Luego el demonio de intercambio se suspende hasta que el temporizador vuelva a expirar. El demonio recuerda cuál fue el último método que empleó para liberar páginas, y la próxima vez que se ejecuta lo vuelve a intentar con el mismo método que tuvo éxito.

Tras haber liberado suficientes páginas, el demonio se duerme hasta que el temporizador lo vuelva a despertar. Si el número de páginas libres había caído por debajo de free_pages_low, el demonio de intercambio sólo dormirá la mitad de su tiempo normal. Sólo volverá a dormir el tiempo normal cuando consiga que el número de páginas libres esté por encima de free_pages_low.

4.8.1  Reduciendo el tamaño de la Cache de Páginas y el Buffer Cache

Las páginas de la cache de páginas y del buffer cache son buenos candidatos a ser liberados en el vector free_area . La Cache de Páginas, que contiene páginas de ficheros proyectados a memoria, puede contener páginas innecesarias que están saturando el sistema de memoria. Igualmente, el Buffer Cache que contiene buffers leídos desde o escritos a dispositivos físicos, también puede contener buffers innecesarios. Cuando comienza a escasear las páginas de memoria física, liberar páginas de estas caches es relativamente sencillo pues no se necesita escribir sobre dispositivos físicos (a diferencia de cuando se intercambian páginas fuera de memoria). Descartando estas páginas no tiene demasiados efectos laterales negativos, excepto reducir un poco la velocidad de acceso de los ficheros proyectados en memoria. Se han de descartar las páginas de estas caches de forma justa para asegurar que todos los procesos se degradan equitativamente.

Cada vez que el demonio de intercambio intenta reducir el tamaño de estas caches examina un bloque de páginas del vector de páginas mem_map para ver si se puede liberar alguna de memoria física. La cantidad de páginas examinadas es mayor si el demonio de intercambio está haciendo muchos intercambios; esto es, si el número de páginas libres ha caído peligrosamente. los bloques de páginas son examinados de forma cíclica, se examina un bloque de páginas en cada intento de reducción del mapa de memoria. Este esquema se conoce como el algoritmo del reloj (clock algorthim), al igual que la manecilla de de minutos de un reloj de pulsera, todo el vector de mem_map es examinado poco a poco.

Cada página que se examina, se comprueba si está en alguna de las dos caches. Es importante destacar que las páginas compartidas no son consideradas en este momento para ser descartadas, y que una página no puede estar en las dos caches al mismo tiempo. Si la página candidato no está en ninguna de las dos tablas, entonces se pasa a examinar la siguiente página del vector mem_map.

Las páginas se guardan en el buffer cache (o mejor dicho, los buffers con las páginas son guardados) para hacer que la asignación de liberación sea más eficiente. El código de reducción del mapa de memoria intenta liberar los buffers contenidos en la página que se está examinando.

Si se consigue liberar todos los buffers, entonces las páginas que los contenían también son liberadas. Si la página considerada está en la cache de páginas, se elimina de esta cache y se libera.

Cuando se ha liberado suficientes páginas en un intento, el demonio de intercambio del núcleo se suspende hasta que llegue el siguiente instante de activación periódico. Si no ha sido capaz de liberar suficientes páginas entonces el demonio pasa a intentar liberar paginas compartidas.

4.8.2  Intercambio de Páginas compartidas (System V Shared Memory Pages)

El esquema de compartición de memoria de System V es un mecanismo de de comunicación entre procesos que permite que dos procesos compartan un espacio de memoria virtual para intercambiarse información entre ellos. La forma en la que la memoria es compartida está descrito con más detalle en el Capítulo  IPC-chapter. Por ahora es suficiente con decir que cada área de memoria compartida System V está descrita por una estructura de datos shmid_ds. Esta contiene un puntero a una lista de estructuras vm_area_struct, una por cada proceso que comparte esta área de memoria virtual. Cada vm_area_struct de memoria compartida System V están enlazadas una lista mediante los punteros vm_next_shared y vm_prev_shared. Cada estructura shmid_ds también contiene una lista de entradas de tabla de página, cada una de las cuales describe la página física sobre la que esta asociada una página de memoria virtual compartida.

El demonio de intercambio del núcleo también utiliza el algoritmo del reloj para intercambiar las páginas compartidas System V. . Cada vez que se ejecuta, recuerda cuál fue la última página de memoria virtual compartida que intercambió. Para lo cual utiliza dos índices, el primero es un índice al conjunto de estructuras shmid_ds, el segundo apunta a la lista de entradas de tablas de páginas de esta área de memoria compartida System V. De esta forma se asegura que todas las áreas de memoria compartida tienen la misma probabilidad de ser intercambiadas.

Puesto que el número de marco de página físico de cada página virtual compartida aparece en las tablas de páginas de todos los procesos que comparten el área, el demonio de intercambio ha de modificar todas estas tablas de páginas para indicar que la página ya no se encuentra en memoria, sino que ahora está en un fichero de intercambio. Para cada página de memoria compartida que se intercambia, el demonio de intercambio busca la entrada de la tabla de páginas en cada proceso que la utiliza (siguiendo un puntero por cada estructura vm_area_struct). Si la entrada de la tabla de páginas de este proceso es valida, entonces la convierte en invalida, se marca como intercambiada y luego decrementa el contador de número de referencias (de la página compartida). El formato de la entrada de la tabla de páginas de una página compartida System V que ha sido intercambiada contiene un índice a la estructura shmid_ds y un índice a la entrada de la tabla de páginas de está memoria compartida. Si el contador de referencias de una página tiene el valor cero después de haber modificado todas las tablas de páginas de páginas de los procesos la comparten, entonces ésta puede ser intercambiada a disco.La entrada de la tabla de páginas apuntada desde la estructura shmid_ds de esta área de memoria compartida se substituye por una entrada de tabla de página previamente intercambiada. Una entrada de página que ha sido intercambiada es invalida, pero contiene un índice al conjunto de ficheros de intercambio abiertos y el desplazamiento dentro de ese fichero que indica donde se encuentra la página. Esta información será utilizada cuando se tenga que volver a traer a memoria física.

4.8.3  Intercambiando y Descartando Páginas

El demonio de swap revisa de uno en uno cada proceso del sistema para ver si es un buen candidato para intercambiar algunas de sus páginas. Buenos candidatos son procesos que pueden ser intercambiados (algunos procesos no pueden) y que tienen una o más páginas que pueden ser enviadas al disco o descartadas de memoria. Las páginas son enviadas al disco sólo si los datos que contienen no pueden ser recuperados de otra forma.

Una gran cantidad del contenido de una imagen ejecutable viene de la imagen del fichero y puede ser fácilmente re-leído desde el mismo fichero. Por ejemplo, las instrucciones ejecutables de una imagen nunca se modifican por el propio proceso y por tanto nunca son enviadas al fichero de intercambio. Estas páginas sencillamente se pueden descartar; cuando vuelven a reverenciarse por el proceso, éstas se traen otra vez a memoria desde la imagen ejecutable del fichero.

Una vez se a localizado un proceso candidato para enviar a disco algunas de sus páginas, el demonio de intercambio examina todas sus regiones de memoria virtual buscando áreas no estén compartidas ni bloqueadas. Linux no intercambiará a disco todas las páginas posibles del proceso que ha sido elegido; sólo quitará un pequeño número de páginas. Las páginas bloqueadas en memoria no se pueden intercambiar ni descartar.

El algoritmo de intercambio de Linux emplea la antigüedad de las páginas. Cada página tiene un contador (localizado en la estructura de datos mem_map_tt) que da al demonio de intercambio una cierta idea de si vale la pena o no intercambiar un página. Las páginas envejecen cuando no son utilizadas y rejuvenecen cuando son accedidas; el demonio de intercambio sólo envía a disco páginas viejas. La acción por defecto cuando se asigna una página por primera vez es darle un valor inicial de antigüedad de 3. Cada vez que se accede, el valor de antigüedad se incrementa en 3 hasta un máximo de 20. Cada vez que el demonio intercambio se ejecuta, envejece las páginas decrementando su valor en 1. Estas acciones por defecto se pueden cambiar y por esta razón (y otra información relacionada con el intercambio) se guarda en la estructura de datos swap_control.

Si una página es vieja (age = 0), el demonio de intercambio la procesará. Páginas sucias (Dirty) son páginas que se pueden intercambiar. Linux utiliza un bit específico de la arquitectura del PTE para describir páginas de esta forma (ver Figura  4.2). Sin embargo, no todas las páginas sucias son necesariamente copiadas al fichero de intercambio. Cada región de memoria virtual de cada proceso puede tener su propia operación de intercambio (apuntada por el puntero vm_ops en la estructura vm_area_struct). En caso contrario, el demonio de intercambio buscará una página libre en el fichero de intercambio y escribirá sobre ésta la página de memoria elegida.

La entrada en la tabla de páginas se reemplaza por una que está marcada como invalida pero que contiene información de donde está la página en el fichero de intercambio. Linux utiliza el campo PFN de la tabla de páginas para guardar el desplazamiento dentro del fichero de intercambio donde está la página más una índice al fichero de intercambio (pues pueden haber varios). Cualquiera que sea el método utilizado, la página física original se ha liberado y se ha devuelto a la free_area. Páginas limpias (o mejor dicho no sucias) se pueden descartar e insertar en la estructura free_area para ser re-utilizada.

Si se han descartado o intercambiado suficientes páginas, el demonio de intercambio se vuelve a suspender. La próxima vez que se despierte considerará al siguiente proceso del sistema. De esta forma el demonio de intercambio mordisquea las páginas de cada proceso hasta que el sistema está otra vez equilibrado. Esto es mucho más equitativo que enviar todo un proceso a disco.

4.9  La cache de Intercambio

Cuando se tiene que intercambiar páginas a disco, Linux intenta evitar escribirlas. Hay veces que una página está a en un fichero de intercambio y en memoria física. Esto sucede cuando una página que fue intercambiada a disco ha sido nuevamente leída a memoria principal cuando un proceso la ha necesitado. Mientras la página que está en memoria no sea modificada por el proceso, la página que está en disco es valida.

Linux utiliza la cache de intercambio para gestionar estas páginas. La cache de intercambio es una lista de entradas de tabla de páginas, una por cada página física del sistema. Si una entrada en la cache de intercambio es distinta de cero, entonces representa una página que está en el fichero de intercambio que no ha sido modificado. Si la página se modifica posteriormente (un proceso escribe sobre ella), su entrada se borra de la cache de intercambio.

Cuando Linux necesita enviar una página física a un fichero de intercambio consulta la cache de intercambio, si hay una entrada valida para está página, entonces no es necesario copiar la página al fichero de intercambio. Pues la página de memoria no ha sido modificada desde la última vez que se leyó del fichero de intercambio.

Las entradas en la cache de intercambio son entradas de la tabla de páginas de páginas que estén en algún fichero de intercambio. Están marcadas como invalidas pero contienen información que permiten a Linux encontrar el fichero de intercambio y el página correcta dentro del fichero de intercambio.

4.10  Cargando Páginas de Intercambio

Una página que ha sido volcada a un fichero de intercambio puede necesitarse después si el proceso accede a alguna de las páginas que han sido salvadas, por ejemplo cuando una aplicación escribe sobre una zona de memoria virtual que está en un fichero de intercambio. Cuando pasa esto se produce un fallo de página. La rutina de manejo de los fallos de página de Linux sabe que este fallo de página se debe una página intercambiada gracias a la entrada en su tabla de páginas. La entrada está marcada como invalida pero el campo PFN es distinto de cero. El procesador no sabe como traducir de dirección virtual a física y por tanto pasa el control al sistema operativo indicándole cuál fue la página que produjo el fallo y la razón del fallo. El formato de esta información y cómo el procesador pasa el control al sistema operativo es específico del procesador. La rutina específica de gestión de fallos de página ha de buscar en la estructura vm_area_struct el área de memoria que contiene la dirección de memoria que ha producido el fallo. Esto lo realiza buscando en las estructuras vm_area_struct de este proceso. Esta búsqueda ha de realizarse muy rápidamente para no degradar las prestaciones del sistema, para lo cual, las estructuras de datos vm_area_struct están organizadas de forma que la búsqueda requiera el menor tiempo posible.

Una vez se han realizado las acciones específicas dependientes del procesador y se ha encontrado que la página virtual que produjo el fallo pertenecía a un área de memoria valida, el resto del procesamiento de fallo de página es genérico y aplicable a todos los procesadores sobre los que se ejecuta Linux. El código genérico de gestión de fallos de página busca la entrada de la tabla de páginas de la dirección que falló. Si encuentra que la página está intercambiada, Linux tiene que traer a memoria física la página. El formato de la tabla de páginas es dependiente del procesador pero todos los procesadores marcan estas páginas como invalidas y ponen en la tabla de páginas la información necesaria para localizarlas en el fichero de intercambio. Linux necesita esta información para volver a traerlas a memoria física.

En este punto, Linux sabe cual es la dirección virtual que fallo y tiene una entrada en la tabla de páginas que contiene información a cerca de dónde fue intercambiada. La estructura vm_area_struct puede contener un puntero a una rutina que puede traer cualquier página de memoria virtual nuevamente a memoria física. Esta es la operación swapin. Si hay una operación swapin para este área de memoria virtual, Linux la utilizará. Este es de hecho cómo las páginas compartidas System V son gestionadas, pues requieren una gestión especial ya que el formato de las páginas intercambiadas compartidas es un poco distinto respecto las páginas no compartidas. Asigna una página libre y lee la página de disco a memoria desde el fichero de intercambio.

Si el acceso que causo el fallo de página no fue un acceso de escritura entonces la página se mantiene en el fichero de intercambio. Su entrada en la tabla de páginas no se marca como modificable (writable). Si se escribe sobre la página, se producirá otro fallo de página y, en este momento, la página se marca como sucia y su entrada se elimina del fichero de intercambio. Si no se escribe sobre la página y se vuelve a necesitar liberar la página, Linux puede evitar tener que escribir la página al fichero de intercambio pues la página ya está en éste.

Si el acceso que produjo el fallo de página de una página localizada en el fichero de intercambio fue una operación de escritura, la pagina se elimina del fichero de intercambio y su entrada en la tabla de página se marca como sucia y modificable (writable).

Capítulo 5
Procesos

Este capítulo describe qué es un proceso, y como el núcleo de Linux crea, gestiona y borra procesos en el sistema.

Los procesos llevan a cabo tareas en el sistema operativo. Un programa es un conjunto de instrucciones de código máquina y datos guardados en disco en una imagen ejecutable y como tal, es una entidad pasiva; podemos pensar en un proceso como un programa de computador en acción.

Es una entidad dinámica, cambiando constantemente a medida que el procesador ejecuta las instrucciones de código máquina. Un proceso también incluye el contador de programa y todos los registros de la CPU, así como las pilas del proceso que contienen datos temporales como parámetros de las rutinas, direcciones de retorno y variables salvadas. El programa que se está ejecutando, o proceso, incluye toda la actividad en curso en el microprocesador. Linux es un sistema multiproceso. Los procesos son tareas independientes, cada una con sus propios derechos y responsabilidades.

Si un proceso se desploma, no hará que otros procesos en el sistema fallen también. Cada proceso se ejecuta en su propio espacio de dirección virtual y no puede haber interacciones con otros procesos excepto a través de mecanismos seguros gestionados por el núcleo.

Durante la vida de un proceso, este hará uso de muchos recursos del sistema. Usará las CPUs del sistema para ejecutar sus instrucciones y la memoria física del sistema para albergar al propio proceso y a sus datos. El proceso abrirá y usará ficheros en los sistemas de ficheros y puede usar dispositivos del sistema directa o indirectamente. Linux tiene que tener cuenta del proceso en sí y de los recursos de sistema que está usando de manera que pueda gestionar este y otros procesos justamente. No sería justo para los otros procesos del sistema si un proceso monopolizase la mayoría de la memoria física o las CPUs.

El recurso más preciado en el sistema es la CPU; normalmente solo hay una. Linux es un sistema operativo multiproceso. Su objetivo es tener un proceso ejecutándose en cada CPU del sistema en todo momento, para maximizar la utilización de la CPU. Si hay más procesos que CPUs (y normalmente así es), el resto de los procesos tiene que esperar a que una CPU quede libre para que ellos ejecutarse. El multiproceso es una idea simple; un proceso se ejecuta hasta que tenga que esperar, normalmente por algún recurso del sistema; cuando obtenga dicho recurso, puede ejecutarse otra vez. En un sistema uniproceso, por ejemplo DOS, la CPU estaría simplemente esperando quieta, y el tiempo de espera se desaprovecharía. En un sistema multiproceso se mantienen muchos procesos en memoria al mismo tiempo. Cuando un proceso tiene que esperar, el sistema operativo le quita la CPU a ese proceso y se la da a otro proceso que se la merezca más. El planificador se encarga de elegir el proceso más apropiado para ejecutar a continuación. Linux usa varias estrategias de organización del tiempo de la CPU para asegurar un reparto justo.

Linux soporta distintos formatos de ficheros ejecutables, ELF es uno, Java es otro, y todos se tienen que gestionar transparentemente. El uso por parte de los procesos de las bibliotecas compartidas del sistema también ha de ser gestionado transparentemente.

5.1  Procesos de Linux

Para que Linux pueda gestionar los procesos en el sistema, cada proceso se representa por una estructura de datos task_struct (las tareas (task) y los procesos son términos intercambiables en Linux). El vector task es una lista de punteros a cada estructura task_struct en el sistema. Esto quiere decir que el máximo número de procesos en el sistema está limitado por el tamaño del vector task; por defecto tiene 512 entradas. 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, hay un puntero (current) que apunta a este proceso.

Linux soporta procesos de tiempo real así como procesos normales. Estos procesos tienen que reaccionar muy rápidamente a sucesos externos (de ahí el término ``tiempo real'') y reciben un trato diferente del planificador. La estructura task_struct es bastante grande y compleja, pero sus campos se pueden dividir en áreas funcionales:

State
(Estado) A medida que un proceso se ejecuta, su estado cambia según las circunstancias. Los procesos en Linux tienen los siguientes estados: 6
Running
(Ejecutándose) El proceso se está ejecutando (es el proceso en curso en el sistema) o está listo para ejecutarse (está esperando a ser asignado a una de las CPUs del sistema).
Waiting
(Esperando) El proceso está esperando algún suceso o por algún recurso. Linux diferencia dos tipos de procesos; interrumpibles e ininterrumpibles. Los procesos en espera interrumpibles pueden ser interrumpidos por señales mientras que los ininterrumpibles dependen directamente de sucesos de hardware y no se pueden interrumpir en ningún caso.
Stopped
(Detenido) EL proceso ha sido detenido, normalmente porque ha recibido una señal. Si se están reparando errores en un proceso, este puede estar detenido.
Zombie
Es un proceso parado cuya estructura task_struct permanece aún en el vector task. Es, como su nombre indica, un proceso muerto.

Información de la Planificación de Tiempo de la CPU
El planificador necesita esta información para hacer una decisión justa sobre qué proceso en el sistema se merece más ejecutarse a continuación.

Identificadores
Cada proceso en el sistema tiene un identificador de proceso. El identificador no es un índice en el vector task, es simplemente un número. Cada proceso también tiene identificadores de usuario y grupo, que se usan para controlar el acceso de este proceso a los ficheros y los dispositivos del sistema.

Comunicación Entre Procesos
Linux soporta los mecanismos clásicos de Unix de IPC (Inter-Process Communication) de señales, tuberías y semáforos y también los mecanismos de IPC de System V de memoria compartida, semáforos y colas de mensajes. Los mecanismos de IPC soportados por Linux se describen en el capítulo  IPC-chapter.

Nexos
En un sistema de Linux ningún proceso es independiente de otro proceso. Cada proceso en el sistema, excepto el proceso inicial, tiene un proceso padre. Los procesos nuevos no se crean, se copian, o más bien se clonan de un proceso existente. Cada estructura task_struct que representa un proceso mantiene punteros a su proceso padre y sus hermanos (los procesos con el mismo proceso padre) así como a sus propios procesos hijos. Se puede ver las relaciones entre los procesos en ejecución en un sistema Linux con la orden pstree:

init(1)-+-crond(98)
        |-emacs(387)
        |-gpm(146)
        |-inetd(110)
        |-kerneld(18)
        |-kflushd(2)
        |-klogd(87)
        |-kswapd(3)
        |-login(160)---bash(192)---emacs(225)
        |-lpd(121)
        |-mingetty(161)
        |-mingetty(162)
        |-mingetty(163)
        |-mingetty(164)
        |-login(403)---bash(404)---pstree(594)
        |-sendmail(134)
        |-syslogd(78)
        `-update(166)

Además, todos los procesos en el sistema también están en una doble lista encadenada cuya raíz es la estructura task_struct del proceso init. Esta lista permite al núcleo de Linux observar cada proceso del sistema. Esto es necesario para soportar órdenes como ps o kill.

Tiempos y Temporizadores
El núcleo mantiene conocimiento de la hora de creación de los procesos así como el tiempo de CPU que consume a lo largo de su vida. En cada paso del reloj, el núcleo actualiza la cantidad de tiempo en jiffies que el proceso en curso ha usado en los modos sistema y usuario. Linux también soporta temporizadores de intervalo específicos a cada proceso; los procesos pueden usar llamadas del sistema para instalar temporizadores para enviarse señales a sí mismos cuando el temporizador acaba. Estos temporizadores pueden ser de una vez, o periódicos.

Sistema de Ficheros
Los procesos pueden abrir y cerrar ficheros y la estructura task_struct de un proceso contiene punteros a los descriptores de cada fichero abierto así como punteros a dos nodos-i del VFS. Cada nodo-i del VFS determina singularmente un fichero o directorio dentro de un sistema de ficheros y también provee un interfaz uniforme al sistema de ficheros base. El soporte de los sistemas de ficheros en Linux se describe en el Capítulo  filesystem-chapter. El primer nodo-i apunta a la raíz del proceso (su directorio inicial) y el segundo al directorio en curso, o al directorio pwd. pwd viene de la orden de Unix pwd, print working directory, o imprimir el directorio de trabajo. Estos dos nodos-i de VFS ven sus campos count incrementados para mostrar que hay uno o más procesos haciendo referencia a ellos. Por esta razón no se puede borrar un directorio que un proceso tenga como su directorio pwd (directorio de trabajo), o por la misma razón, uno de sus subdirectorios.

Memoria Virtual
La mayoría de los procesos tienen memoria virtual (los hilos del núcleo y los demonios no tienen) y el núcleo de Linux debe saber como se relaciona la memoria virtual con la memoria física del sistema.

Contexto Específico del Procesador
Un proceso se puede ver como la suma total del estado actual del sistema. Cuando un proceso se ejecuta, está utilizando los registros, las pilas, etc, del procesador. Todo esto es el contexto del procesador, y cuando se suspende un proceso, todo ese contenido específico de la CPU se tiene que salvar en la estructura task_struct del proceso. Cuando el planificador reinicia un proceso, su contexto se pasa a la CPU para seguir ejecutándose.

5.2  Identificadores

Linux, como todo Unix usa identificadores de usuario y grupo para comprobar los derechos de acceso a ficheros e imágenes en el sistema. Todos los ficheros en un sistema Linux tienen pertenencia y permisos. Los permisos describen qué tipo de acceso tienen los usuarios del sistema a ese fichero o ese directorio. Los permisos básicos son lectura, escritura y ejecución, y se asignan a tres clases de usuarios: el propietario del fichero, un grupo de usuarios y todos los usuarios del sistema. Cada clase de usuarios puede tener diferentes permisos, por ejemplo un fichero puede tener permisos que permiten a su propietario leerlo y escribirlo, permiten al grupo del fichero leerlo solamente, y todos los otros usuarios del sistema no tienen ningún acceso en absoluto.

NOTA DE REVISIÓN: Expand and give the bit assignments (777).

Los grupos son la manera de Linux de asignar privilegios a ficheros y directorios para un grupo de usuarios en vez de a un solo usuario o a todos los los usuarios del sistema. Se puede, por ejemplo, crear un grupo para todos los participantes en un proyecto de software y configurar los ficheros con el código fuente para que solo el grupo de programadores pueda leerlos y escribirlos. Un proceso puede pertenecer a varios grupos (el valor por defecto es un máximo de 32) y estos se mantienen en el vector de grupos en la estructura task_struct de cada proceso. Si un fichero tiene derechos de acceso para uno de los grupos a los que pertenece un proceso, entonces ese proceso tiene los derechos de acceso a ese fichero.

Hay cuatro pares de procesos y identificadores de grupo almacenados en la estructura task_struct de un proceso:

uid, gid
Los identificadores de usuario (uid) y grupo (gid) del usuario que el proceso usa para ejecutarse,

uid y gid efectivos
Algunos programas cambian el uid y el gid del proceso en ejecución a sus propios uid y gid (que son atributos en el nodo-i del VFS que describe la imagen ejecutable). Estos programas se conocen como programas setuid y son útiles porque es una manera de restringir el acceso a algunos servicios, en particular aquellos que se ejecutan por otro usuario, por ejemplo un demonio de red. Los uid y gid efectivos son los del programa setuid, y los uid y gid no cambian. El núcleo comprueba los uid y gid efectivos cuando comprueba los derechos de acceso.

uid y gid del sistema de ficheros
Normalmente son los mismos que los uid y gid efectivos y se usan para comprobar los derechos de acceso al sistema de ficheros. Hacen falta para los sistemas de ficheros montados por NFS (Network File System, o Sistema de Ficheros en Red), porque el servidor de NFS en modo usuario necesita acceder a los ficheros como si fuera un proceso particular. En este caso, solo el los uid y gid del sistema de ficheros se cambian (no los uid y gid efectivos). Esto evita una situación en la que un usuario malicioso podría enviar una señal para terminar el servidor de NFS. Las señales de terminación se entregan a los procesos con unos uid y gid efectivos particulares.

uid y gid salvados
Estos son parte del estándar POSIX y los usan los programas que cambian los uid y gid vía llamadas de sistema. Se usan para salvar los uid y gid reales durante el periodo de tiempo que los uid y gid originales se han cambiado.

5.3  Planificación

Todos los procesos se ejecutan parcialmente en modo usuario y parcialmente en modo sistema. La manera como el hardware soporta estos modos varía, pero en general hay un mecanismo seguro para pasar de modo usuario a modo sistema y de vuelta. El modo usuario tiene muchos menos privilegios que el modo sistema. Cada vez que un proceso hace una llamada de sistema, cambia de modo usuario a modo sistema y sigue ejecutándose. Llegado este punto, el núcleo se está ejecutando por el proceso. En Linux, un proceso no puede imponer su derecho sobre otro proceso que se esté ejecutando para ejecutarse él mismo. Cada proceso decide dejar la CPU que está usando cuando tiene que esperar un suceso en el sistema. Por ejemplo, un proceso puede estar esperando a leer un carácter de un fichero. Esta espera sucede dentro de la llamada de sistema, en modo sistema; el proceso utiliza una función de una biblioteca para abrir y leer el fichero y la función, a su vez, hace una llamada de sistema para leer bytes del fichero abierto. En este caso el proceso en espera será suspendido y se elegirá a otro proceso para ejecutarse.

Los procesos siempre están haciendo llamadas de sistema y por esto necesitan esperar a menudo. Aún así, si un proceso se ejecuta hasta que tenga que esperar, puede ser que llegue a usar una cantidad de tiempo de CPU desproporcionada y por esta razón Linux usa planificación con derecho preferente. Usando esta técnica, se permite a cada proceso ejecutarse durante poco tiempo, 200 ms, 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 porción de tiempo . El planificador tiene que elegir el proceso que más merece ejecutarse entre todos los procesos que se pueden ejecutar en el sistema. Un proceso ejecutable es aquel que esta esperando solamente a una CPU para ejecutarse. Linux usa un algoritmo para planificar las prioridades razonablemente simple para elegir un proceso entre los procesos que hay en el sistema. Cuando ha elegido un nuevo proceso para ejecutar, el planificador salva el estado del proceso en curso, los registros específicos del procesador y otros contextos en la estructura de datos task_struct. Luego restaura el estado del nuevo proceso (que también es específico a un procesador) para ejecutarlo y da control del sistema a ese proceso. Para que el planificador asigne el tiempo de la CPU justamente entre los procesos ejecutables en el sistema, el planificador mantiene cierta información en la estructura task_struct de cada proceso:

policy
(política) Esta es la política de planificación que se aplicará a este proceso. Hay dos tipos de procesos en Linux, normales y de tiempo real. Los procesos de tiempo real tienen una prioridad más alta que todos los otros. Si hay un proceso de tiempo real listo para ejecutarse, siempre se ejecutara primero. Los procesos de tiempo real pueden tener dos tipos de políticas: round robin (en círculo) y first in first out (el primero en llegar es el primero en salir). En la planificación round robin, cada proceso de tiempo real ejecutable se ejecuta por turnos, y en la planificación first in, first out cada proceso ejecutable se ejecuta en el orden que están en la cola de ejecución y el orden no se cambia nunca.

priority
(prioridad) Esta es la prioridad que el planificador dará a este proceso. También es la cantidad de tiempo (en jiffies) que se permitirá ejecutar a este proceso una vez que sea su turno de ejecución. Se puede cambiar la prioridad de un proceso mediante una llamada de sistema y la orden renice.

rt_priority
(prioridad de tiempo real) Linux soporta procesos de tiempo real y estos tienen una prioridad más alta que todos los otros procesos en el sistema que no son de tiempo real. Este campo permite al planificador darle a cada proceso de tiempo real una prioridad relativa. La prioridad del proceso de tiempo real se puede alterar mediante llamadas de sistema.

counter
(contador) Esta es la cantidad de tiempo (en jiffies) que este se permite ejecutar a este proceso. Se iguala a priority cuando el proceso se ejecuta y se decrementa a cada paso de reloj.

El planificador se ejecuta desde distintos puntos dentro del núcleo. Se ejecuta después de poner el proceso en curso en una cola de espera y también se puede ejecutar al finalizar una llamada de sistema, exactamente antes de que un proceso vuelva al modo usuario después de estar en modo sistema. También puede que el planificador se ejecute porque el temporizador del sistema haya puesto el contador counter del proceso en curso a cero. Cada vez que el planificador se ejecuta, hace lo siguiente:

trabajo del núcleo
El planificador ejecuta la parte baja de los manejadores y procesos que el planificador pone en la cola. Estos hilos del núcleo se describen en el capítulo  kernel-chapter.

Proceso en curso
El proceso en curso tiene que ser procesado antes de seleccionar a otro proceso para ejecutarlo.

Si la política de planificación del proceso en curso es round robin entonces el proceso se pone al final de la cola de ejecución.

Si la tarea es INTERRUMPIBLE y ha recibido una señal desde la última vez que se puso en la cola, entonces su estado pasa a ser RUNNING (ejecutándose).

Si el proceso en curso a consumido su tiempo, si estado pasa a ser RUNNING (ejecutándose).

Si el proceso en curso está RUNNING (ejecutándose), permanecerá en ese estado.

Los procesos que no estén ni RUNNING (ejecutándose) ni sean INTERRUMPIBLEs se quitan de la cola de ejecución. Esto significa que no se les considerará para ejecución cuando el planificador busca un proceso para ejecutar.

Selección de un proceso
El planificador mira los procesos en la cola de ejecución para buscar el que más se merezca ejecutarse. Si hay algún proceso de tiempo real (aquellos que tienen una política de planificación de tiempo real) entonces estos recibirán un mayor peso que los procesos ordinarios. El peso de un proceso normal es su contador counter pero para un proceso de tiempo real es su contador counter más 1000. Esto quiere decir que si hay algún proceso de tiempo real que se pueda ejecutar en el sistema, estos se ejecutarán antes que cualquier proceso normal. El proceso en curso, que ha consumido parte de su porción de tiempo (se ha decrementado su contador counter) está en desventaja si hay otros procesos con la misma prioridad en el sistema; esto es lo que se desea. Si varios procesos tienen la misma prioridad, se elige el más cercano al principio de la cola. El proceso en curso se pone al final de la cola de ejecución. En un sistema equilibrado con muchos procesos que tienen las mismas prioridades, todos se ejecutarán por turnos. Esto es lo que conoce como planificación Round Robin (en círculo). Sin embargo, como los procesos normalmente tiene que esperar a obtener algún recurso, el orden de ejecución tiende a verse alterado.

Cambiar procesos
Si el proceso más merecedor de ejecutarse no es el proceso en curso, entonces hay que suspenderlo y poner el nuevo proceso a ejecutarse. Cuando un proceso se está ejecutando está usando los registros y la memoria física de la CPU y del sistema. Cada vez que el proceso llama a una rutina le pasa sus argumentos en registros y puede poner valores salvados en la pila, tales como la dirección a la que regresar en la rutina que hizo la llamada. Así, cuando el planificador se ejecuta, se ejecuta en el contexto del proceso en curso. Estará en un modo privilegiado (modo núcleo) pero aún así el proceso que se ejecuta es el proceso en curso. Cuando este proceso tiene que suspenderse, el estado de la máquina, incluyendo el contador de programa (program counter, PC) y todos los registros del procesador se salvan en la estructura task_struct. A continuación se carga en el procesador el estado del nuevo proceso. Esta operación es dependiente del sistema; diferentes CPUs llevan esta operación a cabo de maneras distintas, pero normalmente el hardware ayuda de alguna manera.

El cambio del contexto de los procesos se lleva a cabo al finalizar el planificador. Por lo tanto, el contexto guardado para el proceso anterior es una imagen instantánea del contexto del hardware del sistema tal y como lo veía ese proceso al final del planificador. Igualmente, cuando se carga el contexto del nuevo proceso, también será una imagen instantánea de cómo estaban las cosas cuando terminó el planificador, incluyendo el contador de programa (program counter, PC) de este proceso y los contenidos de los registros.

Si el proceso anterior o el nuevo proceso en curso hacen uso de la memoria virtual, entonces habrá que actualizar las entradas en la tabla de páginas del sistema. Una vez más, esta operación es específica de cada arquitectura. Procesadores como el Alpha AXP, que usa tablas de traducción Look-aside o entradas en caché de una tabla de páginas, tiene que desechar el caché que pertenecía al proceso anterior.

5.3.1  Planificación en Sistemas Multiprocesador

Los sistemas con múltiples CPUs son más o menos poco comunes en el mundo de Linux, pero ya se ha hecho mucho trabajo para hacer de Linux un sistema operativo SMP (Symmetric Multi-Processing, Multi-Procesamiento Simétrico). Es decir, un sistema capaz de distribuir el trabajo equilibradamente entre las CPUs del sistema. El equilibrio se ve en el planificador más que en cualquier otro sitio.

En un sistema multiprocesador se espera que todos los procesadores estén ocupados ejecutando procesos. Cada uno ejecutará el planificador separadamente cuando el proceso en curso agota su porción de tiempo o tiene que esperar a algún recurso del sistema. Lo primero que hay que destacar en un sistema SMP es que no un solo proceso ocioso en el sistema. En un sistema monoprocesador el proceso ocioso es la primera tarea en el vector task; en un sistema SMP hay un proceso ocioso por CPU, y puede que haya más de una CPU quieta. Además hay un proceso en curso por CPU, de manera que un sistema SMP tiene que llevar cuenta de los procesos en curso y procesos ociosos por cada procesador.

En un sistema SMP la estructura task_struct de cada proceso contiene el número del procesador en el que se está ejecutando (processor) y el número del procesador del último procesador donde se ejecutó (last_processor). No hay ninguna razón que impida a un proceso ejecutarse en una CPU diferente cada vez que tiene que ejecutarse, pero Linux puede restringir un proceso a uno o más procesadores en el sistema utilizando una máscara (processor_mask). Si el bit N está encendido, entonces este proceso puede ejecutarse en el procesador N. Cuando el planificador está eligiendo un nuevo proceso para ejecutar, no considerará aquellos procesos que no tengan el bit correspondiente al número de procesador encendido en su máscara processor_mask. El planificador también da una ligera ventaja a un proceso que se haya ejecutado anteriormente en el mismo procesador porque normalmente supone mucho trabajo adicional el trasladar un proceso a otro procesador.

5.4  Ficheros


Figure 5.1: Los Ficheros de un Proceso

La figura  5.1 muestra que hay dos estructuras de datos que describen información específica del sistema de ficheros para cada proceso del sistema. La primera, fs_struct, contiene punteros a los nodos-i del VFS de este proceso y su umask. La máscara umask es el modo por defecto que se usará para crear nuevos ficheros, y se puede cambiar a través de llamadas de sistema.

La segunda estructura, files_struct, contiene información sobre todos los ficheros que el proceso está usando actualmente. Los programas leen de la entrada estándar y escriben a la salida estándar. Cualquier mensaje de error debería dirigirse a error estándar. Estas pueden ser ficheros, entrada/salida de terminal, o un dispositivo, pero en lo que al programa concierne, todos se tratan como si de ficheros se tratase. Cada fichero tiene su propio descriptor y la estructura files_structcontiene punteros a estructuras de datos file, hasta un máximo de 256, cada una de las cuales describe un fichero que este proceso está usando. El campo f_mode describe el modo en que se creó el fichero; solo lectura, lectura y escritura, o solo escritura. f_pos indica la posición dentro del fichero donde se hará la próxima operación de lectura o escritura. f_inode apunta al nodo-i del VFS que describe el fichero y f_ops es un puntero a un vector de direcciones de rutinas; una para cada función que se pueda realizar sobre un fichero. Por ejemplo, hay una función de escritura de datos. Esta abstracción de la interfaz es muy potente y permite a Linux soportar una amplia variedad de tipos de ficheros. En Linux, las tuberías están implementadas usando este mecanismo como se verá más tarde.

Cada vez que se abre un fichero, se usa un puntero file libre en files_struct para apuntar a la nueva estructura file. Los procesos de Linux esperan que se abran tres descriptores de ficheros al comienzo. Estos se conocen como entrada estándar, salida estándar, y error estándar y normalmente se heredan del proceso padre que creó este proceso. Todo acceso a los ficheros se hace a través de llamadas de sistema estándar que pasan o devuelven descriptores de ficheros. Estos descriptores son índices del vector fd del proceso, de manera que entrada estándar, salida estándar, y error estándar tienen los descriptores de fichero 0, 1 y 2 respectivamente. Cada vez que se accede al fichero, se usa las rutinas de operación sobre ficheros de la estructura file junto con el nodo-i del VFS.

5.5  Memoria Virtual

La memoria virtual de un proceso contiene el código ejecutable y datos de fuentes diversas. Primero se carga la imagen del programa; por ejemplo, una orden como ls. Este comando, como toda imagen ejecutable, se compone de código ejecutable y de datos. El fichero de imagen contiene toda la información necesaria para cargar el código ejecutable y datos asociados con el programa en la memoria virtual del proceso. Segundo, los procesos pueden reservar memoria (virtual) para usarla durante su procesamiento, por ejemplo para guardar los contenidos de los ficheros que esté leyendo. La nueva memoria virtual reservada tiene que asociarse con la memoria virtual que el proceso ya posee para poder usarla. En tercer lugar, los procesos de Linux usan bibliotecas de código común, como por ejemplo rutinas de manejo de ficheros. No tendría sentido que cada proceso tenga su propia copia de la biblioteca, así pues Linux usa bibliotecas compartidas que varios procesos pueden usar al mismo tiempo. El código y los datas de estas bibliotecas compartidas tienen que estar unidos al espacio virtual de direccionamiento de un proceso y también al espacio virtual de direccionamiento de los otros procesos que comparten la biblioteca.

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 solo se usa en ciertas ocasiones, como la inicialización o para procesar un evento particular. Puede que solo haya usado unas pocas rutinas de sus bibliotecas compartidas. Sería superfluo 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 multiplicamos ese gasto de memoria por el número de procesos en el sistema. Para solventar el problema, Linux usa una técnica llamada páginas en demanda ( demand paging) que sólo copia la memoria virtual de un proceso en la memoria física del sistema cuando el proceso trata de usarla. 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 el código o los datos, el hardware del sistema generará una falta de página (page fault) y le pasará el control al núcleo para que arregle las cosas. Por lo tanto, por cada área de memoria virtual en el espacio de direccionamiento de un proceso, Linux necesita saber de dónde viene esa memoria virtual y cómo ponerla en memoria para arreglar las faltas de página.


Figure 5.2: La Memoria Virtual de un Proceso

El núcleo de Linux necesita gestionar todas estas áreas de memoria virtual, y el contenido de la memoria virtual de cada proceso se describe mediante una estructura mm_struct a la cual se apunta desde la estructura task_struct del proceso. La estructura mm_struct del proceso también contiene información sobre la imagen ejecutable cargada y un puntero a las tablas de páginas del proceso. Contiene punteros a una lista de estructuras vm_area_struct, cada una de las cuales representa un área de memoria virtual dentro del proceso.

Esta lista enlazada está organizada en orden ascendiente, la figura  5.2 muestra la disposición en memoria virtual de un simple proceso junto con la estructura de datos del núcleo que lo gestiona. Como estas áreas de memoria virtual vienen de varias fuentes, Linux introduce un nivel de abstracción en la interfaz haciendo que la estructura vm_area_struct apunte a un grupo de rutinas de manejo de memoria virtual (via vm_ops).

De esta manera, toda la memoria virtual de un proceso se puede gestionar de una manera consistente sin que importe las diferentes maneras de gestionar esa memoria por parte de distintos servicios de gestión. Por ejemplo, hay una rutina que se utiliza cuando el proceso trata de acceder la memoria y esta no existe, así es como se resuelven las faltas de página.

El núcleo de Linux accede repetidamente al grupo de estructuras vm_area_struct del proceso según crea nuevas áreas de memoria virtual para el proceso y según corrige las referencias a la memoria virtual que no está en la memoria física del sistema. Por esta razón, el tiempo que se tarda en encontrar la estructura vm_area_struct correcta es un punto crítico para el rendimiento del sistema. Para acelerar este acceso, Linux también organiza las estructuras vm_area_struct en un árbol AVL (Adelson-Velskii and Landis). El árbol está organizado de manera que cada estructura vm_area_struct (o nodo) tenga sendos punteros a las estructuras vm_area_struct vecinas de la izquierda y la derecha. El puntero izquierdo apunta al nodo con una dirección inicial de memoria virtual menor y el puntero derecho apunta a un nodo con una dirección inicial mayor. Para encontrar el nodo correcto, Linux va a la raíz del árbol y sigue los punteros izquierdo y derecho de cada nodo hasta que encuentra la estructura vm_area_struct correcta. Por supuesto, nada es gratis y el insertar una nueva estructura vm_area_struct en el árbol supone un gasto adicional de tiempo.

Cuando un proceso reserva memoria virtual, en realidad Linux no reserva memoria física para el proceso. Lo que hace es describir la memoria virtual creando una nueva estructura vm_area_struct. Esta se une a la lista de memoria virtual del proceso. Cuando el proceso intenta escribir a una dirección virtual dentro de la nueva región de memoria virtual, el sistema creará una falta de página. El procesador tratará de decodificar la dirección virtual, pero dado que no existe ninguna entrada de tabla de páginas para esta memoria, no lo intentará más, y creará una excepción de falta de página, dejando al núcleo de Linux la tarea de reparar la falta. Linux mira a ver si la dirección virtual que se trató de usar está en el espacio de direccionamiento virtual del proceso en curso. Si así es, Linux crea los PTEs apropiados y reserva una página de memoria física para este proceso. Puede que sea necesario cargar el código o los datos del sistema de ficheros o del disco de intercambio dentro de ese intervalo de memoria física. El proceso se puede reiniciar entonces a partir de la instrucción que causó la falta de página y esta vez puede continuar dado que memoria física existe en esta ocasión.

5.6  Creación de un Proceso

Cuando el sistema se inicia está ejecutándose en modo núcleo y en cierto sentido solo hay un proceso, el proceso inicial. Como todo proceso, el proceso inicial tiene el estado de la máquina representado por pilas, registros, etc. Estos se salvan en la estructura de datos task_struct del proceso inicial cuando otros procesos del sistema se crean y ejecutan. Al final de la inicialización del sistema, el proceso inicial empieza un hilo del núcleo (llamado init) y a continuación se sienta en un bucle ocioso sin hacer nada. Cuando no hay nada más que hacer, el planificador ejecuta este proceso ocioso. La estructura task_structdel proceso ocioso es la única que se reserva dinámicamente, está definida estáticamente cuando se construye el núcleo y tiene el nombre, más bien confuso, de init_task.

El hilo del núcleo o proceso init tiene un identificador de proceso de 1, ya que es realmente el primer proceso del sistema. Este proceso realiza algunas tareas iniciales del sistema (como abrir la consola del sistema y montar el sistema de ficheros raíz, y luego ejecuta el programa de inicialización del sistema. Este puede ser uno de entre /etc/init, /bin/init o /sbin/init, dependiendo de su sistema. El programa init utiliza /etc/inittab como un guión para crear nuevos procesos dentro del sistema. Estos nuevos procesos pueden a su vez crear otros procesos nuevos. Por ejemplo, el proceso getty puede crear un proceso login cuando un usuario intenta ingresar en el sistema. Todos los procesos del sistema descienden del hilo del núcleo init.

Los procesos nuevos se crean clonando procesos viejos, o más bien, clonando el proceso en curso. Una nueva tarea se crea con una llamada de sistema (fork o clone) y la clonación ocurre dentro del núcleo en modo núcleo. Al finalizar la llamada de sistema hay un nuevo proceso esperando a ejecutarse cuando el planificador lo seleccione. Se reserva una estructura de datos task_struct nueva a partir de la memoria física del sistema con una o más páginas de memoria física para las pilas (de usuario y de núcleo) del proceso. Se puede crear un nuevo identificador de proceso, uno que sea único dentro del grupo de identificadores de procesos del sistema. Sin embargo, también es posible que el proceso clonado mantenga el identificador de proceso de su padre. La nueva estructura task_struct se introduce en el vector task y los contenidos de la estructura task_structdel proceso en curso se copian en la estructura task_struct clonada.

En la clonación de procesos, Linux permite que los dos procesos compartan recursos en vez de tener dos copias separadas. Esto se aplica a los ficheros, gestor de señales y memoria virtual del proceso. Cuando hay que compartir los recursos, sus campos count (cuenta) respectivos se incrementan de manera que Linux no los libere hasta que ambos procesos hayan terminado de usarlos. De manera que, por ejemplo, si el proceso clonado ha de compartir su memoria virtual, su estructura task_struct contendrá un puntero a la estructura mm_struct del proceso original y esa estructura mm_struct verá su campo count incrementado para mostrar el número de procesos que la están compartiendo en ese momento.

La clonación de la memoria virtual de un proceso es bastante complicada. Hay que generar un nuevo grupo de estructuras vm_area_struct, junto con sus estructuras mm_struct correspondientes y las tablas de páginas del proceso clonado. En este momento no se copia ninguna parte de la memoria virtual del proceso. Hacer eso sería una tarea difícil y larga dado que parte de esa memoria virtual estaría en memoria física, parte en la imagen ejecutable que el proceso está usando, y posiblemente parte estaría en el fichero de intercambio. En cambio, lo que hace Linux es usar una técnica llamada ``copiar al escribir'' que significa que la memoria virtual solo se copia cuando uno de los dos procesos trata de escribir a ella. Cualquier parte de la memoria virtual que no se escribe, aunque se pueda, se compartirá entre los dos procesos sin que ocurra ningún daño. La memoria de sólo lectura, por ejemplo el código ejecutable, siempre se comparte. Para que ``copiar al escribir'' funcione, las áreas donde se puede escribir tienen sus entradas en la tabla de páginas marcadas como sólo lectura, y las estructuras vm_area_struct que las describen se marcan como ``copiar al escribir''. Cuando uno de los procesos trata de escribir a esta memoria virtual, una falta de página ocurrirá. En este momento es cuando Linux hace una copia de la memoria y arregla las tablas de páginas y las estructuras de datos de la memoria virtual de los dos procesos.

5.7  Tiempos y Temporizadores

El núcleo tiene conocimiento de la hora de creación de un proceso así como del tiempo de CPU que consume durante su vida. En cada paso del reloj el núcleo actualiza la cantidad de tiempo en jiffies que el proceso en curso ha consumido en modos sistema y usuario.

Además de estos cronómetros de cuentas, Linux soporta temporizadores de intervalo específicos a cada proceso. Un proceso puede usar estos temporizadores para enviarse a sí mismo varias señales cada vez que se terminen. Hay tres tipos de temporizadores de intervalo soportados:

Reales
el temporizador señala el tiempo en tiempo real, y cuando el temporizador a terminado, se envía una señal SIGALRM al proceso.
Virtuales
El temporizador solo señala el tiempo cuando el proceso se está ejecutando y al terminar, envía una señal SIGVTALRM.
Perfil
Este temporizador señala el tiempo cuando el proceso se ejecuta y cuando el sistema se está ejecutando por el proceso. Al terminar se envía la señal SIGPROF

Uno o todos los temporizadores de intervalo pueden estar ejecutándose y Linux mantiene toda la información necesaria en la estructura task_struct del proceso. Se puede usar llamadas de sistema para configurar los temporizadores y para empezarlos, pararlos y leer sus valores en curso. Los temporizadores virtuales y de perfil se gestionan de la misma manera. A cada paso del reloj, los temporizadores de intervalo del proceso en curso se decrementan y, si han terminado, se envía la señal apropiada.

Los temporizadores de intervalo de tiempo real son algo diferentes. Linux utiliza para estos el mecanismo de temporizador que se describe en el Capítulo  kernel-chapter. Cada proceso tiene su propia estructura timer_list y, cuando el temporizador de tiempo real está corriendo, esta se pone en la cola de la lista de temporizadores del sistema. Cuando el temporizador termina, el gestor de la parte baja del temporizador lo quita de la cola y llama al gestor de los temporizadores de intervalo. Esto genera la señal SIGALRM y reinicia el temporizador de intervalo, añadiéndolo de nuevo a la cola de temporizadores del sistema.

5.8  Ejecución de Programas

En Linux, como en Unix, los programas y las órdenes se ejecutan normalmente por un intérprete de órdenes. Un intérprete de órdenes es un proceso de usuario como cualquier otro proceso y se llama shell (concha, cáscara). 7

Hay muchos shells en Linux, algunos de los más populares son sh, bash y tcsh. Con la excepción de unas pocas órdenes empotradas, como cd y pwd, una orden es un fichero binario ejecutable. Por cada orden introducida, el shell busca una imagen ejecutable con ese nombre en los directorios que hay en el camino de búsqueda, que están en la variable de entorno PATH Si se encuentra el fichero, se carga y se ejecuta. El shell se clona a sí mismo usando el mecanismo fork descrito arriba y entonces el nuevo proceso hijo cambia la imagen binaria que estaba ejecutando, el shell, por los contenidos de la imagen ejecutable que acaba de encontrar. Normalmente el shell espera a que la orden termine, o más bien a que termine el proceso hijo. Se puede hacer que el shell se ejecute de nuevo poniendo el proceso hijo en el fondo tecleando control-Z, que causa que se envíe una señal SIGSTOP al proceso hijo, parándolo. Entonces se puede usar la orden del shell bg para ponerlo en el fondo, y el shell le envía una señal SIGCONT para que continúe, hasta que termine, o hasta que necesite entrada o salida del terminal.

Un fichero ejecutable puede tener muchos formatos, o incluso ser un fichero de órdenes (script). Los ficheros de órdenes se tienen que reconocer y hay que ejecutar el intérprete apropiado para ejecutarlos; por ejemplo /bin/sh interpreta ficheros de órdenes. Los ficheros de objeto ejecutables contienen código ejecutable y datos además de suficiente información para que el sistema operativo los cargue en memoria y los ejecute. El formato de fichero objeto más usado en Linux es ELF pero, en teoría, Linux es lo suficientemente flexible para manejar casi cualquier formato de ficheros de objeto.


Figure 5.3: Formatos Binarios Registrados

Al igual que con los sistemas de ficheros, los formatos binarios soportados por Linux pueden estar incluidos en el núcleo en el momento de construir el núcleo o pueden estar disponibles como módulos. El núcleo mantiene una lista de formatos binarios soportados (ver figura  5.3) y cuando se intenta ejecutar un fichero, se prueba cada formato binario por turnos hasta que uno funcione. Unos formatos muy comunes en Linux son a.out y ELF. No es necesario cargar los ficheros ejecutables completamente en memoria; se usa una técnica conocida como carga en demanda. Cada parte de una imagen ejecutable se pone en memoria según se necesita. Las partes en desuso pueden ser desechadas de la memoria.

5.8.1  ELF

El formato de fichero objeto ELF (Executable and Linkable Format, Formato Ejecutable y ``Enlazable''), diseñado por Unix System Laboratories, se ha establecido firmemente como el formato más usado en Linux. Aunque el rendimiento puede ser ligeramente inferior si se compara con otros formatos como ECOFF y a.out, se entiende que ELF es más flexible. Los ficheros ejecutables ELF contienen código ejecutable, que a veces se conoce como texto y datos. En la imagen ejecutable hay unas tablas que describen cómo se debe colocar el programa en la memoria virtual del proceso. Las imágenes linkadas estáticamente se construyen con el linker (ld), o editor de enlaces, para crear una sola imagen que contiene todo el código y los datos necesarios para ejecutar esta imagen. La imagen también especifica la disposición en memoria de esta imagen, y la dirección del primer código a ejecutar dentro de la imagen.


Figure 5.4: El Formato de Ficheros Ejecutable ELF

La figura  5.4 muestra la disposición de una imagen ejecutable ELF estática. Es un simple programa en C que imprime ``hello world'' y termina. La cabecera lo describe como una imagen ELF con dos cabeceras físicas (e_phnum es 2) que empieza después de 52 bytes del principio del fichero imagen. La primera cabecera física describe el código ejecutable en la imagen. Está en la dirección virtual 0x8048000 y tiene 65532 bytes. Esto se debe a que es una imagen estática que contiene todo el código de la función printf() para producir el mensaje ``hello world''. El punto de entrada de la imagen, la primera instrucción del programa, no está al comienzo de la imagen, sino en la dirección virtual 0x8048090 (e_entry). El código empieza inmediatamente después de la segunda cabecera física. Esta cabecera física describe los datos del programa y se tiene que cargar en la memoria virtual a partir de la dirección 0x8059BB8. Estos datos se pueden leer y escribir. Se dará cuenta que el tamaño de los datos en el fichero es 2200 bytes (p_filesz) mientras que su tamaño en memoria es 4248 bytes. Esto se debe a que los primeros 2200 bytes contienen datos pre-inicializados y los siguientes 2048 bytes contienen datos que el código ejecutable inicializará.

Cuando Linux carga una imagen ejecutable ELF en el espacio de direccionamiento virtual del proceso, la imagen no se carga en realidad. Lo que hace es configurar las estructuras de datos de la memoria virtual, el árbol vm_area_struct del proceso y sus tablas de páginas. Cuando el programa se ejecuta, se originarán faltas de página que harán que el código y los datos del programa se cargarán en la memoria física. Las partes del programa que no se usen no se cargarán en memoria. Una vez que el cargador del formato binario ELF ha comprobado que la imagen es una imagen ejecutable ELF válida, borra de la memoria virtual del proceso la imagen ejecutable que el proceso tenía hasta ahora. Como este proceso es una imagen clonada (todos los procesos lo son), esta imagen anterior es el programa que el proceso padre estaba ejecutando, como por ejemplo un intérprete de órdenes como bash. La eliminación de la antigua imagen ejecutable desecha las estructuras de datos de la memoria virtual anterior y reinicia las tablas de páginas del proceso. Además, también se eliminan los gestores de señales que se hubiesen establecido y se cierran los ficheros que estuviesen abiertos. Después de esta operación el proceso está listo para recibir la nueva imagen ejecutable. La información que se coloca en la estructura mm_structdel proceso es la misma independientemente del formato de la imagen ejecutable. Hay punteros al comienzo y al final del código y los datos de la imagen. Estos valores se averiguan cuando se leen los encabezamientos físicos de la imagen ejecutable ELF y a la vez que se hacen corresponder las secciones del programa que describen al espacio de direccionamiento virtual del proceso. En ese momento también se establecen las estructuras vm_area_struct y se modifican las tablas de páginas de proceso. La estructura mm_structtambién contiene punteros a los parámetros que hay que pasar al programa y a las variables de entorno de este proceso.

Bibliotecas ELF Compartidas

Una imagen enlazada dinámicamente, por otra parte, no contiene todo el código y datos necesarios para ejecutarse. Parte de esos datos están en las bibliotecas compartidas que se enlazan con la imagen cuando esta se ejecuta. El enlazador dinámico también utiliza las tablas de la biblioteca ELF compartida cuando la biblioteca compartida se enlaza con la imagen al ejecutarse esta. Linux utiliza varios enlazadores dinámicos, ld.so.1, libc.so.1 y ld-linux.so.1, todos se encuentran en /lib. Las bibliotecas contienen código que se usa comúnmente como subrutinas del lenguaje. Sin enlaces dinámicos, todos los programas necesitarían su propia copia de estas bibliotecas y haría falta muchísimo más espacio en disco y en memoria virtual. Al usar enlaces dinámicos, las tablas de la imagen ELF tienen información sobre cada rutina de biblioteca usada. La información indica al enlazador dinámico cómo encontrar la rutina en una biblioteca y como enlazarla al espacio de direccionamiento del programa.

NOTA DE REVISIÓN: Do I need more detail here, worked example?

5.8.2  Ficheros de Guión

Los ficheros de guión son ficheros ejecutables que precisan un intérprete para ejecutarse. Existe una amplia variedad de intérpretes disponibles para Linux; por ejemplo, wish, perl y shells de órdenes como tcsh. Linux usa la convención estándar de Unix de que la primera línea de un fichero de guión contenga el nombre del intérprete. Así pues, un fichero de guión típico comenzaría así:
#!/usr/bin/wish

El cargador binario de ficheros de guión intenta encontrar el intérprete para el guión. Esto se hace intentando abrir el fichero ejecutable que se menciona en la primera línea del fichero de guión. Si se puede abrir, se usa el puntero a su nodo-i del VFS y usarse para interpretar el fichero guión. El nombre del fichero de guión pasa a ser el parámetro cero (el primer parámetro) y todos los otros parámetros se trasladan un puesto arriba (el primero original pasa a ser el segundo, etc). La carga del intérprete se hace de la misma manera que Linux carga todos los ficheros ejecutables. Linux prueba cada formato binario por turnos hasta que uno funcione. Esto significa que en teoría se podrían unir varios intérpretes y formatos binarios para convertir al gestor de formatos binarios de Linux en una pieza de software muy flexible.

Capítulo 6
Mecanismos de Comunicacion Interprocesos

Los procesos se comunican con otros procesos y con el núcleo para coordinar sus actividades. Linux soporta un número de mecanismos de comunicación entre procesos (IPC). Las señales y tuberías son dos de ellos pero Linux también soporta el llamado mecanismo System V por la versión Unix en la que apareció por primera vez.

6.1  Señales

Las señales son uno de los métodos más antiguos de comunicación entre procesos usados por los sistemas Unix . Son usados para señalizar sucesos asíncronos a uno o más procesos. Una señal podría ser generada por una interrupción de teclado o por una condición de error como la de un proceso intentando acceder a una localización no existente en su memoria virtual. Las señales son usadas también por las shells para indicar ordenes de control de trabajo a sus procesos hijos.

Hay un conjunto de señales definidas que puede generar el núcleo o que pueden ser generadas por otros procesos en el sistema, siempre que tengan los privilegios correctos. Usted puede listar un conjunto de señales del sistema usando el comando kill -l, en mi Máquina Linux Intel esto proporciona:

 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL
 5) SIGTRAP      6) SIGIOT       7) SIGBUS       8) SIGFPE
 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2
13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD
18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN
22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO
30) SIGPWR      

Los números son diferentes para una Máquina Linux Alpha AXP . Los procesos pueden elegir ignorar la mayoría de las señales que son generadas, con dos excepciones notables: ni la señal SIGSTOP la cual causa que un proceso detenga su ejecución ni la señal SIGKILL que causa que un proceso termine pueden ser ignoradas. En cualquier otro caso un proceso puede elegir solamente de que forma manejar las diferentes señales. Los procesos pueden bloquear las señales y, si no las bloquean pueden elegir entre tratarlas ellos mismos o permitir que sea el núcleo el que lo haga. Si el núcleo maneja las señales, ejecutará las acciones requeridas por defecto para cada una. Por ejemplo, la acción por defecto cuando un proceso recibe la señal SIGFPE (execpción de punto flotante), es realizar un volcado del núcleo y después terminar. Las señales no tienen prioridades relativas inherentes. Si dos señales se generan para un proceso al mismo tiempo puede que sean presentadas al proceso o tratadas en cualquier orden. Tampoco existe mecanismo para tratar múltiples señales de la misma clase. No existe forma de que un proceso pueda informar de si recibió 1 o 42 señales SIGCONT.

Linux implementa las señales usando información almacenada en la task_struct del proceso. El número de señales soportadas está limitado al tamño de palabra del procesador. Procesadores con un tamaño de palabra de 32 bits pueden tener 32 señales mientras que procesadores de 64 bits como el Alpha AXP pueden tener hasta 64 señales Las señales actualmente pendientes son mantenidas con el campo de señal marcado con una mascara de señal bloqueada. Con la excepción de SIGSTOP y SIGKILL, todas las señales pueden ser bloqueadas. Si se genera una señal bloqueada, esta permanece pendiente hasta que es desbloqueada. Linux también mantiene información acerca de como cada proceso trata todas las posibles señales y esta es almacenada en una matriz de estructuras de datos sigaction apuntada por la task_struct de cada proceso. Entre otras cosas esta contiene o la dirección de una rutina que tratará la señal o una bandera que indica a Linux que el proceso o bien desea ignorar la señal o bien permitir que sea el núcleo el que la trate en su lugar. El proceso modifica el tratamiento por defecto de la señal realizando llamadas al sistema las cuales alteran la sigaction de la señal apropiada así como la máscara de bloqueada.

No todo proceso en el sistema puede enviar señales a todo otro proceso, el núcleo puede y los super usuarios también. Los procesos normales solo pueden enviar señales a procesos con el mismo uid y gid o a procesos en el mismo grupo de proceso8. Las señales son generadas estableciendo el bit apropiado el campo señal de la task_structSi el proceso no ha bloqueado la señal y está esperando pero es interrumpible (en estado Interrumpible) entonces es activado cambiando su estado a ejecutándose y asegurándose de que se encuentra en la cola de ejecución. De esa forma el planificador lo considerará un candidato a ejecutar cuando el sistema planifica de nuevo.

Si es necesario el tratamiento por defecto, entonces Linux puede optimizar el manejo de la señal. Por ejemplo si la señal SIGWINCH (el foco ha cambiado en X window) y se usa el manejador por defecto entonces no hay nada que hacer.

Las señales no son presentadas al proceso inmediatamente que son generadas, tienen que esperar hasta que el proceso se está ejecutándose de nuevo. Cada vez que un proceso sale de una llamada al sistema sus campos señal y bloqueado son comprobados y, si hay algunas señales no bloqueadas, ahora pueden ser comunicadas. Esto quizás parezca un método poco fiable pero todo proceso en el sistema está realizando todo el tiempo llamadas al sistema, por ejemplo para escribir un caracter en el terminal. Los procesos pueden elegir esperar a las señales si los desean, quedando suspendidos en estado interrumpible hasta que una señal les es presentada. El código de procesado de señales de Linux comprueba la estructura sigaction para cada una de las señales no bloqueadas.

Si un manejador de señal es establecido a la acción por defecto entonces el núcleo la tratará. El tratamiento por defecto de la señal SIGSTOP cambiará el estado del proceso actual a Detenido y ejecutará el planificador para seleccionar un nuevo proceso. La acción por defecto para la señal SIGFPE realizará un volcado del núcleo del proceso y entonces hará que este termine. Alternativamente, el proceso puede haber especificado su propio manejador de señales. Este es una rutina que será llamada siempre que la seãl es generada y la estructura sigaction almacena la dirección de esa rutina. El núcleo debe de llamar a la rutina de tratamiento de señales del proceso el como esto ocurre es dependiente del procesador pero todas las CPUs deben hacer frente a el hecho de que el proceso actual está ejecutándose en modo núcleo y es así como vuelve al proceso que llamó al núcleo o la rutina de sistema en modo usuario. El problema se resuelve manipulando la pila y registros del proceso. El contador de programa del proceso se establece a la dirección de la rutina de manejo de señales y los parámetros de la rutina son añadidos a la estructura de llamada o pasados en registros. Cuando el proceso reanuda la operación parece como si la rutina de tratamiento de señales hubiese sido llamada de forma normal.

Linux es compatible POSIX por tanto el proceso puede especificar que señales son bloqueadas cuando es llamada una rutina particular de tratamiento de señales. Esto significa cambiar la mascara bloqueado durante la llamada al manejador de señales del proceso. La mascara bloqueado debe ser devuelta a su valor original cuando la rutina de tratamiento de la señal ha terminado. Por lo tanto Linux añade una llamada a una rutina ordenadora la cual restaura la mascara bloqueado en la pila de llamada del proceso señalado. Linux también optimiza el caso en el que varias rutinas de tratamiento de señales deben ser llamadas mediante apilamiento por eso cada vez que una rutina termina, la siguiente es llamada hasta que la rutina ordenadora es llamada.

6.2  Tuberías

Todas las shells habituales en Linux permiten la redirección Por ejemplo

$ ls | pr | lpr

Encauza la salida de el comando ls que lista los ficheros del directorio hacia la entrada estandar de el comando pr el cual la repagina. Finalmente la salida estandar de el comando pr es encauzada hacia la entrada estandar del comando lpr qu imprime los resultados en la impresora por defecto. Las tuberías son por tanto flujos unidireccionales de bytes que conectan la salida estandar de un proceso con la entrada estandar de otro proceso. Ningún proceso es consciente de esta redirección y actúa como lo haría normalmente. Es la shell la que establece estas tubeías temporales entre procesos.


Figure 6.1: Tuberías

En Linux, una tubería se implementa usando dos estructuras de datos de fichero que apuntan ambos al mismo inodo VFS que asimismo apunta a una página física de memoria. La figura  6.1 muestra que cada estructura de datos de fichero contiene punteros a distintos vectores de rutinas de tratamiento de ficheros; una para escribir en la tubería, y la otra para leer de la tubería. Esto oculta las diferencias subyacentes de las llamadas al sistema genéricas la cuales leen y escriben en ficheros ordinarios. Conforme el proceso escritor escribe en la tubería, los bytes son copiados en la página de datos compartida y cuando el proceso lector lee de la tubería los bytes son copiados de la página de datos compatida. Linux debe sincronizar el acceso a la tubería. Debe asegurarse que el lector y el escritor van al paso y para hacer esto utiliza los bloqueos, colas de espera y señales.

Cuando el escritor quiere escribir en la tubeía utiliza la funciones estandar de la librería de escritura. Estas pasan descriptores de ficheros que son índices dentro del conjunto de estructuras de fichero del proceso, cada una representando un fichero abierto o, como en este caso, una tubería abierta. Las llamadas al sitema de Linux usan las rutinas de escritura apuntadas por la estructura de datos que describe esta tubería. Esa rutina de escritura usa información mantenida en el inodo VFS que representa a esta tubería Si hay suficiente espacio para escribir todos los bytes en la tubería y, por tanto a todo su largo la tubería no es bloqueada por su lector, Linux la bloquea para el escritor y copia los bytes que deben de ser escritos desde el espacio de direcciones del proceso en la página de datos compartidos. Si la tubería es bloqueada por el lector o si no hay suficiente espacio para los datos entonces se hace dormir el proceso actual sobre la cola de espera del inodo de la tubería y se llama al planificador con lo que otro proceso puede ejecutarse. El proceso es interrumpible por lo tanto puede recibir señales y será despertado por el lector cuando haya espacio suficiente para los datos escritos o cuando la tubería sea desbloqueada. Cuando los datos han sido escritos, el inodo VFS de la tubería es desbloqueado y cualquier lector dormido que esté esperando en la cola de espera del inodo se despertará a si mismo.

La lectura de datos de la tubería es un proceso muy similar a la escritura en ella. Los procesos están autorizados a realizar lecturas no bloqueantes (depende del modo en que fué abierto el fichero o la tubería) y, en este caso, si no hay datos para ser leidos o si la tubería está bloqueada, se devolverá un error. Esto significa que el proceso puede continuar ejecutándose. La alternativa es esperar en la cola del inodo de la tubería hasta que el proceso escritor ha terminado. Cuando ambos procesos han terminado con la tubería, el inodo de la tubería es desechado junto con la página de datos compartidos.

Linux también soporta tuberías nombradas, también conocidas como FIFOs porque las tuberías funcionan según un principo Primero en Entrar, Primero en Salir. El primer dato escrito en la tubería es el primer dato leido de la tubería. A diferencia de las tuberías los FIFOs no son objetos temporales, son entidades y pueden ser creadas usando el comando makefifo. Los procesos son libres de usar un FIFO tan solo con tener derechos de acceso apropiados para ello. La forma en que los FIFOs son abiertos es un poco diferente de las tuberías. Una tubería (son dos estructuras de ficheros, su inodo VFS y la página de datos compartida) es creada en el momento mientras que un FIFO ya existe y es abierto y cerrado por sus usuarios. Linux debe de tratar los lectores que abren el FIFO antes de que los escritores lo abran así como los lectores antes de que cualquier escritor haya escrito sobre él. De esta forma, los FIFOs son manejados de forma casi idéntica que las tuberías y manejan las mismas estructuras de datos y operaciones.

6.3  Enchufes

NOTA DE REVISIÓN: Añadir cuando sea escrito el capítulo de trabajo en red

6.3.1  Mecanismos IPC System V

Linux soporta tres tipos de mecanismos de comiuncación interprocesos que aparecieron por primera vez en el Unix System V (1983). Estos son colas de mensajes, semaforos y memoria compartida. Estos mecanismos IPC System V comparten todos métodos comunes de autentificación. Los procesos quizás accedan a estos recursos solo mediante el paso de una referencia a un identificador único a el núcleo vía llamadas al sistema. El acceso a estos objetos IPC System V es comprobado usando permisos de acceso, más que comprobando los accesos a ficheros. Los derechos de acceso a los objetos System V son establecidos por el creador del objeto vía llamadas al sistema. El indentificador referencia del objeto es utilizado por cada mecanismo como un índice dentro de una tabla de recursos. No es un índice sencillo pero requiere alguna manipulación antes de generar el índice.

Todas las estructuras de datos Linux que representan objetos IPC System V en el sistema incluyen una estructura ipc_permla cual contiene identificadores de el propietario y el usuario y grupo creador del proceso.

La forma de acceso para este objeto (propietario, grupo y otro) y la clave del objeto IPC. La clave es utilizada como una forma de encontrar el identificador de referencia del objeto IPC System V. Se soportan dos tipos de claves públicas y privadas. Si la clave es pública entonces cualquier proceso en el sistema, sujeto a chequeo de derechos, puede encontrar el identificador de referencia del objeto IPC System V. Los objetos IPC System V nunca pueden ser refernciados con una clave, solo por su identificador de referencia.

6.3.2  Colas de Mensajes

Las colas de mensajes permiten a uno o más procesos escribir mensajes, que serán leidos por uno o más procesos lectores. Linux mantiene una lista de colas de mensajes, el vector msgque; cada elemento del cual apunta a una estructura de datos msqid_ds que describe completamente la cola de mensajes; Cuando las colas de mensajes son creadas se posiciona una nueva estructura de datos msqid_ds en la memoria del sistema y es insertada en el vector.


Figure 6.2: Colas de Mensajes IPC System V

Cada estructura de datos msqid_dscontiene una estructura de datos ipc_perm y punteros a los mensajes introducidos en esta cola. Además, Linux guarda tiempos de modificación de la cola como la última vez en la que se escribió en la cola. La msqid_ds también contiene dos colas de espera; una para escritores de la cola y otra para lectores de la cola.

Cada vez que un proceso intenta escribir un mensaje en la cola de escritura sus identificadores efectivos de usuario y grupo son comparados con el modo en la estructura de datos ipc_perm de esta cola. Si el proceso puede escribir en la cola entonces el mensaje puede ser copiado desde el espacio de direcciones del proceso a una estructura de datos msgy ser puesto al final de la cola. Cada mensaje es etiquetado con un tipo específico de aplicación, acordado entre los procesos cooperantes. Sin embargo, quizás no haya espacio para el mensaje puesto que Linux restringe el número y longitud de los mensajes que pueden ser escritos. En este caso el proceso será añadido a esta cola de espera de esta cola y el planificador será llamado para que ejecute otro proceso. Será despertado cuando uno o más mensajes hayan sido leidos de la cola.

La lectura de la cola es un proceso similar. De nuevo, los derechos de acceso del proceso a la cola de escritura son chequeados. Un proceso lector puede elegir entre coger el primer mensaje de la cola sin importar el tipo o seleccionar mensajes de un determinado tipo. Si no hay mensajes que cumplan este criterio el proceso lector es añadido a la cola de espera de lectura y se ejecuta el planificador. Cuando un mensaje sea escrito a la cola este proceso será despertado y ejecutado de nuevo.

6.3.3  Semaforos

En su forma más simple un semaforo es una zona en memoria cuyo valor puede ser comprobado y establecido por más de un proceso. La comprobación y establecimiento es, más allá de como un proceso está implicado, ininterrumpible o atómico; una vez que ha comenzado nada puede detenerlo. El resultado de la operación de comprobación y establecimiento es la suma del valor actual del semaforo y el valor establecido, que puede ser positivo o negativo. Dependiendo del resultado de la operación de comprobación y establecimiento un proceso quizás tenga que suspenderse hasta que el valor del semaforo sea cambiado por otro proceso. Los semaforos pueden ser utilizados para implementar regiones críticas, áreas de código críticas que un proceso solo debiera ejecutar en un determinado momento.

Diagmos que usted tuviera muchos procesos cooperando leyendo y escribiendo registros en un único fichero de datos. Usted querría que los accesos al fichero estuvieran estrictamente coordinados. Usted podría utilizar un semaforo con un valor incicial de 1 y, alrededor del código de operación del fichero, situar dos operaciones de semaforos, la primera para comprobar y decrementar el valor del semaforo, y la segunda para comprobar e incrementarlo. El primer proceso que acceda al fichero debería intentar decrementar el valor del semaforo y si tuviese exito, el valor del semaforo será 0. Este proceso puede ahora continuar y usar el fichero de datos pero si otro proceso que deseara usar el fichero en ese momento intentara decrementar el valor del semaforo fallaría ya que el resultado debería ser -1. Este proceso deberá ser suspendido hasta que el primer proceso haya terminado con el fichero. Cuando el primer proceso ha terminado con el fichero incrementará el valor del semaforo, poniendolo de nuevo a 1. Ahora del proceso en espera puede ser reanudado y esta vez su intento de modificar el semaforo tendrá exito.


Figure 6.3: Semaforos IPC System V

Cada objeto semaforo IPC System V describe una matriz y Linux usa la estructura de datos semid_dspara representarlo. Todas las estructuras de datos semid_ds en el sistema están apuntadas por el semary, un vector de punteros. En cada matriz de semaforos hay sem_nsems, cada uno descrito por una estructura de datos sem que es apuntado por sem_base. Todos los procesos que están autorizados a manipular la matriz de semaforos de un objeto semaforo IPC System V puede realizar llamadas al sistema que realizan operaciones sobre ellos. Las llamadas al sistema pueden especificar muchas operaciones y cada operación está descrita por tres entradas; el índice de semaforo, el valor de la operación y un conjuto de banderas. El índice de semaforo es un índice dentro de la matriz de semaforos y el valor de la operación es un valor numérico que será añadido a el valor actual del semaforo. Linux primero comprueba si o no el total de la operación podría realizarse. Una operación tendrá exito si el valor de la operación sumado al valor actual del semaforo fuese mayor que cero o si ambos el valor de la operación y el actual del semaforo son cero. Si cualquiera de las operaciones del semaforo fallara Linux puede suspender el proceso pero solo si las banderas de la operación no han solicitado que la llamada al sistema sea no-bloqueante. Si el proceso va a ser suspendido entonces Linux debe guardar el estado de operaciones a ser realizadas en el semaforo y poner el proceso actual en cola de espera. Esto lo hace construyendo una estructura sem_queueen la pila y llenandola. La nueva estructura sem_queue se pone al final de la cola de espera de este objeto semaforo (usando los punteros sem_pending y sem_pending_last). El proceso actual es puesto en cola de espera en la estructura de datos sem_queue(sleeper) y se llama al planificador para que elija otro proceso para ejecutar.

Si todas las operaciones del semaforo hubieran tenido éxito y el proceso actual no necesita ser suspendido, Linux continúa y aplica las operaciones sobre los miembros apropiados de la matriz de semaforos. Ahora Linux debe chequear que quizás ahora cualquier proceso esperando o suspendido aplique sus operaciones. Mira cada miembro de la cola de operaciones pendientes (sem_pending) por turno, probando a ver si las operaciones pueden realizarse esta vez. Si pueden entonces borra la estructura de datos sem_queue de la lista de operaciones pendientes y realiza las operaciones de semaforos en la matriz de semaforos. Despierta los procesos dormidos haciendolos disponibles para ser continuados la próxima ves que se ejcuta el planificador. Linux se mantiene mirando a lo largo de la lista de pendientes hasta que hay un paso en que no se pueden realizar más operaciones de semaforos y por tanto no se pueden despertar más procesos.

Hay un problema con los semaforos, deadlocks. Este tiene lugar cuando un proceso ha alterado el valor de los semaforos y entra en una región critica pero no puede salir de la región critica por que se cuelga o fue matado. Linux se protege frente a esto manteniendo listas de ajustes a las matrices de semaforos. La idea es que cuando estos ajustes se aplican, los semaforos son devueltos al estado en que estaban antes de que el conjunto de operaciones del proceso sobre el semaforo fuese realizado. Estos ajustes están guardados en estructuras de datos sem_undo puestas ambas en cola en la estructura semid_ds y en la estructura task_struct de el proceso usando estas matrices de semaforos.

Cada operación individual de semaforo puede requerir que se mantenga un ajuste. Linux mantendrá como mucho una estructura de datos sem_undo por proceso para cada matriz de semaforo. Si el proceso solicitante no tiene una, entonces se le crea una cuando se necesite. La nueva estructura de datos sem_undo se pone en cola en la estructura de datos task_struct del proceso y en la estructura de datos de la matriz del semaforo semid_ds. Conforme las operaciones son aplicadas a los semaforos en la matriz del semaforo el negativo del valor de la operación se añade a la entrada de este semaforo en la matriz de ajuste de este proceso en la estructura de datos sem_undo. Por tanto, si el valor de la operación es 2, entonces se añade -2 en la entrada de ajuste de este semaforo.

Cuando los procesos son borrados, conforme terminan Linux trabaja a lo largo de su conjunto de estructuras de datos sem_undo aplicando los ajustes a las matrices de semaforos. Si se borra un conjunto de semaforos, la estructura de datos sem_undo queda en cola en la estructura de datos del proceso task_struct pero el identificador de la matriz de semaforo se hace inválido. En este caso el codigo de limpieza del semaforo simplemente desecha la estructura de datos sem_undo.

6.3.4  Memoria Compartida

La memoria comparida permite a uno o más procesos comunicarse por medio de la memoria que aparece en todos su espacios virutales de direcciones. Las páginas de la memoria virtual se referencian por entradas en la tabla de páginas de cada uno de los procesos que comparten tablas de páginas. No tienen que estar en la misma dirección en toda la memoria virtual de los procesos. Como con todos los objetos IPC System V, el acceso a areas de memoria es crotrolado a través de chequeo de derechos de acceso. Una vez que la memoria está siendo compartida, no hay comprobación de como los procesos la utilizan. Esto debe recaer en otros mecanismos, por ejemplo los semaforos System V, para sincronizar el acceso a memoria.


Figure 6.4: Memoria Compartida IPC System V

Cada nueva área creada de memoria compartida está representada por una estructura de datos shmid_ds. Estas son guardadas en el vector shm_segs. La estructura de datos shmid_ds describe lo grande que es el área de memoria compartida, cuantos procesos están usándola e información sobre esa memoria que está siendo mapeada dentro de sus espacios de direcciones. Es el creador de la memoria compartida el que controla los permisos de acceso a esa memoria y si la clave es pública o privada. Si tiene suficientes derechos de acceso puede también fijar la memoria compartida en memoria física.

Cada proceso que desee compartir la memoria debe engancharse a esa memoria virtual por medio de llamadas al sistema. Esto crea una nueva estructura de datos vm_area_struct que describe la memoria compartida para este proceso. El proceso puede elegir en su espacio virtual de direcciones donde va la memoria virtual o puede dejar a Linux que elija un area libre lo suficientemente grande. La nueva estructura vm_area_struct se coloca en la lista de vm_area_struct que es apuntada por la shmid_ds. Los punteros vm_next_shared y vm_prev_shared son usados para enlazarlos a ambos unidos. La memoria virtual no es creada actualmente durante el enganche; sucede cuando el primer proceso intenta acceder a ella.

La primera vez que un proceso accede a un de las páginas de memoria virtual compartida, tiene lugar un fallo de página. Cuando Linux corrige este fallo de página encuentra la estructura de datos vm_area_struct describiendola. Esta contiene punteros a rutinas de tratamiente para este tipo de memoria virtual compartida. El código de tratamiento de fallos de página de memoria compartida busca en las entradas de tablas de páginas de esta shmid_ds para ver si existe alguna para esta página de memoria virtual compartida. Si no existe, asignará una página física y creará una entrada en la tabla de paginas para ella.

Tan pronto como entra en la tabla de páginas del proceso en curso, esta entrada es guardada en la shmid_ds. Esto significa que cuando el siguiente proceso que intenta acceder a esta memoria obtiene un fallo de página, el codigo de tratamiento de fallos de pagina de memoria virtual usará esta página recientemente creada también para este proceso. Por tanto, el primer proceso que accede a una página de memoria compartida hace que esta sea creada y los posteriores accesos por otros procesos hacen que esa página sea añadida a sus espacios virutales de direcciones.

Cuando los procesos no desean compartir más la memoria virtual, se desencadenan de ella. Como otros procesos están usando todavía la memoria el desencadenado solo afecta al proceso actual. Su vm_area_struct es eliminada de la estructura de datos shmid_ds y desasignada. La tabla de páginas del proceso actual son acutalizadas para anular el área de memoria virtual que era utilizada para compartir. Cuando el último proceso que compartía la memoria se suelta de ella, las páginas de memoria compartida actualmente en memoria física son liberadas, de la misma forma que lo es la estructura de datos shmid_ds de esta memoria compartida.

Aparecen complicaciones adicionales cuando la memoria virtual no es bloqueada en memoria física. En este caso las páginas de memoria compartida pueden ser intercambiadas fuera al sistema de intercambio de disco durante periodos de alto uso de memoria. El como la memoria es intercambiada dentro y fuera de memoria física se describe en el capítulo Chapter  4.

Capítulo 7
PCI

Peripheral Component Interconnect (PCI), as its name implies is a standard that describes how to connect the peripheral components of a system together in a structured and controlled way. The standard describes the way that the system components are electrically connected and the way that they should behave. This chapter looks at how the Linux kernel initializes the system's PCI buses and devices.


Figure 7.1: Example PCI Based System

Figure  7.1 is a logical diagram of an example PCI based system. The PCI buses and PCI-PCI bridges are the glue connecting the system components together; the CPU is connected to PCI bus 0, the primary PCI bus as is the video device. A special PCI device, a PCI-PCI bridge connects the primary bus to the secondary PCI bus, PCI bus 1. In the jargon of the PCI specification, PCI bus 1 is described as being downstream of the PCI-PCI bridge and PCI bus 0 is up-stream of the bridge. Connected to the secondary PCI bus are the SCSI and ethernet devices for the system. Physically the bridge, secondary PCI bus and two devices would all be contained on the same combination PCI card. The PCI-ISA bridge in the system supports older, legacy ISA devices and the diagram shows a super I/O controller chip, which controls the keyboard, mouse and floppy. 9

7.1  PCI Address Spaces

The CPU and the PCI devices need to access memory that is shared between them. This memory is used by device drivers to control the PCI devices and to pass information between them. Typically the shared memory contains control and status registers for the device. These registers are used to control the device and to read its status. For example, the PCI SCSI device driver would read its status register to find out if the SCSI device was ready to write a block of information to the SCSI disk. Or it might write to the control register to start the device running after it has been turned on.

The CPU's system memory could be used for this shared memory but if it were, then every time a PCI device accessed memory, the CPU would have to stall, waiting for the PCI device to finish. Access to memory is generally limited to one system component at a time. This would slow the system down. It is also not a good idea to allow the system's peripheral devices to access main memory in an uncontrolled way. This would be very dangerous; a rogue device could make the system very unstable.

Peripheral devices have their own memory spaces. The CPU can access these spaces but access by the devices into the system's memory is very strictly controlled using DMA (Direct Memory Access) channels. ISA devices have access to two address spaces, ISA I/O (Input/Output) and ISA memory. PCI has three; PCI I/O, PCI Memory and PCI Configuration space. All of these address spaces are also accessible by the CPU with the the PCI I/O and PCI Memory address spaces being used by the device drivers and the PCI Configuration space being used by the PCI initialization code within the Linux kernel.

The Alpha AXP processor does not have natural access to addresses spaces other than the system address space. It uses support chipsets to access other address spaces such as PCI Configuration space. It uses a sparse address mapping scheme which steals part of the large virtual address space and maps it to the PCI address spaces.

7.2  PCI Configuration Headers


Figure 7.2: The PCI Configuration Header

Every PCI device in the system, including the PCI-PCI bridges has a configuration data structure that is somewhere in the PCI configuration address space. The PCI Configuration header allows the system to identify and control the device. Exactly where the header is in the PCI Configuration address space depends on where in the PCI topology that device is. For example, a PCI video card plugged into one PCI slot on the PC motherboard will have its configuration header at one location and if it is plugged into another PCI slot then its header will appear in another location in PCI Configuration memory. This does not matter, for wherever the PCI devices and bridges are the system will find and configure them using the status and configuration registers in their configuration headers.

Typically, systems are designed so that every PCI slot has it's PCI Configuration Header in an offset that is related to its slot on the board. So, for example, the first slot on the board might have its PCI Configuration at offset 0 and the second slot at offset 256 (all headers are the same length, 256 bytes) and so on. A system specific hardware mechanism is defined so that the PCI configuration code can attempt to examine all possible PCI Configuration Headers for a given PCI bus and know which devices are present and which devices are absent simply by trying to read one of the fields in the header (usually the Vendor Identification field) and getting some sort of error. The describes one possible error message as returning 0xFFFFFFFF when attempting to read the Vendor Identification and Device Identification fields for an empty PCI slot.

Figure  7.2 shows the layout of the 256 byte PCI configuration header. It contains the following fields:

Vendor Identification
A unique number describing the originator of the PCI device. Digital's PCI Vendor Identification is 0x1011 and Intel's is 0x8086.
Device Identification
A unique number describing the device itself. For example, Digital's 21141 fast ethernet device has a device identification of 0x0009.
Status
This field gives the status of the device with the meaning of the bits of this field set by the standard. .
Command
By writing to this field the system controls the device, for example allowing the device to access PCI I/O memory,
Class Code
This identifies the type of device that this is. There are standard classes for every sort of device; video, SCSI and so on. The class code for SCSI is 0x0100.
Base Address Registers
These registers are used to determine and allocate the type, amount and location of PCI I/O and PCI memory space that the device can use.
Interrupt Pin
Four of the physical pins on the PCI card carry interrupts from the card to the PCI bus. The standard labels these as A, B, C and D. The Interrupt Pin field describes which of these pins this PCI device uses. Generally it is hardwired for a pariticular device. That is, every time the system boots, the device uses the same interrupt pin. This information allows the interrupt handling subsystem to manage interrupts from this device,
Interrupt Line
The Interrupt Line field of the device's PCI Configuration header is used to pass an interrupt handle between the PCI initialisation code, the device's driver and Linux's interrupt handling subsystem. The number written there is meaningless to the the device driver but it allows the interrupt handler to correctly route an interrupt from the PCI device to the correct device driver's interrupt handling code within the Linux operating system. See Chapter  interrupt-chapter on page  for details on how Linux handles interrupts.

7.3  PCI I/O and PCI Memory Addresses

These two address spaces are used by the devices to communicate with their device drivers running in the Linux kernel on the CPU. For example, the DECchip 21141 fast ethernet device maps its internal registers into PCI I/O space. Its Linux device driver then reads and writes those registers to control the device. Video drivers typically use large amounts of PCI memory space to contain video information.

Until the PCI system has been set up and the device's access to these address spaces has been turned on using the Command field in the PCI Configuration header, nothing can access them. It should be noted that only the PCI configuration code reads and writes PCI configuration addresses; the Linux device drivers only read and write PCI I/O and PCI memory addresses.

7.4  PCI-ISA Bridges

These bridges support legacy ISA devices by translating PCI I/O and PCI Memory space accesses into ISA I/O and ISA Memory accesses. A lot of systems now sold contain several ISA bus slots and several PCI bus slots. Over time the need for this backwards compatibility will dwindle and PCI only systems will be sold. Where in the ISA address spaces (I/O and Memory) the ISA devices of the system have their registers was fixed in the dim mists of time by the early Intel 8080 based PCs. Even a $5000 Alpha AXP based computer systems will have its ISA floppy controller at the same place in ISA I/O space as the first IBM PC. The PCI specification copes with this by reserving the lower regions of the PCI I/O and PCI Memory address spaces for use by the ISA peripherals in the system and using a single PCI-ISA bridge to translate any PCI memory accesses to those regions into ISA accesses.

7.5  PCI-PCI Bridges

PCI-PCI bridges are special PCI devices that glue the PCI buses of the system together. Simple systems have a single PCI bus but there is an electrical limit on the number of PCI devices that a single PCI bus can support. Using PCI-PCI bridges to add more PCI buses allows the system to support many more PCI devices. This is particularly important for a high performance server. Of course, Linux fully supports the use of PCI-PCI bridges.

7.5.1  PCI-PCI Bridges: PCI I/O and PCI Memory Windows

PCI-PCI bridges only pass a subset of PCI I/O and PCI memory read and write requests downstream. For example, in Figure  7.1 on page pageref, the PCI-PCI bridge will only pass read and write addresses from PCI bus 0 to PCI bus 1 if they are for PCI I/O or PCI memory addresses owned by either the SCSI or ethernet device; all other PCI I/O and memory addresses are ignored. This filtering stops addresses propogating needlessly throughout the system. To do this, the PCI-PCI bridges must be programmed with a base and limit for PCI I/O and PCI Memory space access that they have to pass from their primary bus onto their secondary bus. Once the PCI-PCI Bridges in a system have been configured then so long as the Linux device drivers only access PCI I/O and PCI Memory space via these windows, the PCI-PCI Bridges are invisible. This is an important feature that makes life easier for Linux PCI device driver writers. However, it also makes PCI-PCI bridges somewhat tricky for Linux to configure as we shall see later on.

7.5.2  PCI-PCI Bridges: PCI Configuration Cycles and PCI Bus Numbering


Figure 7.3: Type 0 PCI Configuration Cycle


Figure 7.4: Type 1 PCI Configuration Cycle

So that the CPU's PCI initialization code can address devices that are not on the main PCI bus, there has to be a mechanism that allows bridges to decide whether or not to pass Configuration cycles from their primary interface to their secondary interface. A cycle is just an address as it appears on the PCI bus. The PCI specification defines two formats for the PCI Configuration addresses; Type 0 and Type 1; these are shown in Figure  7.3 and Figure  7.4 respectively. Type 0 PCI Configuration cycles do not contain a bus number and these are interpretted by all devices as being for PCI configuration addresses on this PCI bus. Bits 31:11 of the Type 0 configuraration cycles are treated as the device select field. One way to design a system is to have each bit select a different device. In this case bit 11 would select the PCI device in slot 0, bit 12 would select the PCI device in slot 1 and so on. Another way is to write the device's slot number directly into bits 31:11. Which mechanism is used in a system depends on the system's PCI memory controller.

Type 1 PCI Configuration cycles contain a PCI bus number and this type of configuration cycle is ignored by all PCI devices except the PCI-PCI bridges. All of the PCI-PCI Bridges seeing Type 1 configuration cycles may choose to pass them to the PCI buses downstream of themselves. Whether the PCI-PCI Bridge ignores the Type 1 configuration cycle or passes it onto the downstream PCI bus depends on how the PCI-PCI Bridge has been configured. Every PCI-PCI bridge has a primary bus interface number and a secondary bus interface number. The primary bus interface being the one nearest the CPU and the secondary bus interface being the one furthest away. Each PCI-PCI Bridge also has a subordinate bus number and this is the maximum bus number of all the PCI buses that are bridged beyond the secondary bus interface. Or to put it another way, the subordinate bus number is the highest numbered PCI bus downstream of the PCI-PCI bridge. When the PCI-PCI bridge sees a Type 1 PCI configuration cycle it does one of the following things:

So, if we want to address Device 1 on bus 3 of the topology Figure  pci-pci-config-eg-4 on page  we must generate a Type 1 Configuration command from the CPU. Bridge1 passes this unchanged onto Bus 1. Bridge2 ignores it but Bridge3 converts it into a Type 0 Configuration command and sends it out on Bus 3 where Device 1 responds to it.

It is up to each individual operating system to allocate bus numbers during PCI configuration but whatever the numbering scheme used the following statement must be true for all of the PCI-PCI bridges in the system:

``All PCI buses located behind a PCI-PCI bridge must reside between the seondary bus number and the subordinate bus number (inclusive).'' If this rule is broken then the PCI-PCI Bridges will not pass and translate Type 1 PCI configuration cycles correctly and the system will fail to find and initialise the PCI devices in the system. To achieve this numbering scheme, Linux configures these special devices in a particular order. Section  pci-pci-bus-numbering on page  describes Linux's PCI bridge and bus numbering scheme in detail together with a worked example.

7.6  Linux PCI Initialization

The PCI initialisation code in Linux is broken into three logical parts:

PCI Device Driver
This pseudo-device driver searches the PCI system starting at Bus 0 and locates all PCI devices and bridges in the system. It builds a linked list of data structures describing the topology of the system. Additionally, it numbers all of the bridges that it finds.
PCI BIOS
This software layer provides the services described in bib-pci-bios-specification. Even though Alpha AXP does not have BIOS services, there is equivalent code in the Linux kernel providing the same functions,
PCI Fixup
System specific fixup code tidies up the system specific loose ends of PCI initialization.

7.6.1  The Linux Kernel PCI Data Structures


Figure 7.5: Linux Kernel PCI Data Structures

As the Linux kernel initialises the PCI system it builds data structures mirroring the real PCI topology of the system. Figure  7.5 shows the relationships of the data structures that it would build for the example PCI system in Figure  7.1 on page pageref.

Each PCI device (including the PCI-PCI Bridges) is described by a pci_dev data structure. Each PCI bus is described by a pci_bus data structure. The result is a tree structure of PCI buses each of which has a number of child PCI devices attached to it. As a PCI bus can only be reached using a PCI-PCI Bridge (except the primary PCI bus, bus 0), each pci_bus contains a pointer to the PCI device (the PCI-PCI Bridge) that it is accessed through. That PCI device is a child of the the PCI Bus's parent PCI bus.

Not shown in the Figure  7.5 is a pointer to all of the PCI devices in the system, pci_devices. All of the PCI devices in the system have their pci_dev data structures queued onto this queue.. This queue is used by the Linux kernel to quickly find all of the PCI devices in the system.

7.6.2  The PCI Device Driver

The PCI device driver is not really a device driver at all but a function of the operating system called at system initialisation time. The PCI initialisation code must scan all of the PCI buses in the system looking for all PCI devices in the system (including PCI-PCI bridge devices). It uses the PCI BIOS code to find out if every possible slot in the current PCI bus that it is scanning is occupied. If the PCI slot is occupied, it builds a pci_dev data structure describing the device and links into the list of known PCI devices (pointed at by pci_devices).

The PCI initialisation code starts by scanning PCI Bus 0. It tries to read the Vendor Identification and Device Identification fields for every possible PCI device in every possible PCI slot. When it finds an occupied slot it builds a pci_dev data structure describing the device. All of the pci_dev data structures built by the PCI initialisation code (including all of the PCI-PCI Bridges) are linked into a singly linked list; pci_devices.

If the PCI device that was found was a PCI-PCI bridge then a pci_busdata structure is built and linked into the tree of pci_bus and pci_dev data structures pointed at by pci_root. The PCI initialisation code can tell if the PCI device is a PCI-PCI Bridge because it has a class code of 0x060400. The Linux kernel then configures the PCI bus on the other (downstream) side of the PCI-PCI Bridge that it has just found. If more PCI-PCI Bridges are found then these are also configured. This process is known as a depthwise algorithm; the system's PCI topology is fully mapped depthwise before searching breadthwise. Looking at Figure  7.1 on page pageref, Linux would configure PCI Bus 1 with its Ethernet and SCSI device before it configured the video device on PCI Bus 0.

As Linux searches for downstream PCI buses it must also configure the intervening PCI-PCI bridges' secondary and subordinate bus numbers. This is described in detail in Section  pci-pci-bus-numbering below.

Configuring PCI-PCI Bridges - Assigning PCI Bus Numbers


Figure 7.6: Configuring a PCI System: Part 1

For PCI-PCI bridges to pass PCI I/O, PCI Memory or PCI Configuration address space reads and writes across them, they need to know the following:

Primary Bus Number
The bus number immediately upstream of the PCI-PCI Bridge,
Secondary Bus Number
The bus number immediately downstream of the PCI-PCI Bridge,
Subordinate Bus Number
The highest bus number of all of the buses that can be reached downstream of the bridge.
PCI I/O and PCI Memory Windows
The window base and size for PCI I/O address space and PCI Memory address space for all addresses downstream of the PCI-PCI Bridge.

The problem is that at the time when you wish to configure any given PCI-PCI bridge you do not know the subordinate bus number for that bridge. You do not know if there are further PCI-PCI bridges downstream and if you did, you do not know what numbers will be assigned to them. The answer is to use a depthwise recursive algorithm and scan each bus for any PCI-PCI bridges assigning them numbers as they are found. As each PCI-PCI bridge is found and its secondary bus numbered, assign it a temporary subordinate number of 0xFF and scan and assign numbers to all PCI-PCI bridges downstream of it. This all seems complicated but the worked example below makes this process clearer.

PCI-PCI Bridge Numbering: Step 1
Taking the topology in Figure  7.6, the first bridge the scan would find is Bridge1. The PCI bus downstream of Bridge1 would be numbered as 1 and Bridge1 assigned a secondary bus number of 1 and a temporary subordinate bus number of 0xFF. This means that all Type 1 PCI Configuration addresses specifying a PCI bus number of 1 or higher would be passed across Bridge1 and onto PCI Bus 1. They would be translated into Type 0 Configuration cycles if they have a bus number of 1 but left untranslated for all other bus numbers. This is exactly what the Linux PCI initialisation code needs to do in order to go and scan PCI Bus 1.


Figure 7.7: Configuring a PCI System: Part 2

PCI-PCI Bridge Numbering: Step 2
Linux uses a depthwise algorithm and so the initialisation code goes on to scan PCI Bus 1. Here it finds PCI-PCI Bridge2. There are no further PCI-PCI bridges beyond PCI-PCI Bridge2, so it is assigned a subordinate bus number of 2 which matches the number assigned to its secondary interface. Figure  7.7 shows how the buses and PCI-PCI bridges are numbered at this point.


Figure 7.8: Configuring a PCI System: Part 3

PCI-PCI Bridge Numbering: Step 3
The PCI initialisation code returns to scanning PCI Bus 1 and finds another PCI-PCI bridge, Bridge3. It is assigned 1 as its primary bus interface number, 3 as its secondary bus interface number and 0xFF as its subordinate bus number. Figure  7.8 on page pageref shows how the system is configured now. Type 1 PCI configuration cycles with a bus number of 1, 2 or 3 wil be correctly delivered to the appropriate PCI buses.


Figure 7.9: Configuring a PCI System: Part 4

PCI-PCI Bridge Numbering: Step 4
Linux starts scanning PCI Bus 3, downstream of PCI-PCI Bridge3. PCI Bus 3 has another PCI-PCI bridge (Bridge4) on it, it is assigned 3 as its primary bus number and 4 as its secondary bus number. It is the last bridge on this branch and so it is assigned a subordinate bus interface number of 4. The initialisation code returns to PCI-PCI Bridge3 and assigns it a subordinate bus number of 4. Finally, the PCI initialisation code can assign 4 as the subordinate bus number for PCI-PCI Bridge1. Figure  7.9 on page pageref shows the final bus numbers.

7.6.3  PCI BIOS Functions

The PCI BIOS functions are a series of standard routines which are common across all platforms. For example, they are the same for both Intel and Alpha AXP based systems. They allow the CPU controlled access to all of the PCI address spaces. Only Linux kernel code and device drivers may use them.

7.6.4  PCI Fixup

The PCI fixup code for Alpha AXP does rather more than that for Intel (which basically does nothing). For Intel based systems the system BIOS, which ran at boot time, has already fully configured the PCI system. This leaves Linux with little to do other than map that configuration. For non-Intel based systems further configuration needs to happen to:

The next subsections describe how that code works.

Finding Out How Much PCI I/O and PCI Memory Space a Device Needs

Each PCI device found is queried to find out how much PCI I/O and PCI Memory address space it requires. To do this, each Base Address Register has all 1's written to it and then read. The device will return 0's in the don't-care address bits, effectively specifying the address space required.


Figure 7.10: PCI Configuration Header: Base Address Registers

There are two basic types of Base Address Register, the first indicates within which address space the devices registers must reside; either PCI I/O or PCI Memory space. This is indicated by Bit 0 of the register. Figure  7.10 shows the two forms of the Base Address Register for PCI Memory and for PCI I/O.

To find out just how much of each address space a given Base Address Register is requesting, you write all 1s into the register and then read it back. The device will specify zeros in the don't care address bits, effectively specifying the address space required. This design implies that all address spaces used are a power of two and are naturally aligned.

For example when you initialize the DECChip 21142 PCI Fast Ethernet device, it tells you that it needs 0x100 bytes of space of either PCI I/O or PCI Memory. The initialization code allocates it space. The moment that it allocates space, the 21142's control and status registers can be seen at those addresses.

Allocating PCI I/O and PCI Memory to PCI-PCI Bridges and Devices

Like all memory the PCI I/O and PCI memory spaces are finite, and to some extent scarce. The PCI Fixup code for non-Intel systems (and the BIOS code for Intel systems) has to allocate each device the amount of memory that it is requesting in an efficient manner. Both PCI I/O and PCI Memory must be allocated to a device in a naturally aligned way. For example, if a device asks for 0xB0 of PCI I/O space then it must be aligned on an address that is a multiple of 0xB0. In addition to this, the PCI I/O and PCI Memory bases for any given bridge must be aligned on 4K and on 1Mbyte boundaries respectively. Given that the address spaces for downstream devices must lie within all of the upstream PCI-PCI Bridge's memory ranges for any given device, it is a somewhat difficult problem to allocate space efficiently.

The algorithm that Linux uses relies on each device described by the bus/device tree built by the PCI Device Driver being allocated address space in ascending PCI I/O memory order. Again a recursive algorithm is used to walk the pci_bus and pci_dev data structures built by the PCI initialisation code. Starting at the root PCI bus (pointed at by pci_root) the BIOS fixup code:

Taking the PCI system in Figure  7.1 on page pageref as our example the PCI Fixup code would set up the system in the following way:

Align the PCI bases
PCI I/O is 0x4000 and PCI Memory is 0x100000. This allows the PCI-ISA bridges to translate all addresses below these into ISA address cycles,
The Video Device
This is asking for 0x200000 of PCI Memory and so we allocate it that amount starting at the current PCI Memory base of 0x200000 as it has to be naturally aligned to the size requested. The PCI Memory base is moved to 0x400000 and the PCI I/O base remains at 0x4000.
The PCI-PCI Bridge
We now cross the PCI-PCI Bridge and allocate PCI memory there, note that we do not need to align the bases as they are already correctly aligned:
The Ethernet Device
This is asking for 0xB0 bytes of both PCI I/O and PCI Memory space. It gets allocated PCI I/O at 0x4000 and PCI Memory at 0x400000. The PCI Memory base is moved to 0x4000B0 and the PCI I/O base to 0x40B0.
The SCSI Device
This is asking for 0x1000 PCI Memory and so it is allocated it at 0x401000 after it has been naturally aligned. The PCI I/O base is still 0x40B0 and the PCI Memory base has been moved to 0x402000.
The PCI-PCI Bridge's PCI I/O and Memory Windows
We now return to the bridge and set its PCI I/O window at between 0x4000 and 0x40B0 and it's PCI Memory window at between 0x400000 and 0x402000. This means that the PCI-PCI Bridge will ignore the PCI Memory accesses for the video device and pass them on if they are for the ethernet or SCSI devices.

Capítulo 8
Interrupciones y Manejo de Interrupciones

Este capítulo explica como maneja las interrupciones el núcleo de Linux Debido a que el núcleo tiene mecanismos genéricos e interfaces para manejar las interrupciones, la mayor parte de los detalles del manejo de interrupciones son específicos para cada arquitectura.


Figure 8.1: Un diagrama lógico del rutado de interrupciones

Linux usa distintos componentes físicos para llevar a cabo trabajos muy diferentes. El dispositivo de vídeo maneja el monitor, el dispositivo IDE maneja los discos, y así sucesivamente. Puede manejar estos dispositivos de forma sincronizada, es decir, puede enviar una petición de alguna operación (como escribir un bloque de memoria a disco) y luego esperar que la operación se complete. Este método, aunque funciona, es muy poco eficiente y el sistema operativo pasaría mucho tiempo «ocupado sin hacer nada» mientras espera que cada operación finalice. Una manera mejor y más eficiente sería realizar la petición y luego realizar otra, un trabajo más útil que luego se interrumpirúa cuando el dispositivo haya finalizado la petición. Con este esquema puede haber muchas peticiones pendientes hacia los dispositivos en el sistema, todas al mismo tiempo.

Tiene que haber algún apoyo físico para los dispositivos de forma que se pueda interrumpir lo que esté haciendo la CPU. La mayoría de los procesadores de propósito general, si no todos, como el Alpha AXP usan un método similar. Algunas de las patillas físicas de la CPU están cableadas de forma que al cambiar el voltaje (por ejemplo cambiandolo de +5v a -5v) se provoca que la CPU pare lo que esté haciendo y empiece a ejecutar un código especial para manejar la interrupción; el código de manejo de interrupción. Una de estas patillas puede estar conectada a un reloj de tiempo real y recibir una interrupción cada milésima de segundo, otras pueden estar conectados a otros dispositivos del sistema, como el controlador SCSI.

Los sistemas usan a menudo un controlador de interrupciones para agrupar las interrupciones de los dispositivos antes de pasar la señal a una patilla de interrupción de la CPU. Esto ahorra patillas de interrupción en la CPU y tambien da flexibilidad a la hora de diseñar sistemas. El controlador de interrupciones tiene registros de máscara y de estado que controlan las interrupciones. Marcando los bits en el registro de máscara activa y desactiva las interrupciones y registro de estado devuelve las interrupciones actualmente activas en el sistema.

Algunas de las interrupciones del sistema pueden estar cableados físicamente, por ejemplo, el reloj de tiempo real puede estar permanentemente conectado a la tercera patilla del controlador de interrupciones. Sin embargo, el donde están conectadas algunas de las patillas puede estar determinado por dónde esté conectada la tarjeta, en una ranura PCI o ISA en particular. Por ejemplo, la cuarta patilla del controlador de interrupciones puede estar conectada a la ranura PCI número 0 que un dia puede tener una tarjeta ethernet, y al dia siguiente tener una controladora SCSI. En resumen, cada sistema tiene sus propios mecanismos de rutado de interrupciones y el sistema operativo debe de ser lo suficientemente flexible para adaptarse a él.

La mayoría de los microprocesadores modernos de propósito general manejan las interrupciones de la misma forma. Cuando se genera una interrupción de hardware la CPU para de ejecutar las instrucciones que estaba ejecutando y salta a la posición de memoria que contiene o el código de manejo de interrupciones o una instrucción bifurcando hacia el código de manejo de interrupciones- Este código suele operar de una forma especial para la CPU, en modo interrupción y , normalmente, no se puede producir ninguna interrupción en este modo. A pesar de todo hay excepciones; algunas CPUs establecen una jerarquía de prioridad de interrupciones y entonces pueden producirse interrupciones de alto nivel. Esto significa que el primer nivel del código de manejo de interrupciones debe de ser muy cuidadoso y a menudo tiene su propia pila, que se usa para guardar el estado de ejecución de la CPU (tanto los registros normales de la CPU como contexto) antes de que la interrupción tome el control. Algunas CPUs tienen un juego especial de registros que solo existen en modo interrupción, y el código de las interrupciones puede usar estos registros para realizar todo el salvado de contexto que necesite.

Cuando la interrupción ha acabado, el estado de la CPU se reestablece y la interrupción se da por concluida. La CPU entonces continua haciendo lo que estuviera haciendo antes de ser interrumpida. Es importante que el código de proceso de la interrupción sea lo más eficiente posible y que el sistema operativo no bloquee interrupciones demasiado a menudo o durante demasiado tiempo.

8.1  Controladores de Interrupciones Programables

Los diseñadores de sistemas son libres de usar cualquier arquitectura de interrupciones pero los IBM PCs usan el Controlador de Interrupciones Programable Intel 82C59A-2 CMOS o sus derivados. Este controlador existe desde el amanecer del PC y es programable, estando sus registros en posiciones muy conocidas del espacio de las dicecciones de memoria ISA. Incluso los conjuntos de chips modernos tienen registros equivalentes en el mismo sitio de la memoria ISA. Sistemas no basados en Intel, como PCs basados en Alpha AXP son libres de esas obligaciones de arquitectura y por ello a menudo usan controladores de interrupciones diferentes.

La figura  8.1 muestra que hay dos controladores de 8 bits encadenados juntos; cada uno tiene una máscara y un registro de estado, PIC1 y PIC2 (de Programmable Interrupt Controller). Los registros de máscara están en las direcciones 0x21 y 0xA1 y los registros de estado están en 0x20 y 0xA0 Escribiendo un uno en un bit particular del registro de máscara activa una interrupción, escribiendo un cero la desactiva. Entonces, escribiendo un uno al bit 3 activaría la interrupción 3, y escribiendo un cero la desactivaría. Desafortunadamente (e irritantemente), los registros de máscara de interrupción son de solo escritura, no se puede leer el valor que se escribió. Esto conlleva que Linux debe guardar una copia local de lo que ha modificado en el registro de máscara. Para ello modifica estas máscaras guardadas en las rutinas de activación y desactivación de interrupciones y escribe las máscaras completas en el registro cada vez.

Cuando se manda una señal a una interrupción, el código de manejo de interrupciones lee los dos registros de estado de interrupciones (ISRs, de Interrupt Status Registers). Al ISR en 0x20 se le trata como los ocho bits más bajos de un registro de interrupciones de dieciseis bits y al ISR en 0xA0 como los ocho bits más altos. Entonces, una interrupción en el bit 1 del ISR de 0xA0 se tratará como la interrupción de sistema 9. El bit 2 de PIC1 no está disponible ya que se usa para encadenar las interrupciones de PIC2, cualquier interrupción de PIC2 provoca que el bit 2 de PIC1 esté activado.

8.2  Inicializando las Estructuras de Datos del Manejo de Interrupciones

Las estructuras de datos del manejo de interrupciones del núcleo las configuran los controladores de dispositivos cuando estos piden el control de las interrupciones del sistema. Para ello, el controlador del dispositivo usa un juego de servicios del núcleo de Linux que se usan para pedir una interrupción, activarla y desactivarla. Los controladores de dispositivos individuales llaman a esas rutinas para registrar las direcciones de sus rutinas de manejo de interrupción.

Algunas interrupciones son fijas por convenio de la arquitectura PC y entonces el controlador simplemente pide esa interrupción cuando se haya inicializado. Esto es lo que hace el controlador de la disquetera; siempre pide la IRQ 6 (IRQ = Interrupt ReQuest, petición de Interrupción). Puede haber ocasiones en las que un controlador no sepa qué interrupción va a usar el dispositivo. Esto no es un problema para los controladores de dispositivos PCI ya que siempre saben cual es su número de interrupción. Desafortunadamente no hay ninguna manera fácil de que los controladores de dispositivos ISA encuentren su número de interrupción. Linux resuelve este problema permitiendo a los controladores sondear en búsqueda de su interrupción.

Lo primero de todo el controlador hace algo al dispositivo que le provoque una interrupción. Luego todas las interrupciones del sistema sin asignar se habilitan, Esto significa que la interrupción de la que depende el dispositivo será repartida a través del controlador de interrupciones programable. Linux lee el registro de estado de las interrupciones y devuelve sus contenidos al controlador del dispositivo. Un resultado distinto a 0 significa que se han producido una o más interrupciones durante el sondeo. El controlador desactiva ahora el sondeo y las interrupciones sin asignar se desactivan. Si el controlador ISA ha lógrado encontrar su número de IRQ entonces puede pedir el control de forma normal.

Los sistemas basados en PCI son mucho más dinámicos que los basados en ISA. La patilla de interrupción que suele usar un dispositivo ISA es a menudo seleccionado usando jumpers en la tarjéta y fijado en el controlador del dispositivo. Por otra parte, los dispositivos PCI tienen sus interrupciones localizadas por la BIOS PCI o el subsitema PCI ya que PCI se configura cuando el sistema arranca. Cada dispositivo PCI puede usar uno de cuatro patillas de interrupción, A, B, C o D. Esto se fija cuando el dispositivo se construye y la mayoría de dispositivos toman por defecto la interrupción en la patilla A. Entonces, la patilla A de la ranura PCI 4 debe ser rutada hacia la patilla 6 del controlador de interrupciones, la patilla B de la ranura PCI 4 a la patilla 7 del controlador de interrupciones y así.

La manera de rutar las interrupciones PCI es completamente específica del sistema y debe haber algún código de inicialización que entienda esa topología PCI de rutado de interrupciones. En PCs basados en Intel es el código de la BIOS del sistema que corre durante el arranque, pero para sistemas sin BIOS (por ejemplo los sistemas basados en Alpha AXP ) el núcleo de Linux hace esta configuración. El código de configuración de PCI escribe el número de patilla del controlador de interrupciones en la cabecera de configuración de PCI para cada dispositivo. Esto determina el número de la patilla de interrupción ( o IRQ) usando su conocimiento de la topología PCI de rutado de interrupciones junto a los números de ranura de los dispositivos PCI y la patilla PCI de interrupción que esté usando. La patilla de interrupción que usa un dispositivo es fija y se guarda en un campo en la cabecera de configuración de PCI para este dispositivo. Entonces se escribe esta información en el campo de línea de interrupción que está reservado para este propósito. Cuando el controlador del dispositivo se carga, lee esta información y la usa para pedir el control de la interrupción al núcleo de Linux.

Puede haber muchas fuentes de interrupciones PCI en el sistema, por ejemplo cuando se usan puentes PCI-PCI. El número de fuentes de interrupciones puede exceder el número de patillas de los controladores de interrupciones programables del sistema. En tal caso, los dispositivos PCI pueden compartir interrupciones, una patilla del controlador de interrupciones recogiendo interrupciones de más de un dispositivo PCI. Linux soporta esto permitiendo declarar a la primera petición de una fuente de interrupciones si ésta puede ser compartida. Compartir interrupciones conlleva que se apunten varias estucturas de datos irqaction por unta entrada en el vector irq_action. Cuando se ejecuta una interrupción compartida, Linux llamará a todos los controladores de esa fuente. Cualquier controlador de dispositivos que pueda compartir interrupciones (que deberían ser todos los controladores de dispositivos PCI) debe estar preparado para que se llame al su controlador de interrupciones cuando no hay interrupción a la que atender.

8.3  Manejo de Interrupciones


Figure 8.2: Estructuras de Datos del Manejo de Interrupciones en Linux

Una de las tareas principales del subsistema de manejo de interrupciones de Linux es rutar las interrupciones a las pártes correspontdientes del código de manejo de interrupciones. Este código debe entender la topología de interrupciones del sistema. Si, por ejemplo, el controlador de la disquetera ejecuta la interrupción en la patilla 6 10 del controlador de interrupciones, se debe reconocer la el origen de la interrupción como la disquetera y entonces rutarlo hacia el código de manejo de interrupciones del controlador del dispositivo. Linux usa un juego de punteros a estructuras de datos que contienen las direcciones de las rutinas que manejan las interrupciones del sistema. Estas rutinas pertenecen a los controladores de los dispositivos del sistema y es responsabilidad de cada controlador el pedir la interrupcion que quiera cuando se inicializa el controlador. La figura  8.2 muestra que irq_action es un vector de punteros a la estructura de datos irqaction. Cada estructura de datos irqaction contiene información sobre el controlador de esa interrupción, incluyendo la dirección de la rutina de manejo de la interrupción. Como tanto el número de interrupciones como la manera de la que se manejan varía entre arquitecturas y, a veces, entre sistemas, el código de manejo de interrupciones de Linux es específico para cada arquitectura. Esto significa que el tamaño del vector irq_action varía dependiendo del número de fuentes de interrupciones que haya.

Cuando se produce la interrupción, Linux debe determinar primero su fuente leyendo el registro de estado de interrupciones de los controladores de interrupciones programables del sistema. Luego convierte esa fuente en un desplazamiento dentro del vector irq_action. Entonces, por ejemplo, una interrupción en la patilla 6 del controlador de interrupciones desde el controlador de la disquetera se traduciría ak séptimo puntero del vector de controladores de interrupciones. Si no hay un controlador de interrupciones para la interrupción que se produjo entonces el núcleo de Linux indicaría un error, en caso contrario, llamaría a las rutinas de manejo de interrupciones de todas las estructuras de datos irqaction para esta fuente de interrupciones.

Cuando el núcleo de Linux llama a la rutina de manejo de interrupciones del controlador de dispositivos esta debe averiguar eficientemente por qué fue interrumpida y responder. Para encontrar la causa de la interrupción el controlador tendrá que leer el registro de estado del dispositivo que causó la interrupción. El dispositivo puede estar comunicando un error o que una operacion pedida ha sido completada. Por ejemplo el controlador de la disquetera puede estar informando que ha completado la posición de la cabecera de lectura de la disquetera sobre el sector correcto del disquete. Una vez que la razón de la interrupción se ha determinado, el controlador puede necesitar trabajar más. Si es así, el núcleo de Linux tiene mecanismos que le permiten posponer ese trabajo para luego. Esto evita que la CPU pase mucho tiempo en modo interrupción. Véase el cap´itulo sobre los Controladores deDispositivos (Chapter  dd-chapter) para más detalles.

NOTA DE REVISIÓN: Interrupciones rápidas y lentes, ¿son esto cosa de Intel?

Capítulo 9
Device Drivers

One of the purposes of an operating system is to hide the peculiarities of the system's hardware devices from its users. For example the Virtual File System presents a uniform view of the mounted filesystems irrespective of the underlying physical devices. This chapter describes how the Linux kernel manages the physical devices in the system.

The CPU is not the only intelligent device in the system, every physical device has its own hardware controller. The keyboard, mouse and serial ports are controlled by a SuperIO chip, the IDE disks by an IDE controller, SCSI disks by a SCSI controller and so on. Each hardware controller has its own control and status registers (CSRs) and these differ between devices. The CSRs for an Adaptec 2940 SCSI controller are completely different from those of an NCR 810 SCSI controller. The CSRs are used to start and stop the device, to initialize it and to diagnose any problems with it. Instead of putting code to manage the hardware controllers in the system into every application, the code is kept in the Linux kernel. The software that handles or manages a hardware controller is known as a device driver. The Linux kernel device drivers are, essentially, a shared library of privileged, memory resident, low level hardware handling routines. It is Linux's device drivers that handle the peculiarities of the devices they are managing.

One of the basic features of  is that it abstracts the handling of devices. All hardware devices look like regular files; they can be opened, closed, read and written using the same, standard, system calls that are used to manipulate files. Every device in the system is represented by a device special file, for example the first IDE disk in the system is represented by /dev/hda. For block (disk) and character devices, these device special files are created by the mknod command and they describe the device using major and minor device numbers. Network devices are also represented by device special files but they are created by Linux as it finds and initializes the network controllers in the system. All devices controlled by the same device driver have a common major device number. The minor device numbers are used to distinguish between different devices and their controllers, for example each partition on the primary IDE disk has a different minor device number. So, /dev/hda2, the second partition of the primary IDE disk has a major number of 3 and a minor number of 2. Linux maps the device special file passed in system calls (say to mount a file system on a block device) to the device's device driver using the major device number and a number of system tables, for example the character device table, chrdevs .

Linux supports three types of hardware device: character, block and network. Character devices are read and written directly without buffering, for example the system's serial ports /dev/cua0 and /dev/cua1. Block devices can only be written to and read from in multiples of the block size, typically 512 or 1024 bytes. Block devices are accessed via the buffer cache and may be randomly accessed, that is to say, any block can be read or written no matter where it is on the device. Block devices can be accessed via their device special file but more commonly they are accessed via the file system. Only a block device can support a mounted file system. Network devices are accessed via the BSD socket interface and the networking subsytems described in the Networking chapter (Chapter  network-chapter).

There are many different device drivers in the Linux kernel (that is one of Linux's strengths) but they all share some common attributes:

kernel code
Device drivers are part of the kernel and, like other code within the kernel, if they go wrong they can seriously damage the system. A badly written driver may even crash the system, possibly corrupting file systems and losing data,

Kernel interfaces
Device drivers must provide a standard interface to the Linux kernel or to the subsystem that they are part of. For example, the terminal driver provides a file I/O interface to the Linux kernel and a SCSI device driver provides a SCSI device interface to the SCSI subsystem which, in turn, provides both file I/O and buffer cache interfaces to the kernel.

Kernel mechanisms and services
Device drivers make use of standard kernel services such as memory allocation, interrupt delivery and wait queues to operate,

Loadable
Most of the Linux device drivers can be loaded on demand as kernel modules when they are needed and unloaded when they are no longer being used. This makes the kernel very adaptable and efficient with the system's resources,
Configurable
Linux device drivers can be built into the kernel. Which devices are built is configurable when the kernel is compiled,

Dynamic
As the system boots and each device driver is initialized it looks for the hardware devices that it is controlling. It does not matter if the device being controlled by a particular device driver does not exist. In this case the device driver is simply redundant and causes no harm apart from occupying a little of the system's memory.

9.1  Polling and Interrupts

Each time the device is given a command, for example ``move the read head to sector 42 of the floppy disk'' the device driver has a choice as to how it finds out that the command has completed. The device drivers can either poll the device or they can use interrupts.

Polling the device usually means reading its status register every so often until the device's status changes to indicate that it has completed the request. As a device driver is part of the kernel it would be disasterous if a driver were to poll as nothing else in the kernel would run until the device had completed the request. Instead polling device drivers use system timers to have the kernel call a routine within the device driver at some later time. This timer routine would check the status of the command and this is exactly how Linux's floppy driver works. Polling by means of timers is at best approximate, a much more efficient method is to use interrupts.

An interrupt driven device driver is one where the hardware device being controlled will raise a hardware interrupt whenever it needs to be serviced. For example, an ethernet device driver would interrupt whenever it receives an ethernet packet from the network. The Linux kernel needs to be able to deliver the interrupt from the hardware device to the correct device driver. This is achieved by the device driver registering its usage of the interrupt with the kernel. It registers the address of an interrupt handling routine and the interrupt number that it wishes to own. You can see which interrupts are being used by the device drivers, as well as how many of each type of interrupts there have been, by looking at /proc/interrupts:

 0:     727432   timer
 1:      20534   keyboard
 2:          0   cascade
 3:      79691 + serial
 4:      28258 + serial
 5:          1   sound blaster
11:      20868 + aic7xxx
13:          1   math error
14:        247 + ide0
15:        170 + ide1

This requesting of interrupt resources is done at driver initialization time. Some of the interrupts in the system are fixed, this is a legacy of the IBM PC's architecture. So, for example, the floppy disk controller always uses interrupt 6. Other interrupts, for example the interrupts from PCI devices are dynamically allocated at boot time. In this case the device driver must first discover the interrupt number (IRQ) of the device that it is controlling before it requests ownership of that interrupt. For PCI interrupts Linux supports standard PCI BIOS callbacks to determine information about the devices in the system, including their IRQ numbers.

How an interrupt is delivered to the CPU itself is architecture dependent but on most architectures the interrupt is delivered in a special mode that stops other interrupts from happening in the system. A device driver should do as little as possible in its interrupt handling routine so that the Linux kernel can dismiss the interrupt and return to what it was doing before it was interrupted. Device drivers that need to do a lot of work as a result of receiving an interrupt can use the kernel's bottom half handlers or task queues to queue routines to be called later on.

9.2  Direct Memory Access (DMA)

Using interrupts driven device drivers to transfer data to or from hardware devices works well when the amount of data is reasonably low. For example a 9600 baud modem can transfer approximately one character every millisecond (1/1000 'th second). If the interrupt latency, the amount of time that it takes between the hardware device raising the interrupt and the device driver's interrupt handling routine being called, is low (say 2 milliseconds) then the overall system impact of the data transfer is very low. The 9600 baud modem data transfer would only take 0.002% of the CPU's processing time. For high speed devices, such as hard disk controllers or ethernet devices the data transfer rate is a lot higher. A SCSI device can transfer up to 40 Mbytes of information per second.

Direct Memory Access, or DMA, was invented to solve this problem. A DMA controller allows devices to transfer data to or from the system's memory without the intervention of the processor. A PC's ISA DMA controller has 8 DMA channels of which 7 are available for use by the device drivers. Each DMA channel has associated with it a 16 bit address register and a 16 bit count register. To initiate a data transfer the device driver sets up the DMA channel's address and count registers together with the direction of the data transfer, read or write. It then tells the device that it may start the DMA when it wishes. When the transfer is complete the device interrupts the PC. Whilst the transfer is taking place the CPU is free to do other things.

Device drivers have to be careful when using DMA. First of all the DMA controller knows nothing of virtual memory, it only has access to the physical memory in the system. Therefore the memory that is being DMA'd to or from must be a contiguous block of physical memory. This means that you cannot DMA directly into the virtual address space of a process. You can however lock the process's physical pages into memory, preventing them from being swapped out to the swap device during a DMA operation. Secondly, the DMA controller cannot access the whole of physical memory. The DMA channel's address register represents the first 16 bits of the DMA address, the next 8 bits come from the page register. This means that DMA requests are limited to the bottom 16 Mbytes of memory.

DMA channels are scarce resources, there are only 7 of them, and they cannot be shared between device drivers. Just like interrupts, the device driver must be able to work out which DMA channel it should use. Like interrupts, some devices have a fixed DMA channel. The floppy device, for example, always uses DMA channel 2. Sometimes the DMA channel for a device can be set by jumpers; a number of ethernet devices use this technique. The more flexible devices can be told (via their CSRs) which DMA channels to use and, in this case, the device driver can simply pick a free DMA channel to use.

Linux tracks the usage of the DMA channels using a vector of dma_chan data structures (one per DMA channel). The dma_chan data structure contains just two fields, a pointer to a string describing the owner of the DMA channel and a flag indicating if the DMA channel is allocated or not. It is this vector of dma_chan data structures that is printed when you cat /proc/dma.

9.3  Memory

Device drivers have to be careful when using memory. As they are part of the Linux kernel they cannot use virtual memory. Each time a device driver runs, maybe as an interrupt is received or as a bottom half or task queue handler is scheduled, the current process may change. The device driver cannot rely on a particular process running even if it is doing work on its behalf. Like the rest of the kernel, device drivers use data structures to keep track of the device that it is controlling. These data structures can be statically allocated, part of the device driver's code, but that would be wasteful as it makes the kernel larger than it need be. Most device drivers allocate kernel, non-paged, memory to hold their data.

Linux provides kernel memory allocation and deallocation routines and it is these that the device drivers use. Kernel memory is allocated in chunks that are powers of 2. For example 128 or 512 bytes, even if the device driver asks for less. The number of bytes that the device driver requests is rounded up to the next block size boundary. This makes kernel memory deallocation easier as the smaller free blocks can be recombined into bigger blocks.

It may be that Linux needs to do quite a lot of extra work when the kernel memory is requested. If the amount of free memory is low, physical pages may need to be discarded or written to the swap device. Normally, Linux would suspend the requestor, putting the process onto a wait queue until there is enough physical memory. Not all device drivers (or indeed Linux kernel code) may want this to happen and so the kernel memory allocation routines can be requested to fail if they cannot immediately allocate memory. If the device driver wishes to DMA to or from the allocated memory it can also specify that the memory is DMA'able. This way it is the Linux kernel that needs to understand what constitutes DMA'able memory for this system, and not the device driver.

9.4  Interfacing Device Drivers with the Kernel

The Linux kernel must be able to interact with them in standard ways. Each class of device driver, character, block and network, provides common interfaces that the kernel uses when requesting services from them. These common interfaces mean that the kernel can treat often very different devices and their device drivers absolutely the same. For example, SCSI and IDE disks behave very differently but the Linux kernel uses the same interface to both of them.

Linux is very dynamic, every time a Linux kernel boots it may encounter different physical devices and thus need different device drivers. Linux allows you to include device drivers at kernel build time via its configuration scripts. When these drivers are initialized at boot time they may not discover any hardware to control. Other drivers can be loaded as kernel modules when they are needed. To cope with this dynamic nature of device drivers, device drivers register themselves with the kernel as they are initialized. Linux maintains tables of registered device drivers as part of its interfaces with them. These tables include pointers to routines and information that support the interface with that class of devices.

9.4.1  Character Devices


Figure 9.1: Character Devices

Character devices, the simplest of Linux's devices, are accessed as files, applications use standard system calls to open them, read from them, write to them and close them exactly as if the device were a file. This is true even if the device is a modem being used by the PPP daemon to connect a Linux system onto a network. As a character device is initialized its device driver registers itself with the Linux kernel by adding an entry into the chrdevsvector of device_struct data structures. The device's major device identifier (for example 4 for the tty device) is used as an index into this vector. The major device identifier for a device is fixed. Each entry in the chrdevs vector, a device_struct data structure contains two elements; a pointer to the name of the registered device driver and a pointer to a block of file operations. This block of file operations is itself the addresses of routines within the device character device driver each of which handles specific file operations such as open, read, write and close. The contents of /proc/devices for character devices is taken from the chrdevs vector.

When a character special file representing a character device (for example /dev/cua0) is opened the kernel must set things up so that the correct character device driver's file operation routines will be called. Just like an ordinairy file or directory, each device special file is represented by a VFS inode . The VFS inode for a character special file, indeed for all device special files, contains both the major and minor identifiers for the device. This VFS inode was created by the underlying filesystem, for example EXT2, from information in the real filesystem when the device special file's name was looked up. Each VFS inode has associated with it a set of file operations and these are different depending on the filesystem object that the inode represents. Whenever a VFS inode representing a character special file is created, its file operations are set to the default character device operations . This has only one file operation, the open file operation. When the character special file is opened by an application the generic open file operation uses the device's major identifier as an index into the chrdevs vector to retrieve the file operations block for this particular device. It also sets up the file data structure describing this character special file, making its file operations pointer point to those of the device driver. Thereafter all of the applications file operations will be mapped to calls to the character devices set of file operations.

9.4.2  Block Devices

Block devices also support being accessed like files. The mechanisms used to provide the correct set of file operations for the opened block special file are very much the same as for character devices. Linux maintains the set of registered block devices as the blkdevs vector. It, like the chrdevs vector, is indexed using the device's major device number. Its entries are also device_struct data structures. Unlike character devices, there are classes of block devices. SCSI devices are one such class and IDE devices are another. It is the class that registers itself with the Linux kernel and provides file operations to the kernel. The device drivers for a class of block device provide class specific interfaces to the class. So, for example, a SCSI device driver has to provide interfaces to the SCSI subsystem which the SCSI subsystem uses to provide file operations for this device to the kernel.

Every block device driver must provide an interface to the buffer cache as well as the normal file operations interface. Each block device driver fills in its entry in the blk_dev vector of blk_dev_struct data structures. The index into this vector is, again, the device's major number. The blk_dev_struct data structure consists of the address of a request routine and a pointer to a list of request data structures, each one representing a request from the buffer cache for the driver to read or write a block of data.


Figure 9.2: Buffer Cache Block Device Requests

Each time the buffer cache wishes to read or write a block of data to or from a registered device it adds a request data structure onto its blk_dev_struct. Figure  9.2 shows that each request has a pointer to one or more buffer_head data structures, each one a request to read or write a block of data. The buffer_head structures are locked (by the buffer cache) and there may be a process waiting on the block operation to this buffer to complete. Each request structure is allocated from a static list, the all_requestslist. If the request is being added to an empty request list, the driver's request function is called to start processing the request queue. Otherwise the driver will simply process every request on the request list.

Once the device driver has completed a request it must remove each of the buffer_head structures from the request structure, mark them as up to date and unlock them. This unlocking of the buffer_head will wake up any process that has been sleeping waiting for the block operation to complete. An example of this would be where a file name is being resolved and the EXT2 filesystem must read the block of data that contains the next EXT2 directory entry from the block device that holds the filesystem. The process sleeps on the buffer_head that will contain the directory entry until the device driver wakes it up. The request data structure is marked as free so that it can be used in another block request.

9.5  Hard Disks

Disk drives provide a more permanent method for storing data, keeping it on spinning disk platters. To write data, a tiny head magnetizes minute particles on the platter's surface. The data is read by a head, which can detect whether a particular minute particle is magnetized.

A disk drive consists of one or more platters, each made of finely polished glass or ceramic composites and coated with a fine layer of iron oxide. The platters are attached to a central spindle and spin at a constant speed that can vary between 3000 and 10,000 RPM depending on the model. Compare this to a floppy disk which only spins at 360 RPM. The disk's read/write heads are responsible for reading and writing data and there is a pair for each platter, one head for each surface. The read/write heads do not physically touch the surface of the platters, instead they float on a very thin (10 millionths of an inch) cushion of air. The read/write heads are moved across the surface of the platters by an actuator. All of the read/write heads are attached together, they all move across the surfaces of the platters together.

Each surface of the platter is divided into narrow, concentric circles called tracks. Track 0 is the outermost track and the highest numbered track is the track closest to the central spindle. A cylinder is the set of all tracks with the same number. So all of the 5th tracks from each side of every platter in the disk is known as cylinder 5. As the number of cylinders is the same as the number of tracks, you often see disk geometries described in terms of cylinders. Each track is divided into sectors. A sector is the smallest unit of data that can be written to or read from a hard disk and it is also the disk's block size. A common sector size is 512 bytes and the sector size was set when the disk was formatted, usually when the disk is manufactured.

A disk is usually described by its geometry, the number of cylinders, heads and sectors. For example, at boot time Linux describes one of my IDE disks as:

hdb: Conner Peripherals 540MB - CFS540A, 516MB w/64kB Cache, CHS=1050/16/63

This means that it has 1050 cylinders (tracks), 16 heads (8 platters) and 63 sectors per track. With a sector, or block, size of 512 bytes this gives the disk a storage capacity of 529200 bytes. This does not match the disk's stated capacity of 516 Mbytes as some of the sectors are used for disk partitioning information. Some disks automatically find bad sectors and re-index the disk to work around them.

Hard disks can be further subdivided into partitions. A partition is a large group of sectors allocated for a particular purpose. Partitioning a disk allows the disk to be used by several operating system or for several purposes. A lot of Linux systems have a single disk with three partitions; one containing a DOS filesystem, another an EXT2 filesystem and a third for the swap partition. The partitions of a hard disk are described by a partition table; each entry describing where the partition starts and ends in terms of heads, sectors and cylinder numbers. For DOS formatted disks, those formatted by fdisk, there are four primary disk partitions. Not all four entries in the partition table have to be used. There are three types of partition supported by fdisk, primary, extended and logical. Extended partitions are not real partitions at all, they contain any number of logical parititions. Extended and logical partitions were invented as a way around the limit of four primary partitions. The following is the output from fdisk for a disk containing two primary partitions:

Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders
Units = cylinders of 2048 * 512 bytes

   Device Boot   Begin    Start      End   Blocks   Id  System
/dev/sda1            1        1      478   489456   83  Linux native
/dev/sda2          479      479      510    32768   82  Linux swap

Expert command (m for help): p

Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders

Nr AF  Hd Sec  Cyl  Hd Sec  Cyl   Start    Size ID
 1 00   1   1    0  63  32  477      32  978912 83
 2 00   0   1  478  63  32  509  978944   65536 82
 3 00   0   0    0   0   0    0       0       0 00
 4 00   0   0    0   0   0    0       0       0 00

This shows that the first partition starts at cylinder or track 0, head 1 and sector 1 and extends to include cylinder 477, sector 32 and head 63. As there are 32 sectors in a track and 64 read/write heads, this partition is a whole number of cylinders in size. fdisk alligns partitions on cylinder boundaries by default. It starts at the outermost cylinder (0) and extends inwards, towards the spindle, for 478 cylinders. The second partition, the swap partition, starts at the next cylinder (478) and extends to the innermost cylinder of the disk.


Figure 9.3: Linked list of disks

During initialization Linux maps the topology of the hard disks in the system. It finds out how many hard disks there are and of what type. Additionally, Linux discovers how the individual disks have been partitioned. This is all represented by a list of gendisk data structures pointed at by the gendisk_head list pointer. As each disk subsystem, for example IDE, is initialized it generates gendisk data structures representing the disks that it finds. It does this at the same time as it registers its file operations and adds its entry into the blk_dev data structure. Each gendisk data structure has a unique major device number and these match the major numbers of the block special devices. For example, the SCSI disk subsystem creates a single gendisk entry (``sd'') with a major number of 8, the major number of all SCSI disk devices. Figure  9.3 shows two gendisk entries, the first one for the SCSI disk subsystem and the second for an IDE disk controller. This is ide0, the primary IDE controller.

Although the disk subsystems build the gendisk entries during their initialization they are only used by Linux during partition checking. Instead, each disk subsystem maintains its own data structures which allow it to map device special major and minor device numbers to partitions within physical disks. Whenever a block device is read from or written to, either via the buffer cache or file operations, the kernel directs the operation to the appropriate device using the major device number found in its block special device file (for example /dev/sda2). It is the individual device driver or subsystem that maps the minor device number to the real physical device.

9.5.1  IDE Disks

The most common disks used in Linux systems today are Integrated Disk Electronic or IDE disks. IDE is a disk interface rather than an I/O bus like SCSI. Each IDE controller can support up to two disks, one the master disk and the other the slave disk. The master and slave functions are usually set by jumpers on the disk. The first IDE controller in the system is known as the primary IDE controller, the next the secondary controller and so on. IDE can manage about 3.3 Mbytes per second of data transfer to or from the disk and the maximum IDE disk size is 538Mbytes. Extended IDE, or EIDE, has raised the disk size to a maximum of 8.6 Gbytes and the data transfer rate up to 16.6 Mbytes per second. IDE and EIDE disks are cheaper than SCSI disks and most modern PCs contain one or more on board IDE controllers.

Linux names IDE disks in the order in which it finds their controllers. The master disk on the primary controller is /dev/hda and the slave disk is /dev/hdb. /dev/hdc is the master disk on the secondary IDE controller. The IDE subsystem registers IDE controllers and not disks with the Linux kernel. The major identifier for the primary IDE controller is 3 and is 22 for the secondary IDE controller. This means that if a system has two IDE controllers there will be entries for the IDE subsystem at indices at 3 and 22 in the blk_dev and blkdevs vectors. The block special files for IDE disks reflect this numbering, disks /dev/hda and /dev/hdb, both connected to the primary IDE controller, have a major identifier of 3. Any file or buffer cache operations for the IDE subsystem operations on these block special files will be directed to the IDE subsystem as the kernel uses the major identifier as an index. When the request is made, it is up to the IDE subsystem to work out which IDE disk the request is for. To do this the IDE subsystem uses the minor device number from the device special identifier, this contains information that allows it to direct the request to the correct partition of the correct disk. The device identifier for /dev/hdb, the slave IDE drive on the primary IDE controller is (3,64). The device identifier for the first partition of that disk (/dev/hdb1) is (3,65).

9.5.2  Initializing the IDE Subsystem

IDE disks have been around for much of the IBM PC's history. Throughout this time the interface to these devices has changed. This makes the initialization of the IDE subsystem more complex than it might at first appear.

The maximum number of IDE controllers that Linux can support is 4. Each controller is represented by an ide_hwif_t data structure in the ide_hwifs vector. Each ide_hwif_t data structure contains two ide_drive_t data structures, one per possible supported master and slave IDE drive. During the initializing of the IDE subsystem, Linux first looks to see if there is information about the disks present in the system's CMOS memory. This is battery backed memory that does not lose its contents when the PC is powered off. This CMOS memory is actually in the system's real time clock device which always runs no matter if your PC is on or off. The CMOS memory locations are set up by the system's BIOS and tell Linux what IDE controllers and drives have been found. Linux retrieves the found disk's geometry from BIOS and uses the information to set up the ide_hwif_tdata structure for this drive. More modern PCs use PCI chipsets such as Intel's 82430 VX chipset which includes a PCI EIDE controller. The IDE subsystem uses PCI BIOS callbacks to locate the PCI (E)IDE controllers in the system. It then calls PCI specific interrogation routines for those chipsets that are present.

Once each IDE interface or controller has been discovered, its ide_hwif_t is set up to reflect the controllers and attached disks. During operation the IDE driver writes commands to IDE command registers that exist in the I/O memory space. The default I/O address for the primary IDE controller's control and status registers is 0x1F0 - 0x1F7. These addresses were set by convention in the early days of the IBM PC. The IDE driver registers each controller with the Linux block buffer cache and VFS, adding it to the blk_dev and blkdevs vectors respectively. The IDE drive will also request control of the appropriate interrupt. Again these interrupts are set by convention to be 14 for the primary IDE controller and 15 for the secondary IDE controller. However, they like all IDE details, can be overridden by command line options to the kernel. The IDE driver also adds a gendisk entry into the list of gendisk's discovered during boot for each IDE controller found. This list will later be used to discover the partition tables of all of the hard disks found at boot time. The partition checking code understands that IDE controllers may each control two IDE disks.

9.5.3  SCSI Disks

The SCSI (Small Computer System Interface) bus is an efficient peer-to-peer data bus that supports up to eight devices per bus, including one or more hosts. Each device has to have a unique identifier and this is usually set by jumpers on the disks. Data can be transfered synchronously or asynchronously between any two devices on the bus and with 32 bit wide data transfers up to 40 Mbytes per second are possible. The SCSI bus transfers both data and state information between devices, and a single transaction between an initiator and a target can involve up to eight distinct phases. You can tell the current phase of a SCSI bus from five signals from the bus. The eight phases are:

BUS FREE
No device has control of the bus and there are no transactions currently happening,
ARBITRATION
A SCSI device has attempted to get control of the SCSI bus, it does this by asserting its SCSI identifer onto the address pins. The highest number SCSI identifier wins.
SELECTION
When a device has succeeded in getting control of the SCSI bus through arbitration it must now signal the target of this SCSI request that it wants to send a command to it. It does this by asserting the SCSI identifier of the target on the address pins.
RESELECTION
SCSI devices may disconnect during the processing of a request. The target may then reselect the initiator. Not all SCSI devices support this phase.
COMMAND
6,10 or 12 bytes of command can be transfered from the initiator to the target,
DATA IN, DATA OUT
During these phases data is transfered between the initiator and the target,
STATUS
This phase is entered after completion of all commands and allows the target to send a status byte indicating success or failure to the initiator,
MESSAGE IN, MESSAGE OUT
Additional information is transfered between the initiator and the target.

The Linux SCSI subsystem is made up of two basic elements, each of which is represented by data structures:

host
A SCSI host is a physical piece of hardware, a SCSI controller. The NCR810 PCI SCSI controller is an example of a SCSI host. If a Linux system has more than one SCSI controller of the same type, each instance will be represented by a separate SCSI host. This means that a SCSI device driver may control more than one instance of its controller. SCSI hosts are almost always the initiator of SCSI commands.

Device
The most common set of SCSI device is a SCSI disk but the SCSI standard supports several more types; tape, CD-ROM and also a generic SCSI device. SCSI devices are almost always the targets of SCSI commands. These devices must be treated differently, for example with removable media such as CD-ROMs or tapes, Linux needs to detect if the media was removed. The different disk types have different major device numbers, allowing Linux to direct block device requests to the appropriate SCSI type.

Initializing the SCSI Subsystem

Initializing the SCSI subsystem is quite complex, reflecting the dynamic nature of SCSI buses and their devices. Linux initializes the SCSI subsystem at boot time; it finds the SCSI controllers (known as SCSI hosts) in the system and then probes each of their SCSI buses finding all of their devices. It then initializes those devices and makes them available to the rest of the Linux kernel via the normal file and buffer cache block device operations. This initialization is done in four phases:

First, Linux finds out which of the SCSI host adapters, or controllers, that were built into the kernel at kernel build time have hardware to control. Each built in SCSI host has a Scsi_Host_Template entry in the builtin_scsi_hosts vector The Scsi_Host_Template data structure contains pointers to routines that carry out SCSI host specific actions such as detecting what SCSI devices are attached to this SCSI host. These routines are called by the SCSI subsystem as it configures itself and they are part of the SCSI device driver supporting this host type. Each detected SCSI host, those for which there are real SCSI devices attached, has its Scsi_Host_Template data structure added to the scsi_hosts list of active SCSI hosts. Each instance of a detected host type is represented by a Scsi_Host data structure held in the scsi_hostlist list. For example a system with two NCR810 PCI SCSI controllers would have two Scsi_Host entries in the list, one per controller. Each Scsi_Host points at the Scsi_Host_Template representing its device driver.


Figure 9.4: SCSI Data Structures

Now that every SCSI host has been discovered, the SCSI subsystem must find out what SCSI devices are attached to each host's bus. SCSI devices are numbered between 0 and 7 inclusively, each device's number or SCSI identifier being unique on the SCSI bus to which it is attached. SCSI identifiers are usually set by jumpers on the device. The SCSI initialization code finds each SCSI device on a SCSI bus by sending it a TEST_UNIT_READY command. When a device responds, its identification is read by sending it an ENQUIRY command. This gives Linux the vendor's name and the device's model and revision names. SCSI commands are represented by a Scsi_Cmnd data structure and these are passed to the device driver for this SCSI host by calling the device driver routines within its Scsi_Host_Template data structure. Every SCSI device that is found is represented by a Scsi_Device data structure, each of which points to its parent Scsi_Host. All of the Scsi_Device data structures are added to the scsi_devices list. Figure  9.4 shows how the main data structures relate to one another.

There are four SCSI device types: disk, tape, CD and generic. Each of these SCSI types are individually registered with the kernel as different major block device types. However they will only register themselves if one or more of a given SCSI device type has been found. Each SCSI type, for example SCSI disk, maintains its own tables of devices. It uses these tables to direct kernel block operations (file or buffer cache) to the correct device driver or SCSI host. Each SCSI type is represented by a Scsi_Device_Template data structure. This contains information about this type of SCSI device and the addresses of routines to perform various tasks. The SCSI subsystem uses these templates to call the SCSI type routines for each type of SCSI device. In other words, if the SCSI subsystem wishes to attach a SCSI disk device it will call the SCSI disk type attach routine. The Scsi_Type_Template data structures are added to the scsi_devicelistlist if one or more SCSI devices of that type have been detected.

The final phase of the SCSI subsystem initialization is to call the finish functions for each registered Scsi_Device_Template. For the SCSI disk type this spins up all of the SCSI disks that were found and then records their disk geometry. It also adds the gendisk data structure representing all SCSI disks to the linked list of disks shown in Figure  9.3.

Delivering Block Device Requests

Once Linux has initialized the SCSI subsystem, the SCSI devices may be used. Each active SCSI device type registers itself with the kernel so that Linux can direct block device requests to it. There can be buffer cache requests via blk_dev or file operations via blkdevs. Taking a SCSI disk driver that has one or more EXT2 filesystem partitions as an example, how do kernel buffer requests get directed to the right SCSI disk when one of its EXT2 partitions is mounted?

Each request to read or write a block of data to or from a SCSI disk partition results in a new request structure being added to the SCSI disks current_request list in the blk_dev vector. If the request list is being processed, the buffer cache need not do anything else; otherwise it must nudge the SCSI disk subsystem to go and process its request queue. Each SCSI disk in the system is represented by a Scsi_Disk data structure. These are kept in the rscsi_disks vector that is indexed using part of the SCSI disk partition's minor device number. For exmaple, /dev/sdb1 has a major number of 8 and a minor number of 17; this generates an index of 1. Each Scsi_Disk data structure contains a pointer to the Scsi_Device data structure representing this device. That in turn points at the Scsi_Host data structure which ``owns'' it. The request data structures from the buffer cache are translated into Scsi_Cmd structures describing the SCSI command that needs to be sent to the SCSI device and this is queued onto the Scsi_Host structure representing this device. These will be processed by the individual SCSI device driver once the appropriate data blocks have been read or written.

9.6  Network Devices

A network device is, so far as Linux's network subsystem is concerned, an entity that sends and receives packets of data. This is normally a physical device such as an ethernet card. Some network devices though are software only such as the loopback device which is used for sending data to yourself. Each network device is represented by a device data structure. Network device drivers register the devices that they control with Linux during network initialization at kernel boot time. The device data structure contains information about the device and the addresses of functions that allow the various supported network protocols to use the device's services. These functions are mostly concerned with transmitting data using the network device. The device uses standard networking support mechanisms to pass received data up to the appropriate protocol layer. All network data (packets) transmitted and received are represented by sk_buff data structures, these are flexible data structures that allow network protocol headers to be easily added and removed. How the network protocol layers use the network devices, how they pass data back and forth using sk_buff data structures is described in detail in the Networks chapter (Chapter  networks-chapter). This chapter concentrates on the device data structure and on how network devices are discovered and initialized.

The device data structure contains information about the network device:

Name
Unlike block and character devices which have their device special files created using the mknod command, network device special files appear spontaniously as the system's network devices are discovered and initialized. Their names are standard, each name representing the type of device that it is. Multiple devices of the same type are numbered upwards from 0. Thus the ethernet devices are known as /dev/eth0,/dev/eth1,/dev/eth2 and so on. Some common network devices are:

/dev/ethN Ethernet devices
/dev/slN SLIP devices
/dev/pppN PPP devices
/dev/lo Loopback devices

Bus Information
This is information that the device driver needs in order to control the device. The irq number is the interrupt that this device is using. The base address is the address of any of the device's control and status registers in I/O memory. The DMA channel is the DMA channel number that this network device is using. All of this information is set at boot time as the device is initialized.

Interface Flags
These describe the characteristics and abilities of the network device:

IFF_UP Interface is up and running,
IFF_BROADCAST Broadcast address in device is valid
IFF_DEBUG Device debugging turned on
IFF_LOOPBACK This is a loopback device
IFF_POINTTOPOINT This is point to point link (SLIP and PPP)
IFF_NOTRAILERS No network trailers
IFF_RUNNING Resources allocated
IFF_NOARP Does not support ARP protocol
IFF_PROMISC Device in promiscuous receive mode, it will receive
all packets no matter who they are addressed to
IFF_ALLMULTI Receive all IP multicast frames
IFF_MULTICAST Can receive IP multicast frames

Protocol Information
Each device describes how it may be used by the network protocool layers:
mtu
The size of the largest packet that this network can transmit not including any link layer headers that it needs to add. This maximum is used by the protocol layers, for example IP, to select suitable packet sizes to send.
Family
The family indicates the protocol family that the device can support. The family for all Linux network devices is AF_INET, the Internet address family.
Type
The hardware interface type describes the media that this network device is attached to. There are many different types of media that Linux network devices support. These include Ethernet, X.25, Token Ring, Slip, PPP and Apple Localtalk.
Addresses
The device data structure holds a number of addresses that are relevent to this network device, including its IP addresses.
Packet Queue
This is the queue of sk_buff packets queued waiting to be transmitted on this network device,
Support Functions
Each device provides a standard set of routines that protocol layers call as part of their interface to this device's link layer. These include setup and frame transmit routines as well as routines to add standard frame headers and collect statistics. These statistics can be seen using the ifconfig command.

9.6.1  Initializing Network Devices

Network device drivers can, like other Linux device drivers, be built into the Linux kernel. Each potential network device is represented by a device data structure within the network device list pointed at by dev_base list pointer. The network layers call one of a number of network device service routines whose addresses are held in the device data structure if they need device specific work performing. Initially though, each device data structure holds only the address of an initialization or probe routine.

There are two problems to be solved for network device drivers. Firstly, not all of the network device drivers built into the Linux kernel will have devices to control. Secondly, the ethernet devices in the system are always called /dev/eth0, /dev/eth1 and so on, no matter what their underlying device drivers are. The problem of ``missing'' network devices is easily solved. As the initialization routine for each network device is called, it returns a status indicating whether or not it located an instance of the controller that it is driving. If the driver could not find any devices, its entry in the device list pointed at by dev_base is removed. If the driver could find a device it fills out the rest of the device data structure with information about the device and the addresses of the support functions within the network device driver.

The second problem, that of dynamically assigning ethernet devices to the standard /dev/ethN device special files is solved more elegantly. There are eight standard entries in the devices list; one for eth0, eth1 and so on to eth7. The initialization routine is the same for all of them, it tries each ethernet device driver built into the kernel in turn until one finds a device. When the driver finds its ethernet device it fills out the ethN device data structure, which it now owns. It is also at this time that the network device driver initializes the physical hardware that it is controlling and works out which IRQ it is using, which DMA channel (if any) and so on. A driver may find several instances of the network device that it is controlling and, in this case, it will take over several of the /dev/ethN device data structures. Once all eight standard /dev/ethN have been allocated, no more ethernet devices will be probed for.

Capítulo 10
The File system

Este cap´itulo describe cómo el kernel de Linux gestiona los ficheros en los sistemas de ficheros soportados por éste. Describe el Sistema de Ficheros Virtual (VFS) y explica cómo los sistemas de ficheros reales del kernel de Linux son soportados.

Una de los rasgos más importantes de Linux es su soporte para diferentes sistemas de ficheros. Ésto lo hace muy flexible y bien capacitado para coexistir con muchos otros sistemas operativos. En el momento de escribir ésto, Linux soporta 15 sistemas de ficheros; ext, ext2, xia, minix, umsdos, msdos, vfat, proc, smb, ncp, iso9660, sysv, hpfs, affs and ufs, y sin duda, con el tiempo se añadirán más.

En Linux, como en Unix, a los distintos sistemas de ficheros que el sistema puede usar no se accede por identificadores de dispositivo (como un número o nombre de unidad) pero, en cambio se combinan en una simple structura jerárquica de árbol que representa el sistema de ficheros como una entidad única y sencilla. Linux añade cada sistema de ficheros nuevo en este simple árbol de sistemas de ficheros cuando se monta. Todos los sistemas de ficheros, de cualquier tipo, se montan sobre un directorio y los ficheros del sistema de ficheros son el contenido de ese directorio. Este directorio se conoce como directorio de montaje o punto de montaje. Cuando el sistema de ficheros se desmonta, los ficheros propios del directorio de montaje son visibles de nuevo.

Cuando se inicializan los discos (usando fdisk, por ejemplo) tienen una estructura de partición inpuesta que divide el disco físico en un número de particiones lógicas. Cada partición puede mantener un sistema de ficheros, por ejemplo un sistema de ficheros EXT2. Los sistemas de ficheros organizan los ficheros en structuras jerárquicas lógicas con directorios, enlaces flexibles y más contenidos en los bloques de los dispositivos físicos. Los dispositivos que pueden contener sistemas de ficheros se conocen con el nombre de dispositivos de bloque. La partición de disco IDE /dev/hda1, la primera partición de la primera unidad de disco en el sistema, es un dispositivo de bloque. Los sistemas de ficheros de Linux contemplan estos dispositivos de bloque como simples colecciones lineales de bloques, ellos no saben o tienen en cuenta la geometría del disco físico que hay debajo. Es la tarea de cada controlador de dispositivo de bloque asignar una petición de leer un bloque particular de su dispositivo en términos comprensibles para su dispositivo; la pista en cuestión, sector y cilindro de su disco duro donde se guarda el bloque. Un sistema de ficheros tiene que mirar, sentir y operar de la misma forma sin importarle con que dispositivo está tratando. Por otra parte, al usar los sistemas de ficheros de Linux, no importa (al menos para el usuario del sistema) que estos distintos sistemas de ficheros estén en diferentes soportes controlados por diferentes controladores de hardware. El sistema de ficheros puede incluso no estar en el sistema local, puede ser perfectamente un disco remoto montado sobre un enlace de red. Considerese el siguiente ejemplo donde un sistema Linux tiene su sistema de ficheros raíz en un disco SCSI:

A         E         boot      etc       lib       opt       tmp       usr
C         F         cdrom     fd        proc      root      var       sbin
D         bin       dev       home      mnt       lost+found

Ni los usuarios ni los programas que operan con los ficheros necesitan saber que /C de hecho es un sistema de ficheros VFAT montado que está en el primer disco IDE del sistema. En el ejemplo (que es mi sistema Linux en casa), /E es el disco IDE primario en la segunda controladora IDE. No importa que la primera controladora IDE sea una controladora PCI y que la segunda sea una controladora ISA que también controla el IDE CDROM. Puedo conectarme a la red donde trabajo usando un modem y el protocolo de red PPP y en este caso puedo remotamente montar mis sistemas de ficheros Linux Alpha AXP sobre /mnt/remote.

Los ficheros en un sistema de ficheros son grupos de datos; el fichero que contiene las fuentes de este capítulo es un fichero ASCII llamado filesystems.tex. Un sistema de ficheros no sólo posee los datos contenidos dentro de los ficheros del sistema de ficheros, además mantiene la estructura del sistema de ficheros. Mantiene toda la información que los usuarios de Linux y procesos ven como ficheros, directorios, enlaces flexibles, información de protección de ficheros y así. Por otro lado debe mantener esa información de forma eficiente y segura, la integridad básica del sistema operativo depende de su sistema de ficheros. Nadie usaria un sistema operativo que perdiera datos y ficheros de forma aleatoria11.

Minix, el primer sistema de ficheros que Linux tuvo es bastante restrictivo y no era muy rápido. Minix, the first file system that Linux had is rather restrictive and lacking in performance. Sus nombres de ficheros no pueden tener más de 14 caracteres (que es mejor que nombres de ficheros 8.3) y el tamaño máximo de ficheros es 64 MBytes. 64 MBytes puede a primera vista ser suficiente pero se necesitan tamaños de ficheros más grandes para soportar incluso modestas bases de datos. El primer sistema de ficheros diseñado especificamente para Linux, el sistema de Ficheros Extendido, o EXT, fue introducido en Abril de 1992 y solventó muchos problemas pero era aun falto de rapidez. Así, en 1993, el Segundo sistema de Ficheros Extendido, o EXT2, fue añadido. Este es el sistema de ficheros que se describe en detalle más tarde en este capítulo.

Un importante desarrollo tuvo lugar cuando se añadió en sistema de ficheros EXT en Linux. El sistema de ficheros real se separó del sistema operativo y servicios del sistema a favor de un interfaz conocido como el sistema de Ficheros Virtual, o VFS. VFS permite a Linux soportar muchos, incluso muy diferentes, sistemas de ficheros, cada uno presentando un interfaz software común al VFS. Todos los detalles del sistema de ficheros de Linux son traducidos mediante software de forma que todo el sistema de ficheros parece idéntico al resto del kernel de Linux y a los programas que se ejecutan en el sistema. La capa del sistema de Ficheros Virtual de Linux permite al usuario montar de forma transparente diferentes sistemas de ficheros al mismo tiempo.

El sistema de Ficheros Virtual está implementado de forma que el acceso a los ficheros es rápida y tan eficiente como es posible. También debe asegurar que los ficheros y los datos que contiene son correctos. Estos dos requisitos pueden ser incompatibles uno con el otro. El VFS de Linux mantiene una antememoria con información de cada sistema de ficheros montado y en uso. Se debe tener mucho cuidado al actualizar correctamente el sistema de ficheros ya que los datos contenidos en las antememorias se modifican cuando cuando se crean, escriben y borran ficheros y directorios. Si se pudieran ver las estructuras de datos del sistema de ficheros dentro del kernel en ejecución, se podria ver los bloques de datos que se leen y escriben por el sistema de ficheros. Las estructuras de datos, que describen los ficheros y directorios que son accedidos serian creadas y destruidas y todo el tiempo los controladores de los dispositivo estarian trabajando, buascando y guardando datos. La antememoria o caché más importantes es el Buffer Cache, que está integrado entre cada sistema de ficheros y su dispositivo de bloque. Tal y como se accede a los bloques se ponen en el Buffer Cache y se almacenan en varias colas dependiendo de sus estados. El Buffer Cache no sólo mantiene buffers de datos, tambien ayuda a administrar el interfaz asíncrono con los controladores de dispositivos de bloque.

10.1  The Second Extended File system (EXT2)


Figure 10.1: Physical Layout of the EXT2 File system

El Segundo sistema de ficheros Extendido fue pensado (por Rémy Card) como un sistema de ficheros extensible y poderoso para Linux. También es el sistema de ficheros más éxito tiene en la comunidad Linux y es básico para todas las distribuciones actuales de Linux. El sistema de ficheros EXT2, como muchos sistemas de ficheros, se construye con la premisa de que los datos contenidos en los ficheros se guarden en bloques de datos. Estos bloques de datos son todos de la misma longitud y, si bien esa longitud puede variar entre diferentes sistemas de ficheros EXT2 el tamaño de los bloques de un sistema de ficheros EXT2 en particular se decide cuando se crea (usando mke2fs). El tamaño de cada fichero se redondea hasta un numero entero de bloques. Si el tamaño de bloque es 1024 bytes, entonces un fichero de 1025 bytes ocupará dos bloques de 1024 bytes. Desafortunadamente esto significa que en promedio se desperdicia un bloque por fichero.12 No todos los bloques del sistema de ficheros contienen datos, algunos deben usarse para mantener la información que describe la estructura del sistema de ficheros. EXT2 define la topologia del sistema de ficheros describiendo cada fichero del sistema con una estructura de datos inodo. Un inodo describe que bloques ocupan los datos de un fichero y también los permisos de acceso del fichero, las horas de modificación del fichero y el tipo del fichero. Cada fichero en el sistema de ficheros EXT2 se describe por un único inodo y cada inodo tiene un único número que lo identifica. Los inodos del sistema de ficheros se almacenan juntos en tablas de inodos. Los directorios EXT2 son simplemente ficheros especiales (ellos mismos descritos por inodos) que contienen punteros a los inodos de sus entradas de directorio.

La figura  10.1 muestra la disposición del sistema de ficheros EXT2 ocupando una serie de bloques en un dispositivo estructurado bloque. Por la parte que le toca a cada sistema de ficheros, los dispositivos de bloque son sólo una serie de bloques que se pueden leer y escribir. Un sistema de ficheros no se debe preocupar donde se debe poner un bloque en el medio físico, eso es trabajo del controlador del dispositivo. Siempre que un sistema de ficheros necesita leer información o datos del dispositivo de bloque que los contiene, pide que su controlador de dispositivo lea un número entero de bloques. El sistema de ficheros EXT2 divide las particiones lógicas que ocupa en Grupos de Bloque (Block Groups). Cada grupo duplica información crítica para la integridad del sistema de ficheros ya sea valiendose de ficheros y directorios como de bloques de información y datos. Esta duplicación es necesaria por si ocurriera un desastre y el sistema de ficheros necesitara recuperarse. Los subapartados describen con más detalle los contenidos de cada Grupo de Bloque.

10.1.1  The EXT2 Inode


Figure 10.2: EXT2 Inode

En el sistema de ficheros EXT2, el inodo es el bloque de construcción básico; cada fichero y directorio del sistema de ficheros es descrito por un y sólo un inodo. Los inodos EXT2 para cada Grupo de Bloque se almacenan juntos en la table de inodos con un mapa de bits que permite al sistema seguir la pista de inodos reservados y libres. La figura  10.2 muestra el formato de un inodo EXT2, entre otra información, contiene los siguientes campos:

mode
Esto mantiene dos partes de información; qué inodo describe y los permisos que tienen los usuarios. Para EXT2, un inodo puede describir un ficheros, directorio, enlace simbólico, dispositivo de bloque, dispositivo de caracter o FIFO.
Owner Information
Los identificadores de usuario y grupo de los dueños de este fichero o directorio. Esto permite al sistema de ficheros aplicar correctamente el tipo de acceso,
Size
El tamaño en del fichero en bytes,
Timestamps
La hora en la que el inodo fue creado y la última hora en que se modificó,
Datablocks
Punteros a los bloques que contienen los datos que este inodo describe. Los doce primeros son punteros a los bloques físicos que contienen los datos descritos por este inodo y los tres últimos punteros contienen más y más niveles de indirección. Por ejemplo, el puntero de doble indirección apunta a un bloque de punteros que apuntan a bloques de punteros que apuntan a bloques de datos. Esto significa que ficheros menores o iguales a doce bloques de datos en longitud son más facilmente accedidos que ficheros más grandes.

Indicar que los inodos EXT2 pueden describir ficheros de dispositivo especiales. No son ficheros reales pero permiten que los programas puedan usarlos para acceder a los dispositivos. Todos los ficheros de dispositivo de /dev están ahi para permitir a los programas acceder a los dispositivos de Linux. Por ejemplo el programa mount toma como argumento el fichero de dispositivo que el usuario desee montar.

10.1.2  The EXT2 Superblock

El Superbloque contiene una descripción del tamaño y forma base del sistema de ficheros. La información contenida permite al administrador del sistema de ficheros usar y mantener el sistema de ficheros. Normalmente sólo se lee el Superbloque del Grupo de Bloque 0 cuando se monta el sistema de ficheros pero cada Grupo de Bloque contiene una copia duplicada en caso de que se corrompa sistema de ficheros. Entre otra información contiene el:

Magic Number
Esto permite al software de montaje comprobar que es realmente el Superbloque para un sistema de ficheros EXT2. Para la versión actual de EXT2 éste es 0xEF53.
Revision Level
Los niveles de revisión mayor y menor permiten al código de montaje determinar si este sistema de ficheros soporta o no características que sólo son disponibles para revisiones particulares del sistema de ficheros. También hay campos de compatibilidad que ayudan al código de montaje determinar que nuevas características se pueden usar con seguridad en ese sistema de ficheros,
Mount Count and Maximum Mount Count
Juntos permiten al sistema determinar si el sistema de ficheros fue comprobado correctamente. El contador de montaje se incrementa cada vez que se monta el sistema de ficheros y cuando es igual al contador máximo de montaje muestra el mensaje de aviso «maximal mount count reached, running e2fsck is recommended»,
Block Group Number
El número del Grupo de Bloque que tiene la copia de este Superbloque,
Block Size
El tamaño de bloque para este sistema deficheros en bytes, por ejemplo 1024 bytes,
Blocks per Group
El número de bloques en un grupo. Como el tamaño de bloque éste se fija cuando se crea el sitema de ficheros,
Free Blocks
EL número de bloques libres en el sistema de ficheros,
Free Inodes
El número de Inodos libres en el sistema de ficheros,
First Inode
Este es el número de inodo del primer inodo en el sistema de ficheros. El primer inodo en un sistema de ficheros EXT2 raíz seria la entrada directorio para el directorio '/'.

10.1.3  The EXT2 Group Descriptor

Cada Grupo de Bloque tiene una estructura de datos que lo describe. Como el Superbloque, todos los descriptores de grupo para todos los Grupos de Bloque se duplican en cada Grupo de Bloque en caso de corrupción del sistema de fichero. Cada Descriptor de Grupo contiene la siguiente información:

Blocks Bitmap
El número de bloque del mapa de bits de bloques reservados para este Grupo de Bloque. Se usa durante la reseva y liberación de bloques,
Inode Bitmap
El número de bloque del mapa de bits de inodos reservados para este Grupo de Bloques. Se usa durante la reserva y liberación de inodos,
Inode Table
El número de bloque del bloque inicial para la tabla de inodos de este Grupo de Bloque. Cada inodo se representa por la estructura de datos inodo EXT2 descrita abajo.
Free blocks count, Free Inodes count, Used directory count

Los descriptores de grupo se colocan uno detrás de otro y juntos hacen la tabla de descriptor de grupo. Cada Grupo de Bloques contiene la tabla entera de descriptores de grupo despues de su copia del Superbloque. Sólo la primera copia (en Grupo de Bloque 0) es usada por el sistema de ficheros EXT2. Las otras copias están ahi, como las copias del Superbloque, en caso de que se corrompa la principal.

10.1.4  EXT2 Directories


Figure 10.3: EXT2 Directory

En el sistema de ficheros EXT2, los directorios son ficheros especiales que se usan para crear y mantener rutas de acceso a los ficheros en el sistema de ficheros. La figura  10.3 muestra la estructura de una estrada directorio en memoria. Un fichero directorio es una lista de entradas directorio, cada una conteniendo la siguiente información:

inode
El inodo para esta entrada directorio. Es un índice al vector de inodos guardada en la Tabla de Inodos del Grupo de Bloque. En la figura  10.3, la entrada directorio para el fichero llamado file tiene una referencia al número de inodo i1,
name length
La longitud de esta entrada directorio en bytes,
name
El nombre de esta entrada directorio.

Las dos primeras entradas para cada directorio son siempre las entradas estandar «.» y «..» significando «este directorio» y «el directorio padre» respectivamente.

10.1.5  Finding a File in an EXT2 File System

Un nombre de fichero Linux tiene el mismo formato que los nombres de ficheros de todos los Unix\. Es una serie de nombres de directorios separados por contra barras («/») y acabando con el nombre del fichero. Un ejemplo de nombre de fichero podria ser /home/rusling/.cshrc donde /home y /rusling son nombres de directorio y el nombre del fichero es .cshrc. Como todos los demas sistemas Unix¸ Linux no tiene encuenta el formato del nombre del fichero; puede ser de cualquier longitud y cualquier caracter imprimible. Para encontrar el inodo que representa a este fichero dentro de un sistema de ficheros EXT2 el sistema debe analizar el nombre del fichero directorio a directorio hasta encontrar el fichero en si. El primer inodo que se necesita es el inodo de la raíz del sistema de ficheros, que está en el superbloque del sistema de ficheros. Para leer un inodo EXT2 hay que buscarlo en la tabla de inodos del Grupo de Bloque apropiado. Si, por ejemplo, el número de inodo de la raíz es 42, entonces necesita el inodo 42avo de la tabla de inodos del Grupo de Bloque 0. El inodo raíz es para un directorio EXT2, en otras palabras el modo del inodo lo describe como un directorio y sus bloques de datos contienen entradas directorio EXT2.

home es una de las muchas entradas directorio y esta entrada directorio indica el número del inodo que describe al directorio /home. Hay que leer este directorio (primero leyendo su inodo y luego las entradas directorio de los bloques de datos descritos por su inodo) para encontrar la entrada rusling que indica el numero del inodo que describe al directorio /home/rusling. Finalmente se debe leer las entradas directorio apuntadas por el inodo que describe al directorio /home/rusling para encontrar el número de inodo del fichero .cshrc y desde ahi leer los bloques de datos que contienen la información del fichero.

10.1.6  Changing the Size of a File in an EXT2 File System

Un problema común de un sistema de ficheros es la tendencia a fragmentarse. Los bloques que contienen los datos del fichero se esparcen por todo el sistema de ficheros y esto hace que los accesos secuenciales a los bloques de datos de un fichero sean cada vez más ineficientes cuanto más alejados estén los bloques de datos. El sistema de ficheros EXT2 intenta solucionar esto reservando los nuevos bloques para un fichero, fisicamente juntos a sus bloques de datos actuales o al menos en el mismo Grupo de Bloque que sus bloques de datos. Sólo cuando esto falla, reserva bloques de datos en otros Grupos de Bloque.

Siempre que un proceso intenta escribir datos a un fichero, el sistema de ficheros Linux comprueba si los datos exceden el final del último bloque para el fichero. Si lo hace, entonces tiene que reservar un nuevo bloque de datos para el fichero. Hasta que la reserva no haya acabado, el proceso no puede ejecutarse; debe esperarse a que el sistema de ficheros reserve el nuevo bloque de datos y escriba el resto de los datos antes de continuar. La primera cosa que hacen las rutinas de reserva de bloques EXT2 es bloquear el Superbloque EXT2 de ese sistema de ficheros. La reserva y liberación cambia campos del superbloque, y el sistema de ficheros Linux no puede permitir más de un proceso haciendo ésto a la vez. Si otro proceso necesita reservar más bloques de datos, debe esperarse hasta que el otro proceso acabe. Los procesos que esperan el superbloque son suspendidos, no se pueden ejecutar, hasta que el control del superbloque lo abandone su usuario actual. El acceso al superbloque se garantiza mediante una política «el primero que llega se atiende primero», y cuando un proceso tiene control sobre el superbloque le pone cerrojo hasta que no lo necesita más. 13 bloqueado el superbloque, el proceso comprueba que hay suficientes bloques libres en ese sistema de ficheros. Si no es así, el intento de reservar más bloques falla y el proceso cederá el control del superbloque del sistema de ficheros.

Si hay suficientes bloques en el sistema de ficheros, el proceso intenta reservar uno. Si el sistema de ficheros EXT2 se ha compilado para prereservar bloques de datos entonces se podrá usar uno de estos. La prereserva de bloques no existe realmente, sólo se reservan dentro del mapa de bits de bloques reservados. El inodo VFS que representa el fichero que intenta reservar un nuevo bloque de datos tiene dos campos EXT2 específicos, prealloc_block y prealloc_count, que son el numero de bloque del primer bloque de datos prereservado y cuantos hay, respectivamente. Si no habian bloques prereservados o la reserva anticipada no está activa, el sistema de ficheros EXT2 debe reservar un nuevo bloque. El sistema de ficheros EXT2 primero mira si el bloque de datos despues del último bloque de datos del fichero está libre. Logicamente, este es el bloque más eficiente para reservar ya que hace el acceso secuencial mucho más rápido. Si este bloque no está libre, la búsqueda se ensancha y busca un bloque de datos dentro de los 64 bloques del bloque ideal. Este bloque, aunque no sea ideal está al menos muy cerca y dentro del mismo Grupo de Bloque que los otros bloques de datos que pertenecen a ese fichero.

Si incluso ese bloque no está libre, el proceso empieza a buscar en los demás Grupos de Bloque hasta encontrar algunos bloques libres. El código de reserva de bloque busca un cluster de ocho bloques de datos libres en cualquiera de los Grupos de Bloque. Si no puede encontrar ocho juntos, se ajustará para menos. Si se quiere la prereserva de bloques y está activado, actualizará prealloc_block y prealloc_count pertinentemente.

Donde quiera que encuentre el bloque libre, el código de reserva de bloque actualiza el mapa de bits de bloque del Grupo de Bloque y reserva un buffer de datos en el buffer caché. Ese buffer de datos se identifica unequivocamente por el identificador de dispositivo del sistema y el número de bloque del bloque reservado. El buffer de datos se sobreescribe con ceros y se marca como «sucio» para indicar que su contenido no se ha escrito al disco físico. Finalmente, el superbloque se marca como «sucio» para indicar que se ha cambiado y está desbloqueado. Si hubiera otros procesos esperando, al primero de la cola se le permitiria continuar la ejecución y terner el control exclusido del superbloque para sus operaciones de fichero. Los datos del proceso se escriben en el nuevo bloque de datos y, si ese bloque se llena, se repite el proceso entero y se reserva otro bloque de datos.

10.2  The Virtual File System (VFS)


Figure 10.4: A Logical Diagram of the Virtual File System

La figura  10.4 muestra la relación entre el Sistema de Ficheros Virtual del kernel de Linux y su sistema de ficheros real. El sistema de ficheros vitual debe mantener todos los diferentes sistemas de ficheros que hay montados en cualquier momento. Para hacer esto mantiene unas estructuras de datos que describen el sistema de ficheros (virtual) por entero y el sistema de ficheros, montado, real. De forma más confusa, el VFS describe los ficheros del sistema en términos de superbloque e inodos de la misma forma que los ficheros EXT2 usan superbloques e inodos. Como los inodos EXT2, los inodos VFS describen ficheros y directorios dentro del sistema; los contenidos y topología del Sistema de Ficheros Virtual. De ahora en adelante, para evitar confusiones, se escribirá inodos CFS y superbloques VFS para distinguirlos de los inodos y superbloques EXT2.

Cuando un sistema de ficheros se inicializa, se registra él mismo con el VFS. Esto ocurre cuando el sistema operativo se inicializa en el momento de arranque del sistema. Los sistemas de ficheros reales están compilados con el nucleo o como módulos cargables. Los módulos de Sistemas de Ficheros se cargan cuando el sistema los necesita, así, por ejemplo, si el sistema de ficheros VFAT está implementado como módulo del kernel, entonces sólo se carga cuando se monta un sistema de ficheros VFAT. Cuando un dispositivo de bloque base se monta, y éste incluye el sistema de ficheros raíz, el VFS debe leer su superbloque. Cada rutina de lectura de superbloque de cada tipo de sistema de ficheros debe resolver la topología del sistema de ficheros y mapear esa información dentro de la estructura de datos del superbloque VFS. El VFA mantiene una lista de los sitema de ficheros montados del sistema junto con sus superbloques VFS. Cada superbloque VFS contiene información y punteros a rutinas que realizan funciones particulares. De esta forma, por ejemplo, el superbloque que representa un sistema de ficheros EXT2 montado contiene un puntero a la rutina de lectura de inodos específica. Esta rutina, como todas las rutinas de lectura de inodos del sistema de ficheros espeífico, rellena los campos de un inodo VFS. Cada superbloque VFS contiene un puntero al primer inodo VFS del sistema de ficheros. Para el sistema de ficheros raíz, éste es el inodo que representa el directorio «/». Este mapeo de información es muy eficiente para el sistema de ficheros EXT2 pero moderadamente menos para otros sistema de ficheros.

Ya que los procesos del sistema acceden a directorios y ficheros, las rutinas del sistema se dice que recorren los inodos VFS del sistema. Por ejemplo, escribir ls en un directorio o cat para un fichero hacen que el Sistema de Ficheros Virtual busque através de los inodos VFS que representan el sistema de ficheros. Como cada fichero y directorio del sistema se representa por un inodo VFS, un número de inodos serán accedidos repetidamente. Estos inodos se mantienen en la antememoria, o caché, de inodos que hace el acceso mucho más rápido. Si un inodo no está en la caché, entonces se llama a una rutina específica del sistema de ficheros para leer el inodo apropiado. La acción de leer el inodo hace que se ponga en la caché de inodos y siguientes accesos hacen que se mantenga en la caché. Los inodos VFS menos usados se quitan de la caché.

Todos los sistemas de ficheros de Linux usan un buffer caché común para mantener datos de los dispositivos para ayudar a acelerar el acceso por todos los sistemas de ficheros al dispositivo físico que contiene los sistemas de ficheros. Este buffer caché es independiente del sistema de ficheros y se integra dentro de los mecanismos que el núcleo de Linux usa para reservar, leer y escribir datos. Esto tiene la ventaja de hacer los sistemas de ficheros de Linux independientes del medio y de los controladores de dispositivos que los soportan. Tofos los dispositivos estructurados de bloque se registran ellos mismos con el núcleo de Linux y presentan una interfaz uniforme, basada en bloque y normalmente asíncrona. Incluso dispositivos de bloque relativamente complejos como SCSI lo hacen.

Cuando el sistema de ficheros real lee datos del disco físico realiza una petición al controlador de dispositivo de bloque para leer los bloques físicos del dispositivo que controla. Integrado en este interfaz de dispositivo de bloque está el buffer caché. Al leer bloques del sistema de ficheros se guardan en un el buffer caché global compartido por todos los sistemas de ficheros y el núcleo de Linux. Los buffers que hay dentro se identifican por su número de bloque y un identificador único para el dispositivo que lo ha leido. De este modo, si se necesitan muy a menudo los mismos datos, se obtendrán del buffer caché en lugar de leerlos del disco, que tarda más tiempo. Algunos dispositivos pueden realizar lecturas anticipadas (read ahead), mediante lo cual se realizan lecturas antes de necesitarlas, especulando con que se utilizarán más adelante.14

El VFS también mantiene una caché de directorios donde se pueden encontrar los inodos de los directorios que se usan de forma más frecuente. Como experimento, probar a listar un directorio al que no se haya accedido recientemente. La primera vez que se lista, se puede notar un pequeño retardo pero la segunda vez el resultado es inmediato. El caché directorio no almacena realmente los inodos de los directorios; éstos estarán en el caché de inodos, el directorio caché simplemente almacena el mapa entre el nombre entero del directorio y sus números de inodo.

10.2.1  The VFS Superblock

Cada sistema de ficheros montado está representado por un superbloque VFS; entre otra información, el superbloque VFS contiene:

Device
Es el identificador de dispositivo para el dispositivo bloque que contiene este a este sistema de ficheros. Por ejemplo, /dev/hda1, el primer disco duro IDE del sistema tiene el identificador de dispositivo 0x301,
Inode pointers
El puntero de inodo montado apunta al primer inodo del sistema de ficheros. El puntero de inodo cubierto apunta al inodo que representa el directorio donde está montado el sistema de ficheros. El superbloque VFS del sistema de ficheros raíz no tiene puntero cubierto,
Blocksize
EL tamaño de bloque en bytes del sistema de ficheros, por ejemplo 1024 bytes,
Superblock operations
Un puntero a un conjunto de rutinas de superbloque para ese sistema de ficheros. Entre otras cosas, estas rutinas las usa el VFS para leer y escribir inodos y superbloques.
File System type
Un puntero a la estructura de datos tipo_sistema_ficheros del sistema de ficheros montado,
File System specific
Un puntero a la información que necesaria este sistema de ficheros.

10.2.2  The VFS Inode

Como el sistema de ficheros EXT2, cada fichero, directorio y demás en el VFS se representa por uno y solo un inodos VFS. La infomación en cada inodo VFS se construye a partir de información del sistema de ficheros por las rutinas específicas del sistema de ficheros. Los inodos VFS existen sólo en la memoria del núcleo y se mantienen en el caché de inodos VFS tanto tiempo como sean útiles para el sistema. Entre otra información, los inodos VFS contienen los siguientes campos:

device
Este es el identificador de dispositivo del dispositivo que contiene el fichero o lo que este inodo VFS represente,
inode number
Este es el número del inodo y es único en este sistema de ficheros. La combinación de device y inode number es única dentro del Sistema de Ficheros Virtual,
mode
Como en EXT2 este campo describe que representa este inodo VFS y sus permisos de acceso,
user ids
Los identificadores de propietario,
times
Los tiempos de creación, modificación y escritura,
block size
El tamaño de bloque en bytes para este fichero, por ejemplo 1024 bytes,
inode operations
Un puntero a un bloque de direcciones de rutina. Estas rutinas son espefíficas del sistema de ficheros y realizan operaciones para este inodo, por ejemplo, truncar el fichero que representa este inodo.
count
El número de componentes del sistema que están usando actualmente este inodo VFS. Un contador de cero indica que el inodo está libre para ser descartado o reusado,
lock
Este campo se usa para bloquear el inodo VFS, por ejemplo, cuando se lee del sistema de ficheros,
dirty
Indica si se ha escrito en este inodo, si es as´i el sistema de ficheros necesitará modificarlo,
file system specific information

10.2.3  Registering the File Systems


Figure 10.5: Registered File Systems

Cuando se compila el núcleo de Linux se pregunta si se quiere cada uno de los sistemas de ficheros soportados. Cuando el núcleo está compilado, el códifo de arranque del sistema de ficheros contiene llamadas a las rutinas de inicialización de todos los sistemas de ficheros compilados. Los sistemas de ficheros de Linux también se pueden compilar como módulos y, en este caso, pueden ser cargados cuando se les necesita o cargarlos a mano usando insmod. Siempre que un módulo de sistema de ficheros se carga se registra él mismo con el núcleo y se borra él mismo cuando se descarga. Cada rutina de inicialización del sistema de ficheros se registra con el Sistema de Ficheros Virtual y se representa por una estructura de datos tipo_sistema_ficherosque contiene el nombre del sistema de ficheros y un puntero a su rutina de lectura de superbloque VFS. La figura  10.5 muestra que las estructuras de datos tipo_sistems_ficheros se ponen en una lista apuntada por el puntero sistemas_ficheros. Cada estructura de datos tipo_sistema_ficheros contiene la siguiente información:

Superblock read routine
Esta rutina se llama por el VFS cuando se monta una instancia del sistema de ficheros,
File System name
El nombre de este sistema de ficheros, por ejemplo ext2,
Device needed
Necesita soportar este sistema de ficheros un dispositivo? No todos los sistemas de ficheros necesitan un dispositivo. El sistema de fichero /proc, por ejemplo, no requiere un dispositivo de bloque,

Se puede ver que sistemas de ficheros hay rgistrados mirando en /proc/filesystems. Por ejemplo:

      ext2
nodev proc
      iso9660

10.2.4  Mounting a File System

Cuando el superusuario intenta montar un sistema de ficheros, el núcleo de Linux debe primero validar los argumentos pasados en la llamada al sistema. Aunque mount hace una comprobación básica, no conoce que sistemas de ficheros soporta el kernel o si existe el punto de montaje propuesto. Considerar el siguiente comando mount:
$ mount -t iso9660 -o ro /dev/cdrom /mnt/cdrom
Este comando mount pasa al núcleo tres trozos de información; el nombre del sistema de ficheros, el dispositivo de bloque físico que contiene al sistema de fichros y, por último, donde, en la topología del sistema de ficheros existente, se montará el nuevo sistema de ficheros.

La primera cosa que debe hacer el Sistema de Ficheros Virtual es encontrar el sistema de ficheros. Para hacer ´esto busca a través de la lista de sistemas de ficheros conocidos y mira cada estructura de datos dstipo_sistema_ficheros en la lista apuntada por sistema_ficheros. Si encuentra una coincidencia del nombre ahora sabe que ese tipo de sistema de ficheros es soportado por el núcleo y tiene la dirección de la rutina específica del sistema de ficheros para leer el superbloque de ese sistema de ficheros. Si no puede encontrar ninguna coindidencia no todo está perdido si el núcleo puede cargar módulos por demanda (ver Capítulo  modules-chapter). En este caso el núcleo piede al demonio del núcleo que cargue el módulo del sistema de ficheros apropiado antes de continuar como anteriormente.

Si el dispositivo físico pasado por mount no está ya montado, debe encontrar el inodo VFS del directorio que será el punto de montaje del nuevo sistema de ficheros. Este inodo VFS debe estar en el caché de inodos o se debe leer del dispositivo de bloque que soporta el sistema de ficheros del punto de montaje. Una vez que el inodo se ha encontrado se comprueba para ver que sea un directorio y que no contenga ya otro sistema de ficheros montado. El mismo directorio no se puede usar como punto de montaje para más de un sistema de ficheros.

En este punto el códifo de montaje VFS reserva un superbloque VFS y le pasa la información de montahe a la rutina de lectura de superblque para este sistema de ficheros. Todos los superbloques VFS del sistema se mantienen en el vector super_bloques de las estructuras de datos super_bloque y se debe reservar una para este montaje. La rutina de lectura de superbloque debe rellenar los campos básicos del superbloque VFS con información que lee del dispositivo físico. Para el sistema de ficheros EXT2 este mapeo o traducción de información es bastante facil, simplemente lee el superbloque EXT2 y rellena el superbloque VFS de ahí. Para otros sistemas de ficheros, como el MS DOS, no es una tarea tán facil. Cualquiera que sea el sistema de ficheros, rellenar el superbloque VFS significa que el sistema de ficheros debe leer todo lo que lo describe del dispositivo de bloque que lo soporta. Si el dispositivo de bloque no se puede leer o si no contiene este tipo de sistema de ficheros entonces el comando mount fallará.


Figure 10.6: A Mounted File System

Cada sistema de ficheros montado es descrito por una estructura de datos vfsmount; ver figura  10.6. Estos son puestos en una cola de una lista apuntada por vfsmntlist. Otro puntero, vfsmnttailapunta a la última entrada de la lista y el puntero mru_vfsmnt apunta al sistemas de ficheros más recientemente usado. Cada estructura vfsmountcontiene el número de dispositivo del dispositivo de bloque que contiene al sistema de ficheros, el directorio donde el sistema de ficheros está montado y un puntero al superbloque VFS reservado cuando se montó. En cambio el superbloque VFS apunta a la estructura de datos tipo_sistema_ficheros para este tipo de sisetma de ficheros y al inodo ra´iz del sistema de ficheros. Este inodo se mantiene residente en el caché de inodos VFS todo el tiempo que el sistema de ficheros esté cargado.

10.2.5  Finding a File in the Virtual File System

Para encontrar el inodo VFS de un fichero en el Sistema de Ficheros Virtual, VFS debe resolver el nombre del directorio, mirando el inodo VFS que representa cada uno de los directorios intermedios del nombre. Mirar cada directorio envuelve una llamada al sistema de ficheros específico cuya dirección se mantiene en el inodo VFS que representa al directorio padre. Esto funciona porque siempre tenemos el inodo VFS del raíz de cada sistema de ficheros disponible y apuntado por el superbloque VFS de ese sistema. Cada vez que el sistema de ficheros real mira un inodo comprueba el caché de directorios. Si no está la entrada en el caché de directorios, el sistema de ficheros real toma el inodo VFS tanto del sistema de ficheros como del caché de inodos.

10.2.6  Creating a File in the Virtual File System

10.2.7  Unmounting a File System

The workshop manual for my MG usually describes assembly as the reverse of disassembly and the reverse is more or less true for unmounting a file system. A file system cannot be unmounted if something in the system is using one of its files. So, for example, you cannot umount /mnt/cdrom if a process is using that directory or any of its children. If anything is using the file system to be unmounted there may be VFS inodes from it in the VFS inode cache, and the code checks for this by looking through the list of inodes looking for inodes owned by the device that this file system occupies. If the VFS superblock for the mounted file system is dirty, that is it has been modified, then it must be written back to the file system on disk. Once it has been written to disk, the memory occupied by the VFS superblock is returned to the kernel's free pool of memory. Finally the vfsmount data structure for this mount is unlinked from vfsmntlist and freed.

10.2.8  The VFS Inode Cache

As the mounted file systems are navigated, their VFS inodes are being continually read and, in some cases, written. The Virtual File System maintains an inode cache to speed up accesses to all of the mounted file systems. Every time a VFS inode is read from the inode cache the system saves an access to a physical device. The VFS inode cache is implmented as a hash table whose entries are pointers to lists of VFS inodes that have the same hash value. The hash value of an inode is calculated from its inode number and from the device identifier for the underlying physical device containing the file system. Whenever the Virtual File System needs to access an inode, it first looks in the VFS inode cache. To find an inode in the cache, the system first calculates its hash value and then uses it as an index into the inode hash table. This gives it a pointer to a list of inodes with the same hash value. It then reads each inode in turn until it finds one with both the same inode number and the same device identifier as the one that it is searching for.

If it can find the inode in the cache, its count is incremented to show that it has another user and the file system access continues. Otherwise a free VFS inode must be found so that the file system can read the inode from memory. VFS has a number of choices about how to get a free inode. If the system may allocate more VFS inodes then this is what it does; it allocates kernel pages and breaks them up into new, free, inodes and puts them into the inode list. All of the system's VFS inodes are in a list pointed at by first_inode as well as in the inode hash table. If the system already has all of the inodes that it is allowed to have, it must find an inode that is a good candidate to be reused. Good candidates are inodes with a usage count of zero; this indicates that the system is not currently using them. Really important VFS inodes, for example the root inodes of file systems always have a usage count greater than zero and so are never candidates for reuse. Once a candidate for reuse has been located it is cleaned up. The VFS inode might be dirty and in this case it needs to be written back to the file system or it might be locked and in this case the system must wait for it to be unlocked before continuing. The candidate VFS inode must be cleaned up before it can be reused.

However the new VFS inode is found, a file system specific routine must be called to fill it out from information read from the underlying real file system. Whilst it is being filled out, the new VFS inode has a usage count of one and is locked so that nothing else accesses it until it contains valid information.

To get the VFS inode that is actually needed, the file system may need to access several other inodes. This happens when you read a directory; only the inode for the final directory is needed but the inodes for the intermediate directories must also be read. As the VFS inode cache is used and filled up, the less used inodes will be discarded and the more used inodes will remain in the cache.

10.2.9  The Directory Cache

To speed up accesses to commonly used directories, the VFS maintains a cache of directory entries. As directories are looked up by the real file systems their details are added into the directory cache. The next time the same directory is looked up, for example to list it or open a file within it, then it will be found in the directory cache. Only short directory entries (up to 15 characters long) are cached but this is reasonable as the shorter directory names are the most commonly used ones. For example, /usr/X11R6/bin is very commonly accessed when the X server is running.

The directory cache consists of a hash table, each entry of which points at a list of directory cache entries that have the same hash value. The hash function uses the device number of the device holding the file system and the directory's name to calculate the offset, or index, into the hash table. It allows cached directory entries to be quickly found. It is no use having a cache when lookups within the cache take too long to find entries, or even not to find them.

In an effort to keep the caches valid and up to date the VFS keeps lists of Least Recently Used (LRU) directory cache entries. When a directory entry is first put into the cache, which is when it is first looked up, it is added onto the end of the first level LRU list. In a full cache this will displace an existing entry from the front of the LRU list. As the directory entry is accessed again it is promoted to the back of the second LRU cache list. Again, this may displace a cached level two directory entry at the front of the level two LRU cache list. This displacing of entries at the front of the level one and level two LRU lists is fine. The only reason that entries are at the front of the lists is that they have not been recently accessed. If they had, they would be nearer the back of the lists. The entries in the second level LRU cache list are safer than entries in the level one LRU cache list. This is the intention as these entries have not only been looked up but also they have been repeatedly referenced.

NOTA DE REVISIÓN: Do we need a diagram for this?

10.3  The Buffer Cache


Figure 10.7: The Buffer Cache

As the mounted file systems are used they generate a lot of requests to the block devices to read and write data blocks. All block data read and write requests are given to the device drivers in the form of buffer_head data structures via standard kernel routine calls. These give all of the information that the block device drivers need; the device identifier uniquely identifies the device and the block number tells the driver which block to read. All block devices are viewed as linear collections of blocks of the same size. To speed up access to the physical block devices, Linux maintains a cache of block buffers. All of the block buffers in the system are kept somewhere in this buffer cache, even the new, unused buffers. This cache is shared between all of the physical block devices; at any one time there are many block buffers in the cache, belonging to any one of the system's block devices and often in many different states. If valid data is available from the buffer cache this saves the system an access to a physical device. Any block buffer that has been used to read data from a block device or to write data to it goes into the buffer cache. Over time it may be removed from the cache to make way for a more deserving buffer or it may remain in the cache as it is frequently accessed.

Block buffers within the cache are uniquely identfied by the owning device identifier and the block number of the buffer. The buffer cache is composed of two functional parts. The first part is the lists of free block buffers. There is one list per supported buffer size and the system's free block buffers are queued onto these lists when they are first created or when they have been discarded. The currently supported buffer sizes are 512, 1024, 2048, 4096 and 8192 bytes. The second functional part is the cache itself. This is a hash table which is a vector of pointers to chains of buffers that have the same hash index. The hash index is generated from the owning device identifier and the block number of the data block. Figure  10.7 shows the hash table together with a few entries. Block buffers are either in one of the free lists or they are in the buffer cache. When they are in the buffer cache they are also queued onto Least Recently Used (LRU) lists. There is an LRU list for each buffer type and these are used by the system to perform work on buffers of a type, for example, writing buffers with new data in them out to disk. The buffer's type reflects its state and Linux currently supports the following types:

clean
Unused, new buffers,
locked
Buffers that are locked, waiting to be written,
dirty
Dirty buffers. These contain new, valid data, and will be written but so far have not been scheduled to write,
shared
Shared buffers,
unshared
Buffers that were once shared but which are now not shared,

Whenever a file system needs to read a buffer from its underlying physical device, it trys to get a block from the buffer cache. If it cannot get a buffer from the buffer cache, then it will get a clean one from the appropriate sized free list and this new buffer will go into the buffer cache. If the buffer that it needed is in the buffer cache, then it may or may not be up to date. If it is not up to date or if it is a new block buffer, the file system must request that the device driver read the appropriate block of data from the disk.

Like all caches, the buffer cache must be maintained so that it runs efficiently and fairly allocates cache entries between the block devices using the buffer cache. Linux uses the bdflush kernel daemon to perform a lot of housekeeping duties on the cache but some happen automatically as a result of the cache being used.

10.3.1  The bdflush Kernel Daemon

The bdflush kernel daemon is a simple kernel daemon that provides a dynamic response to the system having too many dirty buffers; buffers that contain data that must be written out to disk at some time. It is started as a kernel thread at system startup time and, rather confusingly, it calls itself «kflushd» and that is the name that you will see if you use the ps command to show the processes in the system. Mostly this daemon sleeps waiting for the number of dirty buffers in the system to grow too large. As buffers are allocated and discarded the number of dirty buffers in the system is checked. If there are too many as a percentage of the total number of buffers in the system then bdflush is woken up. The default threshold is 60will be woken up anyway. This value can be seen and changed using the update command:

# update -d

bdflush version 1.4
0:    60 Max fraction of LRU list to examine for dirty blocks
1:   500 Max number of dirty blocks to write each time bdflush activated
2:    64 Num of clean buffers to be loaded onto free list by refill_freelist
3:   256 Dirty block threshold for activating bdflush in refill_freelist
4:    15 Percentage of cache to scan for free clusters
5:  3000 Time for data buffers to age before flushing
6:   500 Time for non-data (dir, bitmap, etc) buffers to age before flushing
7:  1884 Time buffer cache load average constant
8:     2 LAV ratio (used to determine threshold for buffer fratricide).

All of the dirty buffers are linked into the BUF_DIRTY LRU list whenever they are made dirty by having data written to them and bdflush tries to write a reasonable number of them out to their owning disks. Again this number can be seen and controlled by the update command and the default is 500 (see above).

10.3.2  The update Process

The update command is more than just a command; it is also a daemon. When run as superuser (during system initialisation) it will periodically flush all of the older dirty buffers out to disk. It does this by calling a system service routine that does more or less the same thing as bdflush. Whenever a dirty buffer is finished with, it is tagged with the system time that it should be written out to its owning disk. Every time that update runs it looks at all of the dirty buffers in the system looking for ones with an expired flush time. Every expired buffer is written out to disk.

10.4  The /proc File System

The /proc file system really shows the power of the Linux Virtual File System. It does not really exist (yet another of Linux's conjuring tricks), neither the /proc directory nor its subdirectories and its files actually exist. So how can you cat /proc/devices? The /proc file system, like a real file system, registers itself with the Virtual File System. However, when the VFS makes calls to it requesting inodes as its files and directories are opened, the /proc file system creates those files and directories from information within the kernel. For example, the kernel's /proc/devices file is generated from the kernel's data structures describing its devices.

The /proc file system presents a user readable window into the kernel's inner workings. Several Linux subsystems, such as Linux kernel modules described in chapter  modules-chapter, create entries in the the /proc file system.

10.5  Device Special Files

Linux, like all versions of Unix  presents its hardware devices as special files. So, for example, /dev/null is the null device. A device file does not use any data space in the file system, it is only an access point to the device driver. The EXT2 file system and the Linux VFS both implement device files as special types of inode. There are two types of device file; character and block special files. Within the kernel itself, the device drivers implement file semantices: you can open them, close them and so on. Character devices allow I/O operations in character mode and block devices require that all I/O is via the buffer cache. When an I/O request is made to a device file, it is forwarded to the appropriate device driver within the system. Often this is not a real device driver but a pseudo-device driver for some subsystem such as the SCSI device driver layer. Device files are referenced by a major number, which identifies the device type, and a minor type, which identifies the unit, or instance of that major type. For example, the IDE disks on the first IDE controller in the system have a major number of 3 and the first partition of an IDE disk would have a minor number of 1. So, ls -l of /dev/hda1 gives:
$ brw-rw----   1 root    disk       3,    1  Nov 24  15:09 /dev/hda1
Within the kernel, every device is uniquely described by a kdev_t data type, this is two bytes long, the first byte containing the minor device number and the second byte holding the major device number. The IDE device above is held within the kernel as 0x0301. An EXT2 inode that represents a block or character device keeps the device's major and minor numbers in its first direct block pointer. When it is read by the VFS, the VFS inode data structure representing it has its i_rdev field set to the correct device identifier.

Capítulo 11
Networks

Networking and Linux are terms that are almost synonymous. In a very real sense Linux is a product of the Internet or World Wide Web (WWW). Its developers and users use the web to exchange information ideas, code, and Linux itself is often used to support the networking needs of organizations. This chapter describes how Linux supports the network protocols known collectively as TCP/IP.

The TCP/IP protocols were designed to support communications between computers connected to the ARPANET, an American research network funded by the US government. The ARPANET pioneered networking concepts such as packet switching and protocol layering where one protocol uses the services of another. ARPANET was retired in 1988 but its successors (NSF15 NET and the Internet) have grown even larger. What is now known as the World Wide Web grew from the ARPANET and is itself supported by the TCP/IP protocols. Unix was extensively used on the ARPANET and the first released networking version of Unix was 4.3 BSD. Linux's networking implementation is modeled on 4.3 BSD in that it supports BSD sockets (with some extensions) and the full range of TCP/IP networking. This programming interface was chosen because of its popularity and to help applications be portable between Linux and other Unix platforms.

11.1  An Overview of TCP/IP Networking

This section gives an overview of the main principles of TCP/IP networking. It is not meant to be an exhaustive description, for that I suggest that you read . In an IP network every machine is assigned an IP address, this is a 32 bit number that uniquely identifies the machine. The WWW is a very large, and growing, IP network and every machine that is connected to it has to have a unique IP address assigned to it. IP addresses are represented by four numbers separated by dots, for example, 16.42.0.9. This IP address is actually in two parts, the network address and the host address. The sizes of these parts may vary (there are several classes of IP addresses) but using 16.42.0.9 as an example, the network address would be 16.42 and the host address 0.9. The host address is further subdivided into a subnetwork and a host address. Again, using 16.42.0.9 as an example, the subnetwork address would be 16.42.0 and the host address 16.42.0.9. This subdivision of the IP address allows organizations to subdivide their networks. For example, 16.42 could be the network address of the ACME Computer Company; 16.42.0 would be subnet 0 and 16.42.1 would be subnet 1. These subnets might be in separate buildings, perhaps connected by leased telephone lines or even microwave links. IP addresses are assigned by the network administrator and having IP subnetworks is a good way of distributing the administration of the network. IP subnet administrators are free to allocate IP addresses within their IP subnetworks.

Generally though, IP addresses are somewhat hard to remember. Names are much easier. linux.acme.com is much easier to remember than 16.42.0.9 but there must be some mechanism to convert the network names into an IP address. These names can be statically specified in the /etc/hosts file or Linux can ask a Distributed Name Server (DNS server) to resolve the name for it. In this case the local host must know the IP address of one or more DNS servers and these are specified in /etc/resolv.conf.

Whenever you connect to another machine, say when reading a web page, its IP address is used to exchange data with that machine. This data is contained in IP packets each of which have an IP header containing the IP addresses of the source and destination machine's IP addresses, a checksum and other useful information. The checksum is derived from the data in the IP packet and allows the receiver of IP packets to tell if the IP packet was corrupted during transmission, perhaps by a noisy telephone line. The data transmitted by an application may have been broken down into smaller packets which are easier to handle. The size of the IP data packets varies depending on the connection media; ethernet packets are generally bigger than PPP packets. The destination host must reassemble the data packets before giving the data to the receiving application. You can see this fragmentation and reassembly of data graphically if you access a web page containing a lot of graphical images via a moderately slow serial link.

Hosts connected to the same IP subnet can send IP packets directly to each other, all other IP packets will be sent to a special host, a gateway. Gateways (or routers) are connected to more than one IP subnet and they will resend IP packets received on one subnet, but destined for another onwards. For example, if subnets 16.42.1.0 and 16.42.0.0 are connected together by a gateway then any packets sent from subnet 0 to subnet 1 would have to be directed to the gateway so that it could route them. The local host builds up routing tables which allow it to route IP packets to the correct machine. For every IP destination there is an entry in the routing tables which tells Linux which host to send IP packets to in order that they reach their destination. These routing tables are dynamic and change over time as applications use the network and as the network topology changes.


Figure 11.1: TCP/IP Protocol Layers

The IP protocol is a transport layer that is used by other protocols to carry their data. The Transmission Control Protocol (TCP) is a reliable end to end protocol that uses IP to transmit and receive its own packets. Just as IP packets have their own header, TCP has its own header. TCP is a connection based protocol where two networking applications are connected by a single, virtual connection even though there may be many subnetworks, gateways and routers between them. TCP reliably transmits and receives data between the two applications and guarantees that there will be no lost or duplicated data. When TCP transmits its packet using IP, the data contained within the IP packet is the TCP packet itself. The IP layer on each communicating host is responsible for transmitting and receiving IP packets. User Datagram Protocol (UDP) also uses the IP layer to transport its packets, unlike TCP, UDP is not a reliable protocol but offers a datagram service. This use of IP by other protocols means that when IP packets are received the receiving IP layer must know which upper protocol layer to give the data contained in this IP packet to. To facilitate this every IP packet header has a byte containing a protocol identifier. When TCP asks the IP layer to transmit an IP packet , that IP packet's header states that it contains a TCP packet. The receiving IP layer uses that protocol identifier to decide which layer to pass the received data up to, in this case the TCP layer. When applications communicate via TCP/IP they must specify not only the target's IP address but also the port address of the application. A port address uniquely identifies an application and standard network applications use standard port addresses; for example, web servers use port 80. These registered port addresses can be seen in /etc/services.

This layering of protocols does not stop with TCP, UDP and IP. The IP protocol layer itself uses many different physical media to transport IP packets to other IP hosts. These media may themselves add their own protocol headers. One such example is the ethernet layer, but PPP and SLIP are others. An ethernet network allows many hosts to be simultaneously connected to a single physical cable. Every transmitted ethernet frame can be seen by all connected hosts and so every ethernet device has a unique address. Any ethernet frame transmitted to that address will be received by the addressed host but ignored by all the other hosts connected to the network. These unique addresses are built into each ethernet device when they are manufactured and it is usually kept in an SROM16 on the ethernet card. Ethernet addresses are 6 bytes long, an example would be 08-00-2b-00-49-A4. Some ethernet addresses are reserved for multicast purposes and ethernet frames sent with these destination addresses will be received by all hosts on the network. As ethernet frames can carry many different protocols (as data) they, like IP packets, contain a protocol identifier in their headers. This allows the ethernet layer to correctly receive IP packets and to pass them onto the IP layer.

In order to send an IP packet via a multi-connection protocol such as ethernet, the IP layer must find the ethernet address of the IP host. This is because IP addresses are simply an addressing concept, the ethernet devices themselves have their own physical addresses. IP addresses on the other hand can be assigned and reassigned by network administrators at will but the network hardware responds only to ethernet frames with its own physical address or to special multicast addresses which all machines must receive. Linux uses the Address Resolution Protocol (or ARP) to allow machines to translate IP addresses into real hardware addresses such as ethernet addresses. A host wishing to know the hardware address associated with an IP address sends an ARP request packet containing the IP address that it wishes translating to all nodes on the network by sending it to a multicast address. The target host that owns the IP address, responds with an ARP reply that contains its physical hardware address. ARP is not just restricted to ethernet devices, it can resolve IP addresses for other physical media, for example FDDI. Those network devices that cannot ARP are marked so that Linux does not attempt to ARP. There is also the reverse function, Reverse ARP or RARP, which translates phsyical network addresses into IP addresses. This is used by gateways, which respond to ARP requests on behalf of IP addresses that are in the remote network.

11.2  The Linux TCP/IP Networking Layers


Figure 11.2: Linux Networking Layers

Just like the network protocols themselves, Figure  11.2 shows that Linux implements the internet protocol address family as a series of connected layers of software. BSD sockets are supported by a generic socket management software concerned only with BSD sockets. Supporting this is the INET socket layer, this manages the communication end points for the IP based protocols TCP and UDP. UDP (User Datagram Protocol) is a connectionless protocol whereas TCP (Transmission Control Protocol) is a reliable end to end protocol. When UDP packets are transmitted, Linux neither knows nor cares if they arrive safely at their destination. TCP packets are numbered and both ends of the TCP connection make sure that transmitted data is received correctly. The IP layer contains code implementing the Internet Protocol. This code prepends IP headers to transmitted data and understands how to route incoming IP packets to either the TCP or UDP layers. Underneath the IP layer, supporting all of Linux's networking are the network devices, for example PPP and ethernet. Network devices do not always represent physical devices; some like the loopback device are purely software devices. Unlike standard Linux devices that are created via the mknodcommand, network devices appear only if the underlying software has found and initialized them. You will only see /dev/eth0 when you have built a kernel with the appropriate ethernet device driver in it. The ARP protocol sits between the IP layer and the protocols that support ARPing for addresses.

11.3  The BSD Socket Interface

This is a general interface which not only supports various forms of networking but is also an inter-process communications mechanism. A socket describes one end of a communications link, two communicating processes would each have a socket describing their end of the communication link between them. Sockets could be thought of as a special case of pipes but, unlike pipes, sockets have no limit on the amount of data that they can contain. Linux supports several classes of socket and these are known as address families. This is because each class has its own method of addressing its communications. Linux supports the following socket address families or domains:

UNIX Unix domain sockets,
INET The Internet address family supports communications via
TCP/IP protocols
AX25 Amateur radio X25
IPX Novell IPX
APPLETALK Appletalk DDP
X25 X25

There are several socket types and these represent the type of service that supports the connection. Not all address families support all types of service. Linux BSD sockets support a number of socket types:

Stream
These sockets provide reliable two way sequenced data streams with a guarantee that data cannot be lost, corrupted or duplicated in transit. Stream sockets are supported by the TCP protocol of the Internet (INET) address family.
Datagram
These sockets also provide two way data transfer but, unlike stream sockets, there is no guarantee that the messages will arrive. Even if they do arrive there is no guarantee that they will arrive in order or even not be duplicated or corrupted. This type of socket is supported by the UDP protocol of the Internet address family.
Raw
This allows processes direct (hence ``raw'') access to the underlying protocols. It is, for example, possible to open a raw socket to an ethernet device and see raw IP data traffic.
Reliable Delivered Messages
These are very like datagram sockets but the data is guaranteed to arrive.
Sequenced Packets
These are like stream sockets except that the data packet sizes are fixed.
Packet
This is not a standard BSD socket type, it is a Linux specific extension that allows processes to access packets directly at the device level.

Processes that communicate using sockets use a client server model. A server provides a service and clients make use of that service. One example would be a Web Server, which provides web pages and a web client, or browser, which reads those pages. A server using sockets, first creates a socket and then binds a name to it. The format of this name is dependent on the socket's address family and it is, in effect, the local address of the server. The socket's name or address is specified using the sockaddr data structure. An INET socket would have an IP port address bound to it. The registered port numbers can be seen in /etc/services; for example, the port number for a web server is 80. Having bound an address to the socket, the server then listens for incoming connection requests specifying the bound address. The originator of the request, the client, creates a socket and makes a connection request on it, specifying the target address of the server. For an INET socket the address of the server is its IP address and its port number. These incoming requests must find their way up through the various protocol layers and then wait on the server's listening socket. Once the server has received the incoming request it either accepts or rejects it. If the incoming request is to be accepted, the server must create a new socket to accept it on. Once a socket has been used for listening for incoming connection requests it cannot be used to support a connection. With the connection established both ends are free to send and receive data. Finally, when the connection is no longer needed it can be shutdown. Care is taken to ensure that data packets in transit are correctly dealt with.

The exact meaning of operations on a BSD socket depends on its underlying address family. Setting up TCP/IP connections is very different from setting up an amateur radio X.25 connection. Like the virtual filesystem, Linux abstracts the socket interface with the BSD socket layer being concerned with the BSD socket interface to the application programs which is in turn supported by independent address family specific software. At kernel initialization time, the address families built into the kernel register themselves with the BSD socket interface. Later on, as applications create and use BSD sockets, an association is made between the BSD socket and its supporting address family. This association is made via cross-linking data structures and tables of address family specific support routines. For example there is an address family specific socket creation routine which the BSD socket interface uses when an application creates a new socket.

When the kernel is configured, a number of address families and protocols are built into the protocols vector. Each is represented by its name, for example ``INET'' and the address of its initialization routine. When the socket interface is initialized at boot time each protocol's initialization routine is called. For the socket address families this results in them registering a set of protocol operations. This is a set of routines, each of which performs a a particular operation specific to that address family. The registered protocol operations are kept in the pops vector, a vector of pointers to proto_ops data structures. The proto_ops data structure consists of the address family type and a set of pointers to socket operation routines specific to a particular address family. The pops vector is indexed by the address family identifier, for example the Internet address family identifier (AF_INET is 2).


Figure 11.3: Linux BSD Socket Data Structures

11.4  The INET Socket Layer

The INET socket layer supports the internet address family which contains the TCP/IP protocols. As discussed above, these protocols are layered, one protocol using the services of another. Linux's TCP/IP code and data structures reflect this layering. Its interface with the BSD socket layer is through the set of Internet address family socket operations which it registers with the BSD socket layer during network initialization. These are kept in the pops vector along with the other registered address families. The BSD socket layer calls the INET layer socket support routines from the registered INET proto_opsdata structure to perform work for it. For example a BSD socket create request that gives the address family as INET will use the underlying INET socket create function. The BSD socket layer passes the socket data structure representing the BSD socket to the INET layer in each of these operations. Rather than clutter the BSD socket wiht TCP/IP specific information, the INET socket layer uses its own data structure, the sock which it links to the BSD socketdata structure. This linkage can be seen in Figure  11.3. It links the sock data structure to the BSD socket data structure using the data pointer in the BSD socket. This means that subsequent INET socket calls can easily retrieve the sock data structure. The sock data structure's protocol operations pointer is also set up at creation time and it depends on the protocol requested. If TCP is requested, then the sock data structure's protocol operations pointer will point to the set of TCP protocol operations needed for a TCP connection.

11.4.1  Creating a BSD Socket

The system call to create a new socket passes identifiers for its address family, socket type and protocol. Firstly the requested address family is used to search the pops vector for a matching address family. It may be that a particular address family is implemented as a kernel module and, in this case, the kerneld daemon must load the module before we can continue. A new socket data structure is allocated to represent the BSD socket. Actually the socket data structure is physically part of the VFS inode data structure and allocating a socket really means allocating a VFS inode. This may seem strange unless you consider that sockets can be operated on in just the same way that ordinairy files can. As all files are represented by a VFS inode data structure, then in order to support file operations, BSD sockets must also be represented by a VFS inode data structure.

The newly created BSD socket data structure contains a pointer to the address family specific socket routines and this is set to the proto_ops data structure retrieved from the pops vector. Its type is set to the sccket type requested; one of SOCK_STREAM, SOCK_DGRAM and so on. The address family specific creation routine is called using the address kept in the proto_ops data structure.

A free file descriptor is allocated from the current processes fd vector and the file data structure that it points at is initialized. This includes setting the file operations pointer to point to the set of BSD socket file operations supported by the BSD socket interface. Any future operations will be directed to the socket interface and it will in turn pass them to the supporting address family by calling its address family operation routines.

11.4.2  Binding an Address to an INET BSD Socket

In order to be able to listen for incoming internet connection requests, each server must create an INET BSD socket and bind its address to it. The bind operation is mostly handled within the INET socket layer with some support from the underlying TCP and UDP protocol layers. The socket having an address bound to cannot be being used for any other communication. This means that the socket's state must be TCP_CLOSE. The sockaddr pass to the bind operation contains the IP address to be bound to and, optionally, a port number. Normally the IP address bound to would be one that has been assigned to a network device that supports the INET address family and whose interface is up and able to be used. You can see which network interfaces are currently active in the system by using the ifconfig command. The IP address may also be the IP broadcast address of either all 1's or all 0's. These are special addresses that mean ``send to everybody''17. The IP address could also be specified as any IP address if the machine is acting as a transparent proxy or firewall, but only processes with superuser privileges can bind to any IP address. The IP address bound to is saved in the sock data structure in the recv_addr and saddr fields. These are used in hash lookups and as the sending IP address respectively. The port number is optional and if it is not specified the supporting network is asked for a free one. By convention, port numbers less than 1024 cannot be used by processes without superuser privileges. If the underlying network does allocate a port number it always allocates ones greater than 1024.

As packets are being received by the underlying network devices they must be routed to the correct INET and BSD sockets so that they can be processed. For this reason UDP and TCP maintain hash tables which are used to lookup the addresses within incoming IP messages and direct them to the correct socket/sock pair. TCP is a connection oriented protocol and so there is more information involved in processing TCP packets than there is in processing UDP packets.

UDP maintains a hash table of allocated UDP ports, the udp_hashtable. This consists of pointers to sock data structures indexed by a hash function based on the port number. As the UDP hash table is much smaller than the number of permissible port numbers (udp_hash is only 128 or UDP_HTABLE_SIZE entries long) some entries in the table point to a chain of sock data structures linked together using each sock's next pointer.

TCP is much more complex as it maintains several hash tables. However, TCP does not actually add the binding sock data stucture into its hash tables during the bind operation, it merely checks that the port number requested is not currently being used. The sock data structure is added to TCP's hash tables during the listen operation.

NOTA DE REVISIÓN: What about the route entered?

11.4.3  Making a Connection on an INET BSD Socket

Once a socket has been created and, provided it has not been used to listen for inbound connection requests, it can be used to make outbound connection requests. For connectionless protocols like UDP this socket operation does not do a whole lot but for connection orientated protocols like TCP it involves building a virtual circuit between two applications.

An outbound connection can only be made on an INET BSD socket that is in the right state; that is to say one that does not already have a connection established and one that is not being used for listening for inbound connections. This means that the BSD socket data structure must be in state SS_UNCONNECTED. The UDP protocol does not establish virtual connections between applications, any messages sent are datagrams, one off messages that may or may not reach their destinations. It does, however, support the connect BSD socket operation. A connection operation on a UDP INET BSD socket simply sets up the addresses of the remote application; its IP address and its IP port number. Additionally it sets up a cache of the routing table entry so that UDP packets sent on this BSD socket do not need to check the routing database again (unless this route becomes invalid). The cached routing information is pointed at from the ip_route_cache pointer in the INET sock data structure. If no addressing information is given, this cached routing and IP addressing information will be automatically be used for messages sent using this BSD socket. UDP moves the sock's state to TCP_ESTABLISHED.

For a connect operation on a TCP BSD socket, TCP must build a TCP message containing the connection information and send it to IP destination given. The TCP message contains information about the connection, a unique starting message sequence number, the maximum sized message that can be managed by the initiating host, the transmit and receive window size and so on. Within TCP all messages are numbered and the initial sequence number is used as the first message number. Linux chooses a reasonably random value to avoid malicious protocol attacks. Every message transmitted by one end of the TCP connection and successfully received by the other is acknowledged to say that it arrived successfully and uncorrupted. Unacknowledges messages will be retransmitted. The transmit and receive window size is the number of outstanding messages that there can be without an acknowledgement being sent. The maximum message size is based on the network device that is being used at the initiating end of the request. If the receiving end's network device supports smaller maximum message sizes then the connection will use the minimum of the two. The application making the outbound TCP connection request must now wait for a response from the target application to accept or reject the connection request. As the TCP sock is now expecting incoming messages, it is added to the tcp_listening_hash so that incoming TCP messages can be directed to this sock data structure. TCP also starts timers so that the outbound connection request can be timed out if the target application does not respond to the request.

11.4.4  Listening on an INET BSD Socket

Once a socket has had an address bound to it, it may listen for incoming connection requests specifying the bound addresses. A network application can listen on a socket without first binding an address to it; in this case the INET socket layer finds an unused port number (for this protocol) and automatically binds it to the socket. The listen socket function moves the socket into state TCP_LISTEN and does any network specific work needed to allow incoming connections.

For UDP sockets, changing the socket's state is enough but TCP now adds the socket's sock data structure into two hash tables as it is now active. These are the tcp_bound_hash table and the tcp_listening_hash. Both are indexed via a hash function based on the IP port number.

Whenever an incoming TCP connection request is received for an active listening socket, TCP builds a new sock data structure to represent it. This sock data structure will become the bottom half of the TCP connection when it is eventually accepted. It also clones the incoming sk_buff containing the connection request and queues it onto the receive_queue for the listening sock data structure. The clone sk_buff contains a pointer to the newly created sock data structure.

11.4.5  Accepting Connection Requests

UDP does not support the concept of connections, accepting INET socket connection requests only applies to the TCP protocol as an accept operation on a listening socket causes a new socket data structure to be cloned from the original listening socket. The accept operation is then passed to the supporting protocol layer, in this case INET to accept any incoming connection requests. The INET protocol layer will fail the accept operation if the underlying protocol, say UDP, does not support connections. Otherwise the accept operation is passed through to the real protocol, in this case TCP. The accept operation can be either blocking or non-blocking. In the non-blocking case if there are no incoming connections to accept, the accept operation will fail and the newly created socket data structure will be thrown away. In the blocking case the network application performing the accept operation will be added to a wait queue and then suspended until a TCP connection request is received. Once a connection request has been received the sk_buff containing the request is discarded and the sock data structure is returned to the INET socket layer where it is linked to the new socket data structure created earlier. The file descriptor (fd) number of the new socket is returned to the network application, and the application can then use that file descriptor in socket operations on the newly created INET BSD socket.

11.5  The IP Layer

11.5.1  Socket Buffers

One of the problems of having many layers of network protocols, each one using the services of another, is that each protocol needs to add protocol headers and tails to data as it is transmitted and to remove them as it processes received data. This make passing data buffers between the protocols difficult as each layer needs to find where its particular protocol headers and tails are. One solution is to copy buffers at each layer but that would be inefficient. Instead, Linux uses socket buffers or sk_buffs to pass data between the protocol layers and the network device drivers. sk_buffs contain pointer and length fields that allow each protocol layer to manipulate the application data via standard functions or ``methods''.


Figure 11.4: The Socket Buffer (sk_buff)

Figure  11.4 shows the sk_buff data structure; each sk_buff has a block of data associated with it. The sk_buff has four data pointers, which are used to manipulate and manage the socket buffer's data:

head
points to the start of the data area in memory. This is fixed when the sk_buff and its associated data block is allocated,
data
points at the current start of the protocol data. This pointer varies depending on the protocol layer that currently owns the sk_buff,
tail
points at the current end of the protocol data. Again, this pointer varies depending on the owning protocol layer,
end
points at the end of the data area in memory. This is fixed when the sk_buff is allocated.

There are two length fields len and truesize, which describe the length of the current protocol packet and the total size of the data buffer respectively. The sk_buff handling code provides standard mechanisms for adding and removing protocol headers and tails to the application data. These safely manipulate the data, tail and len fields in the sk_buff:

push
This moves the data pointer towards the start of the data area and increments the len field. This is used when adding data or protocol headers to the start of the data to be transmitted,
pull
This moves the data pointer away from the start, towards the end of the data area and decrements the len field. This is used when removing data or protocol headers from the start of the data that has been received,
put
This moves the tail pointer towards the end of the data area and increments the len field. This is used when adding data or protocol information to the end of the data to be transmitted,
trim
This moves the tail pointer towards the start of the data area and decrements the len field. This is used when removing data or protocol tails from the received packet.

The sk_buff data structure also contains pointers that are used as it is stored in doubly linked circular lists of sk_buff's during processing. There are generic sk_buff routines for adding sk_buffs to the front and back of these lists and for removing them.

11.5.2  Receiving IP Packets

Chapter  9 described how Linux's network drivers built are into the kernel and initialized. This results in a series of device data structures linked together in the dev_baselist. Each device data structure describes its device and provides a set of callback routines that the network protocol layers call when they need the network driver to perform work. These functions are mostly concerned with transmitting data and with the network device's addresses. When a network device receives packets from its network it must convert the received data into sk_buff data structures. These received sk_buff's are added onto the backlog queue by the network drivers as they are received. If the backlog queue grows too large, then the received sk_buff's are discarded. The network bottom half is flagged as ready to run as there is work to do.

When the network bottom half handler is run by the scheduler it processes any network packets waiting to be transmitted before processing the backlog queue of sk_buff's determining which protocol layer to pass the received packets to. As the Linux networking layers were initialized, each protocol registered itself by adding a packet_type data structure onto either the ptype_all list or into the ptype_base hash table. The packet_type data structure contains the protocol type, a pointer to a network device, a pointer to the protocol's receive data processing routine and, finally, a pointer to the next packet_type data structure in the list or hash chain. The ptype_all chain is used to snoop all packets being received from any network device and is not normally used. The ptype_base hash table is hashed by protocol identifier and is used to decide which protocol should receive the incoming network packet. The network bottom half matches the protocol types of incoming sk_buff's against one or more of the packet_type entries in either table. The protocol may match more than one entry, for example when snooping all network traffic, and in this case the sk_buff will be cloned. The sk_buff is passed to the matching protocol's handling routine.

11.5.3  Sending IP Packets

Packets are transmitted by applications exchanging data or else they are generated by the network protocols as they support established connections or connections being established. Whichever way the data is generated, an sk_buff is built to contain the data and various headers are added by the protocol layers as it passes through them.

The sk_buff needs to be passed to a network device to be transmitted. First though the protocol, for example IP, needs to decide which network device to use. This depends on the best route for the packet. For computers connected by modem to a single network, say via the PPP protocol, the routing choice is easy. The packet should either be sent to the local host via the loopback device or to the gateway at the end of the PPP modem connection. For computers connected to an ethernet the choices are harder as there are many computers connected to the network.

For every IP packet transmitted, IP uses the routing tables to resolve the route for the destination IP address. Each IP destination successfully looked up in the routing tables returns a rtabledata structure describing the route to use. This includes the source IP address to use, the address of the network device data structure and, sometimes, a prebuilt hardware header. This hardware header is network device specific and contains the source and destination physical addresses and other media specific information. If the network device is an ethernet device, the hardware header would be as shown in Figure  11.1 and the source and destination addresses would be physical ethernet addresses. The hardware header is cached with the route because it must be appended to each IP packet transmitted on this route and constructing it takes time. The hardware header may contain physical addresses that have to be resolved using the ARP protocol. In this case the outgoing packet is stalled until the address has been resolved. Once it has been resolved and the hardware header built, the hardware header is cached so that future IP packets sent using this interface do not have to ARP.

11.5.4  Data Fragmentation

Every network device has a maximum packet size and it cannot transmit or receive a data packet bigger than this. The IP protocol allows for this and will fragment data into smaller units to fit into the packet size that the network device can handle. The IP protocol header includes a fragment field which contains a flag and the fragment offset.

When an IP packet is ready to be transmited, IP finds the network device to send the IP packet out on. This device is found from the IP routing tables. Each device has a field describing its maximum transfer unit (in bytes), this is the mtu field. If the device's mtu is smaller than the packet size of the IP packet that is waiting to be transmitted, then the IP packet must be broken down into smaller (mtu sized) fragments. Each fragment is represented by an sk_buff; its IP header marked to show that it is a fragment and what offset into the data this IP packet contains. The last packet is marked as being the last IP fragment. If, during the fragmentation, IP cannot allocate an sk_buff, the transmit will fail.

Receiving IP fragments is a little more difficult than sending them because the IP fragments can be received in any order and they must all be received before they can be reassembled. Each time an IP packet is received it is checked to see if it is an IP fragment. The first time that the fragment of a message is received, IP creates a new ipq data structure, and this is linked into the ipqueue list of IP fragments awaiting recombination. As more IP fragments are received, the correct ipq data structure is found and a new ipfragdata structure is created to describe this fragment. Each ipq data structure uniquely describes a fragmented IP receive frame with its source and destination IP addresses, the upper layer protocol identifier and the identifier for this IP frame. When all of the fragments have been received, they are combined into a single sk_buff and passed up to the next protocol level to be processed. Each ipq contains a timer that is restarted each time a valid fragment is received. If this timer expires, the ipq data structure and its ipfrag's are dismantled and the message is presumed to have been lost in transit. It is then up to the higher level protocols to retransmit the message.

11.6  The Address Resolution Protocol (ARP)

The Address Resolution Protocol's role is to provide translations of IP addresses into physical hardware addresses such as ethernet addresses. IP needs this translation just before it passes the data (in the form of an sk_buff) to the device driver for transmission. It performs various checks to see if this device needs a hardware header and, if it does, if the hardware header for the packet needs to be rebuilt. Linux caches hardware headers to avoid frequent rebuilding of them. If the hardware header needs rebuilding, it calls the device specific hardware header rebuilding routine. All ethernet devices use the same generic header rebuilding routine which in turn uses the ARP services to translate the destination IP address into a physical address.

The ARP protocol itself is very simple and consists of two message types, an ARP request and an ARP reply. The ARP request contains the IP address that needs translating and the reply (hopefully) contains the translated IP address, the hardware address. The ARP request is broadcast to all hosts connected to the network, so, for an ethernet network, all of the machines connected to the ethernet will see the ARP request. The machine that owns the IP address in the request will respond to the ARP request with an ARP reply containing its own physical address.

The ARP protocol layer in Linux is built around a table of arp_table data structures which each describe an IP to physical address translation. These entries are created as IP addresses need to be translated and removed as they become stale over time. Each arp_table data structure has the following fields:

last used the time that this ARP entry was last used,
last updated the time that this ARP entry was last updated,
flags these describe this entry's state, if it is complete and so on,
IP address The IP address that this entry describes
hardware address The translated hardware address
hardware header This is a pointer to a cached hardware header,
timer This is a timer_list entry used to time out ARP requests
that do not get a response,
retries The number of times that this ARP request has been
retried,
sk_buff queue List of sk_buff entries waiting for this IP address
to be resolved

The ARP table consists of a table of pointers (the arp_tables vector) to chains of arp_table entries. The entries are cached to speed up access to them, each entry is found by taking the last two bytes of its IP address to generate an index into the table and then following the chain of entries until the correct one is found. Linux also caches prebuilt hardware headers off the arp_table entries in the form of hh_cache data structures.

When an IP address translation is requested and there is no corresponding arp_table entry, ARP must send an ARP request message. It creates a new arp_table entry in the table and queues the sk_buff containing the network packet that needs the address translation on the sk_buff queue of the new entry. It sends out an ARP request and sets the ARP expiry timer running. If there is no response then ARP will retry the request a number of times and if there is still no response ARP will remove the arp_table entry. Any sk_buff data structures queued waiting for the IP address to be translated will be notified and it is up to the protocol layer that is transmitting them to cope with this failure. UDP does not care about lost packets but TCP will attempt to retransmit on an established TCP link. If the owner of the IP address responds with its hardware address, the arp_table entry is marked as complete and any queued sk_buff's will be removed from the queue and will go on to be transmitted. The hardware address is written into the hardware header of each sk_buff.

The ARP protocol layer must also respond to ARP requests that specfy its IP address. It registers its protocol type (ETH_P_ARP), generating a packet_type data structure. This means that it will be passed all ARP packets that are received by the network devices. As well as ARP replies, this includes ARP requests. It generates an ARP reply using the hardware address kept in the receiving device's devicedata structure.

Network topologies can change over time and IP addresses can be reassigned to different hardware addresses. For example, some dial up services assign an IP address as each connection is established. In order that the ARP table contains up to date entries, ARP runs a periodic timer which looks through all of the arp_table entries to see which have timed out. It is very careful not to remove entries that contain one or more cached hardware headers. Removing these entries is dangerous as other data structures rely on them. Some arp_table entries are permanent and these are marked so that they will not be deallocated. The ARP table cannot be allowed to grow too large; each arp_table entry consumes some kernel memory. Whenever the a new entry needs to be allocated and the ARP table has reached its maximum size the table is pruned by searching out the oldest entries and removing them.

11.7  IP Routing

The IP routing function determines where to send IP packets destined for a particular IP address. There are many choices to be made when transmitting IP packets. Can the destination be reached at all? If it can be reached, which network device should be used to transmit it? If there is more than one network device that could be used to reach the destination, which is the better one? The IP routing database maintains information that gives answers to these questions. There are two databases, the most important being the Forwarding Information Database. This is an exhaustive list of known IP destinations and their best routes. A smaller and much faster database, the route cache is used for quick lookups of routes for IP destinations. Like all caches, it must contain only the frequently accessed routes; its contents are derived from the Forwarding Information Database.

Routes are added and deleted via IOCTL requests to the BSD socket interface. These are passed onto the protocol to process. The INET protocol layer only allows processes with superuser privileges to add and delete IP routes. These routes can be fixed or they can be dynamic and change over time. Most systems use fixed routes unless they themselves are routers. Routers run routing protocols which constantly check on the availability of routes to all known IP destinations. Systems that are not routers are known as end systems. The routing protocols are implemented as daemons, for example GATED, and they also add and delete routes via the IOCTL BSD socket interface.

11.7.1  The Route Cache

Whenever an IP route is looked up, the route cache is first checked for a matching route. If there is no matching route in the route cache the Forwarding Information Database is searched for a route. If no route can be found there, the IP packet will fail to be sent and the application notified. If a route is in the Forwarding Information Database and not in the route cache, then a new entry is generated and added into the route cache for this route. The route cache is a table (ip_rt_hash_table) that contains pointers to chains of rtable data structures. The index into the route table is a hash function based on the least significant two bytes of the IP address. These are the two bytes most likely to be different between destinations and provide the best spread of hash values. Each rtable entry contains information about the route; the destination IP address, the network device to use to reach that IP address, the maximum size of message that can be used and so on. It also has a reference count, a usage count and a timestamp of the last time that they were used (in jiffies). The reference count is incremented each time the route is used to show the number of network connections using this route. It is decremented as applications stop using the route. The usage count is incremented each time the route is looked up and is used to order the rtable entry in its chain of hash entries. The last used timestamp for all of the entries in the route cache is periodically checked to see if the rtable is too old . If the route has not been recently used, it is discarded from the route cache. If routes are kept in the route cache they are ordered so that the most used entries are at the front of the hash chains. This means that finding them will be quicker when routes are looked up.

11.7.2  The Forwarding Information Database


Figure 11.5: The Forwarding Information Database

The forwarding information database (shown in Figure  11.5 contains IP's view of the routes available to this system at this time. It is quite a complicated data structure and, although it is reasonably efficiently arranged, it is not a quick database to consult. In particular it would be very slow to look up destinations in this database for every IP packet transmitted. This is the reason that the route cache exists: to speed up IP packet transmission using known good routes. The route cache is derived from the forwarding database and represents its commonly used entries.

Each IP subnet is represented by a fib_zone data structure. All of these are pointed at from the fib_zones hash table. The hash index is derived from the IP subnet mask. All routes to the same subnet are described by pairs of fib_node and fib_info data structures queued onto the fz_list of each fib_zone data structure. If the number of routes in this subnet grows large, a hash table is generated to make finding the fib_nodedata structures easier.

Several routes may exist to the same IP subnet and these routes can go through one of several gateways. The IP routing layer does not allow more than one route to a subnet using the same gateway. In other words, if there are several routes to a subnet, then each route is guaranteed to use a different gateway. Associated with each route is its metric. This is a measure of how advantagious this route is. A route's metric is, essentially, the number of IP subnets that it must hop across before it reaches the destination subnet. The higher the metric, the worse the route.

Capítulo 12
Kernel Mechanisms

This chapter describes some of the general tasks and mechanisms that the Linux kernel needs to supply so that other parts of the kernel work effectively together.

12.1  Bottom Half Handling


Figure 12.1: Bottom Half Handling Data Structures

There are often times in a kernel when you do not want to do work at this moment. A good example of this is during interrupt processing. When the interrupt was asserted, the processor stopped what it was doing and the operating system delivered the interrupt to the appropriate device driver. Device drivers should not spend too much time handling interrupts as, during this time, nothing else in the system can run. There is often some work that could just as well be done later on. Linux's bottom half handlers were invented so that device drivers and other parts of the Linux kernel could queue work to be done later on. Figure  12.1 shows the kernel data structures associated with bottom half handling. There can be up to 32 different bottom half handlers; bh_base is a vector of pointers to each of the kernel's bottom half handling routines. bh_active and bh_mask have their bits set according to what handlers have been installed and are active. If bit N of bh_mask is set then the Nth element of bh_base contains the address of a bottom half routine. If bit N of bh_active is set then the N'th bottom half handler routine should be called as soon as the scheduler deems reasonable. These indices are statically defined; the timer bottom half handler is the highest priority (index 0), the console bottom half handler is next in priority (index 1) and so on. Typically the bottom half handling routines have lists of tasks associated with them. For example, the immediate bottom half handler works its way through the immediate tasks queue (tq_immediate) which contains tasks that need to be performed immediately.

Some of the kernel's bottom half handers are device specific, but others are more generic:

TIMER
This handler is marked as active each time the system's periodic timer interrupts and is used to drive the kernel's timer queue mechanisms,
CONSOLE
This handler is used to process console messages,
TQUEUE
This handler is used to process tty messages,
NET
This handler handles general network processing,
IMMEDIATE
This is a generic handler used by several device drivers to queue work to be done later.

Whenever a device driver, or some other part of the kernel, needs to schedule work to be done later, it adds work to the appropriate system queue, for example the timer queue, and then signals the kernel that some bottom half handling needs to be done. It does this by setting the appropriate bit in bh_active. Bit 8 is set if the driver has queued something on the immediate queue and wishes the immediate bottom half handler to run and process it. The bh_active bitmask is checked at the end of each system call, just before control is returned to the calling process. If it has any bits set, the bottom half handler routines that are active are called. Bit 0 is checked first, then 1 and so on until bit 31. The bit in bh_active is cleared as each bottom half handling routine is called. bh_active is transient; it only has meaning between calls to the scheduler and is a way of not calling bottom half handling routines when there is no work for them to do.

12.2  Task Queues


Figure 12.2: A Task Queue

Task queues are the kernel's way of deferring work until later. Linux has a generic mechanism for queuing work on queues and for processing them later. Task queues are often used in conjunction with bottom half handlers; the timer task queue is processed when the timer queue bottom half handler runs. A task queue is a simple data structure, see figure  12.2 which consists of a singly linked list of tq_struct data structures each of which contains the address of a routine and a pointer to some data. The routine will be called when the element on the task queue is processed and it will be passed a pointer to the data.

Anything in the kernel, for example a device driver, can create and use task queues but there are three task queues created and managed by the kernel:

timer
This queue is used to queue work that will be done as soon after the next system clock tick as is possible. Each clock tick, this queue is checked to see if it contains any entries and, if it does, the timer queue bottom half handler is made active. The timer queue bottom half handler is processed, along with all the other bottom half handlers, when the scheduler next runs. This queue should not be confused with system timers, which are a much more sophisticated mechanism.
immediate
This queue is also processed when the scheduler processes the active bottom half handlers. The immediate bottom half handler is not as high in priority as the timer queue bottom half handler and so these tasks will be run later.
scheduler
This task queue is processed directly by the scheduler. It is used to support other task queues in the system and, in this case, the task to be run will be a routine that processes a task queue, say for a device driver.

When task queues are processed, the pointer to the first element in the queue is removed from the queue and replaced with a null pointer. In fact, this removal is an atomic operation, one that cannot be interrupted. Then each element in the queue has its handling routine called in turn. The elements in the queue are often statically allocated data. However there is no inherent mechanism for discarding allocated memory. The task queue processing routine simply moves onto the next element in the list. It is the job of the task itself to ensure that it properly cleans up any allocated kernel memory.

12.3  Timers


Figure 12.3: System Timers

An operating system needs to be able to schedule an activity sometime in the future. A mechanism is needed whereby activities can be scheduled to run at some relatively precise time. Any microprocessor that wishes to support an operating system must have a programmable interval timer that periodically interrupts the processor. This periodic interrupt is known as a system clock tick and it acts like a metronome, orchestrating the system's activities. Linux has a very simple view of what time it is; it measures time in clock ticks since the system booted. All system times are based on this measurement, which is known as jiffies after the globally available variable of the same name.

Linux has two types of system timers, both queue routines to be called at some system time but they are slightly different in their implementations. Figure  12.3 shows both mechanisms. The first, the old timer mechanism, has a static array of 32 pointers to timer_struct data structures and a mask of active timers, timer_active. Where the timers go in the timer table is statically defined (rather like the bottom half handler table bh_base). Entries are added into this table mostly at system initialization time. The second, newer, mechanism uses a linked list of timer_list data structures held in ascending expiry time order.

Both methods use the time in jiffies as an expiry time so that a timer that wished to run in 5s would have to convert 5s to units of jiffies and add that to the current system time to get the system time in jiffies when the timer should expire. Every system clock tick the timer bottom half handler is marked as active so that the when the scheduler next runs, the timer queues will be processed. The timer bottom half handler processes both types of system timer. For the old system timers the timer_active bit mask is check for bits that are set. If the expiry time for an active timer has expired (expiry time is less than the current system jiffies), its timer routine is called and its active bit is cleared. For new system timers, the entries in the linked list of timer_list data structures are checked. Every expired timer is removed from the list and its routine is called. The new timer mechanism has the advantage of being able to pass an argument to the timer routine.

12.4  Wait Queues

There are many times when a process must wait for a system resource. For example a process may need the VFS inode describing a directory in the file system and that inode may not be in the buffer cache. In this case the process must wait for that inode to be fetched from the physical media containing the file system before it can carry on.

wait_queue

*task

*next

Figure 12.4: Wait Queue

The Linux kernel uses a simple data structure, a wait queue (see figure  12.4), which consists of a pointer to the processes task_struct and a pointer to the next element in the wait queue.

When processes are added to the end of a wait queue they can either be interruptible or uninterruptible. Interruptible processes may be interrupted by events such as timers expiring or signals being delivered whilst they are waiting on a wait queue. The waiting processes state will reflect this and either be INTERRUPTIBLE or UNINTERRUPTIBLE. As this process can not now continue to run, the scheduler is run and, when it selects a new process to run, the waiting process will be suspended. 18

When the wait queue is processed, the state of every process in the wait queue is set to RUNNING. If the process has been removed from the run queue, it is put back onto the run queue. The next time the scheduler runs, the processes that are on the wait queue are now candidates to be run as they are now no longer waiting. When a process on the wait queue is scheduled the first thing that it will do is remove itself from the wait queue. Wait queues can be used to synchronize access to system resources and they are used by Linux in its implementation of semaphores (see below).

12.5  Buzz Locks

These are better known as spin locks and they are a primitive way of protecting a data structure or piece of code. They only allow one process at a time to be within a critical region of code. They are used in Linux to restrict access to fields in data structures, using a single integer field as a lock. Each process wishing to enter the region attempts to change the lock's initial value from 0 to 1. If its current value is 1, the process tries again, spinning in a tight loop of code. The access to the memory location holding the lock must be atomic, the action of reading its value, checking that it is 0 and then changing it to 1 cannot be interrupted by any other process. Most CPU architectures provide support for this via special instructions but you can also implement buzz locks using uncached main memory.

When the owning process leaves the critical region of code it decrements the buzz lock, returning its value to 0. Any processes spinning on the lock will now read it as 0, the first one to do this will increment it to 1 and enter the critical region.

12.6  Semaphores

Semaphores are used to protect critical regions of code or data structures. Remember that each access of a critical piece of data such as a VFS inode describing a directory is made by kernel code running on behalf of a process. It would be very dangerous to allow one process to alter a critical data structure that is being used by another process. One way to achieve this would be to use a buzz lock around the critical piece of data is being accessed but this is a simplistic approach that would not give very good system performance. Instead Linux uses semaphores to allow just one process at a time to access critical regions of code and data; all other processes wishing to access this resource will be made to wait until it becomes free. The waiting processes are suspended, other processes in the system can continue to run as normal.

A Linux semaphore data structure contains the following information:

count
This field keeps track of the count of processes wishing to use this resource. A positive value means that the resource is available. A negative or zero value means that processes are waiting for it. An initial value of 1 means that one and only one process at a time can use this resource. When processes want this resource they decrement the count and when they have finished with this resource they increment the count,
waking
This is the count of processes waiting for this resource which is also the number of process waiting to be woken up when this resource becomes free,
wait queue
When processes are waiting for this resource they are put onto this wait queue,
lock
A buzz lock used when accessing the waking field.

Suppose the initial count for a semaphore is 1, the first process to come along will see that the count is positive and decrement it by 1, making it 0. The process now ``owns'' the critical piece of code or resource that is being protected by the semaphore. When the process leaves the critical region it increments the semphore's count. The most optimal case is where there are no other processes contending for ownership of the critical region. Linux has implemented semaphores to work efficiently for this, the most common, case.

If another process wishes to enter the critical region whilst it is owned by a process it too will decrement the count. As the count is now negative (-1) the process cannot enter the critical region. Instead it must wait until the owning process exits it. Linux makes the waiting process sleep until the owning process wakes it on exiting the critical region. The waiting process adds itself to the semaphore's wait queue and sits in a loop checking the value of the waking field and calling the scheduler until waking is non-zero.

The owner of the critical region increments the semaphore's count and if it is less than or equal to zero then there are processes sleeping, waiting for this resource. In the optimal case the semaphore's count would have been returned to its initial value of 1 and no further work would be neccessary. The owning process increments the waking counter and wakes up the process sleeping on the semaphore's wait queue. When the waiting process wakes up, the waking counter is now 1 and it knows that it it may now enter the critical region. It decrements the waking counter, returning it to a value of zero, and continues. All access to the waking field of semaphore are protected by a buzz lock using the semaphore's lock.

Capítulo 13
Modules

Este capítulo describe cómo el núcleo de Linux puede cargar funciones dinámicamente, como por ejemplo sistemas de archivos, sólo cuando son necesarios.

Linux es un núcleo monolítico; es decir, es un único programa de gran tamaño donde todos los componentes funcionales del núcleo tienen acceso a todas sus estructuras de datos internas y a sus rutinas. La alternativa es tener una estructura de micro-núcleo donde las partes funcionales del núcleo están divididas en unidades separadas con mecanismos de comunicación estrictos entre ellos. Esto hace que la integración de nuevos componentes en el núcleo mediante el proceso de configuración tarde bastante tiempo. Si usted quisiera usar una controladora SCSI para una NCR 810 SCSI y no la tuviera integrada en el núcleo, tendría que configurar el núcleo para poder usar la NCR 810. Hay una alternativa: Linux le permite cargar y descargar componentes del sistema operativo dinámicamente según los vaya necesitando. Los módulos de Linux son trozos de código que se pueden quedar vinculados dinámicante en el núcleo en cualquier momento después de que el sistema haya arrancado. Una vez que ya no se necesitan, se pueden desvincular y quitar del núcleo. El núcleo está compuesto de módulos que en su mayoría son controladores de dispositivo, pseudo-controladores de dispositivo como controladores de red, o sistemas de archivos. Se puede bien cargar y descargar los módulos del núcleo de Linux explícitamente usando los comandos insmod y rmmodo bien el mismo núcleo puede solicitar que el demonio del núcleo (kerneld) cargue y descargue los módulos según los vaya necesitando. Cargar código dinámicamente según se necesite es atractivo ya que impide que el tamaño del núcleo crezca, además de hacerlo muy flexible. El núcleo Intel que tengo actualmente usa bastantes módulos y tiene un tamaño de sólo 406 K. Sólo uso en pocas ocasiones el sistema de archivos VFAT, por lo que tengo configurado mi núcleo de Linux para que cargue automáticamente el módulo para soporte VFAT mientras monto una partición de tipo VFAT. Cuando he desmontado la partición VFAT, el sistema detecta que ya no necesito el módulo para VFAT y lo quita de mi sistema. Los módulos también pueden ser útiles para comprobar el nuevo código del núcleo sin tener que volver a crear el núcleo y reiniciar el ordenador cada vez que se compruebe. Sin embargo, no hay nada gratuito y hay un significante decremento en el rendimiento y en la memoria asociada con los módulos del núcleo. Un módulo cargable debe proveer un poco más de código, lo que unido a las estructuras de datos adicionales hace que un módulo ocupe un poco más de memoria. Hay también un nivel de ïndirección introducido" que hace que los accesos de los recursos del núcleo sean bastante menos eficientes para los módulos. Una vez que un módulo de Linux ha sido cargado, es tan parte del núcleo como cualquier otro código normal del mismo. Tiene los mismos derechos y responsabilidades que cualquier otro código del núcleo; en otras palabras, los módulos del núcleo de Linux pueden hacer dejar de funcionar al núcleo de la misma manera que cualquier otro código o dispositivo integrado en el mismo pueda hacerlo.

Para que los módulos puedan usar los recursos del núcleo que necesitan, deben ser capaces de encontrarlos. Digamos que un módulo necesita llamar a kmalloc(), la rutina de alojamiento de memoria del núcleo. En el momento en que está construido, un módulo no sabe en qué parte de la memoria está kmalloc(), así que cuando se carga el módulo, todas sus referencias deben ser fijadas por el núcleo para kmalloc() antes de que el módulo pueda funcionar. El núcleo guarda una lista de todos sus recursos en la tabla de símbolos del núcleo, para así porder resolver las referencias a aquellos recursos desde los módulos cuando estén cargados. Linux permite el apilamiento de módulos, que es cuando un módulo requiere los servicios de otro módulo. Por ejemplo, el módulo para VFAT requiere los servicios del módulo FAT, ya que VFAT es más o menos un conjunto de extensiones FAT. Un módulo requiriendo servicios o recursos de otro módulo es muy similar a la situación donde un módulo requiere servicios y recursos del mismo núcleo. Sólo aquí los requeridos están en otro módulo, que ya ha sido previamente cargado. Mientras se carga cada módulo, el núcleo modifica la tabla de símbolos del núcleo, añadiendo a ésta todos los recursos o símbolos exportados por el módulo recién cargado. Esto quiere decir que, cuando el siguiente módulo se carga, tiene acceso a los servicios de los módulos que ya están cargados.

Cuando se intenta descargar un módulo, el núcleo necesita saber que el módulo no está en uso y necesita alguna manera de notificarle que va a ser descargado. De esa forma, el módulo podrá liberar cualquier recurso del sistema que ha usado, como por ejemplo memoria del núcleo o interrupciones, antes de ser quitado del núcleo. Cuando el módulo está descargado, el núcleo quita cualquier símbolo que hubiese sido exportado al interior de la tabla de símbolos del núcleo.

Además de la capacidad que tiene un módulo de poder hacer dejar de funcionar al sistema operativo si no está bien escrito, presenta otro peligro. ¿Qué ocurre si se carga un módulo creado para una versión distinta de la que se está ejecutando actualmente? Esto puede causar un problema si, digamos, el módulo realiza una llamada a una rutina del núcleo y suministra argumentos erróneos. Opcionalmente, el núcleo puede proteger contra esto haciendo rigurosas comprobaciones de las versiones en el módulo mientras éste se carga.

13.1  Loading a Module


Figure 13.1: The List of Kernel Modules

Hay dos formas de cargar un módulo del núcleo. La primera es usar el comando insmodpara insertarlo manualmente en el núcleo. La segunda, y mucho mejor pensada, es cargar el módulo según se necesite; esto se conoce como carga bajo demanda. Cuando el núcleo descubre que necesita un módulo, por ejemplo cuando el usuario monta un sistema de archivos que no está incluido en el núcleo, éste requerirá que el demonio (kerneld) intente cargar el módulo apropiado. El demonio del núcleo es un proceso normal de usuario, pero con privilegios de superusuario. Cuando se inicia, normalmente al arrancar el ordenador, abre un canal de comunicación entre procesos (IPC) al núcleo. Este vínculo lo usa el núcleo para enviar mensajes al kerneld, solicitando que se ejecuten varias tareas. La labor principal de Kerneld es cargar y descargar los módulos del núcleo, pero también es capaz de realizar otras tareas como iniciar un enlace PPP sobre una línea serie cuando sea necesario y cerrarlo cuando deje de serlo. Kerneld no realiza estas tareas por sí mismo, sino que ejecuta los programas necesarios, como insmod, para realizar el trabajo. Kerneld es sólo un agente del núcleo, planificando el trabajo según su comportamiento.

La utilidad insmoddebe encontrar el módulo del núcleo requerido que se va a cargar. Los módulos del núcleo cuya carga ha sido solicitada bajo demanda se guardan normalmente en /lib/modules/kernel-version. Los módulos del núcleo son ficheros objeto vinculados igual que otros programas en el sistema, con la excepción de que son vinculados como imágenes reubicables; es decir, imágenes que no están vinculadas para ser ejecutadas desde una dirección específica. Pueden ser ficheros objeto con el formato a.out o elf. insmodrealiza una llamada al sistema con privilegios para encontrar los símbolos exportados pertenecientes al núcleo. Éstos se guardan en pares conteniendo el nombre del símbolo y su valor, por ejemplo, su dirección. La tabla del núcleo de símbolos exportados se mantiene en la primera estructura de datos moduleen la lista de módulos mantenida por el núcleo, y manteniendo un puntero desde module_list. Sólo los símbolos introducidos específicamente se añaden a la tabla, la cual se construye cuando el núcleo se compila y enlaza, en vez de que cada símbolo del núcleo se exporte a sus módulos. Un símbolo de ejemplo es ``request_irq'', que es la rutina del núcleo a la que hay que llamar cuando un controlador desea tomar el control de una interrupción del sistema en particular. En mi núcleo, ésta tenía el valor 0x0010cd30. Pueden verse fácilmente los símbolos exportados del núcleo y sus valores echando un vistazo a /proc/ksyms o usando la utilidad ksyms. La utilidad ksymspuede mostrarle todos los símbolos exportados del núcleo o sólo aquellos símbolos exportados por los módulos cargados. insmodlee el módulo en el interior de su memoria virtual y fija las referencias propias hacia las rutinas del núcleo y recursos que estén sin resolver usando los símbolos exportados desde el núcleo. Esta labor de reparación toma la forma de un parche a la imagen del módulo en memoria. This fixing up takes the form of patching the module image in memory. insmodescribe físicamente la dirección del símbolo en el lugar apropiada en el módulo.

Cuando insmodha arreglado las referencias al módulo convirtiéndolas en símbolos del núcleo exportados, pide al núcleo que le asigne espacio suficiente para el nuevo núcleo, usando, de nuevo, una llamada del sistema privilegiada. El núcleo ubica una nueva estructura de datos moduley suficiente memoria del núcleo para conservar el nuevo módulo y lo pone al final de la lista de módulos del núcleo. El nuevo módulo se queda marcado como UNINITIALIZED. La figura  13.1 muestra la lista de módulos del núcleo después de que dos módulos, VFAT y VFAT han sido cargados en el núcleo. No se muestra el primer módulo de la lista, que es un pseudo-módulo que sólo está allí para conservar la tabla de símbolos exportados del núcleo. Se puede usar el comando lsmodpara listar todos los módulos del núcleo cargados, así como sus interdependencias. lsmodsimplemente reformatea /proc/modules, que se construye partiendo de la lista de estructuras de datos del núcleo module. La memoria que el núcleo utiliza para esto se puede visualizar en el espacio de dirección de memoria del proceso insmod, de forma que pueda acceder a ella. insmodcopia el módulo en el espacio utilizado y lo reubica, de forma que se ejecutará desde la dirección del núcleo en la que ha sido colocado. Esto debe ser así, ya que el módulo no puede esperar ser cargado dos veces en la misma dirección, y aún menos si se trata de dos sistemas Linux distintos. De nuevo, este proceso de reubicación hace necesario que se ajuste la imagen del módulo con la dirección apropiada. El nuevo módulo también exporta símbolos al núcleo, e insmodconstruye una tabla de estas imágenes exportadas. Cada módulo del núcleo debe contener rutinas de inicialización y limpieza, y estos símbolos no son exportados deliberadamente, pero insmoddebe conocer sus direcciones para así poder pasárselas al núcleo. Si todo va bien, insmodya está listo para incializar el módulo y hace una llamada privilegiada al sistema pasando al núcleo las direcciones de las rutinas de inicialización y limpieza del módulo.

Cuando se añade un módulo nuevo al núcleo, éste debe actualizar el conjunto de símbolos y modificar los módulos que están siendo usados por el módulo nuevo. Los módulos que tienen otros módulos que dependen de ellos, deben mantener una lista de referencias al final de su tabla de símbolos, con un puntero a ella lanzado desde su propia estructura de datos module. La figura  13.1 muestra que el módulo de soporte VFAT depende del módulo de soporte FAT. Así, el módulo FAT contiene una referencia al módulo VFAT; la referencia fue añadida cuando se cargó el módulo VFAT. El núcleo llama a la rutina de inicialización de módulos y, si todo funciona correctamente, continúa con la instalación del módulo. La dirección de la rutina de limpieza del módulo se almacena en su propia estructura de datos module, a la que el núcleo llamará cuando ese módulo esté descargado. Finalmente, el estado del módulo se establece en RUNNING.

13.2  Unloading a Module

Los módulos pueden quitarse usando el comando rmmod, pero kerneld elimina automaticamente del sistema los módulos cargados mediante demanda cuando ya no se usan. Cada vez que su tiempo de inactividad se acaba, kerneld realiza una llamada al sistema solicitando que todos los módulos cargados mediante demanda que no estén en uso se eliminen del sistema. El valor del tiempo de inactividad se establece cuando se inicia kerneld; mi kerneld realiza una comprobación cada 180 segundos. Así, por ejemplo, si se monta un CD ROM iso9660 y el sistema de archivos iso9660 es un módulo cargable, entonces muy poco tiempo después de que se monte el CD ROM, el módulo iso9660 se eliminará del núcleo.

Un módulo no puede ser descargado mientras otros componentes del núcleo dependan de él. Por ejemplo, usted no puede descargar el módulo VFAT si tiene uno o más sistemas de archivos VFAT montados. Si echa un vistazo a la salida de lsmod, verá que cada módulo tiene un contador asociado. Por ejemplo:

Module:         #pages:  Used by:
msdos              5                  1
vfat               4                  1 (autoclean)
fat                6    [vfat msdos]  2 (autoclean)

El contador indica el número de entidades que dependen de este módulo. En el ejemplo anterior, los módulos vfat y msdos dependen ambos del módulo fat, de ahí que el contador sea 2. Los módulos vfat y msdos tienen 1 dependencia, que es relativa a un sistema de archivos montado. Si yo tuviera que cargar otro sistema de archivos VFAT, entonces el contador del módulo vfat pasaría a ser 2. El contador de un módulo se conserva en el primer "longword" de su imagen.

Este campo se sobrecarga significativamente, ya que también conserva los indicadores AUTOCLEAN y VISITED. Estos dos indicadores se usan para módulos cargados bajo demanda. Estos módulos se marcan como AUTOCLEAN para que el sistema pueda reconocer cuáles puede descargar automáticamente. El indicador VISITED marca el módulo como que está en uso por uno o más componentes del sistema; esta marca se establece en cualquier momento que otro componente hace uso del módulo. Cada vez que kerneld pregunta al sistema si puede eliminar módulos cargados bajo demanda, éste inspecciona todos los módulos del sistema buscando candidatos idóneos. Sólo se fija en los módulos marcados como AUTOCLEAN y en el estado RUNNING. Si el candidato no tiene marcado el indicador VISITED, entonces eliminará el módulo; de lo contrario quitará la marcha del indicador VISITED y continuará buscando el siguiente módulo que haya en el sistema.

Teniendo en cuenta que un módulo puede ser descargado, las llamadas a su rutina de limpieza se producen para permitir liberar los recursos del núcleo que éste ha utilizado. La estructura de datos modulequeda marcada como DELETED y queda desvinculada de la lista de módulos del núcleo. Todos los demás módulos de los que dependa el módulo, tienen sus listas de referencia modificadas de forma que ya no lo tienen como dependiente. Toda la memoria del núcleo que necesita el módulo queda desalojada.

Capítulo 14
El código fuente del núcleo de Linux

En este capítulo trataremos de explicarle cómo buscar ciertas funciones del núcleo en el propio código fuente.

La lectura de este libro no exige un conocimiento profundo de la programación en lenguaje 'C' ni tampoco la disponibilidad de una copia del código fuente de Linux. Sin embargo, será interesante ejercitar los conocimientos adquiridos mirando el código fuente para así comprender mejor cómo funciona internamente Linux. Este capítulo da una visión general del código fuente, en lo que respecta a cómo está organizado y en qué puntos debe mirarse para buscar alguna cosa concreta.

Dónde obtener el código fuente de Linux

Todas las distribuciones conocidas ( Craftworks, Debian, Slackware, Red Hat, etcétera) incluyen como opción las fuentes del núcleo de Linux. Normalmente, el binario que corre en su máquina ha sido construido con esas fuentes. Debido a la naturaleza del propio sistema operativo, esas fuentes estarán seguramente obsoletas, por lo que lo más interesante será obtener la última versión de uno de los servidores de Internet mencionados en el apéndice  www-appendix. El código fuente es mantenido en ftp://ftp.cs.helsinki.fi y en cualquier réplica suya. Esto hace al servidor de Helsinki el más actualizado, pero en otros servidores como el del MIT o el de Sunsite encontraremos seguramente las mismas versiones que en el primero.

Si no dispone de acceso a la Red, aun dispone de muchos CD ROM con copias de lo que esos servidores ofrecen en cada momento, a un precio muy razonable. Algunos fabricantes ofrecen un servicio de suscripción con los que puede obtener actualizaciones cada cierto tiempo (incluso cada mes). Otro lugar interesante para buscar es en su grupo local de usuarios.

Las versiones del código fuente de Linux se numeran de una manera simple. Todo núcleo con un número de versión par (tal como 2.0.30) es un núcleo estable, y si el número de versión es impar (como 2.1.42) se trata de una versión de desarrollo. En este libro nos basamos en el código fuente de la versión estable 2.0.30. Las versiones de desarrollo están a la última en cuanto a características generales y soporte de dispositivos. Aunque pueden ser inestables, y por lo tanto no ser lo que usted necesita, es muy importante que la comunidad de usuarios los prueben. Recuerde, en caso de usar núcleos de desarrollo, hacer siempre copias de respaldo por si fuera necesario recuperarse de alguna catástrofe.

Cuando se publica un cambio en el núcleo, éste se distribuye en forma de parche. La utilidad patch se puede utilizar para aplicar parches que editan ficheros del código fuente. Por ejemplo, si tenemos instalado el núcleo 2.0.29 y queremos pasar a la versión 2.0.30, no tenemos que borrar la antigua y obtener toda la nueva versión (que pueden ser decenas de megabytes). En su lugar, podemos obtener el parche de la 2.0.30, mucho más pequeño, y aplicarlo sobre la 2.0.29 con el comando:


$ cd /usr/src/linux
$ patch -p1 < patch-2.0.30

Esto guarda también, por seguridad, copias de cada fichero modificado. Un buen sitio donde podemos buscar parches (oficiales y no oficiales) es en el servidor http://www.linuxhq.com.

Organización del Código Fuente

En el directorio principal del árbol del código fuente, /usr/src/linux, podemos unos cuantos directorios:

arch
El subdirectorio arch contiene todo el código específico de una arquitectura. Dentro hay más subdirectorios, uno por cada arquitectura soportada. Por ejemplo, i386 o alpha.
include
El directorio include contiene casi todos los ficheros que se necesitan incluir durante la compilación del código. También contiene diversos subdirectorios, incluyendo uno por cada arquitectura soportada. El subdirectorio include/asm es realmente un enlace simbólico al que corresponda para la arquitectura, como include/asm-i386. Para cambiar de arquitectura hay que editar el fichero Makefile del núcleo y volver a ejecutar el programa de configuración del núcleo.
init
Este directorio incluye el código de iniciación del núcleo, y es un buen sitio donde mirar para comenzar a entender cómo funciona el mismo.
mm
Aquí está todo el código de gestión de memoria independiente de la arquitectura. La parte dependiente estará bajo arch/*/mm/, como por ejemplo arch/i386/mm/fault.c.
drivers
Todos los manejadores de dispositivos se encuentran aquí. A su vez se divide en clases de controlador, como block para los dispositivos de bloques.
ipc
Este directorio contiene todo el código para la comunicación entre procesos.
modules
Este directorio se utiliza para montar los módulos cuando sea necesario.
fs
Contiene todo el código para el sistema de ficheros. A su vez se divide en subdirectorios, uno para cada sistema de ficheros soportado, como vfat y ext2.
kernel
Aquí tenemos el código principal del núcleo. Una vez más, aquella parte específica para una arquitectura se encontrará en arch/*/kernel.
net
Código para trabajo con redes.
lib
Aquí se encuentran librerías necesarias para el núcleo. De nuevo, hay librerías que son dependientes de la arquitectura, y se encontrarán en arch/*/lib/.
scripts
En este directorio se encuentran los scripts (tipo awk o tk) que son necesarios para configurar o compilar el núcleo.

Dónde empezar a mirar

Cuando nos enfrentamos a consultar el código de un programa tan grande como el núcleo de Linux, en general no sabremos por dónde empezar. Todo se muestra como una gran cadena de la que no se ve su principio o final. Muchas veces comenzamos a estudiar el código y mirando aquí y allá acabamos olvidando qué estábamos buscando. En los siguientes párrafos le daremos unas ideas sobre los puntos del código fuente donde puede encontrar respuestas a sus dudas.

Arranque e inicialización del sistema

En un sistema basado en Intel, el núcleo comienza a ejecutarse cuando lo carga y le pasa el control un programa como loadlin.exe o LILO. Esta parte puede verse en arch/i386/kernel/head.S. Este programa inicial realiza ciertas preparaciones propias de la arquitectura y a continuación salta a la rutina main() del fichero init/main.c.

Gestión de la Memoria

Esta parte se encuentra principalmente en mm pero la parte más específica de cada arquitectura la encontraremos en arch/*/mm. El código de tratamiento de fallo de página se encuentra en mm/memory.c, y la parte correspondiente al mapeado de la memoria y la cache de páginas se encuentra en mm/filemap.c. La cache de buffer se detalla en mm/buffer.c y la cache del intercambio (swap), en mm/swap_state.c y mm/swapfile.c.

El núcleo

La parte común a todas las arquitecturas está en kernel, y la parte específica de cada una de ellas, en arch/*/kernel. El planificador lo encontramos en kernel/sched.c y el código para creación de procesos, en kernel/fork.c. La parte de bajo nivel de los manejadores se encuentra en include/linux/interrupt.h. La estructura de datos task_struct se localiza en include/linux/sched.h.

PCI

El pseudo-controlador de PCI se encuentra en drivers/pci/pci.c, con las definiciones especificadas en include/linux/pci.h. Cada arquitectura tiene una parte específica al correspondiente BIOS de PCI: los Alpha AXP se tratan en arch/alpha/kernel/bios32.c.

Comunicación entre procesos

Todo ello está en ipc. Los objetos del IPC de Unix Sistema V tienen una estructura ipc_perm y puede encontrarse en include/linux/ipc.h. Los mensajes de Sistema V se implementan en ipc/msg.c, la memoria compartida en ipc/shm.c y los semáforos en ipc/sem.c. Las tuberías se implementan en ipc/pipe.c.

Tratamiento de interrupciones

El código de tratamiento de interrupciones es en su mayor parte propio de cada microprocesador (y prácticamente distinto en cada plataforma). El código correspondiente a Intel (en un PC) está en arch/i386/kernel/irq.c y las definiciones necesarias se declaran en include/asm-i386/irq.h.

Controladores de dispositivo

El grueso del código fuente de Linux lo forman los controladores de dispositivos. Todos ellos se encuentran bajo el directorio drivers, a su vez organizados según su tipo, en otros subdirectorios:

/block
Aquí están los dispositivos de bloque, como los discos IDE (en ide.c). Si queremos ver cómo los dispositivos pueden contener sistemas de ficheros e inicializarse, miraremos en la función device_setup() de drivers/block/genhd.c. Aquí, no solo se preparan discos: también la red si es necesario (por ejemplo, cuando queremos montar sistemas de ficheros nfs). Los dispositivos de bloques incluyen a los discos IDE y SCSI.

/char
En este directorio se pueden encontrar los dispositivos de carácter tales como los ttys, los puertos serie o el ratón.

/cdrom
Aquí se encuentra todo el código referente a CDROMs especiales (como la interfaz con CD de SoundBlaster). Nótese que el controlador del CD tipo IDE/ATAPI se encuentra junto a los demás controladores IDE (drivers/block/ide-cd.c) y que el encargado de los CD SCSI se encuentra en el fichero scsi.c de drivers/scsi.

/pci
Aquí encontraremos el código fuente del pseudo-controlador de PCI. Es un buen sitio para ver cómo el subsistema PCI es mapeado e iniciado. El código específico para el PCI de Alpha AXP se encuentra en arch/alpha/kernel/bios32.c.

/scsi
Este es el lugar donde encontraremos todo lo referente al SCSI así como los diferentes controladores SCSI existentes y soportados en Linux.

/net
En este directorio debemos mirar para buscar los controladores de tarjetas de red. Por ejemplo, el controlador de la tarjeta de red DECChip 21040 (PCI) se encuentra en tulip.c.

/sound
Aquí se implementa todo lo relacionado con las diferentes tarjetas de sonido.

Sistemas de Ficheros

Las fuentes para el sistema EXT2 están en el directorio fs/ext2/, residiendo las definiciones necesarias en include/linux/ext2_fs.h, ext2_fs_i.h y ext2_fs_sb.h. Las estructuras de datos correspondientes al Sistema de Ficheros Virtual (VFS) se declaran en include/linux/fs.h y el código está en fs/*. La implementación de la cache de buffer se reparte entre el archivo fs/buffer.c y el demonio update.

Redes

El código para el tema de las redes se encuentra en net y los ficheros de inclusión correspondientes en include/net principalmente. El código para los sockets BSD está en net/socket.c y el correspondiente a los sockets de IP versión 4, en net/ipv4/af_inet.c. El código de soporte de protocolo genérico (incluyendo las rutinas de manejo de sk_buff) está en net/core y la parte correspondiente a TCP/IP en net/ipv4. Los controladores de las tarjetas de red están en drivers/net.

Módulos

Para usar módulos, parte del código va incluido en el núcleo, y parte en el paquete de los módulos. El código del núcleo va todo él en kernel/modules.c, declarándose las estructuras y los mensajes del demonio kerneld en include/linux/module.h e include/linux/kerneld.h, respectivamente. Si desea conocer la estructura de un fichero objeto ELF, deberá mirar en include/linux/elf.h.

Appendix A
Las estructuras de datos de

Este apéndice enumera las principales estructuras de datos que utiliza  y que se han descrito en este libro. Se las ha editado un poco para que quepan en la página.

block_dev_struct

Las estructuras de datos block_dev_struct se utilizan para registrar los dispositivos de bloques como disponibles, para el uso del búfer caché. Se agrupan en el vector blk_dev.
struct blk_dev_struct {
    void (*request_fn)(void);
    struct request * current_request;
    struct request   plug;
    struct tq_struct plug_tq;
};

buffer_head

La estructura de datos buffer_head mantiene información sobre un bloque de búfer en el caché de búfer.

/* bh state bits */
#define BH_Uptodate  0   /* 1 si el búfer contiene datos válidos     */
#define BH_Dirty     1   /* 1 si el búfer está sucio                 */
#define BH_Lock      2   /* 1 si el búfer tiene cerrojo puesto       */
#define BH_Req       3   /* 0 si el búfer ha sido invalidado         */
#define BH_Touched   4   /* 1 si el búfer ha sido tocado (envejecido)*/
#define BH_Has_aged  5   /* 1 si el búfer ha envejecido (aging)      */
#define BH_Protected 6   /* 1 si el búfer está protegido             */
#define BH_FreeOnIO  7   /* 1 para descartar el buffer_head después  */
                              de IO                                  */

struct buffer_head {
  /* primera línea de caché: */
  unsigned long      b_blocknr;    /* número de bloque               */
  kdev_t             b_dev;        /* dispositivo (B_FREE = libre)   */
  kdev_t             b_rdev;       /* dispositivo real               */
  unsigned long      b_rsector;    /* ubicación real del búfer       */
                                        en el disco                  */
  struct buffer_head *b_next;      /* lista de la cola hash          */
  struct buffer_head *b_this_page; /* lista circular de búferes en
                                        una página                   */
  /* Segunda línea de caché : */
  unsigned long      b_state;      /* buffer state bitmap (above)    */
  struct buffer_head *b_next_free;
  unsigned int       b_count;      /* usuarios que usan este bloque  */
  unsigned long      b_size;       /* tamaño del bloque              */

  /* Siguen datos que nos son críticos para la performance: */
  char               *b_data;      /* apuntador al bloque de datos   */
  unsigned int       b_list;       /* Lista en la cual aparece este
                                       búfer                         */
  unsigned long      b_flushtime;  /* Momento en el cual este búfer
                                        (sucio) deberá escribirse    */
  unsigned long      b_lru_time;   /* Momento cuando se usó por última
                                         vez este búfer.             */
  struct wait_queue  *b_wait;
  struct buffer_head *b_prev;      /* lista hash doblemente enlazada */
  struct buffer_head *b_prev_free; /* lista doblemente enlazada 
                                        de búferes                   */
  struct buffer_head *b_reqnext;   /* cola de solicitudes            */
};

device

Cada uno de los dispositivos de red que hay en el sistema se representa mediante una estructura de datos device.

struct device 
{

  /*
   * Este es el primer campo de la parte «visible» de esta estructura
   * (o sea, como la ven los usuarios en el fichero «Space.c».  Es el
   * nombre de la interfaz.
   */
  char                    *name;

  /* I/O specific fields                                           */
  unsigned long           rmem_end;        /* fin "recv" mem compartida */
  unsigned long           rmem_start;      /* comienzo "recv" mem compartida*/
  unsigned long           mem_end;         /* fin mem compartida   */
  unsigned long           mem_start;       /* comienzo memoria compartida*/
  unsigned long           base_addr;       /* dirección E/S dispositivo*/
  unsigned char           irq;             /* número IRQ dispositivo*/

  /* Low-level status flags. */
  volatile unsigned char  start,           /* comenzar una operación */
                          interrupt;       /* llegó una interrupción */
  unsigned long           tbusy;           /* transmisor ocupado     */
  struct device           *next;

  /* Función de inicialización de dispositivo. Sólo se llama una vez */
  int                     (*init)(struct device *dev);

  /* Algún hardware también requiere estos campos, pero no forman
     parte del conjunto que se especifica usualmente en Space.c.   */
  unsigned char           if_port;         /* Seleccionable AUI,TP,*/
  unsigned char           dma;             /* canal DMA            */

  struct enet_statistics* (*get_stats)(struct device *dev);

  /*
   * Hasta aquí llegó la parte «visible» de la estructura. Todos los
   * campos que haya de acá en adelante son internos del sistema y
   * pueden cambiarse sin previo aviso (debe leerse: pueden eliminarse
   * sin previo aviso).
   */

  /* Los siguientes pueden ser necesarios para código futuro que vea
     si no hay energía en la red */
  unsigned long           trans_start;     /* Momento (jiffies) de la 
                                              última transmisión   */
  unsigned long           last_rx;         /* Momento de última Rx */
  unsigned short          flags;           /* indicadores de interfaz (BSD)*/
  unsigned short          family;          /* ID de familia de direcciones*/
  unsigned short          metric;          /* métrica de enrutamiento*/
  unsigned short          mtu;             /* valor de MTU           */
  unsigned short          type;            /* tipo de hardware       */
  unsigned short          hard_header_len; /* long. cabeceras de hardware*/
  void                    *priv;           /* datos privados         */

  /* Información de dirección de interfaz. */
  unsigned char           broadcast[MAX_ADDR_LEN];
  unsigned char           pad;               
  unsigned char           dev_addr[MAX_ADDR_LEN];  
  unsigned char           addr_len;        /* long. dirección hardware    */
  unsigned long           pa_addr;         /* dirección de protocolo      */
  unsigned long           pa_brdaddr;      /* direción difusión protocol  */
  unsigned long           pa_dstaddr;/* dirección del otro en protocol P-P*/
  unsigned long           pa_mask;         /* máscara de red de protocol  */
  unsigned short          pa_alen;         /* protocol address len */

  struct dev_mc_list      *mc_list;        /* direcc. mac para M'cast */
  int                     mc_count;        /* No hay mcasts instalado */
  
  struct ip_mc_list       *ip_mc_list;     /* cadena de filtro de m'cast IP*/
  __u32                   tx_queue_len;    /* máx cant. tramas en cada cola*/
    
  /* For load balancing driver pair support */
  unsigned long           pkt_queue;       /* paquetes encolados      */
  struct device           *slave;          /* dispositivo esclavo       */
  struct net_alias_info   *alias_info; /* info de alias del dispo. ppal */
  struct net_alias        *my_alias;       /* dispositivos alias        */
  
  /* Apuntadores a los búferes de la interfaz. */
  struct sk_buff_head     buffs[DEV_NUMBUFFS];

  /* Apuntadores a las rutinas de servicio de la interfaz. */
  int                     (*open)(struct device *dev);
  int                     (*stop)(struct device *dev);
  int                     (*hard_start_xmit) (struct sk_buff *skb,
                                              struct device *dev);
  int                     (*hard_header) (struct sk_buff *skb,
                                          struct device *dev,
                                          unsigned short type,
                                          void *daddr,
                                          void *saddr,
                                          unsigned len);
  int                     (*rebuild_header)(void *eth, 
                                          struct device *dev,
                                          unsigned long raddr,
                                          struct sk_buff *skb);
  void                    (*set_multicast_list)(struct device *dev);
  int                     (*set_mac_address)(struct device *dev,
                                          void *addr);
  int                     (*do_ioctl)(struct device *dev, 
                                          struct ifreq *ifr,
                                          int cmd);
  int                     (*set_config)(struct device *dev,
                                          struct ifmap *map);
  void                    (*header_cache_bind)(struct hh_cache **hhp,
                                          struct device *dev, 
                                          unsigned short htype,
                                          __u32 daddr);
  void                    (*header_cache_update)(struct hh_cache *hh,
                                          struct device *dev,
                                          unsigned char *  haddr);
  int                     (*change_mtu)(struct device *dev,
                                          int new_mtu);
  struct iw_statistics*   (*get_wireless_stats)(struct device *dev);
};

device_struct

Las estructuras de datos device_struct se utilizan para registrar dispositivos de caracteres y de bloques (mantienen el nombre y el conjunto de operaciones de fichero que pueden utilizarse sobre este dispositivo). Cada miembro válido de los vectores chrdevs y blkdevs vectors representa un dispositivo de caracteres o de bloque, respectivamente

struct device_struct {
    const char * name;
    struct file_operations * fops;
};

file

Cada fichero, socket, etc. que está abierto, se representa mediante una estructura de datos file.

struct file {
  mode_t f_mode;
  loff_t f_pos;
  unsigned short f_flags;
  unsigned short f_count;
  unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
  struct file *f_next, *f_prev;
  int f_owner;         /* pid o -pgrp a quien se debe enviar SIGIO */
  struct inode * f_inode;
  struct file_operations * f_op;
  unsigned long f_version;
  void *private_data;  /* necesario para el controlador de tty,
                          y tal vez además para otros */
};

files_struct

La estructura de datos files_struct describelos ficheros que tiene abiertos un determinado proceso.

struct files_struct {
  int count;
  fd_set close_on_exec;
  fd_set open_fds;
  struct file * fd[NR_OPEN];
};

fs_struct

struct fs_struct {
  int count;
  unsigned short umask;
  struct inode * root, * pwd;
};

gendisk

La estructura de datos gendisk mantiene información sobre el disco rígido. Se utiliza durante la inicialización, cuando se encuentran los discos, y se los prueba para ver si hay particiones.

struct hd_struct {
    long start_sect;
    long nr_sects;
};

struct gendisk {
    int major;               /* número mayor del controlador */
    const char *major_name;  /* nombre del controlador mayor */
    int minor_shift;         /* cantidad de veces que se debe desplazar
                                el número menor para obtener el número
                                menor real */
    int max_p;               /* max. particiones por cada disposit. */
    int max_nr;              /* max. cant. de dispositivos reales   */

    void (*init)(struct gendisk *); 
                             /* Initialización llamada antes de que
                                hagamos nuestras cosas */
    struct hd_struct *part;  /* tabla de partición     */
    int *sizes;              /* tamaño del dispositivo en bloques, 
                                se copia a blk_size[] */
    int nr_real;             /* cantidad de dispositivos reales */

    void *real_devices;      /* uso interno */
    struct gendisk *next;
};

inode

La estructura de datos del VFS denominada inode mantiene la información sobre un fichero o directorio que reside en el disco.

struct inode {
    kdev_t                       i_dev;
    unsigned long                i_ino;
    umode_t                      i_mode;
    nlink_t                      i_nlink;
    uid_t                        i_uid;
    gid_t                        i_gid;
    kdev_t                       i_rdev;
    off_t                        i_size;
    time_t                       i_atime;
    time_t                       i_mtime;
    time_t                       i_ctime;
    unsigned long                i_blksize;
    unsigned long                i_blocks;
    unsigned long                i_version;
    unsigned long                i_nrpages;
    struct semaphore             i_sem;
    struct inode_operations      *i_op;
    struct super_block           *i_sb;
    struct wait_queue            *i_wait;
    struct file_lock             *i_flock;
    struct vm_area_struct        *i_mmap;
    struct page                  *i_pages;
    struct dquot                 *i_dquot[MAXQUOTAS];
    struct inode                 *i_next, *i_prev;
    struct inode                 *i_hash_next, *i_hash_prev;
    struct inode                 *i_bound_to, *i_bound_by;
    struct inode                 *i_mount;
    unsigned short               i_count;
    unsigned short               i_flags;
    unsigned char                i_lock;
    unsigned char                i_dirt;
    unsigned char                i_pipe;
    unsigned char                i_sock;
    unsigned char                i_seek;
    unsigned char                i_update;
    unsigned short               i_writecount;
    union {
        struct pipe_inode_info   pipe_i;
        struct minix_inode_info  minix_i;
        struct ext_inode_info    ext_i;
        struct ext2_inode_info   ext2_i;
        struct hpfs_inode_info   hpfs_i;
        struct msdos_inode_info  msdos_i;
        struct umsdos_inode_info umsdos_i;
        struct iso_inode_info    isofs_i;
        struct nfs_inode_info    nfs_i;
        struct xiafs_inode_info  xiafs_i;
        struct sysv_inode_info   sysv_i;
        struct affs_inode_info   affs_i;
        struct ufs_inode_info    ufs_i;
        struct socket            socket_i;
        void                     *generic_ip;
    } u;
};

ipc_perm

La estructura de datos ipc_perm describe los permisos de acceso de un objeto de IPC al estilo System V.
struct ipc_perm
{
  key_t  key;
  ushort uid;   /* euid y egid del propietario */
  ushort gid;
  ushort cuid;  /* euid y egid del creador */
  ushort cgid;
  ushort mode;  /* modos de acceso, vea indicadores de modo más abajo */
  ushort seq;   /* número de secuencia  */
};

irqaction

La estructura de datos irqaction se utiliza para describir los gestionadores de interrupción del sistema.

struct irqaction {
  void (*handler)(int, void *, struct pt_regs *);
  unsigned long flags;
  unsigned long mask;
  const char *name;
  void *dev_id;
  struct irqaction *next;
};

linux_binfmt

Cada uno de los formatos de ficheros binarios que entiende  se representa mediante una estructura de datos linux_binfmt.

struct linux_binfmt {
  struct linux_binfmt * next;
  long *use_count;
  int (*load_binary)(struct linux_binprm *, struct  pt_regs * regs);
  int (*load_shlib)(int fd);
  int (*core_dump)(long signr, struct pt_regs * regs);
};

mem_map_t

La estructura de datos mem_map_t (que se conoce también como page) se utiliza para mantener la información acerca de cada página de memoria física.

typedef struct page {
  /* esto debe ir primero (gestión del área libre) */
  struct page        *next;
  struct page        *prev;
  struct inode       *inode;
  unsigned long      offset;
  struct page        *next_hash;
  atomic_t           count;
  unsigned           flags;     /* indicadores atómicos, algunos
                                   posiblemente se actualizan 
                                   asincrónicamente */
  unsigned           dirty:16,
                     age:8;
  struct wait_queue  *wait;
  struct page        *prev_hash;
  struct buffer_head *buffers;
  unsigned long      swap_unlock_entry;
  unsigned long      map_nr;    /* page->map_nr == page - mem_map */
} mem_map_t;

mm_struct

La estructura de datos mm_struct se utiliza para describir la memoria virtual de un proceso o tarea.

struct mm_struct {
  int count;
  pgd_t * pgd;
  unsigned long context;
  unsigned long start_code, end_code, start_data, end_data;
  unsigned long start_brk, brk, start_stack, start_mmap;
  unsigned long arg_start, arg_end, env_start, env_end;
  unsigned long rss, total_vm, locked_vm;
  unsigned long def_flags;
  struct vm_area_struct * mmap;
  struct vm_area_struct * mmap_avl;
  struct semaphore mmap_sem;
};

pci_bus

Cada bus PCI del sistema se representa mediante un estructura de datos pci_bus.

struct pci_bus {
  struct pci_bus  *parent;     /* bus padre, adonde está este puente */
  struct pci_bus  *children;   /* cadena de puentes P2P en este bus */
  struct pci_bus  *next;       /* cadena de todos los buses PCI */

  struct pci_dev  *self;       /* disposit. puente, como lo ve el padre */
  struct pci_dev  *devices;    /* disposit. detrás de este puente */

  void    *sysdata;            /* gancho para extensiones dependientes
                                  del sistema */

  unsigned char  number;       /* número de bus */
  unsigned char  primary;      /* número del puente primario */
  unsigned char  secondary;    /* número del puente secundario */
  unsigned char  subordinate;  /* max número de buses subordinados */
};

pci_dev

Cada disositivo PCI del sistema, incluso los puentes PCI-PCI y PCI-ISA se representan mediante una estructura de datos pci_dev.

/*
 * Existe una estructura pci_dev para cada combinación
 * número-de-ranura/número-de-función:
 */
struct pci_dev {
  struct pci_bus  *bus;      /* bus sobre el cual está este dispositivo */
  struct pci_dev  *sibling;  /* próximo dispositivo en este bus  */
  struct pci_dev  *next;     /* cadena de todos los dispositivos */

  void    *sysdata;          /* gancho para extensiones dependientes
                                del sistema */

  unsigned int  devfn;       /* índice de dispositivo y función codificado */
  unsigned short  vendor;
  unsigned short  device;
  unsigned int  class;       /* 3 bytes: (base,sub,prog-if) */
  unsigned int  master : 1;  /* puesto a uno si el dispositivo es
                                capaz de ser maestro */
  /*
   * En teoría, el nivel de irq se puede leer desde el espacio de
   * configuración y todo debe ir bien.  Sin embargo, los viejos chips
   * PCI no manejan esos registros y retornan 0.  Por ejemplo, el chip 
   * Vision864-P rev 0 puede usar  INTA, pero retorna 0 en la línea de
   * interrupción y registro pin.  pci_init() inicializa este campo
   * con el valor  PCI_INTERRUPT_LINE y es trabajo de  pcibios_fixup()
   * cambiearlo si es necesario.  El campo no debe ser 0 a menos que
   * el dispositivo no tenga modo de generar interrupciones.
   */
  unsigned char  irq;        /* irq generada por este dispositivo */
};

request

Las estructuras de datos request se utilizan para realizar solicitudes a los dispositivos de bloques del sistema. Estas solicitudes son siempre para leer o escribir bloques de datos hacia o desde el caché del búfer.

struct request {
    volatile int rq_status;    
#define RQ_INACTIVE            (-1)
#define RQ_ACTIVE              1
#define RQ_SCSI_BUSY           0xffff
#define RQ_SCSI_DONE           0xfffe
#define RQ_SCSI_DISCONNECTING  0xffe0

    kdev_t rq_dev;
    int cmd;        /* READ o WRITE */
    int errors;
    unsigned long sector;
    unsigned long nr_sectors;
    unsigned long current_nr_sectors;
    char * buffer;
    struct semaphore * sem;
    struct buffer_head * bh;
    struct buffer_head * bhtail;
    struct request * next;
};

rtable

Cada estructura de datos rtable mantiene información sobre la ruta que se debe tomar para enviar paquetes a un IP dado. Las estructuras rtable se utilizan dentro del caché de rutas IP.

struct rtable 
{
    struct rtable     *rt_next;
    __u32             rt_dst;
    __u32             rt_src;
    __u32             rt_gateway;
    atomic_t          rt_refcnt;
    atomic_t          rt_use;
    unsigned long     rt_window;
    atomic_t          rt_lastuse;
    struct hh_cache   *rt_hh;
    struct device     *rt_dev;
    unsigned short    rt_flags;
    unsigned short    rt_mtu;
    unsigned short    rt_irtt;
    unsigned char     rt_tos;
};

semaphore

Los semáforos se utilizan para proteger estructuras de datos y regiones de código críticas.

struct semaphore {
    int count;
    int waking;
    int lock ;                /* para hacer que la comprobación de
                                «waking» sea atómica */
    struct wait_queue *wait;
};

sk_buff

La estructura de datos sk_buff se utiliza para describir los datos de red a medida que se mueven entre las capas de protocolo.

struct sk_buff 
{
  struct sk_buff      *next;       /* próximo búfer en la lista             */
  struct sk_buff      *prev;       /* Previo búfer en la lista              */
  struct sk_buff_head *list;       /* Listas en las que estamos             */
  int                 magic_debug_cookie;
  struct sk_buff      *link3;      /* enlace para las cadenas de
                                      búferes de nivel de protocolo IP      */
  struct sock         *sk;         /* Socket que es nuestro dueño           */
  unsigned long       when;        /* usado para calcular rtt               */
  struct timeval      stamp;       /* momento en el cual llegamos           */
  struct device       *dev;        /* Disposit. al cual llegamos/desde
                                      el cual partimos                     */
  union 
  {
      struct tcphdr   *th;
      struct ethhdr   *eth;
      struct iphdr    *iph;
      struct udphdr   *uh;
      unsigned char   *raw;
      /* para pasar «handles» de ficheros en un socket de dominio unix */
      void            *filp;
  } h;
  
  union 
  {    
      /* vista de la capa física, hasta aquí incompleta */
      unsigned char   *raw;
      struct ethhdr   *ethernet;
  } mac;
  
  struct iphdr        *ip_hdr;     /* Para IPPROTO_RAW                      */
  unsigned long       len;         /* longitud de los datos actuales        */
  unsigned long       csum;        /* suma de comprobación                  */
  __u32               saddr;       /* dirección IP fuente                   */
  __u32               daddr;       /* dirección IP destino                  */
  __u32               raddr;       /* dirección IP del próximo salto        */
  __u32               seq;         /* número de secuencia TCP               */
  __u32               end_seq;     /* seq [+ fin] [+ syn] + largo de datos  */
  __u32               ack_seq;     /* número de secuencia del ack TCP       */
  unsigned char       proto_priv[16];
  volatile char       acked,       /* Are we acked ?                        */
                      used,        /* Are we in use ?                       */
                      free,        /* Cuando liberar este búfer             */
                      arp;         /* ha terminado la resolución IP/ARP?    */
  unsigned char       tries,       /* Veces ya intentado                    */
                      lock,        /* Are we locked ?                       */
                      localroute,  /* Local routing asserted for this frame */
                      pkt_type,    /* clase de paquete                      */
                      pkt_bridged, /* Tracker for bridging                  */
                      ip_summed;   /* Driver fed us an IP checksum          */
#define PACKET_HOST         0        /* para nosotros                       */
#define PACKET_BROADCAST    1        /* para todos                          */
#define PACKET_MULTICAST    2        /* para grupo                          */
#define PACKET_OTHERHOST    3        /* para algún otro                     */
  unsigned short      users;       /* contador de usuarios,vea datagram.c,tcp.c     */
  unsigned short      protocol;    /* prot. del paquete desde controlador   */
  unsigned int        truesize;    /* Tamaño del búfer                      */
  atomic_t            count;       /* cuenta de referencia                  */
  struct sk_buff      *data_skb;   /* Link to the actual data skb           */
  unsigned char       *head;       /* Cabeza del búfer                      */
  unsigned char       *data;       /* Apuntador a los datos en cabeza       */
  unsigned char       *tail;       /* Apuntador a la cola                   */
  unsigned char       *end;        /* Apuntador al final                    */
  void                (*destructor)(struct sk_buff *); /* función destructora */
  __u16               redirport;   /* Redirect port                         */
};

sock

Cada estructura de datos sock mantiene información específica de protocolo con referencia a un socket BSD. Por ejemplo, para un socket INET (Internet Address Domain) esta estructura de datos debería tener toda la información específica de TCP/IP y UDP/IP.

struct sock 
{
    /* This must be first. */
    struct sock             *sklist_next;
    struct sock             *sklist_prev;

    struct options          *opt;
    atomic_t                wmem_alloc;
    atomic_t                rmem_alloc;
    unsigned long           allocation;       /* Allocation mode */
    __u32                   write_seq;
    __u32                   sent_seq;
    __u32                   acked_seq;
    __u32                   copied_seq;
    __u32                   rcv_ack_seq;
    unsigned short          rcv_ack_cnt;      /* count of same ack */
    __u32                   window_seq;
    __u32                   fin_seq;
    __u32                   urg_seq;
    __u32                   urg_data;
    __u32                   syn_seq;
    int                     users;            /* user count */
  /*
   *    Not all are volatile, but some are, so we
   *     might as well say they all are.
   */
    volatile char           dead,
                            urginline,
                            intr,
                            blog,
                            done,
                            reuse,
                            keepopen,
                            linger,
                            delay_acks,
                            destroy,
                            ack_timed,
                            no_check,
                            zapped,
                            broadcast,
                            nonagle,
                            bsdism;
    unsigned long           lingertime;
    int                     proc;

    struct sock             *next;
    struct sock             **pprev;
    struct sock             *bind_next;
    struct sock             **bind_pprev;
    struct sock             *pair;
    int                     hashent;
    struct sock             *prev;
    struct sk_buff          *volatile send_head;
    struct sk_buff          *volatile send_next;
    struct sk_buff          *volatile send_tail;
    struct sk_buff_head     back_log;
    struct sk_buff          *partial;
    struct timer_list       partial_timer;
    long                    retransmits;
    struct sk_buff_head     write_queue,
                            receive_queue;
    struct proto            *prot;
    struct wait_queue       **sleep;
    __u32                   daddr;
    __u32                   saddr;            /* Sending source */
    __u32                   rcv_saddr;        /* Bound address */
    unsigned short          max_unacked;
    unsigned short          window;
    __u32                   lastwin_seq;      /* sequence number when we last 
                                                 updated the window we offer */
    __u32                   high_seq;         /* sequence number when we did 
                                                 current fast retransmit */
    volatile unsigned long  ato;              /* ack timeout */
    volatile unsigned long  lrcvtime;         /* jiffies at last data rcv */
    volatile unsigned long  idletime;         /* jiffies at last rcv */
    unsigned int            bytes_rcv;
/*
 *    mss is min(mtu, max_window) 
 */
    unsigned short          mtu;              /* mss negotiated in the syn's */
    volatile unsigned short mss;              /* current eff. mss - can change */
    volatile unsigned short user_mss;         /* mss requested by user in ioctl */
    volatile unsigned short max_window;
    unsigned long           window_clamp;
    unsigned int            ssthresh;
    unsigned short          num;
    volatile unsigned short cong_window;
    volatile unsigned short cong_count;
    volatile unsigned short packets_out;
    volatile unsigned short shutdown;
    volatile unsigned long  rtt;
    volatile unsigned long  mdev;
    volatile unsigned long  rto;
 
    volatile unsigned short backoff;
    int                     err, err_soft;    /* Soft holds errors that don't
                                                 cause failure but are the cause
                                                 of a persistent failure not
                                                 just 'timed out' */
    unsigned char           protocol;
    volatile unsigned char  state;
    unsigned char           ack_backlog;
    unsigned char           max_ack_backlog;
    unsigned char           priority;
    unsigned char           debug;
    int                     rcvbuf;
    int                     sndbuf;
    unsigned short          type;
    unsigned char           localroute;       /* Route locally only */
/*
 *    This is where all the private (optional) areas that don't
 *    overlap will eventually live. 
 */
    union
    {
          struct unix_opt   af_unix;
#if defined(CONFIG_ATALK) || defined(CONFIG_ATALK_MODULE)
        struct atalk_sock   af_at;
#endif
#if defined(CONFIG_IPX) || defined(CONFIG_IPX_MODULE)
        struct ipx_opt      af_ipx;
#endif
#ifdef CONFIG_INET
        struct inet_packet_opt  af_packet;
#ifdef CONFIG_NUTCP        
        struct tcp_opt      af_tcp;
#endif        
#endif
    } protinfo;          
/* 
 *    IP 'private area'
 */
    int                     ip_ttl;           /* TTL setting */
    int                     ip_tos;           /* TOS */
    struct tcphdr           dummy_th;
    struct timer_list       keepalive_timer;  /* TCP keepalive hack */
    struct timer_list       retransmit_timer; /* TCP retransmit timer */
    struct timer_list       delack_timer;     /* TCP delayed ack timer */
    int                     ip_xmit_timeout;  /* Why the timeout is running */
    struct rtable           *ip_route_cache;  /* Cached output route */
    unsigned char           ip_hdrincl;       /* Include headers ? */
#ifdef CONFIG_IP_MULTICAST  
    int                     ip_mc_ttl;        /* Multicasting TTL */
    int                     ip_mc_loop;       /* Loopback */
    char                    ip_mc_name[MAX_ADDR_LEN]; /* Multicast device name */
    struct ip_mc_socklist   *ip_mc_list;      /* Group array */
#endif  

/*
 *    This part is used for the timeout functions (timer.c). 
 */
    int                      timeout;         /* What are we waiting for? */
    struct timer_list        timer;           /* This is the TIME_WAIT/receive 
                                               * timer when we are doing IP
                                               */
    struct timeval           stamp;
 /*
  *    Identd 
  */
    struct socket            *socket;
  /*
   *    Callbacks 
   */
    void                     (*state_change)(struct sock *sk);
    void                     (*data_ready)(struct sock *sk,int bytes);
    void                     (*write_space)(struct sock *sk);
    void                     (*error_report)(struct sock *sk);
  
};

socket

Cada estructura de datos socket mantiene información sobre un socket BSD. No existe de manera independiente; sino que es parte de la estructura de datos inode (del VFS).

struct socket {
  short                type;         /* SOCK_STREAM, ...             */
  socket_state         state;
  long                 flags;
  struct proto_ops     *ops;         /* protocols do most everything */
  void                 *data;        /* protocol data                */
  struct socket        *conn;        /* server socket connected to   */
  struct socket        *iconn;       /* incomplete client conn.s     */
  struct socket        *next;
  struct wait_queue    **wait;       /* ptr to place to wait on      */
  struct inode         *inode;
  struct fasync_struct *fasync_list; /* Asynchronous wake up list    */
  struct file          *file;        /* File back pointer for gc     */
};

task_struct

Cada estructura de datos task_struct describe un proceso o tarea en el sistema.
struct task_struct {
/* these are hardcoded - don't touch */
  volatile long        state;          /* -1 unrunnable, 0 runnable, >0 stopped */
  long                 counter;
  long                 priority;
  unsigned             long signal;
  unsigned             long blocked;   /* bitmap of masked signals */
  unsigned             long flags;     /* per process flags, defined below */
  int errno;
  long                 debugreg[8];    /* Hardware debugging registers */
  struct exec_domain   *exec_domain;
/* various fields */
  struct linux_binfmt  *binfmt;
  struct task_struct   *next_task, *prev_task;
  struct task_struct   *next_run,  *prev_run;
  unsigned long        saved_kernel_stack;
  unsigned long        kernel_stack_page;
  int                  exit_code, exit_signal;
  /* ??? */
  unsigned long        personality;
  int                  dumpable:1;
  int                  did_exec:1;
  int                  pid;
  int                  pgrp;
  int                  tty_old_pgrp;
  int                  session;
  /* boolean value for session group leader */
  int                  leader;
  int                  groups[NGROUPS];
  /* 
   * pointers to (original) parent process, youngest child, younger sibling,
   * older sibling, respectively.  (p->father can be replaced with 
   * p->p_pptr->pid)
   */
  struct task_struct   *p_opptr, *p_pptr, *p_cptr, 
                       *p_ysptr, *p_osptr;
  struct wait_queue    *wait_chldexit;  
  unsigned short       uid,euid,suid,fsuid;
  unsigned short       gid,egid,sgid,fsgid;
  unsigned long        timeout, policy, rt_priority;
  unsigned long        it_real_value, it_prof_value, it_virt_value;
  unsigned long        it_real_incr, it_prof_incr, it_virt_incr;
  struct timer_list    real_timer;
  long                 utime, stime, cutime, cstime, start_time;
/* mm fault and swap info: this can arguably be seen as either
   mm-specific or thread-specific */
  unsigned long        min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
  int swappable:1;
  unsigned long        swap_address;
  unsigned long        old_maj_flt;    /* old value of maj_flt */
  unsigned long        dec_flt;        /* page fault count of the last time */
  unsigned long        swap_cnt;       /* number of pages to swap on next pass */
/* limits */
  struct rlimit        rlim[RLIM_NLIMITS];
  unsigned short       used_math;
  char                 comm[16];
/* file system info */
  int                  link_count;
  struct tty_struct    *tty;           /* NULL if no tty */
/* ipc stuff */
  struct sem_undo      *semundo;
  struct sem_queue     *semsleeping;
/* ldt for this task - used by Wine.  If NULL, default_ldt is used */
  struct desc_struct *ldt;
/* tss for this task */
  struct thread_struct tss;
/* filesystem information */
  struct fs_struct     *fs;
/* open file information */
  struct files_struct  *files;
/* memory management info */
  struct mm_struct     *mm;
/* signal handlers */
  struct signal_struct *sig;
#ifdef __SMP__
  int                  processor;
  int                  last_processor;
  int                  lock_depth;     /* Lock depth. 
                                          We can context switch in and out
                                          of holding a syscall kernel lock... */  
#endif   
};

timer_list

Las estructuras de datos timer_list se utilizan para implantar temporizadores de tiempo real para los procesos.
struct timer_list {
  struct timer_list *next;
  struct timer_list *prev;
  unsigned long expires;
  unsigned long data;
  void (*function)(unsigned long);
};

tq_struct

Cada estructura de datos de cola de tarea (tq_struct) mantiene información acerca del trabajo que se ha puesto en cola. En general se trata de trabajos que necesita realizar un controlador de dispositivos, pero que no tiene que hacerse de inmediato.
struct tq_struct {
    struct tq_struct *next;   /* linked list of active bh's */
    int sync;                 /* must be initialized to zero */
    void (*routine)(void *);  /* function to call */
    void *data;               /* argument to function */
};

vm_area_struct

Cada estructura de datos vm_area_struct describe un área memoria virtual para un proceso

struct vm_area_struct {
  struct mm_struct * vm_mm;  /* VM area parameters */
  unsigned long vm_start;
  unsigned long vm_end;
  pgprot_t vm_page_prot;
  unsigned short vm_flags;
/* AVL tree of VM areas per task, sorted by address */
  short vm_avl_height;
  struct vm_area_struct * vm_avl_left;
  struct vm_area_struct * vm_avl_right;
/* linked list of VM areas per task, sorted by address */
  struct vm_area_struct * vm_next;
/* for areas with inode, the circular list inode->i_mmap */
/* for shm areas, the circular list of attaches */
/* otherwise unused */
  struct vm_area_struct * vm_next_share;
  struct vm_area_struct * vm_prev_share;
/* more */
  struct vm_operations_struct * vm_ops;
  unsigned long vm_offset;
  struct inode * vm_inode;
  unsigned long vm_pte;      /* shared mem */
};

Appendix B
El procesador AXP de Alpha

La arquitectura Alpha AXP es una arquitectura RISC con carga/almacenamiento de 64 bits, diseñada con la mira puesta en la velocidad. Todos los registros son de 64 bits de longitud; hay 32 registros enteros y 32 de coma flotante. El registro entero 31 y el coma flotante 31 se utilizan para operaciones nulas: un lectura desde ellos genera un valor cero, y la escritura en los mismos no tiene ningún efecto. Todas las instrucciones son de 32 bits de longitud, y las operaciones con memoria son bien lecturas, bien escrituras. La arquitectura permite diferentes implementaciones, siempre que las mismas sigan las líneas fijadas por la arquitectura.

No existen instrucciones que operan directamente sobre valores almacenados en memoria; toda la manipulación de datos se hace sobre los registros. Así que si Ud. desea incrementar un contador en memoria, primero lo debe leer en un registro, luego lo modifica y lo escribe en la memoria. Las instrucciones sólo interactúan entre ellas si una instrucción escribe en un registro o una posición de memoria, y otra instrucción lee el mismo registro o posición de memoria. Una característica interesante del Alpha AXP es que hay instrucciones que generan indicadores ("<flags">) -por ejemplo al controlar si dos registros son iguales-pero en lugar de almacenar el resultado de la comparación en el registro de estado del procesador, lo almacenan en un tercer registro. Esto puede sonar extraño a primera vista, pero al quitar esta dependencia del registro de estado del procesador, es más fácil construir CPUs que puedan lanzar varias instrucciones en cada ciclo. Las instrucciones que se realizan sobre registros distintos no tienen que esperarse unas a otras, como sería el caso si dependieran del registro de estado del procesador. La ausencia de operaciones directas sobre memoria, y la gran cantidad de registros también ayudan al lanzamiento de múltiples instrucciones simultáneas.

La arquitectura Alpha AXP utiliza un conjunto de subrutinas, denominadas "<Privileged architecture library code"> (Código de biblioteca de arquitectura privilegiada) (PALcode). El PALcode es específico del sistema operativo, la implementación en la CPU de la arquitectura Alpha AXP, y el hardware del sistema. Estas subrutinas proveen primitivas del sistema operativo para el intercambio de contextos, interrupciones, excepciones y administración de memoria. Estas subrutinas pueden ser llamadas por el hardware o mediante las instrucciones CALL_PAL. El PALcode está escrito en ensamblador Alpha AXP estándar con algunas extensiones específicas de la implementación para proveer acceso directo a las funciones de hardware de bajo nivel, por ejemplo a los registros internos del procesador. El PALcode se ejecuta en modo PALmode, que es un modo privilegiado en el cual se impide que sucedan ciertos eventos del sistema, y le proporciona total control del hardware físico del sistema al código PALcode.

Appendix C
Lugares útiles en Web y FTP

Los siguientes lugares de la Red WWW y de FTP le serán de utilidad:

http://www.azstarnet.com/ [\tilde]axplinux
Este es el lugar en la web del  para Alpha AXP de David Mosberger-Tang, y es el lugar donde debe ir a buscar todos los HOWTOs sobre Alpha AXP. También tiene una gran cantidad de punteros hacia información sobre  y específica sobre Alpha AXP, como por ejemplo las hojas de datos de la CPU.
http://www.redhat.com/
El lugar de Red Ha en la web. Hay aquí un montón de punteros útiles.
ftp://sunsite.unc.edu
Este es el principal lugar para un montón de software libre. El software específico sobre  se encuentra en pub/Linux.
http://www.intel.com
El lugar de Intel en la web, y un buen lugar donde encontrar información sobre chips Intel.
http://www.ssc.com/lj/index.html
"<Linux Journal"> es una muy buena revista sobre  y bien vale la pena el precio de la suscripción anual para leer sus excelentes artículos.
http://www.blackdown.org/java-linux.html
Este es el lugar principal con respecto a la información sobre Java para .
ftp://tsx-11.mit.edu/ [\tilde]ftp/pub/linux
El lugar FTP sobre  del MIT.
ftp://ftp.cs.helsinki.fi/pub/Software/Linux/Kernel
Fuentes del núcleo de Linus.
http://www.linux.org.uk
El "<UK Linux User Group">.
http://sunsite.unc.edu/mdw/linux.html
Página principal para el "<Linux Documentation Project"> (Proyecto de documentación para ), al cual está afiliado LuCAS (http://lucas.ctv.es/) que se dedica a traducir al castellano -como en este caso- dicha documentación.
http://www.digital.com
El lugar en la web de "<Digital Equipment Corporation">
http://altavista.digital.com
La máquina de búsqueda Altavista es un producto de la empresa Digital, y un muy buen lugar para buscar información dentrl de la web y los grupos de discusión.
http://www.linuxhq.com
El lugar web "<Linux HQ"> contiene los actualizados parches tanto oficiales como no-oficiales, consejos, y punteros a la web que le ayudarán a conseguir el mejor conjunto de fuentes posibles para su sistema.

http://www.amd.com
El lugar de AMD en la web.
http://www.cyrix.com
El lugar de Cyrix en la web.

Appendix D
The GNU General Public License

Printed below is the GNU General Public License (the GPL or copyleft), under which Linux is licensed. It is reproduced here to clear up some of the confusion about Linux's copyright status-Linux is not shareware, and it is not in the public domain. The bulk of the Linux kernel is copyright © 1993 by Linus Torvalds, and other software and parts of the kernel are copyrighted by their authors. Thus, Linux is copyrighted, however, you may redistribute it under the terms of the GPL printed below.



GNU GENERAL PUBLIC LICENSE

Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

D.1  Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software-to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

D.2  Terms and Conditions for Copying, Distribution, and Modification

  1. [0.] This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The ``Program'', below, refers to any such program or work, and a ``work based on the Program'' means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term ``modification''.) Each licensee is addressed as ``you''.

    Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

  2. [1.] You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

    You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

  3. [2.] You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

    1. [a.] You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.

    2. [b.] You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.

    3. [c.] If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)

    These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

    Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

    In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

  4. [3.] You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:

    1. [a.] Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

    2. [b.] Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

    3. [c.] Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)

    The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

    If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

  5. [4.] You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

  6. [5.] You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

  7. [6.] Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

  8. [7.] If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.

    If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.

    It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

    This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

  9. [8.] If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

  10. [9.] The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

    Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and ``any later version'', you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

  11. [10.] If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.



    NO WARRANTY

  12. [11.] BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM ``AS IS'' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  13. [12.] IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

D.3  Appendix: How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the ``copyright'' line and a pointer to where the full notice is found.

Copyright © 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this when it starts in an interactive mode:

Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items-whatever suits your program.

You should also get your employer (if you work as a programmer) or your school, if any, to sign a ``copyright disclaimer'' for the program, if necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker.

, 1 April 1989 Ty Coon, President of Vice

This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.

Appendix E
Glosario

Apuntador
Ver Puntero.
Argumento
Funciones y rutinas pasan argumentos a los procesos.
ARP
Address Resolution Protocol (Protocolo de resolución de direcciones). Se utiliza para traducir direcciones IP a su correspondiente dirección de hardware físico.
Ascii
American Standard Code for Information Interchange. Cada letra del alfabeto se representa mediante un código de 8 bits19. En general se utilizan para almacenar carácteres de escritura.
Bit
Pequeña porción de datos que puede tener valor 0 ó 1 (encendido o apagado)
Bottom Half Handler
Manejadores para el trabajo que se pone en cola dentro del núcleo.
Byte
8 bits de datos.
C
Lenguaje de programación de alto nivel. La mayor parte del núcleo de Linux está escrito en C.
Controlador de dispositivos
Se dice del software que controla un determinado dispositivo, por ejemplo el controlador de dispositivo NCR 810 controla el dispositivo SCSI NCR 810.
Cola de tareas
Mecanismo que permite diferir trabajo dentro del núcleo de .
CPU
Central Processing Unit (Unidad central de procesamiento). El motor principal de la computadora, vea también microprocesador y procesador.
DMA
Direct Memory Access. (Acceso a memoria directo).
Estructura de datos
Conjunto de datos en memoria, que se compone de varios campos.
EIDE
Extended IDE (IDE Extendido).
ELF
Executable and Linkable Format (Formato ejecutable y enlazable). Este formato de ficheros objeto fue diseñado por los "<Unix System Laboratories"> y está en la actualidad firmemente establecido como el formato más usado en .
Fichero objeto
Se dice de un fichero que contiene código de máquina y datos que aún no ha sido sometido al proceso de edición de enlaces (o enlazado) para transformarlo en una imagen ejecutable.
Función
Una parte de un producto de software que realiza una cierta acción. Por ejemplo, retorna el mayor enrte dos números.
IDE
Integrated Disk Electronics (Electrónica de disco integrada).
Interfaz
Manera estándar de llamar rutinas y pasar estructuras de datos. Por ejemplo, la interfaz entre dos capas de código puede expresarse en términos de las rutinas que pasan y retornan una determinada estructura de datos. El sistema virtual de ficheros (VFS) de  es un buen ejemplo de interfaz.
Imagen
Vea imagen ejecutable.
Imagen ejecutable
Fichero estructurado que contiene datos e instrucciones en lenguaje de máquina. Este fichero puede cargarse en la memoria virtual de un proceso y ejecutarse. Vea además programa.
IP
Internet Protocol (Protocolo de Internet).
IPC
Interprocess Communication (Comunicación entre procesos).
IRQ
Interrupt Request Queue (Cola de solicitudes de interrupción)..
ISA
Industry Standard Architecture (Arquitectura estándar en la industria). Se trata de un estándar, aunque al momento es un tanto antiguo, para las interfaces de bus de datos para los componentes del sistema tales como controladores de disquete.
Kilobyte
Millar de bytes, frecuentemente escrito como Kbyte20
Megabyte
Un millón de bytes, en general escrito como Mbyte21
Memoria virtual
Mecanismo de hardware y software que hace aparecer como que la memoria de un sistema es más grande de lo que en realidad es.
Microprocesador
CPU altamente integrada. La mayoría de las modernas CPUs son Microprocesadores.
Módulo
Fichero que contiene instrucciones para la CPU en la forma de código en lenguaje ensamblador, o en algún lenguaje de alto nivel como C.
Módulo del núcleo
Funcionalidad del núcleo que se carga dinámicamente, como por ejemplo un sistema de ficheros, o un controlador de dispositivo.
Página
La memoria física se divide en páginas de igual tamaño.
PCI
Peripheral Component Interconnect (Interconexión de componentes periféricos). Estándar que describe cómo los componentes periféricos de un sistema de cómputo pueden interconectarse entre sí.
Periférico
Procesador inteligente que trabaja on ***behalf*** de la CPU del sistema. Por ejemplo, el controlador IDE.
Procesador
Forma breve de la expresión "<Microprocesador">, equivalente a CPU.
Proceso
Entidad que puede ejecutar programas. Un proceso puede pensarse como si fuera un programa en acción.
Programa
Conjunto coherente de instrucciones de CPU que "<Hola mundo">. Vea también imagen ejecutable .
Protocolo
Especie de lenguaje de red que se utiliza para transferir datos de aplicaciones entre dos procesos que cooperan entre sí, o entre dos capas de la red.
Puntero
Una posición de memoria que contiene la dirección de otra posición de memoria.
Registro
Lugar dentro de un chip que se utiliza para almacenar información o instrucciones.
Rutina
Similar a una función, excepto que si nos expresamos conpropiedad, las rutinas no retornan valores.
SCSI
Small Computer Systems Interface (Interfaz para sistemas de computación pequeños).
Shell
(Cáscara, intérprete de órdenes) Este es el programa que actúa de interfaz entre el sistema operativo y el usuario humano. También se lo llama en inglés command shell. El intérprete de órdenes más utilizado en  es bash.
SMP
Symmetrical multiprocessing (Multiprocesamiento simétrico). Sistemas con más de un procesador que comparten por partes iguales la carga de trabajo entre los procesadores existentes.
Socket
Punto extremo de una conección de red.  provee la interfaz denominada "<BSD Socket interface"> (BSD proviene de "Berkeley Software Distribution">).
Software
Instrucciones para la CPU (tanto en ensamblador como en otros lenguajes de alto nivel como C) y datos. En la mayoría de los casos se trata de u término intercambiable con programa.
System V
Variante de Unix que se produjo en 1983, y que incluye, entre otras cosas, los mecanismos de comunicación entre procesos que le son característicos, denominados System V IPC.
TCP
Transmission Control Protocol (Protocolo de control de transmisión).
UDP
User Datagram Protocol (Protocolo de datagramas de usuario).

Bibliografía

[]
Richard L. Sites. Alpha Architecture Reference Manual Digital Press
[]
Matt Welsh y Lar Kaufman. Running Linux O'Reilly & Associates, Inc, ISBN 1-56592-100-3
[]
PCI Special Interest Group PCI Local Bus Specification
[]
PCI Special Interest Group PCI BIOS ROM Specification
[]
PCI Special Interest Group PCI to PCI Bridge Architecture Specification
[]
Intel Peripheral Components Intel 296467, ISBN 1-55512-207-8
[]
Brian W. Kernighan y Dennis M. Richie The C Programming Language Prentice Hall, ISBN 0-13-110362-8
[]
Steven Levy Hackers Penguin, ISBN 0-14-023269-9
[]
Intel Intel486 Processor Family: Programmer's Reference Manual Intel
[]
Comer D. E. Interworking with TCP/IP, Volume 1 - Principles, Protocols and Architecture Prentice Hall International Inc
[]
David Jagger ARM Architectural Reference Manual Prentice Hall, ISBN 0-13-736299-4

Index

/proc file system, 121
_PAGE_ACCESSED, bit en Alpha AXP PTE, 22
_PAGE_DIRTY, bit en Alpha AXP PTE, 22

all_requests, list of request data structures, 91
Alpha AXP PTE, 21
Alpha AXP, arquitectura, 181
Altair 8080, 1
arp_table data structure, 138, 139
arp_tables vector, 138
Asignación de páginas, 25
awk command, 157

backlog queue, 135, 136
bash command, 15, 16, 26, 195
bdflush, kernel daemon, 120
bg command, 50
bh_active bitmask, 143
bh_base vector, 143
bh_mask bitmask, 143
Bibliotecas Compartidas, ELF, 53
Bibliotecas ELF Compartidas, 53
Binario, 3
Bind, 131
blk_dev vector, 91, 93, 94, 98, 161
blk_dev_struct data structure, 91
blkdevs
     vector, 164
blkdevs vector, 90, 94, 98
block_dev_struct data structure, 161
bloqueada data structure, 56
bloqueado data structure, 56, 57
Bottom Half Handling, 143
BSD Sockets, 126
BSD Sockets, creating, 131
Buffer caches, 119
buffer_head data structure, 91, 119, 161
builtin_scsi_hosts vector, 96
Buzz locks, 147

C, Lenguaje de Programación, 8
Cache de Intercambio, 22, 33
Cache de Páginas, 22, 28
Cache, intercambio, 22
Caches, buffer, 119
Caches, directory, 118
Caches, VFS inode, 117
Carga en demanda, 51
cat command, 88, 112, 121
cd command, 50
character devices, 89
chrdevs
     vector, 164
chrdevs vector, 89, 90
Control de Acceso de Páginas, 20
Controladores de Interrupciones Programables, 81
Controladores de Unidad, 11
CPU, 3
Creación de Procesos, 48
Creating a file, 117
current data structure, 38
current process, 88

Data fragmentation, 137
Data structures, EXT2 Directory, 108
Data structures, EXT2 inode, 106
Demand Paging, 18
Demanda, Paginación, 27
Demonio de Intercambio, 29
dev_base data structure, 135
dev_base list pointer, 100
device data structure, 98-101, 135-139, 162
Device Drivers, polling, 86
Device Special Files, 121
device_struct data structure, 89, 90, 164
DIGITAL, iii
Direcciones del núcleo, Alpha AXP, 20
Direct Memory Access (DMA), 87
Directory cache, 118
Disks, IDE, 94
Disks, SCSI, 95
DMA, Direct Memory Access, 87
dma_chan data structure, 88

Ejecución de Programas, 50
ELF, 51
Enlazadores, 9
Ensamblador, Lenguaje, 7
Envejecimiento de páginas, 32
estructura de datos free_area, 25
estructura de datos mm_struct, 46
estructura de datos msg, 60
Estructura de datos msqid_ds, 60
Estructura de datos, shmid_ds, 63
EXT, 104
EXT2, 104, 105
EXT2 Block Groups, 106
EXT2 Directories, 108
EXT2 Group Descriptor, 108
EXT2 Inode, 106
EXT2 Superblock, 107
Extended File system, 104

fd data structure, 46
fd vector, 131
fdisk command, 92, 93, 103
fib_info data structure, 140
fib_node data structure, 140
fib_zone data structure, 140
fib_zones vector, 140
fichero data structure, 57, 58
Ficheros, 44, 57
ficheros data structure, 59
Ficheros de Guión, 53
Ficheros, Sistema de, 11
file data structure, 45, 46, 90, 131, 165
File system, 103
File System, mounting, 115
File System, registering, 114
File System, unmounting, 117
Files, creating, 117
Files, finding, 117
files_struct data structure, 45, 165
Finding a File, 117
first_inode data structure, 118
Free Software Foundation, iv
free_area vector, 33
free_area vector, 25
free_area vector, 25
free_area vector, 24-26, 30, 33
free_area, estructura de datos, 25
fs_struct data structure, 44

GATED daemon, 139
gendisk data structure, 93-95, 98, 165
Gestión de Memoria, 10
GNU, iv

Hard disks, 92
Hexadecimal, 3
hh_cache data structure, 138

IDE disks, 94
ide_drive_t data structure, 95
ide_hwif_t data structure, 94, 95
ide_hwifs vector, 94
Identificadores, 40
Identificadores de derechos, 40
ifconfig command, 100, 131
INET socket layer, 129
init_task data structure, 48
Initializing Network devices, 100
Initializing the IDE subsystem, 94
Inode cache, 117
inode data structure, 28, 131, 166, 176
inode, VFS, 90, 113
insmod, 150
insmod command, 114, 149-152
Intercambiando y Descartando Páginas, 32
Intercambio, 29, 32
Intercambio, Cache, 33
Intercambio, Demonio, 29
IP routing, 139
ip_rt_hash_table table, 139
IPC, Mecanismos de Comunicacion Interprocesos, 55
ipc_perm data structure, 59, 60, 158, 167
ipc_perm estructura de datos, 59
ipfrag data structure, 137
ipq data structure, 137
ipqueue list, 137
irq_action, 83
irqaction data structure, 82-84, 167

jiffies, 39, 42, 49, 145, 146

kdev_t data type, 122
Kernel daemon, the, 150
Kernel daemons, bdflush, 120
Kernel, monolithic, 149
kerneld, 149
kerneld, the kernel daemon, 150
kill -l command, 55
kill command, 39
ksyms command, 151

last_processor data structure, 44
ld command, 51
Lenguaje de Programación C, 8
Lenguaje Ensamblador, 7
Liberación de páginas, 26
linux_binfmt data structure, 168
Locks, buzz, 147
lpr command, 57
ls command, 9, 10, 46, 57, 112, 122
lsmod command, 150, 152, 153

makefifo command, 59
tilde no, 30
Mecanismos de Comunicacion Interprocesos (IPC), 55
mem_map data structure, 24
mem_map page vector, 30, 31
mem_map_t data structure, 24, 28, 168
mem_map_tt data structure, 32
Memoria Compartida, 63
Memoria virtual compartida, 20
Memoria Virtual, Procesos, 46
Memoria, compartida, 63
Memoria, Gestión de, 10
Memoria, Proyeccuón, 26
Microprocesador, 3
Minix, iii, 104
mke2fs command, 105
mknod command, 85, 99, 126
mm_struct data structure, 26, 32, 46, 49, 52, 168
Modelo Abstracto de Memoria Virtual, 16
module data structure, 151-153
module_list data structure, 151
Modules, 149
Modules, demand loading, 150
Modules, loading, 150
Modules, unloading, 153
mount command, 107, 115, 116
Mounting a File System, 115
mru_vfsmnt pointer, 116
msg data structure, 60
msgque vector, 60
msqid_ds data structure, 60
Multiproceso, 10

Número de Marco de Página, 17
número de marco de página, 21
Network devices, 98
Network devices, initializing, 100

Páginas, Cache, 22, 28
Páginas, envejecimiento, 32
packet_type data structure, 136, 138
page data structure, 24, 168
page_hash_table, 28
Paginación por Demanda, 27
PALcode, 181
Paradis, Jim, iv
patch command, 156
PC, 1
PCI, 65
PCI, Address Spaces, 65
PCI, Base Address Registers, 76
PCI, BIOS Functions, 74
PCI, Configuration Headers, 66
PCI, Fixup Code, 76
PCI, I/O, 68
PCI, Interrupt Routing, 68
PCI, Linux Initialisation of, 70
PCI, Linux PCI Psuedo Device Driver, 72
PCI, Memory, 68
PCI, Overview, 65
PCI-PCI Bridges, 69
PCI-PCI Bridges, Bus Numbering, 69
PCI-PCI Bridges, Bus Numbering Example, 72
PCI-PCI Bridges: The Bus Numbering Rule, 70
PCI: Type 0 and Type 1 Configuration cycles, 69
pci_bus data structure, 71, 72, 77, 169
pci_dev data structure, 71, 72, 77, 169
pci_devices data structure, 72
PDP-11/45, iii
PDP-11/70, iii
PDP-7, iii
perl command, 53
PFN, 17
PFN, Page Frame Number, 17
Planificación, 41
Planificación en Sistemas Multiprocesador, 44
Planificación SMP, 44
planificador, 42
pol í ticas data structure, 42
Polling, Device Drivers, 86
pops vector, 129
porción de tiempo, 42
pr command, 57
priority data structure, 42
Procesador, 3
Proceso, Memoria Virtual del, 46
Procesos, 10, 37, 38
processor data structure, 44
processor_mask data structure, 44
proto_ops data structure, 129, 131
protocols vector, 129
Proyección de Memoria, 26
ps command, 10, 39, 120
pstree command, 39
PTE, Alpha AXP, 21
ptype_all list, 136
ptype_base hash table, 136
pwd command, 40, 50

Registering a file system, 114
renice command, 42
request data structure, 91, 98, 170
Richie, Dennis, iii
rmmod command, 149, 150, 153
Routing, IP, 139
rscsi_disks vector, 98
rtable data structure, 136, 139, 140, 170

SCSI disks, 95
SCSI, initializing, 96
Scsi_Cmd data structure, 98
Scsi_Cmnd data structure, 97
Scsi_Device data structure, 97, 98
Scsi_Device_Template data structure, 98
scsi_devicelist list, 98
scsi_devices list, 97
Scsi_Disk data structure, 98
Scsi_Host data structure, 97, 98
Scsi_Host_Template data structure, 96, 97
scsi_hostlist list, 97
scsi_hosts list, 97
Scsi_Type_Template data structure, 98
tilde nales, 55
tilde nal data structure, 56
Second Extended File system, 104
sem data structure, 61
sem_queue data structure, 62
sem_undo data structure, 62, 63
Semaforos, 61
Semaforos System V, 61
Semaforos, System V, 61
semaphore data structure, 147
Semaphores, 147
semary data structure, 61
semid_ds data structure, 61, 62
semid_ds, estructura de datos, 61
Shells, 50
shells de órdenes, 50
shm_segs data structure, 63
shmid_ds data structure, 31, 32, 63, 64
sigaction data structure, 56, 57
Sistema de Ficheros, 11
Sistema Operativo, 9
sistema_ficheros data structure, 115
sistemas_ficheros data structure, 115
sk_buff data structure, 99, 100, 133-138, 159, 171
sk_buffs data structure, 134, 135
sock data structure, 129, 131-134, 172
sockaddr data structure, 128, 131
socket data structure, 129, 131-134, 176
Socket layer, INET, 129
Sockets, BSD, 126
Spin locks, see Buzz locks, 147
Star Trek, 1
super_bloque data structure, 116
super_bloques data structure, 116
Superblock, VFS, 113
swap_control data structure, 32
Swapping, 19
system clock, 145

Tablas de Páginas, 23
task data structure, 38, 39, 44, 48
Task Queues, 144
task_struct data structure, 38-44, 46, 48-50, 56, 62, 63, 146, 157, 176
task_struct estructura de datos, 38
tcp_bound_hash table, 133
tcp_listening_hash table, 133
tcsh command, 53
Thompson, Ken, iii
timer_active bit mask, 145
timer_list data structure, 50, 138, 145, 146, 178
timer_struct data structure, 145
Timers, 145
tipo_sistema_ficheros data structure, 113, 115, 116
tipo_sistems_ficheros data structure, 115
tk command, 157
TLB, translation lookaside buffer, 22
Torvalds, Linus, iii
tq_immediate task queue, 144
tq_struct data structure, 144, 145, 178
Translation lookaside buffer, 22

umask data structure, 44
Unidad, Controladores de, 11
Unmounting a File System, 117
upd_hash table, 132
update command, 120, 121
update process, 121

vector free_area, 24
vector de grupos, 40
vector free_area, 24
VFS, 104, 111
VFS inode, 90, 113
VFS superblock, 113
vfsmntlist data structure, 116, 117
vfsmnttail data structure, 116
vfsmount data structure, 116, 117
Virtual File system, 104
Virtual File System (VFS), 111
vm_area_struct data structure, 26-28, 31-34, 46-49, 51, 52, 63, 64, 178
vm_next data structure, 32
vm_ops data structure, 47

wish command, 53


Footnotes:

1 N. del T.: en inglés multi-task

2 Un COMO es, como dice la palabra, un documento que describe como hacer algo. En inglés se denominan HOWTOs.

3 N.T.: En el i386 un fallo de una TLB no produce una invocación al procesador, sino que el i386 accede a memoria principal para buscar la entrada en la tabla correspondiente. Si al acceder a memoria principal no encuentra una entrada valida, entonces sí se produce un Fallo de Página que es enviado al S.O. (mediante una interrupción).

4 Confusamente conocida como la estructura page

5 Bibliography reference here

6 NOTA DE REVISIÓN: SWAPPING no se incluye porque no parece que se use.

7 Imagínese una nuez: el núcleo es la parte comestible en el centro y la cáscara está alrededor, presentando una interfaz.

8 NOTA DE REVISIÓN: Explican los grupos de procesos.

9 For example?

10 Actualmente, el controlador de la disquetera es una de las interrupciones fijas en un sistema PC, ya que, por convenio, la controladora de la disquetera está siempre conectada a la interrupción 6.

11 Bueno, no con conocimiento, sin embargo me he topado con sistemas operativos con más abogados que programadores tiene Linux

12 REVISAR!!!!!

13 REVISAR!!!

14 REVISAR!!!!!

15 National Science Foundation

16 Synchronous Read Only Memory

17 duh? What used for?

18 NOTA DE REVISIÓN: What is to stop a task in state INTERRUPTIBLE being made to run the next time the scheduler runs? Processes in a wait queue should never run until they are woken up.

19 N. del T.: Tengo entendido que el código ASCII es de sólo 7 bits, pero el original expresaba 8.

20 N. del T.: Habitualmente se utiliza la "<k"> de múltiplo de miles, que en el Sistema Internacional es en minúsculas, pero se denota un número de 1024 bytes, y no de 1000.

21 N. del T.: En este caso se respeta la mayúscula del Sistema Internacional, pero se le asigna un valor de 1024 ×1024 ó 1048576 bytes.


File translated from TEX by TTH, version 1.0.