You are on page 1of 22

Temario:

0. Introduccin:
Estructura
Sistemas de archivos
Arranque

1. Procesos e Hilos:
Mecanismos de sincronizacin.
Intercambio de informacin.
Seguimiento.
Programacin multihilo y multiproceso.

2. Depuracin de aplicaciones:
GDB
Depuracin entorno Eclipse lenguaje C con depuracin de aplicacin en equipo remoto.
Gestin de memoria.
Trazabilidad de seales.
Consumo de recursos.

3. Ejecutables:
Libreras estticas.
Libreras dinmicas.
Ejecutables.

4. Scheduler:
Multiprocesador.
Prioridades entre hilos-procesos.
Ejecucin en distintos ncleos.
Optimizacin.

5. Nociones bsicas de crosscompiling.


TEMA 0. Introduccin
Linux es un sistema operativo de software libre (no es propiedad de ninguna persona o empresa),
por ende no es necesario comprar una licencia para instalarlo y utilizarlo en un equipo informtico.
Es un sistema multitarea, multiusuario, compatible con UNIX y proporciona una interfaz de
comandos y una interfaz grfica que lo convierte en un sistema muy atractivo y con estupendas
perspectivas de futuro. Veamos en primer lugar la estructura de este tipo de sistemas:

1. Estructura
La estructura de Linux est basada en un microncleo hbrido que ejecuta los servicios ms
bsicos del sistema operativo. A este ncleo se le denomina kernel, y supone la base que va a
soportar todo el sistema que se construir sobre l. Las tareas o funciones principales que lleva a
cabo este ncleo son:

La planificacin y ejecucin de los diferentes procesos, repartiendo el tiempo entre ellos


en funcin de prioridades establecidas y algoritmos determinantes de dicha
planificacin.

La gestin de la entrada/salida y la jerarqua del sistema de ficheros.

La administracin de las funciones de bajo nivel para controlar todo el hardware.

El usuario casi nunca va a necesitar tocar nada en el ncleo del sistema salvo que le surja la
necesidad de actualizarlo o modificar su configuracin. Esto es algo totalmente normal, puesto
que la modificacin del kernel es una tarea bastante delicada y que requiere de un conocimiento
avanzado. Un kernel tpico puede constar de ms de 20.000 lneas de cdigo, de las cuales un
80% est escrito en C, por ello, es muy aconsejable que cualquier persona que quiera retocar
algo en el ncleo conozca en profundidad este lenguaje de programacin.

En un sistema GNU/Linux, Linux es el ncleo. El resto del sistema consiste en otros programas,
muchos de los cuales han sido escritos por o para el proyecto GNU. Dado que el ncleo de
Linux en s mismo no forma un sistema operativo funcional, es preferible utilizar el trmino
GNU/Linux para referirnos a los sistemas que comunmente se denominan de manera informal
como Linux.

Estos sistemas disponen adems de un programa que asla al usuario del ncleo, conocido como
shell o intrprete de comandos y cuya funcin es interpretar las rdenes o aplicaciones que el
usuario mande al sistema para posteriormente traducirlas a instrucciones que el sistema
operativo entienda.

Las diferentes variantes de Linux se denominan distribuciones, de las cuales, las ms conocidas
son: Red Hat-Fedora, Suse, Debian, Ubuntu, y Mandriva. Cada distribucin de Linux distribuye
el ncleo mediante las actualizaciones del propio sistema operativo, distinguiendose las
versiones mediante 3 o 4 nmeros separados por puntos. El significado de cada nmero es el
siguiente:

1. Versin del ncleo; vara si hay una gran modificacin en el cdigo del ncleo.
2. Principal revisin del ncleo.
3. Revisin menor, como la inclusin de nuevos drivers o algunas caractersticas nuevas.
4. Correcciones de errores o fallos de seguridad dentro de una misma revisin.

2. Sistema de archivos
Como ya hemos dicho antes, el propio kernel es el encargado de gestionar la jerarqua del
sistema de ficheros. Es vital conocer la jerarqua del sistema que utilicemos para poder tener un
control absoluto sobre las operaciones que realizamos en l. En Linux, las libreras, los binarios,
los programas instalados, los archivos temporales, etc., se encuentran contenidos en sitios
especficos donde podemos ubicarlos si precisamos en algn momento de ellos. Algunas de las
rutas ms importantes son las siguientes:

/: directorio raz donde se encuentran todos los directorios y archivos de forma lgica.
/bin: en este directorio podemos encontrar todos los archivos ejecutables del sistema. En el
estn muchos de los comandos que usaremos habitualmente, como por ejemplo: ls, cat,
more, cp, tar, etc.
/home: aqu se encuentran todos los archivos de los usuarios del sistema.
/media: generalmente, aqu es donde se montan los cds y dvds adems de los sticks USB y
discos duros externos.
/sbin: contiene archivos ejecutables que por lo general son comandos usados para la
administracin del sistema. Los comandos mount, halt, umount y shutdown son algunos de
ellos.
/usr: contiene varios archivos y subdirectorio importantes, como pueden ser las
configuraciones del entorno grafico X, fuentes del kernel, librerias, juegos y un largo etc.
/boot: aqu se encuentran todos los archivos necesarios para el arranque, incluidos el
cargador Grub y los kernels disponibles.
/mnt: directorio vaco, normalmente se suele usar para montajes de unidades temporales que
deseamos cargar en ese momento.
/var: contiene varios archivos que definen el sistema, as como archivos log.
/cdrom: normalmente es un enlace simblico hacia /media/cdrom.
/dev: aqu es donde estn todos los drivers y los dispositivos, estos se identifican en forma
de archivo.
/lib: contiene libreras para C y otros lenguajes de programacin.
/proc: directorio que contiene informacin sobre diferentes partes del sistema, cpu, discos,
tiempo de uptime, irqs, memoria, etc.
/opt: en este directorio se suelen almacenar todos los archivos de una instalacin ajena a los
repositorios del sistema.
/etc: contiene prcticamente todos los archivos de configuracin del equipo y los demonios
de inicio en /etc/init.d, entre otras cosas.
/root: directorio particular del superusuario del sistema (root).
/tmp: directorio temporal que pueden usar todos los usuarios para archivos temporales y del
sistema.

3. Arranque
El arranque del kernel de Linux se gestiona mediante dos etapas: en la primera etapa el kernel se
carga y se descomprime en memoria, y algunas funciones fundamentales como la gestin de
memoria se establecen. El control entonces se cambia a la etapa final para iniciar el kernel
principal. Una vez que el ncleo est en pleno funcionamiento, el kernel busca un proceso de
inicio para ejecutar, que de forma separada, fija un espacio de usuario y los procesos necesarios
para un entorno de usuario. A lo largo de la historia de Linux, se han llegado a tener tres tipos de
procesos de inicio diferentes:

