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.
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:
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.
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:
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:
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:
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.
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.
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.
La figura 4.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.
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.
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 4.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.
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.
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:
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.
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.
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 4.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.
La figura 4.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.
NOTA DE REVISIÓN: Do I need more detail here, worked example?
#!/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.
1 NOTA DE REVISIÓN: SWAPPING no se incluye porque no parece que se use.
2 Imagínese una nuez: el núcleo es la parte comestible en el centro y la cáscara está alrededor, presentando una interfaz.