Control de E/S en Linux con select()

La función select() es una primitiva de sistema utilizada para monitorear múltiples descriptores de archivo y determinar cuáles están listos para operaciones de entrada/salida (E/S) sin bloquear la ejecución. Permite a un programa esperar hasta que uno o más descriptores de archivo estén "listos" para leer, escribir, o tienen una condición excepcional, o hasta que expire un tiempo de espera especificado.

Sintaxis y Parámetros

La declaración de la función es la siguiente:

#include <sys/select.h> // o <sys/time.h> y <sys/types.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds: Es el número más alto de descriptor de archivo en cualquiera de los conjuntos, más uno. Indica el rango de descriptores de archivo que select() debe verificar.
  • readfds: Un puntero a un conjunto de descriptores de archivo que se desean monitorear para ver si están listos para ser leídos.
  • writefds: Un puntero a un conjunto de descriptores de archivo que se desean monitorear para ver si están listos para ser escritos.
  • exceptfds: Un puntero a un conjunto de descriptores de archivo que se desean monitorear para condiciones excepcionales o datos fuera de banda (generalmente para sockets).
  • timeout: Un puntero a una estructura struct timeval que especifica el tiempo máximo que select() debe esperar. Si es NULL, select() espera indefinidamente. Si los campos tv_sec y tv_usec son ambos cero, select() regresa inmediatamente.

La estructura timeval se define así:

struct timeval {
   long tv_sec;  // Segundos
   long tv_usec; // Microsegundos
 };

Manipulación de Conjuntos de Descriptores de Archivo

Se utilizan macros para manipular los conjuntos de descriptores de archivo (fd_set):

  • FD_ZERO(fd_set *set): Inicializa un conjunto de descriptores de archivo, eliminando todos los descriptores.
  • FD_SET(int fd, fd_set *set): Añade un descriptor de archivo específico al conjunto.
  • FD_CLR(int fd, fd_set *set): Elimina un descriptor de archivo del conjunto.
  • FD_ISSET(int fd, fd_set *set): Verifica si un descriptor de archivo está presente en el conjunto. Devuelve un valor distinto de cero si está presente, y cero en caso contrario.

Es importante notar que los conjuntos de descriptores de archivo son modificados por la llamada a select(). Al regresar, los cnojuntos solo contendrán los descriptores que están listos para la operación de E/S especificada. Por lo tanto, si se desea usar select() repetidamente, los conjuntos deben ser reconstruidos antes de cada llamada.

Existe un límite práctico en el número de descriptores de archivo que se pueden monitorear, definido por FD_SETSIZE, que en la mayoría de los sistemas Linux es 1024.

Valores de Retorno y Códigos de Error

  • Si select() tiene éxito, dveuelve el número total de descriptores de archivo listos para E/S en todos los conjuntos combinados.
  • Si el tiempo de espera expira antes de que algún descriptor esté listo, devuelve 0.
  • Si ocurre un error, devuelve -1 y establece la variable global errno. Los códigos de error comunes incluyen:
    • EBADF: Un descriptor de archivo en uno de los conjuntos es inválido.
    • EINTR: La llamada fue interrumpida por una señal. Se puede reintentar la llamada.
    • EINVAL: El parámetro nfds es negativo, o el tiempo de espera es inválido.
    • ENOMEM: No hay suficiente memoria disponible.

Ejemplo de Uso

El siguiente ejemplo demuestra cómo usar select() para esperar hasta 5 segundos por entrada en la entrada estándar (STDIN_FILENO).

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#define TIEMPO_ESPERA_SEGUNDOS 5
#define LONGITUD_BUFFER 1024

int main(void) {
   struct timeval tv;
   fd_set descriptores_lectura;
   char buffer[LONGITUD_BUFFER + 1];
   int longitud_leida;
   int resultado_select;

   // Inicializa el conjunto de descriptores para lectura
   FD_ZERO(&descriptores_lectura);
   // Añade la entrada estándar al conjunto
   FD_SET(STDIN_FILENO, &descriptores_lectura);

   // Configura el tiempo de espera
   tv.tv_sec = TIEMPO_ESPERA_SEGUNDOS;
   tv.tv_usec = 0;

   // Llama a select() para esperar E/S en STDIN_FILENO
   // El primer argumento es el descriptor más alto + 1
   resultado_select = select(STDIN_FILENO + 1, &descriptores_lectura, NULL, NULL, &tv);

   if (resultado_select == -1) {
       perror("select");
       return 1;
   } else if (resultado_select == 0) {
       // El tiempo de espera expiró
       printf("%d segundos transcurrieron sin entrada.\n", TIEMPO_ESPERA_SEGUNDOS);
       return 0;
   } else {
       // Hay datos disponibles para leer en STDIN_FILENO
       if (FD_ISSET(STDIN_FILENO, &descriptores_lectura)) {
           longitud_leida = read(STDIN_FILENO, buffer, LONGITUD_BUFFER);
           if (longitud_leida == -1) {
               perror("read");
               return 1;
           }
           if (longitud_leida > 0) {
               buffer[longitud_leida] = '\0'; // Asegura terminación de cadena
               printf("Se leyó: %s\n", buffer);
           }
           return 0;
       }
   }

   // Este punto no debería alcanzarse si select() funcionó correctamente
   fprintf(stderr, "Error inesperado en la lógica del programa.\n");
   return 1;
}

Implementación de Suspense (Sleep) de Precisión

select() puede ser utilizado para implementar suspensiones de corta duración con una precisión de microsegundos, estableciendo el timeout adecuadamente y pasando NULL para los conjuntos de descriptores de archivo.

struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500; // Suspender por 500 microsegundos

// Llamada a select para pausar la ejecución
select(0, NULL, NULL, NULL, &tv);

Etiquetas: select fd_set sys_select IO linux

Publicado el 6-18 08:03