Init: En los sistemas tipo Unix, init (abreviatura de initialization) es el primer proceso en
ejecucin tras la carga del kernel y el que a su vez genera todos los dems procesos. Se
ejecuta como demonio y por lo general tiene PID 1. En el Unix original, el proceso init
arrancaba los servicios mediante un nico script denominado /etc/rc. Posteriormente, la
versin System V del Unix de AT&T introdujo un nuevo esquema de directorios en /etc/rc.d/
que contena scripts de arranque/parada de servicios. El demonio init tradicional es
estrictamente sncrono, bloqueando futuras tareas hasta que la actual se haya completado.

Upstart: Upstart es un reemplazo basado en eventos para el demonio init. Upstart trabaja de
forma asncrona supervisando las tareas mientras el sistema esta arrancado, a la vez que
gestiona las tareas y servicios de inicio cuando el sistema arranca y los detiene cuando el
sistema se apaga. La fcil transicin y la perfecta retrocompatibilidad con sysvinit fueron
objetivos explcitos en el diseo. Por lo tanto, Upstart es capaz de ejecutar scripts de sysvinit
sin modificaciones.

Systemd: Systemd es un sustituto para el Init de Linux. Est hecho para proveer una mejor
forma de expresar las dependencias de un servicio, permitiendo as hacer ms trabajo
paralelamente al inicio del sistema y reducir la sobrecarga del shell. Comparado con System
V init, systemd puede tomar ventaja de nuevas tcnicas:

Los servicios de activacin de sockets y la activacin de buses, que conducen a una


mejor paralelizacin de servicios independientes.
La utilizacin de cgroups para realizar un seguimiento de los procesos de servicio, en
lugar de PIDs. Esto significa que los demonios no pueden escapar de systemd aunque
estn doblemente bifurcados.

TEMA 1. Procesos e hilos


Los procesos son programas que estn siendo ejecutados en un SSOO y que requieren distintos
recursos para realizar su tarea con xito. Cuando se ejecuta un programa cualquiera, por ejemplo, un
editor de texto como Gedit, se iniciarn uno o varios procesos en el sistema que realizarn las tareas
necesarias para que el usuario pueda utilizar el software. Es importante tener en cuenta, que aunque
dos procesos estn asociados al mismo programa, su secuencia de ejecucin es totalmente
independiente.

Un proceso puede estar formado por varios hilos de ejecucin o threads. Un hilo es una unidad
bsica de utilizacin de la CPU, y consiste en un contador de programa, un juego de registros y un
espacio de pila. Los hilos permiten la ejecucin simultnea de varias secuencias de instrucciones
independientes dentro de un mismo proceso, compartiendo un mismo espacio de direcciones y las
mismas estructuras de datos del ncleo.

Los procesos son ms costosos de lanzar, puesto que se necesita crear una copia de toda la memoria
del programa, mientras que los hilos comparten la memoria y los recursos. Esto supone una
limitacin en el uso de los hilos, y es por esto que habr que usar mecanismos de sincronizacin
para decidir qu hilo accede a los recursos en un determinado momento. En general, esto hace que
la programacin sea algo ms compleja.

1. Mecanismos de sincronizacin
Cuando un conjunto de procesos o hilos acceden a un mismo recurso compartido, puede
producirse lo que se denomina como condicin de carrera. Es decir, el primero que haga antes el
acceso al recurso ser el que lo procese y modifique si fuese necesario. Esto no es algo deseable,
puesto que lo normal es que haya un orden en las operaciones a realizar, de forma que el
resultado final sea el esperado. Para controlar esto, existen los mecanismos de sincronizacin:

Semforos: un semforo lleva asociada una cola de procesos bloqueados en l y una cuenta
de seales de despertar recibidas, lo que permite su utilizacin general para gestionar
recursos. Los semforos se pueden compartir entre procesos y pueden ser accedidos por
parte de todos los hilos del proceso.

Cerrojos de exclusin mutua (mutex): se utilizan nicamente para garantizar la gestin de


exclusin mutua. Conceptualmente funcionan como un cerrojo, es decir, disponemos de dos
estados, abierto o cerrado:

Cuando un hilo invoca a la operacin de cierre:


Si el mutex estaba abierto (y por tanto, sin propietario), lo cierra y pasa a ser su
propietario.
Si el mutex ya estaba cerrado, el hilo que invoca a la operacin de cierre se
suspende.

Cuando el propietario del mutex invoca a la operacin de apertura:


Se abre el mutex.
Si existan hilos suspendidos (esperando por el cerrojo), se selecciona uno y se
despierta, con lo que puede cerrarlo (y pasar a ser el nuevo propietario).

Variables de condicin: las variables de condicin se llaman as por el hecho de que


siempre se usan con una condicin, es decir, un predicado. Un hilo prueba un predicado y
llama a ESPERA si el predicado es falso. Cuando otro hilo modifica variables que pudieran
hacer que se cumpla la condicin, activa al hilo bloqueado invocando un AVISO. Las
variables de condicin estn siempre asociadas a un mutex.

2. Intercambio de informacin
La sincronizacin entre procesos e hilos que hemos visto en el apartado anterior se realiza
mediante el intercambio de informacin entre ellos. Esto es posible gracias a los medios de
comunicacin entre procesos (Inter-Process Communication o IPC) de Linux. Hay varios
mtodos de IPC disponibles en Linux:

Tuberas pipes UNIX: proporcionan una comunicacin unidireccional entre procesos.


Una tubera (pipe) es simplemente un mtodo de conexin que une la salida estndar de un
proceso a la entrada estndar de otro. Para esto se utilizan descriptores de archivos
reservados, los cuales de forma general son:

0: entrada estndar (stdin)


1: salida estndar (stdout)
2: salida de error (stderr)

Colas de mensajes: permiten definir un espacio, a los que llamaremos colas, para recibir
mensajes de otros procesos. Los mensajes se encolan (envan) y se desencolan (reciben)
siguiendo una disciplina FIFO, siempre y cuando el proceso involucrado tenga los permisos
necesarios. Direccionaremos los buzones mediante un identificador, con el que
contactaremos mediante dos primitivas bsicas para enviar y recibir.

