Análisis Técnico de las Llamadas al Sistema open y openat en Linux

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 open como openat ignoran el descriptor de directorio base (dirfd) y abren el archivo directamente desde la raíz del sistema de archivos.
  • Ruta relativa: open resuelve la ruta desde el directorio de trabajo actual del proceso. openat la resuelve desde el directorio referenciado por dirfd. Si dirfd tiene el valor especial AT_FDCWD, openat utiliza el directorio de trabajo actual, comportándose igual que open.

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 familia exec. 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 argumenot permisos.

  • O_EXCL: Usado con O_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 inmediatamente EAGAIN o EWOULDBLOCK si la operación bloquearía.
  • O_SYNC / O_DSYNC: Garantizan que los datos (y metadatos en el caso de O_SYNC) se escriban físicamente en el disco antes de que la llamada write regrese.
  • O_PATH: Obtiene un descriptor solo para operaciones a nivel de sistema de archivos (como fstat o fchdir), 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:

  1. Determine si la ruta es absoluta o relativa. Prefiera openat con AT_FDCWD para consistencia.
  2. Defina el modo de acceso (O_RDONLY, O_WRONLY, O_RDWR).
  3. Añada O_CLOEXEC por defecto para evitar fugas en procesos hijos.
  4. 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.

Etiquetas: linux-kernel system-calls file-descriptors openat c-programming

Publicado el 7-19 07:36