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 queselect()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 estructurastruct timevalque especifica el tiempo máximo queselect()debe esperar. Si esNULL,select()espera indefinidamente. Si los campostv_secytv_usecson 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ámetronfdses 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);