Números de Llamada al Sistema
En el núcleo de Linux, las llamadas al sistema para la apertura de archivos están identificadas numéricamente: open corresponde al número 2, mientras que openat utiliza el número 257.
Firmas de las Funciones
Interfaz del Núcleo
asmlinkage long sys_open(const char __user *ruta_archivo, int opciones, umode_t permisos);
asmlinkage long sys_openat(int dir_fd, const char __user *ruta_archivo, int opciones, umode_t permisos);
Encapsulamiento de glibc
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *ruta, int opciones);
int open(const char *ruta, int opciones, mode_t permisos);
int openat(int dirfd, const char *ruta, int opciones);
int openat(int dirfd, const char *ruta, int opciones, mode_t permisos);
Comportamiento de Rutas
La mayoría de las operaciones de archivo en Linux requieren un descriptor de archivo. Las llamadas open y openat son los mecanismos para generar estos descriptores. La diferencia principal radica en cómo resuelven las rutas relativas:
- Ruta absoluta: Tanto
opencomoopenatignoran el descriptor de directorio base (dirfd) y abren el archivo directamente desde la raíz del sistema de archivos. - Ruta relativa:
openresuelve la ruta desde el directorio de trabajo actual del proceso.openatla resuelve desde el directorio referenciado pordirfd. Sidirfdtiene el valor especialAT_FDCWD,openatutiliza el directorio de trabajo actual, comportándose igual queopen.
Configuración mediante Banderas
El comportamiento de apertura y las operaciones posteriores se controlan mediante banderas definidas en <fcntl.h>.
Modos de Acceso
El estándar POSIX exige el uso de exactamente una de estas banderas de acceso:
O_RDONLY: Solo lectura.O_WRONLY: Solo escritura.O_RDWR: Lectura y escritura.O_EXEC: Solo ejecución (para rutas que no son directorios).O_SEARCH: Solo búsqueda (exclusivo para directorios).
Banderas de Creación
Estas banderas alteran la creación y el estado inicial del archivo:
-
O_CLOEXEC: Cierra el descriptor automáticamente al ejecutar una llamada a la familiaexec. Esto previene fugas de descriptores en procesos hijos. ``` #include <fcntl.h> #include <unistd.h> #include <stdlib.h>int main(void) { // Apertura segura con O_CLOEXEC int desc_seguro = open("configuracion.dat", O_RDONLY | O_CLOEXEC); int desc_inseguro = open("registro.log", O_RDONLY);
if (fork() == 0) { char *args[] = {"./tarea_secundaria", NULL}; execve("./tarea_secundaria", args, NULL); // desc_seguro se cierra aquí, desc_inseguro permanece abierto } sleep(30); return EXIT_SUCCESS;}
Para verificar, se puede inspeccionar `/proc/<pid>/fd/`. La alternativa manual de cerrar todos los descriptores entre `fork` y `exec` es propensa a errores y difícil de mantener. -
O_CREAT: Crea el archivo si no existe. Requiere el argumenotpermisos. -
O_EXCL: Usado conO_CREAT, falla si el archivo ya existe. -
O_DIRECTORY: Falla si la ruta no es un directorio. -
O_TRUNC: Trunca el archivo a cero bytes si existe y se abre para escritura.
Banderas de Estado
Afectan las operaciones de E/S posteriores sobre el descriptor:
O_APPEND: Fuerza que cada escritura se realice al final del archivo.O_ASYNC: Habilita la E/S impulsada por señales (principalmente para sockets).O_NONBLOCK: Modo no bloqueante. Las operaciones de E/S devuelven inmediatamenteEAGAINoEWOULDBLOCKsi la operación bloquearía.O_SYNC/O_DSYNC: Garantizan que los datos (y metadatos en el caso deO_SYNC) se escriban físicamente en el disco antes de que la llamadawriteregrese.O_PATH: Obtiene un descriptor solo para operaciones a nivel de sistema de archivos (comofstatofchdir), sin permisos de lectura/escritura de contenido. Reduce el consumo de recursos y permisos requeridos.
Conceptos Clave del Núcleo y Wrapper de glibc
En versiones modernas de glibc (>= 2.26), la función open es simplemente un wrapper que invoca a openat pasando AT_FDCWD:
int __libc_open(const char *archivo, int banderas, ...) {
int modo = 0;
if (__OPEN_NEEDS_MODE(banderas)) {
va_list argumentos;
va_start(argumentos, banderas);
modo = va_arg(argumentos, int);
va_end(argumentos);
}
return SYSCALL_CANCEL(openat, AT_FDCWD, archivo, banderas, modo);
}
Es crucial distinguir entre descriptor de archivo (un entero no negativo que devuelve la llamada) y descripción de archivo (la entrada en la tabla global del núcleo que mantiene el desplazamiento y las banderas de estado). Múltiples descriptores (creados vía dup o fork) pueden apuntar a la misma descricpión. Además, la descripción persiste incluso si el archivo original se mueve o renombra en el sistema de archivos.
Guía de Uso Práctico
Al invocar estas llamadas, siga este flujo de decisión:
- Determine si la ruta es absoluta o relativa. Prefiera
openatconAT_FDCWDpara consistencia. - Defina el modo de acceso (
O_RDONLY,O_WRONLY,O_RDWR). - Añada
O_CLOEXECpor defecto para evitar fugas en procesos hijos. - Especifique comportamientos de escritura (
O_TRUNC,O_APPEND,O_CREAT).
Ejemplos válidos de invocación:
int fd_lectura = open("datos.bin", O_RDONLY | O_CLOEXEC);
int fd_log = open("app.log", O_WRONLY | O_APPEND | O_CREAT, 0644);
int fd_temp = openat(AT_FDCWD, "salida.tmp", O_RDWR | O_TRUNC | O_CLOEXEC);
Implementación en el Núcleo
La lógica central reside en fs/open.c dentro de la función do_sys_open:
long do_sys_open(int dir_fd, const char __user *nombre_ruta, int banderas, umode_t modo_archivo) {
struct open_flags config_apertura;
int resultado_fd = build_open_flags(banderas, modo_archivo, &config_apertura);
struct filename *ruta_tmp;
if (resultado_fd)
return resultado_fd;
ruta_tmp = getname(nombre_ruta);
if (IS_ERR(ruta_tmp))
return PTR_ERR(ruta_tmp);
resultado_fd = get_unused_fd_flags(banderas);
if (resultado_fd >= 0) {
struct file *archivo = do_filp_open(dir_fd, ruta_tmp, &config_apertura);
if (IS_ERR(archivo)) {
put_unused_fd(resultado_fd);
resultado_fd = PTR_ERR(archivo);
} else {
fsnotify_open(archivo);
fd_install(resultado_fd, archivo);
}
}
putname(ruta_tmp);
return resultado_fd;
}
Cada invocación exitosa asigna un nuevo descriptor, incluso para la misma ruta y banderas. La resolución real de la ruta y apertura se delega a do_filp_open, que utiliza path_openat en fs/namei.c:
static struct file *path_openat(struct nameidata *nd, const struct open_flags *op, unsigned int banderas_busqueda) {
struct file *archivo_objetivo;
int codigo_error;
archivo_objetivo = alloc_empty_file(op->open_flag, current_cred());
if (IS_ERR(archivo_objetivo))
return archivo_objetivo;
if (unlikely(archivo_objetivo->f_flags & __O_TMPFILE)) {
codigo_error = do_tmpfile(nd, banderas_busqueda, op, archivo_objetivo);
} else if (unlikely(archivo_objetivo->f_flags & O_PATH)) {
codigo_error = do_o_path(nd, banderas_busqueda, archivo_objetivo);
} else {
const char *segmento_ruta = path_init(nd, banderas_busqueda);
while (!(codigo_error = link_path_walk(segmento_ruta, nd)) &&
(codigo_error = do_last(nd, archivo_objetivo, op)) > 0) {
nd->flags &= ~(LOOKUP_OPEN | LOOKUP_CREATE | LOOKUP_EXCL);
segmento_ruta = trailing_symlink(nd);
}
terminate_walk(nd);
}
if (likely(!codigo_error)) {
if (likely(archivo_objetivo->f_mode & FMODE_OPENED))
return archivo_objetivo;
WARN_ON(1);
codigo_error = -EINVAL;
}
fput(archivo_objetivo);
if (codigo_error == -EOPENSTALE) {
codigo_error = (banderas_busqueda & LOOKUP_RCU) ? -ECHILD : -ESTALE;
}
return ERR_PTR(codigo_error);
}
Esta función recorre el árbol de directorios, resolviendo enlaces simbólicos, hasta que do_last finaliza la apertura del nodo objetivo.