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
el día 25 de Junio 2003, para La Gaceta de Linux
 

Introducción

La función de sistema sendfile es una adición relativamente nueva al kernel de Linux, la cual ofrece mejoras de desempeño significativas para aplicaciones como ftp y servidores web, mismos que requieren transferir archivos de una manera eficiente. En este artículo, voy a explorar sendfile, que es lo que hace, como utilizarlo, ilustrándolo con algunos programas de ejemplo.

Antecedentes

Una aplicación de servidor, como es el caso de un servidor web, utiliza gran parte de su tiempo en transferir archivos almacenados en el disco, hacia una conexión de red enlazada a un cliente ejecutando un navegador web. Un sencillo pseudocódigo para la transferencia de datos podría verse como el siguiente:
    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.

Una mirada a detalle

Para utilizar sendfile, incluyan el archivo de encabezado <sys/sendfile.h>, en el cual se declara una función con el siguiente prototipo:
    ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
Los parámetros son los siguientes:
out_fd
un indicador de archivo, abierto para escritura, donde se escribirán los datos
in_fd
un indicador de archivo, abierto para lectura, de donde se leerán los datos
offset
un offset en el archivo de entrada para iniciar la transferencia (p.ej. un valor de 0 indica el principio del archivo). Este valor se manda a la función y se actualiza cuando ésta regresa.
count
el número de bytes que van a transferirse
La función regresa el número de bytes escritos, o -1 en caso de que ocurra un error.

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.

Ejemplo 1

Veamos un ejemplo sencillo para ilustrar el uso de sendfile. El Listado 1 muestra fastcp.c, un sencillo programa para copiar archivos que usa sendfile para llevar a cabo la copia.

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:

Ejemplo 2

El primer ejemplo fue muy sencillo, pero no muy representativo del uso típico que se da a sendfile al usar un destino en una red. El segundo ejemplo ilustra el envío de un archivo a través de un socket de red. Este programa es más largo, principalmente debido a la preparación que se requiere para trabajar con sockets, así que no lo incluyo entre líneas. Pueden ver el código fuente completo aquí.

El programa, llamado server, hace lo siguiente:

Supongo que están familiarizados con los puntos básicos de los sockets de red. De no ser así, existen muchos buenos libros acerca del tema, como UNIX Network Programming de Richard Stevens.

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.

Resumen

La función de sistema sendfile facilita la transferencia de archivos en redes de alto desempeño, un requerimiento para aplicaciones como ftp y servidores web. Si se encuentran desarrollando alguna aplicación para servidor, tengan en cuenta el uso de sendfile para obtener una mejora considerable en el desempeño. Fuera del ámbito de los servidores, representa una característica interesante a la cual se le pueden encontrar otros usos creativos.

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?

Referencias

  1. Página man de sendfile(2).
  2. Código fuente del kernel de la implementación de sendfile.

 

[BIO] Jeff ha estado utilizando, escribiendo y contribuyendo con Linux desde 1992. Trabaja para Xandros Corporation en Ottawa, Canada.


Copyright © 2003, Jeff Tranter. Copying license http://www.linuxgazette.com/copying.html
Publicado en la Edición 91 de Linux Gazette, Junio 2003