|
G A C E T A D E L I N U X
...haciendo a Linux un poco más divertido! |
|
Explorando la Función de Sistema sendfile
Por Jeff Tranter Traducción al español por Jorge Eduardo Ibarra Esquer
|
abrir fuente (archivo en disco)
abrir destino (conexión de red)
mientras existan datos para transmitir:
leer datos de la fuente hacia un buffer
escribir los datos del buffer hacia el destino
cerrar fuente y destino
La lectura y escritura de datos, por lo general utilizará los comandos de
sistema read y write respectivamente, o funciones de
bibliotecas construídas a partir de ellos.
Si seguimos el camino de los datos desde el disco hasta la red, veremos que necesitan ser copiados varias veces. Cada vez que se llama a la función read, se transfieren datos del disco hacia un buffer del kernel (por lo general utilizando DMA). Después, necesita copiarse al buffer utilizado por la aplicación. Cuando se llama a write, los datos en el buffer de la aplicación necesitan transferirse al buffer del kernel, y de ahi al dispositivo de hardware (p.ej. una tarjeta de red). Cada vez que una función del sistema es llamada por un programa de usuario, se da un cambio de contexto entre los modos de usuario y kernel, la cual es una operación relativamente costosa. Si en un programa hay muchas llamadas a read y write, entonces se requerirán muchos cambios de contexto.
El copiado de los datos entre los buffers del kernel y de la aplicación y viceversa, resulta redundante si no hay necesidad de modificar los datos. Muchos sistemas operativos, incluyendo Windows NT, FreeBSD y Solaris, ofrecen una función de sistema que se conoce como copiado-cero, que puede llevar a cabo una transferencia de archivo con una sola operación. Las primeras versiones de Linux fueron criticadas for la ausencia de esta característica, hasta que fue implementada en los kernels de la serie 2.2. Hoy en día es muy utilizada por aplicaciones de servidor muy comunes, como Apache y Samba.
La implementación de sendfile varía en diferentes sistemas operativos. En el resto de este artículo, nos enfocaremos solamente en la versión para Linux. Es importante notar que existe una utilería para transferencia de archivos llamada sendfile; pero no tiene nada que ver con la función de sistema implementada en el kernel.
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
Los parámetros son los siguientes:
En Linux, los indicadores de archivos pueden ser archivos reales o dispositivos, como puede ser un socket de red. La implementación de sendfile actualmente requiere que el indicador del archivo de entrada corresponda a un archivo real, o a algún dispositivo que soporte mmap. Esto quiere decir, por ejemplo, que no puede tratarse de un socket de red. El indicador del archivo de salida si puede corresponder a un socket, el cual es el caso más común al utilizarlo.
El listado que se muestra está un poco abreviado para hacerlo más claro. El listado completo, disponible aquí, contiene una verificación de errores y las directivas include necesarias para compilarlo.
Listado 1: fastcp.c
1 int main(int argc, char **argv) {
2 int src; /* indicador del archivo fuente */
3 int dest; /* indicador del archivo destino */
4 struct stat stat_buf; /* estructura con información acerca del archivo de entrada */
5 off_t offset = 0; /* offset, en bytes, utilizado por sendfile */
6
7 /* verificar que el archivo fuente exista y que puede ser abierto */
8 src = open(argv[1], O_RDONLY);
9 /* obtener el tamaño y los permisos del archivo fuente */
10 fstat(src, &stat_buf);
11 /* abrir el archivo de destino */
12 dest = open(argv[2], O_WRONLY|O_CREAT, stat_buf.st_mode);
13 /* copiar el archivo usando sendfile */
14 sendfile (dest, src, &offset, stat_buf.st_size);
15 /* cerrar archivos y terminar */
16 close(dest);
17 close(src);
18 }
En la línea 8 abrimos el archivo de entrada, enviado como el primer argumento desde la línea de comando. En la línea 10, obtenemos la información de éste archivo usando fstat, ya que posteriormente necesitaremos el tamaño del archivo y sus permisos. En la línea 12 abrimos el archivo que se utilizará para escritura. En la línea 14 se hace la llamada a sendfile, enviando los indicadores de archivos de entrada y salida, el offset (en este caso es cero), y se especifica el número de bytes que se van a transferir, utilizando el tamaño del archivo de entrada. Al final, se cierran los archivos en las líneas 16 y 17.
Traten de compilar el programa (usando la versión completa). Sugiero que experimenten tratando de copiar varios tipos de archivos, como los siguientes, y ver que dispositivos fuente y destino soporta sendfile:
El programa, llamado server, hace lo siguiente:
El servidor utiliza por default el puerto 1234, pero pueden cambiarlo con una opción de la línea de comando. Inicien el servidor ("./server"). Para la parte del cliente, pueden usar el programa telnet. Ejecútenlo desde otra ventana de consola mientras el servidor esté corriendo, especificando el 'host name' y el número del puerto (p.ej. "telnet localhost 1234"). Ya que telnet indique que está conectado, escriban el nombre de un archivo existente, como /etc/hosts. El servidor deberá enviar el contenido del archivo al cliente, y después cerrará la conexión.
El servidor debe permanecer corriendo para que puedan volver a conectarse. Si usan "quit" como nombre de archivo, entonces el servidor terminará su ejecución. Si tienen otra máquina en la red, traten de verificar que puedan conectarse al servidor y transferir un archivo desde otra máquina..
Hay que ver que este es un ejemplo muy sencillo de un servidor: sólo puede manejar un cliente a la vez y hace una pequeña verificación de error, terminando su ejecución en caso de que un error se presente. Hay algunas otras optimizaciones para el desempeño que pueden hacerse sobre la capa TCP, mismas que se encuentran fuera del ámbito que se pretende cubrir en este artículo.
Finalmente, después de todo este análisis de sendfile, los pondré a pensar con la siguiente pregunta: ¿por qué no existe la función de sistema correspondiente receivefile?
Jeff ha estado utilizando, escribiendo y contribuyendo con Linux
desde 1992. Trabaja para Xandros Corporation en Ottawa, Canada.