Memoria compartida: permite establecer una zona comn de memoria entre varias
aplicaciones. La memoria convencional que puede direccionar un proceso a travs de su
espacio de direcciones virtuales es local, por lo que cualquier intento de direccionar esa
memoria desde otro proceso va a provocar una violacin de segmento. El mecanismo de
memoria compartida permite a dos o ms procesos compartir un segmento de memoria, y
por consiguiente, los datos que hay en l. Es por ello el mtodo ms rpido de comunicacin
entre procesos. Al ser el objetivo de este tipo de comunicacin la transferencia de datos entre
varios procesos, los programas que utilizan memoria compartida deben normalmente
establecer algn tipo de protocolo para el bloqueo.

Seales (signals): El mecanismo estndar para informar a un proceso de que ha ocurrido un


evento es la seal. En un sistema, los procesos que se ejecutan simultneamente interactan
entre s. Esta interaccin se produce incluso en el caso de procesos independientes, esto es,
los que no necesitan cooperar para completar sus tareas. Esto ocurre cuando varios procesos
quieren acceder a los mismos recursos del sistema operativo, por ejemplo un dispositivo de
entrada y salida. Para resolver esta situacin, el sistema operativo dispone de un gestor de
procesos que determina el orden de acceso a esos recursos y adicionalmente, cuenta con un
mecanismo para poder enviar mensajes a esos procesos. Las seales pueden considerarse un
tipo de mensajes, aunque, si se comparan con otros medios de comunicacin de procesos,
resultan un mecanismo ms pobre, ya que no permiten transmitir datos.

Se considera que una seal se ha entregado a un proceso cuando ste realiza la accin
asociada a ella. Durante el tiempo que transcurre entre la generacin de la seal y su entrega
a un proceso, se suele decir que la seal est pendiente. En consecuencia, es preciso
disponer de algn mecanismo (en este caso una funcin) que permita averiguar si en un
determinado instante hay seales pendientes, que an no se han entregado al proceso
correspondiente. Las seales pueden aparecer en cualquier instante, por lo que los procesos
no pueden limitarse a verificar una variable para comprobar si ha llegado una seal, de ah la
necesidad de una rutina de tratamiento que gestione de manera automtica su recepcin en el
momento en que aparezca.

Como las seales pueden aparecer en cualquier instante, el proceso debe indicar al kernel
qu es lo que ha de hacer cuando recibe una seal determinada. El kernel puede actuar de
tres formas diferentes: ignorar la seal, capturarla o aplicar la rutina por defecto:

Seales ignoradas: Mediante el establecimiento de la constante SIG_IGN, el proceso


ignorar la recepcin de la seal. Esto implica que el proceso no realizar ninguna
accin al recibirla. Un proceso que ignore todas las seales no podra ser gestionado por
el administrador de la mquina, ya que sta no respondera a sus instrucciones. Por este
motivo, existen ciertas seales que no pueden ser ignoradas, como por ejemplo
SIGKILL.

Seales capturadas: El programador del proceso puede preparar una rutina que se
ejecute al recibirse una seal determinada. El cdigo de la rutina debe desarrollarse
teniendo en cuenta que esta rutina no se va a ejecutar en el momento deseado por el
programador, sino en cualquier instante en que se produzca un evento. Una vez
programada, se debe especificar a qu seal responder la rutina propia que gestiona una
seal concreta. Para ello, existe una funcin que asigna la direccin de la rutina a la
seal. El kernel llamar a esa funcin cada vez que se reciba la seal. La accin o
acciones a las que d lugar dicha rutina dependern del programador: continuar en el
punto en que se haba interrumpido, finalizar o volver a un punto anterior.

Seales tratadas por defecto: Mediante la especificacin de la constante SIG_DFL, el


kernel llama a una funcin determinada cada vez que la seal reaparece.

Cada seal posee un nombre que comienza por SIG, mientras que el resto de los caracteres
se relacionan con el tipo de evento que representa. Asimismo, cada seal lleva asociado un
nmero entero positivo, que es el que intercambia con el proceso cuando ste recibe la seal.
Las seales que pueden ser manejadas por el sistema, junto con sus nombres, se definen en
el fichero de cabecera </linux/signal.h>, que a su vez se halla incluido en <signal.h>.
Se considera que una seal se ha entregado a un proceso cuando ste realiza la accin
asociada a ella. Durante el tiempo que transcurre entre la generacin de la seal y su entrega
a un proceso, se suele decir que la seal est pendiente. En consecuencia, es preciso
disponer de algn mecanismo que permita averiguar si en un determinado instante hay
seales pendientes que an no se han entregado al proceso correspondiente. Esto se
gestionar mediante la funcin sigpending.

Por otro lado, hay que apuntar que los procesos tienen la posibilidad de bloquear la
recepcin de una determinada seal. Si el programador bloquea una seal cuya accin
asociada es la captura de la seal o su tratamiento por defecto, la accin queda pendiente. Es
decir, el kernel genera la seal y la enva al proceso que, en principio, tiene definido cmo
responder a ella; al descubrir que la seal est bloqueada, el kernel la guarda hasta que el
proceso o bien la desbloquea, o bien modifica la accin que lleva asociada, pasando
entonces a ignorarla. El kernel determina qu debe hacerse con una seal bloqueada cuando
se entrega, y no cuando se genera, lo que permite que los procesos puedan modificar la
accin que van a realizar ante ella antes de su recepcin. En las ltimas versiones de Linux,
la gestin de los bloqueos de seales se realiza mediante la funcin sigprocmask. La
sintaxis de la funcin es:

include <signal.h>
int sigprocmask (int how, const sigset_t *set, sigset_t *oldset);

El comportamiento de esta funcin es distinto segn cul sea el valor de how:


SIG_BLOCK: el conjunto de seales bloqueadas es la unin del actual y el del
argumento set.
SIG_UNBLOCK: las seales incluidas en set se eliminan del conjunto de seales
bloqueadas en la actualidad. Se considera permitido el intento de desbloquear una seal
que no se halla bloqueada.
SIG_SETMASK: el conjunto de seales bloqueadas pasa a ser el contenido en el
argumento set.

Si el contenido del argumento oldset no es nulo, el valor previo de la mscara de seales se


almacena en oldset. Un ejemplo de uso de esta funcin podra ser el siguiente, en el que se
muestra como bloquear la seal SIGPIPE:

sigset_t *antiguo = 0;
sigprocmask (SIG_BLOCK, 8192, antiguo);

donde 8192 es el nmero entero que, en binario, posee un 1 en la posicin 13, siendo 13 el
nmero entero asociado a SIGPIPE.

El tipo de datos sigset_t se usa para representar un conjunto de seales. Existen dos formas
de inicializarlo: o bien mediante un entero (como en el ejemplo anterior) o como una
estructura de datos mediante las funciones sigemptyset sigfillset. Para aadir o quitar
seales a estas estructuras, tendramos que usar las funciones sigaddset sigdelset
respectivamente.

