PRÁCTICAS DE SISTEMAS OPERATIVOS I

SEXTA SESIÓN


  1. Primera prueba de programación en C.

    Vamos a compilar un programa de prueba en C, como los que tendréis que hacer en las sesiones restantes:

    Ya tenemos los ficheros fuente que componen el programa. Ahora es el momento de crear los ejecutables. Para ello, debéis crear lo que se denomina un makefile. Un makefile es un fichero que le dará al ordenador una serie de reglas acerca de los pasos que ha de ejecutar para llegar a producir el ejecutable. Para recordar lo que vimos en teoría acerca de los makefiles, pulsa aquí.

    Recordemos también los pasos que hay que dar para llegar al ejecutable:



    El orden en que se escriben las reglas en un fichero makefile no importa, salvo la primera, que ha de ser la más general. Si observamos el dibujo, la primera regla debe decir algo así como: "Para obtener el fichero ejecutable prueba, necesito los ficheros objeto prueba.o y raiz.o y lo obtengo mediante la orden gcc prueba.o raiz.o -o prueba -lm". Esta regla, traducida al lenguaje del fichero makefile quedaría así:
    prueba: prueba.o raiz.o
    <TAB>gcc prueba.o raiz.o -o prueba -lm
                
    Observad cómo se añade -lm a la orden de enlazado porque el código de la función sqrt no se encuentra en raiz.o, sino en la biblioteca del sistema libm.a. El código de la mayor parte de funciones del C estándar se encuentra también en otra biblioteca de funciones, la libc.a pero, como excepción, no hay que indicarle al enlazador que use esa biblioteca, lo hace automáticamente.
    El fichero makefile completo queda así:
    prueba: prueba.o raiz.o
    <TAB>gcc prueba.o raiz.o -o prueba -lm
    
    prueba.o: prueba.c raiz.h
    <TAB>gcc -c prueba.c
    
    raiz.o: raiz.c
    <TAB>gcc -c raiz.c
    
    Tenemos, pues, cuatro ficheros en el directorio PRUEBA. Podemos decirle al ordenador que compile el programa:
    <T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l
    total 8
    -rw-r--r--   1 gyermo     profes         156 Feb 16 12:01 makefile
    -rw-r--r--   1 gyermo     profes         316 Feb 16 11:17 prueba.c
    -rw-r--r--   1 gyermo     profes         110 Feb 16 11:18 raiz.c
    -rw-r--r--   1 gyermo     profes          29 Feb 16 11:18 raiz.h
    <T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ make
            gcc -c prueba.c
            gcc -c raiz.c
            gcc prueba.o raiz.o -o prueba -lm
                
    Vemos que tenemos los ficheros objeto (.o) y el ejecutable, que ya podemos ejecutar:
    <T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l
    total 132
    -rw-r--r--   1 gyermo     profes         135 Feb 16 12:29 makefile
    -rwxr-xr-x   1 gyermo     profes       61440 Feb 16 12:37 prueba
    -rw-r--r--   1 gyermo     profes         314 Feb 16 12:32 prueba.c
    -rw-r--r--   1 gyermo     profes         988 Feb 16 12:37 prueba.o
    -rw-r--r--   1 gyermo     profes          77 Feb 16 12:37 raiz.c
    -rw-r--r--   1 gyermo     profes          29 Feb 16 12:37 raiz.h
    -rw-r--r--   1 gyermo     profes         764 Feb 16 12:37 raiz.o
    <T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ prueba
    La raíz cuadrada de 2 es 1.414214.
                
    Lo bueno de los ficheros makefile es que si realizáis alguna modificación, el ordenador sólo realizará las compilaciones necesarias. Probad a cambiar el fichero prueba.c para que calcule la raíz cuadrada de 3 y haced a continuación un make.

    Puede que, en alguna circunstancia, deseéis que make recompile todo de nuevo. En ese caso, teclead touch *.c *.h. Esta orden actualiza la fecha de modificación de todos los ficheros con extensiones .c o .h para que make crea que los acabamos justo de editar.

    NOTA: Si no os funciona touch, en el servidor, probad con tocar.

  2. Argumentos y códigos de retorno de los programas.

    Los programas que se lanzan en la shell pueden llevar argumentos y devolver valores al sistema operativo. En el caso de la orden:
    <T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l
    total 132
    -rw-r--r--   1 gyermo     profes         135 Feb 16 12:29 makefile
    -rwxr-xr-x   1 gyermo     profes       61440 Feb 16 12:37 prueba
    [...]       
    diremos que el fichero ejecutable /bin/ls se ha ejecutado con un argumento (-l). Diremos que -l es el argumento número 1 de esa ejecución. Abusando del lenguaje, el propio nombre del fichero ejecutable (en este caso ls) se dice que es el argumento número 0.

    Los programas también devuelven un valor al sistema operativo. Para conocer qué valor ha devuelto un programa, hay que dar justo a continuación de haber ejecutado el programa la orden echo $? a la shell:
    <T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l
    total 132
    -rw-r--r--   1 gyermo     profes         135 Feb 16 12:29 makefile
    -rwxr-xr-x   1 gyermo     profes       61440 Feb 16 12:37 prueba
    [...]
    <T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ echo $?
    0
    <T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l NoWomanNoCry.mp3
    NoWomanNoCry.mp3 not found
    <T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ echo $?
    2           
    El valor devuelto por un programa ha de ser un número comprendido entre 0 y 255. Es costumbre en UNIX que un valor de 0 signifique ejecución exitosa y los valores mayores que cero indiquen error.

  3. El prototipo de main.

    main es la primera función que se ejecutará dentro de un programa. Todo ejecutable debe contener una función main. El prototipo es variable, sin embargo. Si definimos el prototipo de la función main de la manera siguiente, podemos saber qué argumentos nos ha pasado el usuario en esta ejecución y devolver un valor al sistema operativo:
    int main(int argc, char *argv[]);
                
    El valor que devolveremos al sistema operativo será justo ese entero que devuelve la función main. Los argumentos los recibiremos en los parámetros con los que el sistema operativo va a invocar a la función main cuando comience la ejecución del programa.

    El primer parámetro es un entero e indica cuántos argumentos ha tecleado el usuario, incluyendo el propio nombre del programa. El segundo parámetro es un array de cadenas de caracteres de longitud igual al número de argumentos y que contiene los propios argumentos en sí.

    Ejemplo: la función main del programa ls recibe los siguientes parámetros cuando un usuario teclea "ls -l":

    Argumentos de main para ls -l
    argc2
    argv[0]"ls"
    argv[1]"-l"

    Nota: al ser argc y argv parámetros de una función (la función main), son variables mudas, es decir, no tienen por qué llamarse así. Lo que importa es que coincida su tipo. De todos modos, es costumbre en C denominarlos así (argc el primero y argv el segundo).

  4. Las llamadas al sistema.

    Manejar bien las llamadas al sistemas es un objetivo muy muy importante en esta parte de la asignatura. Una llamada al sistema tiene el aspecto de una función de C. Sin embargo, se trata de una auténtica invocación al núcleo del sistema operativo para que realice una tarea para nuestro programa. En cada sesión de las restantes se presentarán unas cuantas llamadas al sistema para vuestro estudio.

    Para usar una llamada al sistema en vuestros programas sólo debéis conocer el nombre de la llamada, qué parámetros hay que pasarle, qué devuelve y el fichero de cabecera o los ficheros de cabecera que tenéis que incluir en vuestro programa.

    Las llamadas al sistema de cada sesión las podréis encontrar aquí. Es conveniente que manejéis este fichero con soltura. Para una descripción detallada de lo que hace la llamada al sistema, debéis consultar la página de manual de la llamada. Por ejemplo, man -s 2 chmod. El -s 2 viene de que todas las llamadas al sistema se encuentran en la sección 2 del manual.

    Ejemplo: si queremos cambiar los permisos del fichero pepe.txt para que todo el mundo tenga acceso de lectura y escritura a él, la llamada al sistema sería:
    #include <sys/types.h>
    #include <sys/stat.h>
    [...]
    chmod("pepe.txt",0666);
                
    Todas las funciones de biblioteca de C (p. ej. printf), se basan internamente en las llamadas al sistema y sus páginas de manual se encuentran en la sección 3c.

  5. Condiciones de error.

    Una llamada al sistema puede fallar. Por ejemplo, la llamada al sistema del apartado anterior puede fallar porque el fichero pepe.txt no exista, porque no tengamos los permisos adecuados para cambiar los permisos del fichero, etc. Es responsabilidad del programador detectar cuándo se ha producido un error. Normalmente sabemos que se ha producido un error porque la llamada al sistema devuelve un valor concreto, habitualmente -1 (menos uno). En cualquier caso, se puede consultar el resumen o las páginas de manual. Por lo tanto, mejor que el código del apartado anterior deberíamos poner algo así:
    #include <sys/types.h>
    #include <sys/stat.h>
    [...]
    if (chmod("pepe.txt",0666)==-1)
       {fprintf(stderr,"No se puede cambiar los permisos.\n");
        return 1;}
                
    Para saber qué tipo de error se ha producido, debemos consultar una variable global del sistema denominada errno. En la página de manual de la llamada al sistema con la que estemos trabajando se nos dice qué valores toma errno según el error que se produzca. En particular, en la página de manual de chmod podemos leer:
    [...]
     ERRORES
          Si chmod() o fchmod() fallan, el modo del fichero sigue igual. A errno
          se le asigna uno de los siguientes valores.
    [...]
               [ENOENT]            Un componente de path o el fichero nombrado
                                   por path no existe.
    [...]
                
    Por lo tanto, si queremos considerar ese error en concreto, el código quedaría:
    #include <sys/stat.h>
    #include <errno.h>
    [...]
    if (chmod("pepe.txt",0666)==-1)
       {if (errno==ENOENT)
            fprintf(stderr,"El fichero pepe.txt no existe.\n");
        else
            fprintf(stderr,"Imposible cambiar los permisos debido a un error.\n");
        return 1;}
                
    Las acciones que el programa debe tomar si se produce un error pueden variar. Puede ser que el programa se pueda recuperar del error o puede que tenga que acabar su ejecución. En todo caso, es el programador el que debe situarse en las circunstancias de cada error y decidir. En el caso del ejemplo, decidimos acabar con el programa y devolver 1 al sistema operativo.

  6. La función de biblioteca perror.

    Como considerar todos y cada uno de los posibles errores de una llamada al sistema sería una labor muy tediosa y costosa, existe una función de biblioteca de C que nos ahorra todas estas comprobaciones. La función perror comprueba la variable errno y escribe por el canal de error estándar del programa una etiqueta que le pasamos como parámetro, dos puntos y un mensaje indicativo del error que se ha producido. La etiqueta suele hacer referencia al nombre del programa, a la función dentro del programa o cualquier otra referencia que sirva al programador para detectar en su código fuente dónde se produjo el error. Aunque es una función muy útil, hay veces que no aporta toda la información deseada.

    El código del apartado anterior puede quedar así usando la función perror:
    #include <sys/stat.h>
    #include <stdio.h>
    [...]
    if (chmod("pepe.txt",0666)==-1)
       {char etiqueta[80];
        sprintf(etiqueta,"%s:chmod:pepe.txt",argv[0]);
        perror(etiqueta);
        return 1;}
                

  7. Práctica.

    Como primera práctica (que no hay que entregar), hay que realizar un programa que admita el nombre de un fichero como argumento. El programa debe cambiar el grupo al que pertenece el fichero al grupo cuyo identificador de grupo es el 666. El programa debe detectar e imprimir las codiciones de error y devolver 0 al sistema operativo si no se produjeron errores y 1 en caso contrario. Notas: en caso de que el fichero sea un enlace simbólico, debe cambiar el grupo del propio enlace, no de aquello a lo que apunta. El propietario del fichero debe permanecer inalterado.

    Soluciones y comentarios.

  8. Órdenes de la shell relacionadas.

    chmod
    modifica los permisos de un fichero
    chown
    cambia el propietario de un fichero
    chgrp
    cambia el grupo de un fichero
    ln
    hace un enlace duro o simbólico a un fichero
    rm
    borra un fichero
    ls
    lista el contenido de un directorio


  9. Funciones de biblioteca relacionadas.



  10. LPEs.


© 2011 Guillermo González Talaván.