|
|
Tema 3. Manejo de Ficheros Ordinarios. |
1. ENTRADA/SALIDA SOBRE FICHEROS ORDINARIOSAPERTURA DE UN FICHERO (OPEN)Open es la llamada para indicarle al núcleo que habilite las estructuras necesarias para trabajar con un fichero especificado con una ruta. El núcleo devolverá un descriptor de fichero con el que podremos referenciar el fichero para las llamadas posteriores. La declaración de open es: #include <sys/types.h>#include <sys/stat.h> #include <fcntl.h> int open (char *path, int flag [, mode_t mode]); path puntero a la ruta del fichero que se quiere abrir. Puede ser absoluta o relativa y la longitud no puede exceder de PATH_MAX bytes. Flags es una mascara de bits que le indica al núcleo como queremos que se abra el fichero. Solamente uno de los bits debe estar presente ( O_RDONLY, O_WRONLY u O_DRWR ) al componer la mascara, sino el modo de apertura quedaría indefinido. Los flags más significativos son:
Mode es el tercer parámetro de open y sólo tiene significado cuando está activo el indicador O_CREAT. Le indica al núcleo qué permisos tendrá el fichero que va a crear. Mode es también una máscara de bits y se suele expresar en octal mediante un número de dígitos. El primero de los dígitos hace referencia a los permisos de lectura, escritura y ejecución para el propietario del fichero; el segundo se refiere a los mismos permisos para el grupo de usuarios al que pertenece el propietario, y el tercero se refiere a los permisos del resto de usuarios. Así, por ejemplo, 0644 -110 100 100- indica permisos de lectura y escritura para el propietario, y permiso de lectura para el grupo y para el resto de usuarios. Si el núcleo realiza satisfactoriamente la apertura del fichero, open devolverá un descriptor de fichero. En caso contrario, devolverá -1 y en la variable errno pondrá el valor del tipo de error producido. Los siguientes son ejemplos de apertura de ficheros: int fd;---- fd = open ("mifichero", O_RDONLY); / / Abre un fichero para leer datos de él. fd = open ("mifichero", O_WRONLYI O_TRUNC I O_CREAT, 0600); / / Abre un fichero para escribir datos en él. Si el fichero existe, trunca su tamaño / / a 0 bytes. Si el fichero no existe, lo crea con permiso de lectura y escritura para / / el propietario y ningún permiso para el grupo y demás usuarios. fd = open ("miiichero", RDWR | O_APPEND); / / Abre un fichero en modo lectura/escritura y fuerza a que el puntero de / / Lectura/escritura se sitúe al final del fichero. LECTURA DE DATOS DE UN FICHERO (READ)Read es la llamada que emplearemos para leer datos de un fichero. Su declaración es la siguiente: #include <unistd.h>int read (int i ildes, char *bui, unsigned nbyte) ; Read lee nbyte bytes del fichero asociado al descriptor fildes y los coloca en la memoria intermedia apuntada por buf. Si la lectura se lleva a cabo correctamente, read devuelve el número de bytes realmente leídos y copiados en la memoria intermedia. Este número puede ser menor que nbyte en el caso de que el fichero esté asociado a una línea de comunicaciones, o de que quedasen menos de nbyte bytes por leer. Cuando se intenta leer más allá del final del fichero, read devuelve el valor O. Sólo en el caso de que read falle, devuelve el valor -1y errno contendrá el tipo de error que se ha producido. En los ficheros con capacidad de acceso aleatorio, la lectura empieza en la posición indicada por el puntero de lectura/escritura del fichero. Este puntero queda actualizado después de efectuar la lectura. En los ficheros asociados a dispositivos sin capacidad de acceso aleatorio -por ejemplo, líneas serie-, read siempre lee de la misma posición y el valor del puntero no tiene significado. Los siguientes ejemplos muestran algunas formas de invocar a read. En estos ejemplos suponemos que id es el descriptor de un fichero correctamente abierto. char mem [4096];int nbytes, fd; ....... nbytes = read (fd, mem, sizeof (mem)); / / Lee 4.096 bytes que se almacenan en mem La lectura no tenemos por qué hacerla siempre sobre un array de caracteres, también se puede hacer sobre una estructura. Supongamos que queremos leer 40 registros con un formato concreto de un fichero de datos. Si la composición de cada registro la tenemos definida en una estructura de nombre REGISTRO, una secuencia de código para efectuar esta lectura puede ser: struct REGISTRO mem [40];int nbytes, id; nbytes = read (fd, mem, 40 * sizeof (REGISTRO)); ESCRITURA DE DATOS EN UN FICHERO (WRITE)Utilizaremos la llamada write para escribir datos en un fichero. Su declaración es muy parecida a la de read: int write (int fildes, char *buf, unsigned nbyte); write escribe nbyte bytes de la memoria referenciada por buf en el fichero asociado al descriptor fildes. Si la escritura se lleva a cabo correctamente, write devuelve el número de bytes realmente escritos; en caso contrario, devuelve -1 y errno contendrá el tipo del error producido. En los ficheros con capacidad de acceso aleatorio, la escritura se realiza en la posición indicada por el puntero de lectura/escritura del fichero. Después de la escritura, el puntero queda actualizado. En los ficheros sin capacidad de acceso aleatorio, la escritura siempre tiene efecto sobre la misma posición. Si el indicador O_APPENDestaba presente al abrir el fichero, el puntero se situará al final del mismo para que las llamadas de escritura añadan información al fichero. En los ficheros ordinarios, la escritura se realiza a través del buffer caché, por lo que una llamada a write no implica una actualización inmediata del disco. Este mecanismo acelera la gestión del disco, pero presenta problemas de cara a la consistencia de los datos. Si no ocurre ningún imprevisto, no hay nada que temer, pero en el caso de fallo no previsto -un corte de la alimentación del equipo, por ejemplo- es posible que se pierdan datos del buffer caché que no habían sido actualizados. Si al abrir el fichero estaba presente el indicador O_SYNC, forzamos que las llamadas a write no devuelvan el control hasta que se escriban los datos en el disco, asegurando así la consistencia. Naturalmente, este modo de trabajo está penalizado con un mayor tiempo de ejecución de nuestro proceso. Algunos ejemplos de uso de write son: char *str = "En un lugar de la Mancha...";int nbytes,id; nbytes = write (fd, str, strlen (str)); y para escritura con un formato concreto: struct REGISTRO reg;int nbytes, id; nbytes = write (fd, ®, sizeof (reg)); CIERRE DE UN FICHERO (CLOSE)Utilizaremos la llamada close para indicarle al núcleo que dejamos de trabajar con un fichero previamente abierto. El núcleo se encargará de liberar las estructuras que había montado para trabajar con el fichero. La declaración de close es: #include <unistd.h>int close (int fildes) ; Si fildes es un descriptor de fichero correcto devuelto por una llamada a creat, open, dup, fcntlo pipa, close cierra su fichero asociado y devuelve el valor O; en caso contrario, devuelve -1 y errno contendrá el tipo de error producido. El único error que se puede producir en una llamada a close es que fildes no sea un descriptor válido. Al cerrar un fichero, la entrada que ocupaba en la tabla de descriptores de ficheros del proceso queda libre para que la pueda utilizar una llamada posterior a open. Por otro lado, el núcleo analiza la entrada correspondiente en la tabla de ficheros del sistema y, si el contador que tiene asociado este fichero es 1 -esto quiere decir que no hay más procesos que estén unidos a esta entrada-, esa entrada también se libera. Si un proceso no cierra los ficheros que tiene abiertos, al terminar su ejecución el núcleo analiza la tabla de descriptores y se encarga de cerrar los ficheros que aún estén abiertos. CREACIÓN DE UN FICHERO (CREAT)La llamada creat permite crear un fichero ordinario o rescribir sobre uno existente. Su declaración es: #include <fcntl.h>int creat (char *path, mode - t moda) ; Path es un puntero al nombre del fichero que queremos crear. Mode es una máscara de bit s con el mismo significado que vimos para la llamada open. En esta máscara se especifican los permisos de lectura, escritura y ejecución para el propietario, grupo al que pertenece el propietario y el resto de los usuarios. Si creat funciona correctamente, devuelve un descriptor de fichero y el fichero es abierto en modo sólo escritura, incluso si mode no permite este tipo de acceso. Si el fichero ya existe, su tamaño es truncado a O bytes y su puntero de escritura se sitúa al principio. Si la llamada a creat falla, por ejemplo, si no tenemos permiso para crear un fichero en el directorio en el que intentamos hacerlo, la función devolverá -1 y en errno estará el código del tipo de error producido. La llamada a creat tiene la misma funcionalidad que una llamada a open con los indicadores O_WRONLY | O_CREAT | O_TRUNC activos. Así, las siguientes llamadas tienen la misma funcionalidad: fd = creat C "mif ichero" , 0666);fd = open C"mifichero", O_WRONLY| I O_CREAT I O_TRUNC, 0666); DUPLICADO DE UN DESCRIPTOR (DUP)La llamada dup duplica un descriptor de fichero que ya ha sido asignado y que está ocupando una entrada en la tabla de descriptores de fichero. Su declaración es: #include <unistd.h>int dup Cint fildes); fildes es un descriptor obtenido a través de una llamada previa a creat, open, dup, fcntl o pipe. La llamada a dup recorre la tabla de descriptores y va a marcar como ocupada la primera entrada que encuentre libre, pasando a devolvemos el descriptor asociado a esa entrada. Si falla en su ejecución, devolverá el valor -1, indicando a través de errno el error producido. Los dos descriptores -original y duplicado- tienen en común que comparten el mismo fichero, por lo que a la hora de leer o escribir podemos usados indistintamente. Cuando estudiemos las tuberías sin nombre, veremos la utilidad de esta llamada. ACCESO ALEATORIO (LSEEK)Con la llamada lseek podremos modificar el puntero de lectura/escritura de un fichero. Su declaración es la siguiente: #include <sys/types.h> .#include <unistd.h> / / Para las constantes simbólicas. Off_t lseek C int f ildes, off - t off set, int whence) ; Lseek modifica el puntero de lectura/escritura del fichero asociado a fildes de la siguiente forma:
Si offset es un número positivo, los avances deben entenderse en su sentido natural; es decir, desde el inicio del fichero hacia el final del mismo. Sin embargo, también se puede conseguir que el puntero retroceda pasándole a lseekun desplazamiento negativo. Cuando lseek se ejecuta correctamente devuelve un numero entero no negativo que es la nueva posición del puntero medida con respecto al principio del fichero. Si falla devuelve -1 y en errno estará el código del error producido. En algunos ficheros no esta permitido el acceso aleatorio y por lo tanto la llamada a lseek no tiene sentido. Ejemplos de esto son las tuberías con nombre y los ficheros de dispositivo en los que la lectura se realice siempre a través de un mismo registro o posición de memoria. CONSISTENCIA DE UN FICHEROLa entrada-salida con el disco se realiza a través del buffer caché para agilizar la transferencia de datos. Hay aplicaciones cuyas especificaciones obligan a que se prescinda del buffer caché y que las escrituras en un fichero se reflejen de forma inmediata en el disco. Se consigue pasándole a open, dependiendo del sistema, alguno de los indicadores (O_SYNC, O_SYNCW). Otra solución es hacer llamadas a fsync. 2. BIBLIOTECA ESTANDAR DE FUNCIONES DE ENTRADA/SALIDALa biblioteca estándar de funciones de entrada/salida, que forma parte de la definición del C estándar ANSI, hace uso de las llamadas al sistema para presentarnos una interfaz de alto nivel que permite al programador trabajar con los ficheros desde un punto de vista mas abstracto. INTERFAZ DE LA BIBLIOTECA ESTANDARReferencian los ficheros mediante punteros a estructuras de tipo FILE. Las cuatro primeras funciones son fopen, fread, fwrite y fclose. Apertura (fopen)Abre el fichero cuyo nombre esta apuntado por file_name y le asocia un flujo. El modo de acceso al fichero ,entre otros, puede tomar diferentes valores como estos:
Cada proceso que se ejecuta en UNIX y que se esta enlazando con la biblioteca estándar C, tiene asociado una tabla de flujos que se define de la siguiente forma: FILE _ _ iob[OPEN_MAX]; // tabla de flujosLa tres primeras entradas de la tabla de flujos están ocupadas por los ficheros estándar (#define stdin, #define stdout, #define stderr). El resto de los elementos quedaran iniciados a cero ya que se produce una variante global y esa zona además la memoria se inicializa a cero. Lectura de datos (fread)La declaración es: #include <stdio.h>size_t fread (char ptr, size_t size,size___t nitems, FILE * stream); Fread copia en el array apuntando por ptr un total de nitems bloques de datos procedentes del fichero apuntado por stream. Fread termina su lectura cuando encuentra el fichero final y su lectura se realiza correctamente. Escritura de datos (fwrite)Permite escribir datos en un fichero a través de su flujo asociado. La declaración es: #include <stdio.h>size_t fwrite(const char * ptr, size_t size,size_t nitems, FILE* streams); Fwrite copia en el fichero apuntado por stream el número de bloques indicado en nitems, cada uno de un tamaño byte. Cierre (fclose)Fclose cierra un fichero que ha sido abierto con fopen. La declaración es: #include <stdio.h>int fclose (FILE*stream); fclose hace que toda memoria intermedia de datos asociada a streams sea escrita en el disco, que el espacio de memoria reservado para las memorias intermedias sea liberado y que el flujo sea cerrado, devuelve cero si la llamada funciona correctamente y EOF si se produce algún error. ENTRADA / SALIDA DE CARACTERES CON LA BIBLIOTECA ESTANDARHay dos funciones para leer y escribir caracteres y son fgetc(lectura de caracteres) y fputc(escritura de caracteres). Fgetc devuelve el carácter siguiente al ultimo leído del fichero asociado a stream. Fgetc lee caracteres del fichero, devuelve un entero y consigue 2 objetivos: el byte leido se devuelve como un carácter sin signo, o que, se detecta el final del fichero se puede devolver EOF(-1) sin que haya lugar a confundirlo con un dato valido. En fputc se quiere escribir en el fichero y tiene dos marcos equivalentes: getc y putc. Estos actúan sobre la entrada estándar y la salida estándar, pueden codificarse como marcos a partir de getc y putc: #define getchar() getc (stdin)#define putchar(c) putc((c) , stdout) IMPLEMENTACION DE LA BIBLIOTECA ESTANDAR DE ENTRADA \ SALIDAPara que nuestras funciones no interfieran con las que ya existen, las nombraremos de igual forma que a las funciones estándar pero anteponiendo en carácter m a cada nombre.3. CONTROL DE FICHEROS ABIERTOS (fcntl)Con fcntl se puede controlar un fichero abierto mediante una llamada previa a open, creat, dup, la propia fcntl o pipe. Consiste en las posibilidades de cambiar los modos permitidos de acceso al fichero y de bloquear el acceso a parte del mismo o a su totalidad. Si no implementamos ningún mecanismo de sincronización, puede darse el caso de que el proceso lector lea una información parcialmente actualizada. Esto ocurrirá cuando el proceso que actualiza interrumpa al proceso lector en mitad de una operación de consulta de la base de datos. La declaración es: #include<sys/types.h>#include<stdio.h> #include<fcntl.h> int fcntl (int fildes, int cmd, union {int val; struct flock*lockdes}arg); Los siguientes son valores permitidos para cmd:
Un cerrojo de lectura indica que el proceso actual esta leyendo del fichero y ningún otro proceso podrá escribir en el área bloqueada. Un cerrojo de escritura indica que el proceso esta escribiendo en el fichero y ningún otro proceso se debe leer o escribir del área bloqueada. Los cerrojos fijados por un proceso sobre un fichero se borran cuando el proceso termina. Si fcntl no se ejecuta satisfactoriamente, devuelve el valor -1 y en errno estará codificado el tipo de error producido. La función getpid, devuelve el valor del identificador del proceso que la llama. La ejecución del programa arroja resultados comos los siguientes: $ fcntl & fcntl &PID = 154, nro = 1 PID = 154, nro = 6 PID = 155, nro = 1 PID = 155, nro = 6 PID = 154, nro = 2 PID = 154, nro = 7 PID = 155, nro = 2 PID = 155, nro = 7 PID = 154, nro = 3 PID = 154, nro = 8 PID = 155, nro = 3 PID = 155, nro = 8 PID = 154, nro = 4 PID = 154, nro = 9 PID = 155, nro = 4 PID = 155, nro = 9 PID = 154, nro = 5 PID = 154, nro = 10 PID = 155, nro = 5 PID = 155, nro = 10 Si la lectura como la escritura la realizamos con bloqueo, el resultado es: $ fcntl & fcntl &PID = 154, nro = 1 PID = 154, nro = 11 PID = 155, nro = 2 PID = 155, nro = 12 PID = 154, nro = 3 PID = 154, nro = 13 PID = 155, nro = 4 PID = 155, nro = 14 PID = 154, nro = 5 PID = 154, nro = 15 PID = 155, nro = 6 PID = 155, nro = 16 PID = 154, nro = 7 PID = 154, nro = 17 PID = 155, nro = 8 PID = 155, nro = 18 PID = 154, nro = 9 PID = 154, nro = 19 PID = 155, nro = 10 PID = 155, nro = 20 4. ADMINISTRACION DE FICHEROSSTAT, LSTAT Y FSTATEstas llamadas devuelven la información que se almacena en la tabla de nodos-i sobre el estado de un fichero concreto. Su declaración: #include <sys/types.h>#include <sys/stat.h> int stat (char *path, struct stat *buf); int lstat (int fildes, struct stat *buf); int fstat (int fildes, struct stat *buf); La diferencia entra stat y fstat es que la primera recibe como primer parámetro un puntero al nombre del fichero, mientras que la segunda trabaja con un fichero ya abierto y le debemos pasar su descriptor. Ambas devuelven la información estadística del fichero. Lstat trabaja de forma parecida a stat. , menos cuando el nombre del fichero corresponde a un enlace simbólico. La información administrativa del fichero se almacena en una estructura de tipo struct stat. Esta definido en el fichero sys/stat.h. Algunos de los campos estándar de esta estructura junto con su tipo asociado:
MODOS DE UN FICHEROSi queremos saber si un fichero es un directorio o no, se debe usar una expresión como: Porque si utilizamos, If ((mode & S_IFDIR) == S_IFDIR) Nos dará también el valor lógico VERDAD cuando ese fichero sea de tipo especial modo bloque. Hay 3 bits cuyo significado no se ha definido de momento, son: S_ISUIS-nº11-,S_ISGID -nº10- y S_ISVTX -nº9-. Significan:
Cambio de modo : chmod y fchmodEstas llamadas se utilizan para cambiar el modo de un fichero. Sus declaraciones son: #include <sys/types.h>#include <sys/stat.h> int chmod (char *path, mode_t mode); int fchmod (int fildes, mode_t mode) ; En chmod especificamos el fichero por su ruta, path, y con fchmod actuamos sobre un fichero ya abierto y que tiene asociado el descriptor fildes. Accesibilidad (access)Determina la accesibilidad de un fichero por parte de un proceso. Declaración: #include <unistd.h>int access (char *path, int amode); Path es un puntero a la ruta del fichero al que queremos acceder, amode es una máscara que codifica el tipo de acceso por el que preguntamos. En unistd.h están definidos los siguientes valores para mode: R_Ok permiso para leer.W_OK permiso para escribir. X_OK permiso para ejecutar. Máscara de permisos (umask)La usamos para definir la máscara de permisos que tendrá asociado un proceso a la hora de crear ficheros. Declaración: #include <sys/types.h>#include <sys/stat.h> mode_t umask (mode_t cmask); La nueva máscara por defecto se indica en cmask y umask devuelve el valor que tenía la máscara anterior. CAMBIO DE LA INFORMACIÓN ESTADÍSTICA DE UN FICHEROCambio del nombre de un fichero (rename)Declaración: #include <stdio.h>int rename (const char *source, const char *target); El argumento source apunta al nombre inicial del fichero y target al nuevo nombre. Cambio del propietario y del grupo de un fichero: chown y fchown Sirven tanto para cambiar el identificador del propietario de un fichero como el identificador del grupo. Declaración: #include <sys/types.h>int chown (char *path, uid_t owner, gid_t group); int fchown (int fildes, uid_t owner, gid_t group); La diferencia entre chown y fchown es que la primera trabaja con la ruta -path- de un fichero mientras que la segunda lo hace con el descriptor -fildes- de un fichero ya abierto. Cambio de la fecha de un fichero (utime)Declaración: #include <sys/types.h> #include <utime.h> int utime (char *path, struct utimebuf *times);path es el puntero al nombre del fichero cuyas fechas queremos cambiar, times es un puntero a una estructura de tipo struct utimebuf definida en <utime.h>. Tanto actime como modotime se expresan en segundos. El cambio de fecha solo puede ser ejecutado por el propietario del fichero y por el superusuario. Si utime se ejecuta correctamente nos devuelve el valor 0 pero si por el contrario no se ejecuta correctamente devuelve el valor -1 junto con errno (contiene el código del tipo del error). Longitud de un fichero: truncate y ftruncate.Se puede modificar la longitud de un fichero para que este tome cualquier valor comprendido entre la longitud nula y la actual del fichero mediante las siguientes sintaxis: Truncate ( char *path, unsigned long length );Ftruncate ( int fildes, unsigned long length ); En donde length es la nueva longitud del fichero ( bytes ). Truncate trabaja con un fichero mediante la especificación de su nombre ( path ) y ftruncate trabaja con un fichero ya abierto en modo lectura ( fildes ). Si se ejecutan correctamente devuelven el valor 0, y en caso contrario, -1 junto con errno y el código del tipo del error. Getpwuid y getgrgidGetpwuid y getgrgid son dos funciones de biblioteca. La primera de ellas sirve para leer información relativa al propietario del fichero y se ubica en / etc / passwd #include <pwd.h>struct passwd *getpwuid ( uid_t uid ); El campo pw_name contiene el nombre del usuario. La función getgrgid sirve para buscar información sobre el grupo al que pertenece su propietario. Esta búsqueda se realiza en / etc / group. #include <grp.h>struct group *getgrgid ( gid_t gid); El campo gr_name contiene el nombre del grupo 5. COMPARTICION Y BLOQUEO DE FICHEROSHay dos tipos de bloqueos: Bloqueo consultivo: el sistema conoce que recursos están bloqueados y que procesos los bloquean pero permite que estos recursos sean usados por otros procesos. Solo se puede trabajar con los recursos en caso de que estos se encuentren libres. Bloqueo adecuado a procesos cooperativos. Bloqueo obligatorio: el sistema comprueba los accesos a los recursos compartidos para denegarle el acceso a procesos no autorizados. No hace falta mirar el estado del recurso ya que el sistema impedirá utilizarlos. La función lockf bloquea total o parcialmente un fichero impidiendo que oros procesos accedan a esa región. La sintaxis es: #include <unistd.h>int lockf ( int fildes, int function, long size ); Cuando un proceso termina su ejecución se eliminan todos los cerrojos definidos sobre el fichero. Los ficheros deben estar en modo escritura o lectura / escritura para poder definir los cerrojos:
Size indica los bytes contiguos que se van a bloquear o desbloquear. El bloqueo empieza en el puntero hasta donde indique size. Si size vale 0 el bloqueo es hasta el final del fichero. La mejor solución para realizar cerrojos es con F_TLOCK ya que sus cerrojos son no bloqueantes y si el proceso no puede seguir adelante con el bloqueo no se queda durmiendo en espera de fijar el cerrojo. Con las funciones creat y open la única forma de bloquear un fichero es haciendo uno auxiliar. Si las llamadas a creat y open fallan significa que el fichero ha sido bloqueado por otro proceso, pero en caso contrario, el fichero se bloqueo por nuestro proceso. Para quitar el bloqueo basta con borrar el fichero auxiliar. La función lockf que se ha implementado es compatible con todas las versiones UNÍX que admitan la llamada open con parámetros como O_CREAT | O_EXCL. Pero lockf tiene también inconvenientes como:
Bajate esta documentación en un archivo Acrobat Reader |