Se han visto los casos de consulta de seales pendientes y de bloqueos de las mismas, pero
un ltimo punto, y el ms importante de todos, es su gestin. La consecuencia de enviar una
seal a un proceso, si ste no est preparado para aceptar dicha seal, es la finalizacin de su
ejecucin. Si el proceso dispone de un gestor de seales, este responder a la seal
ejecutando la rutina asignada por el programador. Esta rutina podr ser desarrollada por el
propio programador, o bien ser la asociada por defecto a la seal. La funcin sigaction es
el gestor de seales por excelencia. Esta funcin permite modificar o examinar la accin
asociada con una seal determinada, lo que resulta de gran utilidad, puesto que permite
averiguar la disposicin respecto a una determinada seal sin modificarla. Su definicin es:

include <signal.h>
int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);

donde:
El argumento signum es el nmero de la seal cuya accin se desea examinar o
modificar.
El puntero *act representa la funcin con la que se desea gestionar la seal.
El puntero *oldact representa la funcin que estaba gestionando la seal.
Si el puntero *act no es nulo, entonces se est modificando la accin, que pasa a ser la
contenida en la direccin de *act.
Si el puntero *oldact no es nulo, la accin que va a ser modificada se almacena en
*oldact.

La estructura empleada por esta funcin es:


struct sigaction {
void (*sa_handler)();
/* direccin del gestor de seales o SIG_IGN, SIG_DFL, segn corresponda */
sigset_t sa_mask;
/* seales adicionales al bloque */
int sa_flags;
/* flags opcionales para la gestin de cada seal*/
};
donde:
sa_handler ser bien la constante SIG_DFL, para la accin por defecto, o bien SIG_IGN,
para ignorar la seal o la direccin a una funcin que maneje esa seal.
sa_mask proporciona una mscara para las seales que deben ser bloqueadas durante la
ejecucin del gestor de seales.
sa_flags es la combinacin or de ninguna o alguna de las siguientes flags:

Veamos un ejemplo con SIG_IGN:

main(void){
int codigo_error=0;
struct sigaction gestion;
gestion.sa_handler = SIG_IGN;
gestion.sa_mask = 0;
gestion.sa_flags = SA_ONESHOT;
codigo_error = sigaction ( SIGINT, gestion, 0);
if( codigo_error == SIG_ERR ){
perror(Error al definir el gestor de SIGINT);
exit(-1);
}

/** Cdigo del programa ***/


while(1);
}

Una vez visto el funcionamiento de las seales y sus principales funciones y estructuras,
consideremos por ejemplo el caso de un proceso cuyos hilos estn ejecutando un proceso de
consulta importante. Un problema habitual en el uso de gestores de seales es que la rutina
de tratamiento de la seal no conozca el estado del programa cuando aparece la misma. Un
ejemplo clsico de esta situacin aparece al gestionar la seal SIGCHLD. Cuando se trabaja
con procesos hijo, el gestor de seales puede interferir en el uso de la funcin wait, porque
cuando el proceso padre est esperando la terminacin del proceso hijo, si el usuario enva
una seal a ambos procesos, la gestin de sta saca al proceso padre de su estado de espera.
Es decir, mientras el padre espera, las seales deben ser gestionadas en el proceso hijo; por
tanto, deben activarse los gestores de seales en el padre para que las ignore. Una vez que
esto est controlado, tan slo habra que gestionar que la seal SIGCHLD usara su accin
por defecto, o bien que fuese ignorada o bloqueada directamente en el hijo, de forma que
continuemos la ejecucin de nuestro cdigo por el punto en el que se encontraba. Algo
similar se podra aplicar para una seal SIGTERM, con la diferencia de que slo podramos
bloquearla, puesto que su accin por defecto es interrumpir la ejecucin.
Otro caso de inters, podra ser la ejecucin de la funcin system, cuyo funcionamiento
interno hace que se lance en la shell de Linux el comando que indiquemos mediante un fork.
Durante la ejecucin del comando especificado, las seales SIGCHLD sern bloqueadas y
las seales SIGINT y SIGQUIT sern ignoradas, por lo que en caso de que quisieramos
gestionar otras seales tendramos que indicarlo explcitamente. El valor que retorna esta
funcin es -1 en caso de error al realizar el fork, y el cdigo de salida del comando ejecutado
en otro caso. sto ltimo es precisamente lo interesante, conocer el cdigo de salida con el
que ha finalizado un proceso hijo. Para ello, se hace uso de las macros WIFEXITED y
WEXITSTATUS. Veamos un ejemplo:

int estadoHijo;
...
wait (&estadoHijo);

if ( WIFEXITED(estadoHijo) != 0) {
printf ("Mi hijo ha hecho exit (%d)\n", WEXITSTAUS (estadoHijo));
}

WIFEXITED(estadoHijo) es 0 si el hijo ha terminado de una manera anormal (caida,


matado con un kill, etc) y distinto de 0 si ha terminado porque ha hecho una llamada a la
funcin exit().
WEXITSTATUS(estadoHijo) devuelve el valor que ha pasado el hijo a la funcin
exit(), siempre y cuando la macro anterior indique que la salida ha sido por una llamada
a exit().

3. Seguimiento
Los SSOO de tipo Linux disponen de varias herramientas para conocer el uso de los recursos
que estn haciendo los procesos y sus hilos. Un comando muy til es ps, que tiene algunos
argumentos que pueden resultar interesantes:

ps -ef
ps -p <pid> -o %cpu,%mem,cmd

Otro comando de inters puede ser top, que muestra informacin similar a la de ps.
top -p <pid>

Por otro lado, en un entorno de C++, podemos utilizar la funcin getrusage() para obtener la
informacin anterior. Un ejemplo de uso para obtener la informacin del proceso actual podra
ser el siguiente:

#include <sys/resource.h>

int who = RUSAGE_SELF;
struct rusage usage;
int ret;

ret = getrusage(who, &usage);

En cuanto a dependencias, hay que destacar principalmente dos comandos Linux para obtener
dichos datos: pldd y pmap.
pldd <PID> : muestra las dependencias sobre libreras compartidas de un proceso.
pmap <PID> : muestra el mapa de memoria de un proceso.

4. Programacin multihilo y multiproceso


Cuando hablamos de aplicaciones multiproceso, lo que realmente estamos diciendo es que
nuestra aplicacin dispone de un proceso principal, que a su vez lanza otros subprocesos para
realizar su tarea. La programacin de este tipo de aplicaciones incluye el uso de dos llamadas
importantes al sistema: fork() y exec().

