Entrada y Salida Básica en Linux

Entrada y Salida Básica en Linux

Repaso de Interfaz de Archivos en C

Detalle importante: Las cadenas de caracteres en C terminan con '\0', pero los archivos no tienen esta restricción.

const char *cadena_msg = "hola mundo";
write(descriptor_archivo, cadena_msg, strlen(cadena_msg)); // strlen no requiere +1

char buffer_entrada[1024];
ssize_t bytes_leidos = read(descriptor_archivo, buffer_entrada, sizeof(buffer_entrada) - 1);
if (bytes_leidos > 0) {
    buffer_entrada[bytes_leidos] = '\0';
}

Interfaz de Llamadas al Sistema de Archivos

Tabla de Comparación entre fopen de C y open de Linux

Propósito Modo fopen de C Flag open de Linux Descripción
Modos de Acceso Básicos
Solo lectura "r" O_RDONLY Permiso de solo lectura básico
Solo escritura "w" O_WRONLY Permiso de solo escritura básico
Lectura y escritura "r+", "w+", "a+" O_RDWR Permiso de lectura y escritura
Creación y Vaciado
Crear archivo Implícito en "w", "a", etc. O_CREAT Crea el archivo si no existe
Creación forzada Sin equivalente directo O_CREAT | O_EXCL Creación atómica; falla si el archivo ya existe
Vaciar archivo "w", "w+" vacían automáticamente O_TRUNC Vacía el contenido al abrir
Modo de Adición
Adición al final "a", "a+" O_APPEND Toda escritura se añade al final del archivo
Modo Binario
Archivo binario "rb", "wb", etc. Sin flag correspondiente Linux no distingue entre texto y binario
Sincronización y Rendimiento
Escritura síncrona Sin equivalente directo O_SYNC Retorna solo después de que los datos se escriben en disco
E/S directa Sin equivalente directo O_DIRECT Evita la caché de página, acceso directo al dispositivo
No bloqueante Sin equivalente directo O_NONBLOCK Apertura no bloqueante (para dispositivos/tuberías)

Tabla Rápida de Correspondencia

Modo fopen Combinación equivalente de flags open ¿Archivo debe existir? ¿Vacía el archivo? Posición de escritura Descripción
"r" O_RDONLY No - Solo lectura; falla si el archivo no existe
"w" O_WRONLY | O_CREAT | O_TRUNC No Inicio Crea o sobreescribe
"a" O_WRONLY | O_CREAT | O_APPEND No No Final Crea o añade
"r+" O_RDWR No Desplazable Lectura y escritura; falla si no existe
"w+" O_RDWR | O_CREAT | O_TRUNC No Desplazable Crea o sobreescribe en lectura/escritura
"a+" O_RDWR | O_CREAT | O_APPEND No No Lectura desplazable, escritura siempre al final Crea o añade en lectura/escritura

Comparación de Interfaz de E/S de Archivos

Operación Biblioteca Estándar de C (stdio.h) Llamada al Sistema de Linux Diferencia Principal
Objeto FILE* (puntero a archivo) int fd (descriptor de archivo) FILE* incluye fd y un búfer
Abrir fopen(ruta, modo) - "r", "w", "a" open(ruta, flags) - O_RDONLY, O_CREAT Cadena de modo vs flags de bits
Leer fread(ptr, tamaño, n, fp) retorna número de bloques read(fd, búfer, cantidad) retorna número de bytes 1. Tiene búfer 2. Unidad diferente
Escribir fwrite(ptr, tamaño, n, fp) retorna número de bloques write(fd, búfer, cantidad) retorna número de bytes 1. Tiene búfer 2. Unidad diferente
Posicionar fseek(fp, desplazamiento, origen) lseek(fd, desplazamiento, origen) Función similar, fseek debe manejar búfer
Cerrar fclose(fp) close(fd) fclose vacía el búfer primero
Búfer Tiene búfer - fflush() para vaciar al kernel Sin búfer - Cada operación es una llamada al sistema directa El búfer es la mayor diferencia
Características Portable, eficiente, fácil de usar Bajo nivel, flexible, controlable Alto nivel vs bajo nivel
printf("salida por printf\n");
fprintf(stdout, "salida por fprintf\n");
fwrite(cadena_fwrite, strlen(cadena_fwrite), 1, stdout);
write(1, cadena_write, strlen(cadena_write));

