Este documento aborda dos áreas fundamentales en el desarrollo de sistemas bajo Linux: el manejo directo de archivos mediante llamadas al sistema y la creación de bibliotecas de código reutilizable.
Parte I: Operaciones de E/S de Archivos a Bajo Nivel
Este apartado se enfoca en funciones que interactúan directamente con el kernel, sin buffering intermedio.
1. Manipulación de Descriptores de Archivo
El núcleo de la E/S de archivos es el descriptor (fd), un identificador entero no negativo.
La función para abrir un archivo y su contraparte para cerrarlo son:
int descriptor = open(const char *ruta, int banderas, ...);
int estado_cierre = close(int descriptor);
Las banderas (banderas) principales son O_RDONLY (lectura), O_WRONLY (escritura) y O_RDWR (lectura/escritura). Se pueden combinar con O_CREAT (crear si no existe), O_APPEND (añadir al final) y O_TRUNC (truncar a longitud cero). Al crear, se especifica el modo de permisos, el cual se ve afectado por la umask del proceso (p.ej., 0666 & ~umask).
2. Lectura y Escritura de Datos
Para transferir datos entre el descriptor y un búfer en memoria:
ssize_t bytes_leidos = read(int descriptor, void *búfer, size_t cantidad);
ssize_t bytes_escritos = write(int descriptor, const void *búfer, size_t cantidad);
Un bucle de lectura/escritura es esencial para manejar casos donde no se transfieren todos los bytes de una sola vez (común en dispositivos y sockets).
3. Control de la Posición en el Archivo
La posición (cursor) dentro de un archivo se mueve con lseek:
off_t nueva_pos = lseek(int descriptor, off_t desplazamiento, int punto_referencia);
Los puntos de referencia (punto_referencia) son SEEK_SET (inicio absoluto), SEEK_CUR (relativo a la posición actual) y SEEK_END (relativo al final). Esta función es clave para determinar el tamaño de un archivo (moviéndose al final y obteniendo la posición) o para leer en posiciones arbitrraias.
4. Control Avanzado y Bloqueo de Archivos
La función fcntl proporciona control avanzado sobre descriptores:
int resultado = fcntl(int descriptor, int comando, ... /* arg */ );
Permite, por ejemplo, duplicar descriptores (F_DUPFD) o gestionar bloqueos de archivo. Para bloqueos, se usa la estructura struct flock:
struct flock candado;
candado.l_type = F_WRLCK; // Tipo: F_RDLCK, F_WRLCK, F_UNLCK
candado.l_whence = SEEK_SET;
candado.l_start = 0;
candado.l_len = 100; // Longitud del segmento a bloquear
fcntl(descriptor, F_SETLK, &candado);
Parte II: Metadatos, Directorios y Mapeo de Usuarios
Esta sección cubre la inspección de atributos de archivos y la navegación por el sistema de archivos.
1. Consulta de Atributos de Archivo
Para obtener información detallada de un archivo:
struct stat info_archivo;
int exito = stat(const char *ruta, &info_archivo); // Sigue enlaces simbólicos
int exito = lstat(const char *ruta, &info_archivo); // No sigue enlaces simbólicos
int exito = fstat(int descriptor, &info_archivo); // Usando descriptor
La estructura stat contiene campos clave como st_mode (tipo y permisos), st_size (tamaño), st_uid/st_gid (propietario/grupo) y st_mtime (última modificación). Para interpretar st_mode, se usan macros como S_ISREG(modo) (archivo regular) o S_ISDIR(modo) (directorio).
2. Iteración sobre Contenidos de Directorios
Para listar los elementos dentro de un directorio:
DIR *flujo_dir = opendir(const char *ruta_directorio);
struct dirent *entrada;
while ((entrada = readdir(flujo_dir)) != NULL) {
// entrada->d_name contiene el nombre del archivo/subdirectorio
// entrada->d_type indica el tipo (DT_REG, DT_DIR, etc.)
}
closedir(flujo_dir);
El flujo (flujo_dir) debe cerrarse después de su uso. Las entradas incluyen los directorios especiales . (actual) y .. (padre).
3. Resolución de Identificadores de Usuario y Grupo
Para traducir IDs numéricos a nombres legibles:
struct passwd *datos_usuario = getpwuid(uid_t uid); // Busca por UID
struct group *datos_grupo = getgrgid(gid_t gid); // Busca por GID
Estas estructuras proporcionan campos como pw_name (nombre de usuario) y gr_name (nombre del grupo).
Aspectos Críticos a Considerar
- Enlaces Simbólicos: Usar
staten un enlace devuelve información del archivo objetivo. Para información del propio enlace, se debe usarlstat. - Gestión de Descriptores: Verificar que
openno retorne -1. Siempre cerrar descriptores conclosepara evitar fugas de recursos. - Umask: Los permisos al crear archivos se enmascaran (
&) conumask, generalmente 0002.
Parte III: Bibliotecas Estáticas y Dinámicas en Linux
Las bibliotecas permiten encapsular y reutilizar código compilado.
1. Concepto y Nomenclatura
Una biblioteca es un archivo que contiene código objeto precompilado. En Linux:
- Biblioteca Estática: Archivo con extensión
.a(p.ej.,libmatematicas.a). - Biblioteca Compartida (Dinámica): Archivo con extensión
.so(p.ej.,libssl.so.1.1). Puede incluir información de versión.
2. Bibliotecas Estáticas
Su código se copia enlazado al ejecutable final durante la compilación.
Creación:
- Compilar el código fuente a código objeto:
gcc -c modulo.c -o modulo.o - Empaquetar los objetos en una biblioteca:
ar -crv libnombre.a modulo.o modulo2.o
Uso: Al compilar el programa principal, se enlaza con -lnombre -L/ruta/a/biblioteca.
3. Bibliotecas Compartidas (Dinámicas)
Su código se carga en memoria en tiempo de ejecución y puede ser compartido por múltiples procesos.
Creación:
- Generar código independiente de posición (PIC):
gcc -c -fPIC modulo.c -o modulo.o - Enlazar los objetos en una biblioteca compartida:
gcc -shared modulo.o -o libnombre.so
Uso: Se enlaza durante la compilación con -lnombre -L/ruta, pero el sistema debe encontrar el archivo .so en tiempo de ejecución.
Configuración de Rutas en Tiempo de Ejecución:
- Variable de Entorno:
export LD_LIBRARY_PATH=/ruta/a/libreria:$LD_LIBRARY_PATH(efímera). - Configuración del Sistema: Añadir la ruta a
/etc/ld.so.confy ejecutarldconfig(permanente). - Rutas por Defecto: Copiar el archivo
.soa/usr/libo/lib.
4. Comparativa Clave
- Enlace: La estática ocurre en compilación; la dinámica, en ejecución.
- Tamaño del Ejecutable: Con estática, el ejecutable es más grande; con dinámica, es más pequeño.
- Consumo de Memoria: La estática duplica código en memoria por proceso; la dinámica lo comparte.
- Actualización: Cambiar una biblioteca estática requiere recompilar; una dinámica, solo reemplazar el archivo
.so(si la API es compatible).