La llamada fork() crea una copia casi identica del proceso padre, haciendo que tanto el hijo
como el padre pasen a ejecutarse en paralelo. En caso de que se produjese un error, la
llamada devolver el valor -1. El proceso padre recibe de fork() el pid del hijo, mientras que
el hijo recibe un 0.

La funcin exec(), por su parte, realiza un cambio de imagen con respecto al proceso actual,
ya que se funcin es la de sustituir en memoria el programa por otro diferente. Existen
diferentes funciones para exec, sus declaraciones son:

int execl(const char *path, const char *arg, ...);


int execv(const char* path, char* const argv[]);
int execve(const char* path, char* const argv[], char* const envp[]);
int execvp(const char *file, char *const argv[]);

Esta funcin retorna -1 en caso de error, en caso contrario no retorna.


Es importante tener en cuenta, que por regla general, el proceso padre slo deber desempear
la funcin de crear hijos, mientras que estos ltimos se encargarn de realizar todo el trabajo. El
proceso padre puede crear cuantos hijos quiera, o bien esperar a que finalicen. Dicha espera se
gestiona con la funcin wait(), que hace que el padre entre en estado de bloqueo hasta que
acabe el hijo.

Ejemplo en C++:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main() {
int pid;
int status;
char *argumentos[3] = {ls,-l,NULL);
pid = fork();
switch(pid){
case -1: /* Fallo creando el hijo */
perror (Error en fork);
exit(-1);
case 0: /* Estamos en el hijo */
execvp(argumentos[0],argumentos);
perror(Error de exec. Si todo ha ido bien esto no se ejecuta.);
exit(-1);
break;
default: /* Padre */
while(wait(&status) != pid);
if(status == 0)
printf(Ejecucin normal del hijo \n);
else
printf(Error del hijo \n);
}
exit (0);
}

En el ejemplo, el proceso padre genera un hijo y espera a que termine. El proceso hijo crea una
nueva imagen de programa llamando al comando ls -l. El switch nos sirve para saber donde
nos encontramos. Hay que recordar que el hijo tiene como pid 0 mientras que el padre tiene el
pid del hijo.

Por otro lado, si hablamos de aplicaciones multihilo, lo que estamos diciendo en este caso es
que un slo proceso se divide en varios hilos de ejecucin, es decir, en varias unidades simples
de procesamiento. Las funciones necesarias para el manejo de hilos en C se encuentran en la
biblioteca pthread.h. Estas funciones son:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*rutina)(void *), void *arg)
pthread_t pthread_self(void)
int pthread_join(pthread_t thread, void **value)
int pthread_exit(void *value)

Adems, a la hora de compilar, ser necesario enlazar la librera libpthread:

gcc -lpthread -o foo.exe -c bar.c

Un ejemplo del uso de hilos en C++ podra ser el siguiente:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

struct param{
char *frase;
int numero;
};

// Funcin que ejecutarn los hilos.


void hiloMensaje(struct param *mensa){
printf("%s %d\n",mensa->frase, mensanumero);
}

int main() {
pthread_t thd1, thd2;
struct param param1 = {"Soy el hilo: ",1};
struct param param2 = {"Digo otra cosa ",2};

/* Creamos dos hilos.


* La funcin la pasaremos como (void*)nombreFuncion.
* Es decir, hacemos un CAST a void*.
* Tambin es importante realizar esto con la direccin de memoria
* de la variable que contiene los parmetros.
*/
pthread_create (&thd1, NULL, (void*)hiloMensaje, (void*)&param1);
pthread_create (&thd2, NULL, (void*)hiloMensaje, (void*)&param2);

// Esperamos la finalizacin de los hilos


pthread_join(thd1, NULL);
pthread_join(thd2, NULL);

printf("Han finalizado los thread.\n");


}

Los hilos pueden ejecutarse en otro orden, lo que depender del algoritmo de planificacin de
nuestro sistema. Con respecto a las variables o recursos compartidos, hay que tener en cuenta
que si no controlamos el orden de ejecucin de los hilos, podra producirse corrupcin en los
datos. Aqu es donde entraran los mecanismos de sincronizacin que vimos anteriormente.

TEMA 2. Depuracin de aplicaciones

1. GDB

2. Depuracin entorno Eclipse lenguaje C con depuracin de aplicacin en equipo remoto

3. Gestin de memoria

4. Trazabilidad de seales

5. Consumo de recursos
Los procesos usan durante su ejecucin una gran variedad de recursos, como por ejemplo
ficheros, dispositivos y/o memoria RAM. Aunque en primera instancia los dispositivos puedan
parecer un elemento complejo y dificil de gestionar, en realidad no es as, puesto que Linux los
considera como simples ficheros. Estos ficheros, tanto los de dispositivos, como los
tradicionales (ficheros de texto) son tratados mediante descriptores, que no son ms que un tipo
especial de indicadores que se usan para el acceso a los ficheros en s. Los descriptores se
almacenan en arrays que contienen informacin variada sobre el fichero que manejan, por
ejemplo: ruta del fichero, el proceso o usuario que est accediendo al mismo o el tipo de fichero.

Linux, ofrece un comando muy interesante para ver esta informacin: lsof (LiSts Open Files).
Este comando chequea la lista de descriptores del kernel para mostrar todos los ficheros que se
encuentran abiertos. Entre la informacin que muestra podemos observar el identificador del
descriptor asociado a un archivo, as como el proceso que se ha encargado de abrirlos.
Esta ltima informacin podemos obtenerla tambin de otra forma muy sencilla si conocemos el
PID del proceso que queremos consultar. Para ello slo tendremos que ejecutar el comando ls
con el siguiente argumento: ls /proc/<PID>fd/. Si se ejecuta este comando para varios
procesos diferentes se puede observar que los descriptores 0, 1 y 2 estn abiertos en la
mayora. Esto es porque estos descriptores son especiales, y tienen una correspondencia
concreta:

0: entrada estndar (stdin)


1: salida estndar (stdout)
2: salida estndar de error (stderr)

Los procesos pueden adems compartir descriptores entre ellos, por ejemplo, en el caso de que
se hiciera un fork de un proceso, el proceso hijo heredara los mismos descriptores de ficheros
que el proceso padre.

Otro de los recursos que consumen los procesos y sus hilos es la memoria RAM. Anteriormente
ya se coment sobre un comando Linux que nos permite ver dicha informacin: pmap. Este
comando nos desglosa toda la informacin sobre los recursos de memoria que est usando un
proceso, y al final, incluye un resumen del total de KB utilizados que nos puede ser de mucha
ayuda. La sintaxis para lanzarlo es: pmap -x <PID>

