2012-09-27 9 views
16

No sé por qué me está costando encontrar esto, pero estoy buscando algún código de Linux donde está usando select() esperando un descriptor de archivo para informar que está listo. Desde la página del manual de select:¿Alguien me puede dar un ejemplo de cómo select() recibe una alerta para que un fd esté "listo"

select() and pselect() allow a program to monitor multiple file descriptors, 
waiting until one or more of the file descriptors become "ready" for some 
class of I/O operation 

Por lo tanto, eso es genial ... Me llamo selección en algunos descriptor, darle un poco de valor de tiempo y empezar a esperar a la indicación de ir. ¿Cómo informa el descriptor del archivo (o el propietario del descriptor) que está "listo" para que la declaración select() regrese?

+1

http://beej.us/guide/bgnet/output/html/multipage/selectman.html –

+1

@NikolaiNFetissov - Desde su enlace, 'Después de que select() regrese, los valores en los conjuntos se cambiarán para mostrar qué están listos para leer o escribir, y que tienen excepciones. Entonces, ¿qué causó la devolución de 'select()' que nos dijo que el socket está listo para leer? Eso es lo que no entiendo – Mike

+0

Cuando la pila de red del núcleo detecta que hay un evento pendiente en cualquiera de los descriptores de socket, su proceso se despierta desde la espera y 'select' regresa. Los conjuntos de FD son parámetros de entrada y salida; usted le dice al núcleo en qué le interesa, le dice qué sucedió. –

Respuesta

23

Informa que está listo por que vuelve.

select espera los eventos que normalmente están fuera del control de su programa. En esencia, al llamar al select, su programa dice "No tengo nada que hacer hasta ... suspenda mi proceso".

La condición que especifique es un conjunto de eventos, cualquiera de los cuales lo despertará. Por ejemplo, si está descargando algo, su bucle tendría que esperar a que lleguen nuevos datos, un tiempo de espera para que ocurra si la transferencia está bloqueada, o el usuario para interrumpir, que es exactamente lo que select hace. Cuando tiene múltiples descargas, los datos que llegan a cualquiera de las conexiones desencadenan actividad en su programa (necesita escribir los datos en el disco), por lo que daría una lista de todas las conexiones de descarga a select en la lista de descriptores de archivos para mirar para "leer".

Cuando carga datos en algún lugar al mismo tiempo, nuevamente usa select para ver si la conexión acepta actualmente datos. Si el otro lado está en acceso telefónico, reconocerá los datos solo lentamente, de modo que su búfer de envío local esté siempre lleno, y cualquier intento de escribir más datos se bloquearía hasta que el espacio del búfer esté disponible o fallará. Al pasar el descriptor de archivo al que enviamos al select como un descriptor de "escritura", se nos notifica tan pronto como el espacio de búfer está disponible para el envío.

La idea general es que su programa se convierta en controlado por eventos, es decir, reacciona a eventos externos desde un bucle de mensaje común en lugar de realizar operaciones secuenciales. Usted le dice al kernel que "este es el conjunto de eventos para el cual quiero hacer algo", y el kernel le proporciona un conjunto de eventos que se han producido. Es bastante común que ocurran dos eventos simultáneamente; por ejemplo, se incluyó un reconocimiento de TCP en un paquete de datos, esto puede hacer que el mismo fd sea legible (datos disponibles) y grabable (los datos confirmados se han eliminado del búfer de envío), por lo que debe estar preparado para manejar todos los eventos antes de llamar de nuevo al select.

Uno de los puntos más importantes es que select básicamente le da una promesa que una invocación de read o write no bloqueará, sin hacer ningún tipo de garantía sobre la propia llamada.Por ejemplo, si hay disponible un byte de espacio en el búfer, puede intentar escribir 10 bytes, y el kernel volverá y dirá "He escrito 1 byte", por lo que debe estar preparado para manejar este caso también. Un enfoque típico es tener un buffer "data para escribir en este fd", y mientras no esté vacío, el fd se agrega al conjunto de escritura, y el evento "writeable" se maneja intentando escribir todo los datos actualmente en el búfer. Si el búfer está vacío después, bien, de lo contrario, simplemente espere de nuevo "writeable".

El conjunto "excepcional" rara vez se usa: se utiliza para protocolos que tienen datos fuera de banda donde es posible bloquear la transferencia de datos, mientras que otros datos deben pasar. Si su programa no puede aceptar actualmente datos de un descriptor de archivo "legible" (por ejemplo, está descargando y el disco está lleno), no desea incluir el descriptor en el conjunto "legible" porque no puede manejar el evento. y select volvería inmediatamente si se invoca de nuevo. Si el receptor incluye el fd en el conjunto "excepcional", y el emisor solicita a su pila de IP que envíe un paquete con datos "urgentes", el receptor se despierta y puede decidir descartar los datos no controlados y volver a sincronizarse con el remitente. . El protocolo telnet usa esto, por ejemplo, para el manejo de Ctrl-C. A menos que esté diseñando un protocolo que requiera tal característica, puede dejar esto sin daños.