Esencia del Acceso a Archivos

¿Qué es fd y la Organización de Archivos Abiertos en un Proceso?

fd es el índice del arreglo fd_array[]. El proceso solo reconoce el descriptor de archivo fd.

¿Por qué los descriptores de archivo comienzan desde 3? ¿Qué son 0, 1, 2?

Al iniciar un proceso, el kernel abre automáticamente tres descriptores estándar: stdin, stdout, stderr.

Relación entre FILE* en C y fd

FILE es una encapsulación de fd.

¿Cerrar fd cierra directamente el archivo?

Se decrementa el contador de referencias.

Redirección y Búferes

Redirección

¿Cuál es la regla de asignación para descriptores de archivo?

Encontrar la posición vacía más baja en fd_array[].

La esencia de dup2() es copiar [oldfd] a [newfd] en el arreglo fd_array[]
#include <unistd.h>
int duplicar_descriptor(int descriptor_original, int descriptor_nuevo);

Redirección de Salida y Entrada
// Redirección de salida
int descriptor_archivo = open("salida.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);
duplicar_descriptor(descriptor_archivo, STDOUT_FILENO);
close(descriptor_archivo);

// Redirección de entrada
FILE *archivo_prueba = fopen("entrada.txt", "w");
fprintf(archivo_prueba, "hola\n");
fclose(archivo_prueba);
int descriptor_lectura = open("entrada.txt", O_RDONLY);
duplicar_descriptor(descriptor_lectura, STDIN_FILENO);
close(descriptor_lectura);

// Ejemplo de shell
// ls -l > lista_archivos.txt
// wc -l < lista_archivos.txt

Implementar Redirceción en una Shell Personalizada
  1. Durante la interacción, detectar si se necesita redirección.
  2. En la función EjecuciónNormal(), agregar lógica para redirección de salida y entrada.
  3. No olvide limpiar la información de redirección al inicio del bucle.

Búferes

Búfer proporcionado por C (búfer a nivel de usuario) y Búfer del Sistema
Esencia del vaciado del usuario (fflush()) y exit() vs _exit()

El vaciado del usuario transfiere datos del búfer de nivel de usuario al búfer del sistema. Actualmente, asumimos que una vez que los datos están en el kernel, pueden llegar al hardware. exit() vacía el búfer (sistema) antes de terminar, ya que es una función a nivel de usuario, mientras que _exit() no vacía el búfer, pues es una interfaz proporcionada por el sistema que no ve el búfer a nivel de usuario.

Problemas de Vaciado del Búfer
  • Sin búfer: stderr
  • Búfer de línea: stdout
  • Búfer completo: archivos abiertos con fopen

Notas adicionales:
Al terminar el proceso, también se vacía el búfer.
¿Por qué existe este búfer?
- Eficiencia para el usuario.
- Compatibilidad con el formateo.
¿Dónde está este búfer?
El búfer de nivel de usuario está en la pila o heap de la memoria del proceso, parte del espacio de usuario. Al abrir 10 archivos, hay 10 búferes a nivel de usuario.

Otros Conceptos

Ejemplo confuso: ./mi_test >todo.log 2>&1
Es equivalente a ./mi_test 1>todo.log 2>&1. Redirige la salida estándar (stdout) de mi_test a todo.log, y stderr también se redirige a todo.log (porque 1 ya apunta a todo.log).
Entender que "todo es un archivo".

Sistema de Archivos

Conociendo el Hardware — Disco Duro

Disco y disco duro, véase notas de arquitectura de computadoras.

Sistema de Archivos

El disco entero se divide en múltiples particiones, cada una correspondiente a un sistema de archivos independiente.
Una partición se divide en múltiples bloques de grupo, y la estructura de cada grupo es la misma. A continuación se detalla un grupo de bloques.

Nombre del Componente Función
Superblock (Bloque Super) Registra la "información global" del sistema de archivos: número total de bloques, cantidad de grupos de bloques, tamaño de cada bloque, etc. (equivalente al "manual" del sistema de archivos). Solo algunos bloques lo tienen (algunos tienen múltiples, otros no).
Descriptor de Grupo Registra el estado del grupo de bloques actual: posiciones de los mapas de bits de bloques e inodos, cantidad de bloques/inodos libres, etc. (equivalente a una "tarjeta de información" del grupo de bloques).
Mapa de Bits de Bloques Usa "bits" para marcar qué bloques de datos en el grupo están libres o en uso (por ejemplo, un bit 1 = en uso, 0 = libre, para encontrar bloques vacíos rápidamente).
Mapa de Bits de Inodos Usa "bits" para marcar qué inodos en el grupo están libres o en uso (similar al mapa de bits de bloques, gestiona la asignación de inodos).
Tabla de Inodos Almacena la información específica de todos los inodos del grupo de bloques actual (cada archivo tiene un inodo, más adelante se explica).
Bloques de Datos Almacenan el contenido real de los archivos (por ejemplo, bytes de documentos, imágenes).

Los atributos del archivo se almacenan en el inodo, y los datos del archivo se almacenan en los bloques de datos (el inodo no registra el nombre del archivo).

Estructura del Inodo
#define MAX_BLOQUES 15

struct inodo_archivo {
    unsigned int numero_inodo;
    unsigned short tipo_archivo; // Archivo normal, directorio, enlace simbólico, etc.
    unsigned short permisos;     // Permisos de lectura, escritura, ejecución
    unsigned short cuenta_enlaces; // Número de enlaces duros
    unsigned int propietario;    // ID de usuario propietario
    unsigned int grupo;          // ID de grupo
    time_t tiempo_acceso;
    time_t tiempo_cambio;
    time_t tiempo_modificacion;
    int bloques_datos[MAX_BLOQUES]; // Arreglo de punteros a bloques de datos
};

Crear un Nuevo Archivo: ¿Qué Hace el Sistema?

Siguiendo el flujo del sistema de archivos:

  • Asignar inodo: Consultar el mapa de bits de inodos del grupo objetivo, encontrar un inodo libre, marcarlo como "en uso", e inicializar los metadatos del inodo en la tabla de inodos (por ejemplo, tiempo de creación, permisos).
  • Asignar bloques de datos: Consultar el mapa de bits de bloques del grupo objetivo, encontrar bloques de datos libres, marcarlos como "en uso", y registrar los números de estos bloques en el inodo recién creado.
  • Asociar nombre de archivo: En el archivo de directorio correspondiente (un directorio es también un archivo, almacenado en bloques de datos), agregar un mapeo "nombre de archivo → número de inodo nuevo".
Eliminar un Archivo: ¿Qué Hace el Sistema?
  • Eliminar mapeo del directorio: Borrar el registro "nombre de archivo → número de inodo" del contenido del directorio correspondiente.
  • Marcar inodo como libre: Encontrar el inodo correspondiente al archivo y marcarlo como "libre" en el mapa de bits de inodos.
  • Marcar bloques de datos como libres: Leer los números de bloques de datos del inodo y marcarlos como "libres" en el mapa de bits de bloques.
Modificar un Archivo: ¿Qué Hace el Sistema?

Se divide en "modificar contenido" y "modificar metadatos":

  • Modificar contenido del archivo: Escribir datos directamente en los bloques de datos ya asignados al archivo (si el espacio es insuficiente, se asignan nuevos bloques y se actualiza la lista de bloques en el inodo).
  • Modificar metadatos (por ejemplo, cambiar permisos, renombrar):
    Cambiar permisos: Actualizar directamente el campo de permisos en el inodo del archivo.
    Renombrar: Modificar en el archivo de directorio el registro "nombre antiguo → número de inodo" a "nombre nuevo → número de inodo" (el inodo en sí no cambia).

En el sistema de archivos, los directorios y los archivos normales son similares: ambos tienen su propio inodo y bloques de datos, pero los bloques de datos de un directorio almacenan una tabla de mapeo "nombre de archivo → número de inodo" en lugar de texto o imágenes.

Notas Adicionales

La unidad básica de intercambio entre memoria física y disco es 4 KB (marcos de página).
Gestión de memoria segmentada paginada.
En Linux, cada archivo abierto por cada proceso debe tener sus propios atributos de inodo y su propio búfer de página de archivo.
Los datos desde un dispositivo externo hasta el disco pasan por tres copias: búfer de C, búfer de archivo del proceso, disco.

Enlaces Duros y Enlaces Suaves

Enlaces Duros

Comprensión: Un inodo correspondiente a múltiples nombres de archivo.
El número de enlaces duros es el contador de referencias, por lo que eliminar un enlace duro solo reduce el contador de referencias; eliminar el archivo fuente de un enlace suave deja el enlace suave en el aire.
Creación: El sistema operativo no permite a los usuarios crear enlaces duros de directorios, ya que podría formar bucles.
Comando: ln archivo_original nombre_enlace por ejemplo, ln archivo.txt copia_seguridad.txt

Enlaces Suaves

Comprensión: Un archivo que almacena la ruta absoluta o relativa del archivo al que apunta el enlace suave.

  • Tiene su propio número de inodo.
  • Almacena la ruta del archivo objetivo (puede ser relativa o absoluta).
  • Puede cruzar sistemas de archivos.
  • Puede crearse para directorios.
  • Si el archivo objetivo se elimina, el enlace suave se convierte en un "enlace colgante" (dangling link).

Creación: ln -s objetivo nombre_enlace por ejemplo, ln -s /usr/bin/python3 python
Eliminar enlace: unlink nombre_enlace

Bibliotecas Estáticas y Dinámicas

Comando para verificar bibliotecas: ldd ejecutable

Creación de Bibliotecas Estáticas y Dinámicas

Crear Biblioteca Estática
// Compilar archivos .o
gcc -c mi_modulo.c -o mi_modulo.o

// Crear biblioteca estática
ar -rc libmimodulo.a mi_modulo.o

// Preparar para distribución
mkdir -p mi_biblioteca/include
mkdir -p mi_biblioteca/lib
cp mi_modulo.h mi_biblioteca/include
cp libmimodulo.a mi_biblioteca/lib
// Se puede agregar a un Makefile (no olvide la limpieza)

Al distribuir mi_biblioteca, otros no pueden ver mi_modulo.c.

Crear Biblioteca Dinámica

Solo se necesita cambiar los comandos:

// Compilar a archivos de objeto (debe usar -fPIC)
gcc -fPIC -c add.c -o add.o
gcc -fPIC -c sub.c -o sub.o

// Crear biblioteca dinámica (usar -shared)
gcc -shared -o libmimodulo.so add.o sub.o

Uso/Descarga de Bibliotecas Estáticas y Dinámicas

Biblioteca Estática

gcc main.c -I ./mi_biblioteca/include -L ./mi_biblioteca/lib -lmimodulo
-I especifica el directorio de búsqueda de archivos de encabezado.
-L especifica el directorio de búsqueda de archivos de biblioteca.
-l enlaza la biblioteca llamada libmimodulo.a.

Biblioteca Dinámica

gcc main.c -I ./mi_biblioteca/include -L ./mi_biblioteca/lib -lmimodulo
Ahora, al ejecutar ldd a.out, se muestra que libmimodulo.so no se encuentra, porque el comando anterior solo enlaza la biblioteca durante la compilación; la carga no se realiza.
Para ver cómo se carga la biblioteca dinámica:

// Ejemplo de configuración en Linux
sudo vim /etc/ld.so.conf.d/mi_biblioteca.conf
// Insertar la ruta, por ejemplo: /home/usuario/mi_biblioteca/lib
ldconfig

¿Cómo se Carga la Biblioteca Dinámica?
  1. Copiar a la ruta predeterminada del sistema /lib64
  2. Crear un enlace suave a la ruta predeterminada /lib64: ln -s /ruta/completa/libmimodulo.so /lib64/libmimodulo.so
  3. Agregar la ruta de la biblioteca a la varible de entorno LD_LIBRARY_PATH: export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/ruta/completa/libmimodulo.so
  4. Crear un archivo de configuración en /etc/ld.so.conf.d con la ruta de la biblioteca y ejecutar ldconfig.
    En la práctica, generalmente usamos bibliotecas maduras de otros y las instalamos directamente en el sistema (opción 1).

Explorar la biblioteca ncurses.
Sobre las direcciones al cargar bibliotecas dinámicas.

Etiquetas: linux C-language file-io system-calls filesystem

Publicado el 7-2 21:46