A la hora de controlar los recursos utilizados, es muy importante tambin el gestionar que los
procesos finalicen de forma correcta. Es muy habitual que durante el desarrollo de un cdigo se
cometan errores y haya procesos que aunque han finalizado su ejecucin, siguen existiendo en
la tabla de procesos. A estos procesos se les denomina procesos zombies. Cuando un proceso
finaliza en sistemas Linux, toda su memoria y recursos asociados a l se desreferencian para que
puedan ser usados por otros procesos. En ese espacio de tiempo, la entrada del proceso hijo en
la tabla de procesos permanece un mnimo tiempo, hasta que el padre conoce que el estado de su
proceso hijo es finalizado y entonces lo saca de la tabla de procesos. Para que el proceso padre
sepa el estado de su hijo, se le enva una seal SIGCHLD indicando que el proceso hijo ha
finalizado. Esa seal es generada gracias a llamadas del sistema como wait(), waitpid()
waitid(). Sin embargo, cuando no se usan esos manejadores para conocer el estado de los
hijos, el padre no sabe que su hijo ha terminado y por lo tanto sigue en la lista de procesos. Los
procesos zombies se generan por tanto, cuando el padre no recibe esa seal o bien la ignora,
generalmente por bugs o aplicaciones mal programadas.

Una manera de identificar estos procesos es mediante la salida del comando ps. Cuando se
ejecuta este comando y la columna STAT muestra una Z, esto nos indicar que el proceso
de esa misma fila se trata de un zombie. Tenemos dos opciones para limpiarlos de la tabla de
procesos:

Enviar la senal SIGCHLD al proceso padre es la primera medida recomendada y sensata:

kill -s SIGCHLD <ppid>

Si no funciona, podemos matar el proceso padre y los hijos activos pasarn a depender del
proceso init:

kill -s SIGHUP <ppid>


TEMA 3. Ejecutables
Para crear ejecutables en Linux generalmente se utiliza un compilador del estilo de GCC. Este
compilador, por ejemplo, es capaz de recibir un cdigo fuente escrito en C, C++, Objective C
Fortran y generar un programa ejecutable binario en lenguaje mquina. En general, este compilador,
interpreta automticamente el lenguaje del cdigo fuente en funcin del tipo de extensin que tenga
el fichero:

.c : fuente en C
.C .cc .cpp .c++ .cp .cxx : fuente en C++ (.cpp por defecto)

En cuanto a la forma de lanzarlo, suele ser la habitual en entornos Linux, es decir:

gcc [ opcin | archivo ]

Si bien, los parmetros que se le pueden indicar a GCC son numerosos y muy variados, ser en
apartados posteriores donde los trataremos en profundidad. Por el momento nos quedaremos con
algunas de las opciones ms interesantes:

gcc -o hola hola.c : compila el programa en C hola.c y genera un archivo ejecutable hola.
gcc -c -o objeto.o hola.c : genera el cdigo objeto del programa en C hola.c.
gcc -L/lib -L/usr/lib hola.cpp : indica los directorios donde deben buscarse libreras.

Las libreras son un elemento muy importante a la hora de programar en cualquier lenguaje.
Bsicamente consisten en ficheros que podemos importar o incluir en nuestro programa y cuyo
contenido incluye especificaciones de diferentes funcionalidades ya construidas y utilizables por
nuestro programa. Por ejemplo, alguna de estas funcionalidad podra ser leer el teclado, escribir por
pantalla, manejar nmeros, realizar funciones matemticas, etc. Al poder incluir estas libreras
podremos ahorrarnos gran cantidad de tiempo, y adems, se aumentar la modularidad de nuestro
cdigo. Existen dos tipos de librerias: las estticas y las dinmicas, que se detallarn en los
prximos apartados.

1. Libreras estticas
Las libreras estticas tambin son denominadas libreras-objeto y consisten en colecciones de
ficheros objeto agrupados en un solo fichero de extensin .lib, .a, etc. junto con uno o varios
ficheros de cabecera (generalmente .h).

Una posicin extrema la constituyen aquellas libreras en las que toda la funcionalidad se ha
incluido en el fichero de cabecera .h, en cuyo caso no existen los mdulos compilados .lib, .a,
etc. No obstante, lo anterior representa un caso extremo que suele ser evitado, ya que por lo
general, los autores incluyen en los ficheros de cabecera la informacin mnima indispensable
para utilizar la librera (la interfaz), incluyendo la operatoria en forma de ficheros compilados.

Durante la compilacin del programa, el preprocesador incluye en los fuentes los ficheros de
cabecera. Posteriormente, durante la fase de enlazado, el linker incluye en el ejecutable los
mdulos correspondientes a las funciones y clases de librera que hayan sido utilizadas en el
programa, de forma que el conjunto entra a formar parte del ejecutable.

Dejando aparte consideraciones de comodidad y rapidez, el resultado de utilizar una de tales


libreras no se diferencia en nada al que puede obtenerse escribiendo en la fuente las funciones o
clases correspondientes y compilndolas como un mdulo ms de nuestra aplicacin.
Un ejemplo sencillo de creacin de una librera esttica podra ser el siguiente:

Fichero libreriaPi.c (cdigo)


#include <stdio.h>
#include <math.h>
#include <stdlib.h>

void funcion (double *t, double *k, double *l){


(*t) += (*l)/(*k);
(*k) += 2.0;
(*l) *= -1.0;
}

Fichero libreriaPi.h (cabecera)


void funcion (double *t, double *k, double *l);

Una vez escrito el cdigo de nuestra librera, creamos el objeto de la librera tal y como vimos
anteriormente:

gcc -c libreriaPi.c -o libreriaPi.o

Y creamos el archivado, es decir, la librera en s. La librera tiene que empezar


obligatoriamente con el prefijo lib y tener la extensin .a :
ar rcs libpi.a libreriaPi.o

Una vez creada nuestra librera podramos enlazarla con cualquier programa haciendo un
include de la cabecera y aadiendo a GCC las siguientes opciones:

gcc -static programaPi.c -L. -lpi -o piEstatico

La opcin -l, indica explcitamente que se enlace la librera con nombre: lib<nombre>.a. En
nuestro caso libpi.a.

2. Libreras dinmicas
Las libreras dinmicas tambin son conocidas como librerias compartidas y llevan el sufijo .so,
siendo su equivalente en Windows las .dll. Las libreras compartidas se vinculan a un programa
en tiempo de ejecucin, permitiendo que el cdigo de la librera se cargue en memoria una
nica vez y pueda ser usado por varios programas. De esta forma se consigue que el tamao del
cdigo sea menor y a su vez que se ahorre ms espacio en memoria.

