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 | Sí | No | - | Solo lectura; falla si el archivo no existe |
| "w" | O_WRONLY | O_CREAT | O_TRUNC | No | Sí | Inicio | Crea o sobreescribe |
| "a" | O_WRONLY | O_CREAT | O_APPEND | No | No | Final | Crea o añade |
| "r+" | O_RDWR | Sí | No | Desplazable | Lectura y escritura; falla si no existe |
| "w+" | O_RDWR | O_CREAT | O_TRUNC | No | Sí | 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
- Durante la interacción, detectar si se necesita redirección.
- En la función EjecuciónNormal(), agregar lógica para redirección de salida y entrada.
- 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?
- Copiar a la ruta predeterminada del sistema /lib64
- Crear un enlace suave a la ruta predeterminada /lib64:
ln -s /ruta/completa/libmimodulo.so /lib64/libmimodulo.so - 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 - 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.