obligatorio ejemplo de código:

#include <sys/types.h> 
#include <sys/select.h> 

#include <unistd.h> 

#include <stdbool.h> 

static inline int max(int lhs, int rhs) { 
    if(lhs > rhs) 
     return lhs; 
    else 
     return rhs; 
} 

void copy(int from, int to) { 
    char buffer[10]; 
    int readp = 0; 
    int writep = 0; 
    bool eof = false; 
    for(;;) { 
     fd_set readfds, writefds; 
     FD_ZERO(&readfds); 
     FD_ZERO(&writefds); 

     int ravail, wavail; 
     if(readp < writep) { 
      ravail = writep - readp - 1; 
      wavail = sizeof buffer - writep; 
     } 
     else { 
      ravail = sizeof buffer - readp; 
      wavail = readp - writep; 
     } 

     if(!eof && ravail) 
      FD_SET(from, &readfds); 
     if(wavail) 
      FD_SET(to, &writefds); 
     else if(eof) 
      break; 
     int rc = select(max(from,to)+1, &readfds, &writefds, NULL, NULL); 
     if(rc == -1) 
      break; 
     if(FD_ISSET(from, &readfds)) 
     { 
      ssize_t nread = read(from, &buffer[readp], ravail); 
      if(nread < 1) 
       eof = true; 
      readp = readp + nread; 
     } 
     if(FD_ISSET(to, &writefds)) 
     { 
      ssize_t nwritten = write(to, &buffer[writep], wavail); 
      if(nwritten < 1) 
       break; 
      writep = writep + nwritten; 
     } 
     if(readp == sizeof buffer && writep != 0) 
      readp = 0; 
     if(writep == sizeof buffer) 
      writep = 0; 
    } 
} 

Tratamos de leer si tenemos espacio de búfer disponible y no había fin de archivo o error en el lado de lectura, y se intenta escribir si tenemos datos en el buffer; si se llega al final del archivo y el búfer está vacío, entonces hemos terminado.

Este código se comportará claramente por debajo de lo óptimo (es código de ejemplo), pero debería poder ver que es aceptable que el kernel haga menos de lo que pedimos tanto en lecturas como escrituras, en cuyo caso simplemente volvemos y diga "siempre que esté listo", y que nunca leemos o escribimos sin preguntar si se bloqueará.

+0

En realidad, no debe suponer que la lectura (o escritura) no se bloqueará, ya que puede pasar algo entre la devolución select() y la lectura() o write() que se está emitiendo. Por ejemplo, alguien más podría leer los datos/llenar el conducto.Es más como una señal de que la operación tiene una posibilidad de no bloquear. Además, select() se activará si hay una condición de error en el fd, ya que eso provocaría que read()/write() inmediatamente vuelva inmediatamente con la condición de error. – rici

+0

Si soy el único que accede a este descriptor de archivo, entonces tengo esa garantía, al menos para sockets y pipes. Y si alguien más accede a mi fds, obtendré datos entrelazados extraños de todos modos. –

+0

Sí, si tiene el archivo fd abierto en un solo proceso, y es una canalización sin nombre o un socket. Pero no si se trata de una tubería con nombre. (Técnicamente, si de alguna manera pudieras saber que eras el único proceso de lectura/escritura de la tubería con nombre, es cierto, pero ¿cómo lo sabes?) Levanto el tema solo porque es un problema clásico alias "¿por qué mi servidor se congela en intervalos aleatorios? " – rici

7

De la misma página del manual:

A la salida, los conjuntos son modificados en su lugar para indicar qué descriptores de fichero en realidad ha cambiado de estado.

Utilice FD_ISSET() en los juegos pasados ​​para seleccionar para determinar qué FD están listos.

+1

Me falta el punto aquí, estoy preguntando "qué causó que select() devuelva el descriptor de archivo está' listo' "y estoy oyendo," selecciona devoluciones cuando están 'listos', porque están Listo". ¿Cuál es la definición de un socket "listo"? – Mike

+2

Uno que puede leerse, escribirse o haber tenido alguna otra ocurrencia excepcional, dependiendo de los conjuntos en los que estaba. "Los enumerados en lecturas se verán para ver si los caracteres están disponibles para la lectura (más precisamente, para ver si una lectura no se bloquea, en particular, un descriptor de archivo también está listo al final del archivo), aquellos en writefds se verán para ver si una escritura no se bloqueará, y aquellos en exceptofds se verán para excepciones ". –

Cuestiones relacionadas