Ademas de esto, con las libreras compartidas se cumple el principio de modularidad, de forma
que si necesitamos modificar alguna funcionalidad nos bastara con editar la librera que la
contiene, evitando as modificar el programa que las utiliza.

En el caso de las libreras compartidas, tambin es necesario crear un fichero objeto primero.
Reutilizando el ejemplo anterior para la librera esttica, tenemos que hacer lo siguiente para
compilarla de forma dinmica:

gcc -c -fPIC libreriaPi.c -o libreriaPi.o


Una vez creado el objeto, creamos la librera dinmica en s. Tal y como ocurra en el ejemplo
anterior, el prefijo de la librera tiene que seguir siendo lib:

gcc -shared -Wl,-soname,libpi.so.1 -o libpi.so.1.0.1 libreriaPi.o

El ltimo paso consistira en enlazar la librera con nuestro cdigo:

gcc main.c -o dynamically_linked -L. -lpi

3. Ejecutables
Se ha comentado en apartados anteriores, de forma indirecta, alguna de las fases que sigue un
cdigo fuente a la hora de convertirse en ejecutable. Detallmoslas:

1. Preprocesado: En esta etapa se interpretan las directivas del cdigo fuente. Entre otras
cosas, las variables inicializadas con #define son sustitudas en el cdigo por su valor en
todos los lugares donde aparece su nombre.

2. Compilacin: La compilacin transforma el cdigo C en el lenguaje ensamblador


propio del procesador de nuestra mquina.

3. Ensamblado: El ensamblado transforma el programa escrito en lenguaje ensamblador a


cdigo objeto, un archivo binario en lenguaje de mquina ejecutable por el procesador.

4. Enlazado: Las funciones de C/C++ includas en nuestro cdigo, como por ejemplo la
funcin printf(), se encuentran ya compiladas y ensambladas en libreras existentes en
el sistema. Es preciso incorporar de algn modo el cdigo binario de estas funciones a
nuestro ejecutable. Como ya hemos visto, esto se puede hacer de forma esttica o
dinmica.

De todas las fases que se han visto, la ms importante probablemente sea la de compilacin.
Esto es porque en dicha fase es cuando se aplican las optimizaciones al cdigo. Aunque hay
multitud de parmetros individuales con los que indicar a GCC las optimizaciones que
deseamos, tambin hay una serie de niveles que engloban diferentes conjuntos de estos
parmetros. Los ms importantes son:

-O / O1 : el compilador trata de reducir el tamao del cdigo y el tiempo de ejecucin, sin


realizar ninguna optimizacin que requiera demasiado tiempo de compilacin.

-O2 : Optimiza an ms. GCC realiza casi todas las optimizaciones soportadas. Comparado
con -O, esta opcin incrementa tanto el tiempo de compilacin como el rendimiento del
cdigo generado.

-O3 : Optimiza ms que la opcin anterior. Realiza todas las optimizaciones de -O2 y
adems utiliza las siguientes optimizaciones adicionales:
-finline-functions
-funswitch-loops
-fpredictive-commoning
-fgcse-after-reload
-ftree-loop-vectorize
-ftree-loop-distribute-patterns
-fsplit-paths
-ftree-slp-vectorize
-fvect-cost-model
-ftree-partial-pre
-fpeel-loops
-fipa-cp-clone

-O0 : Reduce el tiempo de compilacin. Esta es la opcin usada por defecto.

-Os : Optimizacin del tamao. Esta opcin habilita todas las optimizaciones de O2 que no
incrementan el tamao del cdigo.

TEMA 4. Scheduler
El planificador de procesos o scheduler, es el componente del kernel de Linux encargado de
controlar el acceso de los procesos o hilos a los recursos. Desde la versin 2.6.23, el planificador
tradicional de Linux fue reemplazado por CFS (Completely Fair Scheduler), cuyo diseo modela un
procesador multitarea ideal. CFS busca mantener el equilibrio en el tiempo de procesador que se
asigna a los recursos, de forma que cuando un proceso est fuera de ese equilibrio, se le asigna
tiempo de ejecucin en el procesador. No obstante, esto variar mucho en funcin del tipo de
proceso a ejecutar:

Procesos normales: utilizan la poltica SCHED_OTHER. Esta es la poltica de


planificacin clsica de Linux, y no es aplicable a tiempo real. Los procesos que llevan ms
tiempo en el sistema van perdiendo prioridad.

Procesos de tiempo real: los procesos de tiempo real son considerados prioritarios sobre
cualquier otro proceso en el sistema, por lo que sern ejecutados antes y no podrn ser
bloqueados por procesos de menor prioridad. Linux permite adems indicar una prioridad
relativa entre procesos de tiempo real, algo que pueden alterar ellos mismo mediante
llamadas al sistema. Estos procesos utilizan dos tipos de polticas diferentes:

SCHED_FIFO: con el sistema FIFO (First In First Out), los procesos esperan en cola y
se ejecutan secuencialmente.

SCHED_RR: con el sistema RR (Round Robin) cada proceso de tiempo real se ejecuta
por turnos.

Adems de la poltica de planificacin utilizada, hay otro elemento que juega un papel importante
en la planificacin de los procesos, la prioridad. La planificacin se ejecuta conforme a un ranking
de prioridades:

Prioridad esttica: es asignada cuando el proceso es creado. Los procesos en tiempo real
tambin tienen prioridad esttica (de 0 a 99). Estos procesos tienen una prioridad mayor a
la de los procesos comunes y no puede ser cambiada por el planificador.

Prioridad dinmica: se ajusta la prioridad de los procesos a travs del tiempo. Los
procesos que no han sido ejecutados durante un largo perodo de tiempo aumentan su
prioridad.
1. Multiprocesador
Consideramos un multiprocesador como una mquina con un conjunto de procesadores que
comparten un mismo espacio de direcciones de memoria fsica. Por esta razn, tambin se les
llama multiprocesadores de memoria compartida. La planificacin en los multiprocesadores
permite la ejecucin en paralelo de procesos, lo que implica que habr que decidir a qu
procesador se asignan cada uno de los procesos en ejecucin. Hay dos estrategias de asignacin
en sistemas multiprocesadores, que son:

Asignacin esttica: cuando un proceso pasa a estado activo por primera vez, se le asigna
un determinado procesador (el que menor carga de trabajo tenga en ese momento) al que se
asocia hasta el fin de la ejecucin. Dicho de otra manera, no es posible migrar procesos de
un procesador a otro incluso si hay procesadores ociosos.

Asignacin dinmica: en este caso, nuestros procesos en estado preparado pueden cambiar
de procesador para pasar a estado activo si resulta que el ltimo procesador en el que se
ejecut est ocupado y existe un procesador ocioso. Este mtodo de asignacin permite la
migracin de procesos de un procesador a otro, llevando a cabo un balanceo de carga de
procesos entre los procesadores existentes. No obstante, la migracin de procesos es bastante
costosa debido a los fallos de cach, por lo tanto, siempre que sea posible se intentar
permanecer en el mismo procesador.

Los planificadores de los sistemas operativos modernos aplican una asignacin hbrida, en la
que se emplea asignacin esttica la mayor parte del tiempo. Sin embargo, si el nmero de
procesos asociados a un procesador supera en N unidades al de otro, se procede a migrarlos
a otro procesador ms libre para balancear la carga de trabajo. Este es el caso del
planificador del ncleo de Linux.

2. Prioridades entre hilos-procesos


Aunque en principio pueda pensarse que los procesos y los hilos son planificados para su
ejecucin de forma diferente, no es as realmente. Linux ya no planifica procesos, sino hilos
nada ms. El concepto de proceso en Linux es actualmente una construccin artificial para el
kernel, que tiene que saber como estn agrupados los hilos, pero no para propsitos de
planificacin. En general, cada uno de estos grupos tiene un hilo lder, que es el que es visto
desde fuera como un proceso.

Cada hilo dispone de un identificador propio y otro de grupo, algo parecido a la relacin entre
proceso padre e hijo. En base a esto podemos distinguir dos casos:

Cuando se crea un hilo, el kernel le proporciona un identificador, pero el identificador de


grupo es el mismo que el del hilo que lo cre. Esto hace que el hilo recin creado parezca
como propio de un proceso.

Cuando se hace un fork, el kernel le asigna un nuevo identificador de hilo y un


identificador de grupo idntico. De esta manera, parece como un proceso de cara al exterior.
Muchas utilidades informan sobre los procesos en ejecucin del sistema, cuando en realidad
slo estn informando sobre los hilos que tienen el mismo identificador tanto propio como de
grupo.

3. Ejecucin en distintos ncleos


La planificacin de los procesos para su ejecucin dentro de un sistema multincleo puede ser
modificada en cierta manera para decidir los ncleos a utilizar. Esto se hace mediante la
definicin de la mscara de afinidad de un hilo. Esta mscara determina el conjunto de CPUs en
las que el hilo puede llegar a ejecutarse. En un sistema multiprocesador, configurar la mscara
de afinidad puede servir para obtener un aumento del rendimiento. Por ejemplo, si
configuramos la mscara de afinidad de un hilo para ejecutarse en una CPU concreta y la del
resto de procesos para excluir dicha CPU, ser posible garantizar la mayor velocidad de
ejecucin para ese primer hilo. Adems, como ya se coment antes , al restringir un hilo a
ejecutarse en una nica CPU, tambin se evita el coste en rendimiento causado por los fallos de
cach que ocurren cuando un hilo para de ejecutarse en una CPU para ms tarde volver a
ejecutarse en otra.

Linux dispone de una herramienta de inters para modificar la mscara de afinidad de un


proceso: taskset. Para utilizar este comando podemos hacer lo siguiente:

taskset -p -c <nmero de la cpu> <pid>

4. Optimizacin
Una optimizacin que podramos aplicar a nuestros cdigos para aprovechar los entornos
multiprocesador, podra ser la de aplicar mscaras de afinidad. Un ejemplo bsico de esto es el
siguiente:

#include <sched.h>

int main(int argc, char *argv[]) {


int cpuAffinity = argc > 1 ? atoi(argv[1]) : -1;

if (cpuAffinity > -1) {


cpu_set_t mask;
int status;

CPU_ZERO(&mask);
CPU_SET(cpuAffinity, &mask);
status = sched_setaffinity(0, sizeof(mask), &mask);

if (status != 0) {
perror("sched_setaffinity");
}
}

// programa
}

Mediante la intruccin sched_setaffinity definida en la librera sched.h, podremos indicar el


conjunto de CPUs donde queremos que se ejecute un hilo de nuestro cdigo.
TEMA 5. Nociones bsicas de crosscompiling
Tal y como se ha hablado en apartados anteriores, los compiladores son herramientas que nos
permiten convertir el cdigo fuente de nuestra aplicacin en un programa o cdigo ejecutable. Lo
ms sencillo es que el cdigo sea ejecutado en el mismo tipo de mquina en la que fue compilado,
sin embargo, esta situacin no siempre ocurre. En estos casos, al compilador se le denomina cross-
compiler, puesto que compila cdigo para una plataforma que no es en la que est funcionando.

En general, cuando un usuario quiere generar cdigo para una arquitectura diferente a la que est
utilizando para trabajar, debe conseguir los medios necesarios para compilar su cdigode forma
correcta. Esto implica conseguir el hardware final en el que vaya a ser ejecutado el cdigo, o bien,
virtualizar el entorno adecuado. Ninguna de estas cosas suele ser fcil de conseguir, y ah es donde
entra el cross-compiling.

Los componentes necesarios para implementar el entorno de cross-compiling consisten


bsicamente en tres elementos:
Compilador C : compilador de C bsico
Librera C: implementa las llamadas al sistema mediante APIs.
Binutils: conjunto de programas para compilacin, enlazado, ensamblado y depuracin de
cdigo.

Cada una de estas herramientas funciona con dependencia de las otras, motivo por el cual es
bastante delicado el configurarlas y compilarlas. La complejidad radica en que no existe una
compatibilidad asegurada entre las diferentes versiones de los componentes, por eso, la primera
etapa para configurar un entorno de cross-compiling consiste en seleccionar las versiones de cada
uno de estos componentes, de forma que sean compatibles entre s.

Linux dispone de un paquete instalable para facilitar esta tarea: buildroot. Buildroot nos brinda
una interfaz amigable para seleccionar las versiones de las herramientas y las configuraciones extras
a implementar en el entorno de cross-compiling. Concretamente, buildroot es un conjunto de
makefiles y parches que permiten generar tanto el entorno de cross-compiling como el sistema de
archivo raz para el sistema destino, proporcionando una manera organizada de realizar las
configuraciones.

Un problema adicional surge a la hora de testear nuestro cdigo, ya que necesitaremos de un


bootloader, un kernel, un sistema de ficheros raz y libreras compartidas para probarlo en la
arquitectura destino.

You might also like