En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

sábado, 23 de junio de 2018

Toma el makefile y corre
cómo escribir un makefile universal - pt.2

Louise: Virgil, vamos a tener un niño.
Virgil: No exageres...
Louise: ¡No! No. Vamos a ser padres: he ido a ver el medico. Es mi regalo de Navidad.
Virgil: ¡Hubiese preferido una corbata!
Para Virgil Starkwell era suficiente una corbata... para nosotros es suficiente un buen makefile universal ¿Os acordáis de ese antiguo post en el que he propuesto uno? ¿No os acordáis? Volver inmediatamente a leerlo y luego regresar aquí.
....vamos a tener un makefile...
Aquí estamos (y pequeña premisa: algunos puntos de este post están parcialmente copiados de mi antiguo post... puedo copiar a mi mismo, ¿no?). Si habéis releído el post ya sabéis que será una post rápido, y no exactamente sobre el C: he pensado que era hora de escribir y ofrecer una versión extendida y mejorada del antiguo makefile universal: en comparación con el original esto usa más las variables , por lo que el código es más limpio y legible (¡el estilo, en primer lugar!) y agrega una característica muy interesante: la creación de una shared library (una .so, para los amigos). Veamos esquemáticamente cuáles son los pasos a seguir (en un Linux de la familia Debian) para crear y usar una shared-lib:
1. crear una directory para compartir la shared-lib, por ejemplo: 
       /usr/share/pluto/lib
2. modificar (como veremos luego) el makefile para generar la librería 
   (que llamaremos "libmyutils.so") y copiarla en "/usr/share/pluto/lib".
3. añadir en "/etc/ld.so.conf.d" un nuevo file "libmyutils.conf" que 
   contiene las siguientes dos lineas (la primera es solo un comentario): 
       # libmyutils.so default configuration
       /usr/share/pluto/lib
4. hacer disponible la nueva shared-lib ejecutando: 
       sudo ldconfig
Seguimos: supongamos de usar el mismo proyecto de la otra vez (se llamaba pluto). Nuestros archivos están organizados de manera canónica, esta vez en cuatro directory (la otra vez había tres): pluto, lib, libmyutils e include. La nueva directory es libmyutils, y contiene las fuentes de la shared-lib que queremos crear. En la directory pluto encontramos el main() y el makefile, en la directory lib encontramos las otras fuentes de la aplicación pluto y, finalmente, en la directory include encontramos los header-files comunes a main(), lib y libmyutils. Veamos el nuevo makefile:
# variables
CC = gcc
CPPFLAGS = -I../include -g -Wall -Wshadow -pedantic -std=c11
CPPFLAGS_UTI = -fpic -I../include -g -Wall -Wshadow -pedantic -std=c11
LDFLAGS = -lmyutils -std=c11
LDFLAGS_UTI = -shared -fpic -lcurl -std=c11

# fuentes, objetos y dependencias
SRCS = $(wildcard *.c)
SRCS_LIB = $(wildcard ../lib/*.c)
SRCS_LIB_UTI = $(wildcard ../libmyutils/*.c)
OBJS = $(SRCS:.c=.o)
OBJS_LIB = $(SRCS_LIB:.c=.o)
OBJS_LIB_UTI = $(SRCS_LIB_UTI:.c=.ou)
DEPS = $(SRCS:.c=.d)
DEPS_LIB = $(SRCS_LIB:.c=.d)
DEPS_LIB_UTI = $(SRCS_LIB_UTI:.c=.d)

# tudos los target
all: libmyutils pluto

# creación del target file ejecutable
pluto: $(OBJS) $(OBJS_LIB)
 $(CC) $^ -o $@ $(LDFLAGS) -L/usr/share/pluto/lib

# creación de la shared-lib libmyutils.so
libmyutils: $(OBJS_LIB_UTI)
 $(CC) $^ -o libmyutils.so $(LDFLAGS_UTI)
 mv libmyutils.so /usr/share/pluto/lib

# creación de los object file (para la aplicación y la shared-lib)
%.o: %.c
 $(CC) -MMD -MP $(CPPFLAGS) -c $< -o $@

%.ou: %.c
 $(CC) -MMD -MP $(CPPFLAGS_UTI) -c $< -o $@

# directivas phony
.PHONY: clean

# limpieza proyecto ($(RM) es de default "rm -f")
clean:
 $(RM) $(OBJS) $(OBJS_LIB) $(OBJS_LIB_UTI) $(DEPS) $(DEPS_LIB) $(DEPS_LIB_UTI)

# creación dependencias
-include $(DEPS) $(DEPS_LIB) $(DEPS_LIB_UTI)
Como se puede ver, el nuevo makefile presentado es un pariente cercano del anterior y sigue siendo realmente simple y universal: hace todo lo necesario, incluyendo la generación de los files de dependencia de los header, y podemos utilizarlo para cualquier proyecto, sin importar el número de file (las directory lib e include pueden estar vacías o contener cientos de file). Podemos agregar y eliminar fuentes y header y re-compilar sin cambiar una sola línea del makefile, porque el se adapta automáticamente a lo que encuentra en las directory del proyecto: ¿Qué queremos más?
 
Añado algunos pequeños detalles sobre los bloques (comentados) que componen el  makefile universale:

# variables
Aqui están las variables que se utilizan en el resto del makefile. Cabe destacar que en CPPFLAGS y LDFLAGS están contenidas todas las opciones de compilación y link necesarias en las etapas de creación de la aplicación, mientras que en CPPFLAGS_UTI y LDFLAGS_UTI están contenidas las relativas a la shared-lib). No daré más detalles sobre el significado de las opciones individuales: tal vez podrían ser el tema de un próximo post... sin embargo, las opciones que he utilizado son definitivamente "universales". Si se utiliza alguna librería externa se puede añadir aquí: en el ejemplo (recordar: ¡es sólo un ejemplo!) he escrito que pluto utiliza libmyutils (con el comando -lmyutils en LDFLAGS) y he escrito que libmyutils utiliza la librería pen-source libcurl (con el comando -lcurl en LDFLAGS_UTI). Tener en cuenta que para compilar y linkar la shared-lib se utilizan dos directivas fundamentales:-fpic (en compilación y link) y -shared (solo en link). Y repito: -fpic se usa tanto en compilación como en link: este es un detalle de que muchas de las guías que están en la red omiten y pueden ser una posible causa de anomalías extrañas de una shared-lib.

# fuentes, objetos y dependencias
Aquí están las directivas que usan internamente el programa make para decidir cómo y dónde buscar fuentes, objetos y dependencias.

# todos los target
Aquí están los objetivos de creación: en nuestro caso, la libreria libmyutils y la aplicación pluto: el comando make "en solo" ejecuta ambos objetivos (la palabra clave es "all"), pero se puede omitir esto ejecutando, por ejemplo, "make libmyutils" que crea solo la shared-lib.

# creación del target file ejecutable
Aquí pone el comando para linkar los file objetos creados y producir el file ejecutable final. Tener en cuenta que con la directiva -L/usr/share/pluto/lib indicamos al linker donde se encuentra la libreria libmyutils.so. También tener en cuenta que esta directiva solo sirve en el nivel del linker, mientras que, en el nivel de ejecución de las aplicaciones que usan la nuestra shared-lib, necesitamos los pasos de la lista descrita al comienzo del post (en particular, los pasos 3 y 4).

# creación de la shared-lib libmyutils.so
Aquí están las instrucciones para la creación de la shared-lib libmyutils.so y par moverla (con el comando "mv") en la directory de destino.

# creación de los object file (para la aplicacion y la shared-lib)
Aquí se pone el comando para compilar cada fuente y crear el file objeto correspondiente, activando (a través de las variables definidas al inicio) todas las opciones del compilador que necesitamos.

# directivas phony
Aquí se ponen las directivas phony (es un poco largo de explicar: mirar en el link, que está muy claro). 

# limpieza proyecto ($(RM) es de default "rm -f")
Aquí se pone el comando de borrado de los objetos para forzar, eventualmente, una siguiente completa re-compilación.

# creacion dependencias
Aquí se pone el comando para generar los file de dependencia que nos permiten volver a compilar sólo lo que necesita cuando se modifica un header file. 

Creo que por hoy es suficiente... probad en hacer un pequeño proyecto de prueba (por ejemplo, usar funciones medio vacías que solo escriben "Hola, soy la función xyz") y probar el nuevo makefile universale: ¡vais a descubrir que es realmente fácil de usar!

¡Hasta el próximo post!

sábado, 19 de mayo de 2018

El gran lighttpd
cómo escribir un módulo lighttpd en C - pt.4

Maude: ¿Y en que te dedicas en tu tiempo libre?
El Nota: Bueno, ya sabes: jugar a los bolos, conducir por ahí­, un viaje ácido de vez en cuando...
Dando por echo que para escribir un buen Software no hay necesidad de imitar los hábitos del mítico El Nota de El gran Lebowski, volvemos al tema lighttpd, porque en el tercer post de la serie (aquí, aquí y también aquí) prometí expandir la discusión. ¡Bien, ha llegado el momento, las promesas se mantienen!
...Bueno, ya sabes: jugar a los bolos, conducir por ahí­, un módulo lighttpd de vez en cuando...
Pues bien, en "El gran lighttpd - pt.3" (que acabáis de releer, supongo...) escribimos un agradable módulo elemental para lighttpd (era un clásico "Helloworld": escribía solo una presentación en el browser), pero era un buen comienzo para entrar en el mundo de los módulos para Web Servers. Vale, ahora vamos a reanudar ese módulo y les vamos a añadir una función que extrae los datos POST incluidos en una eventual petición HTTP de tipo POST que llega al módulo. ¿Por qué he elegido añadir esa funcionalidad? Bueno, obviamente porque es bastante normal que un módulo cuente con varios tipos de peticiones (GET, POST, etc.), y luego, en particular, recuerdo que la primera vez que añadí esta funcionalidad en un módulo Apache que escribí, me di cuenta de que no estaban disponibles muchas explicaciones sobre este tema (y esto a pesar de la enorme cantidad de documentación de Apache disponible respeto a la de lighttpd)... os dejo imaginar, por lo tanto, cuánta documentación y cuántos ejemplos encontré para hacer lo mismo con lighttpd: prácticamente nada.

Pues bien, sin molestarse en repetir todo el código y la forma de generarlo, sólo reescribiremos la función mod_helloworld_uri_handler() y añadiremos una nueva función getPost(). Añado que, para integrar el código que he escrito, es necesario seguir la guía de instalación indicada en "El gran lighttpd - pt.1" y repetir los 10 pasos indicados en "El gran lighttpd - pt.2", usando esta vez la versión 1.4.45 de lighttpd (ojo: usando una versión anterior, el código que estoy a punto de mostrar no funciona). Hecho? entonces ¡Vamos con el código
URIHANDLER_FUNC(
    mod_helloworld_uri_handler)
{
    plugin_data *p = p_d;

    UNUSED(srv);

    // test modo (return si error)
    if (con->mode != DIRECT)
        return HANDLER_GO_ON;

    // test uri path (return si error)
    size_t s_len = buffer_string_length(con->uri.path);
    if (s_len == 0)
        return HANDLER_GO_ON;

    mod_helloworld_patch_connection(srv, con, p);

    // test handler (return si error)
    if (con->uri.path->ptr && strstr(con->uri.path->ptr, "helloworld")) {
        // prepara el buffer para la respuesta
        buffer *buf = buffer_init();

        // test método http
        if (strstr(con->request.request->ptr, "GET")) {
            // método GET: escribe respuesta
            buffer_append_string(buf, "<big>Hello, world!</big>");
        }
        else if (strstr(con->request.request->ptr, "POST")) {
            // método POST: controla la presencia de post-data
            size_t len = con->request.content_length;
            if (len) {
                // post-data presentes; los lee para escribir la respuesta
                char *data = malloc(len + 1);
                if (readPost(con, data, len)) {
                    // escribe la respuesta
                    buffer_append_string(buf, data);
                }

                // libera la memoria
                free(data);
            }
            else {
                // error: mensaje POST sin post-data
                buffer_append_string(buf, "mod_helloworld: error: POST sin post-data");
            }
        }
        else {
            // error: mensaje con método no tratado
            buffer_append_string(buf, "mod_helloworld error: método no tratado");
        }

        // escribe el buffer y lo libera
        chunkqueue_append_buffer(con->write_queue, buf);
        buffer_free(buf);

        // envía el header
        response_header_overwrite(
                srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
        con->http_status = 200;
        con->file_finished = 1;

        // handling terminado
        return HANDLER_FINISHED;
    }

    // handler no encontrado
    return HANDLER_GO_ON;
}

static bool readPost(
    connection *con,                // datos conexión
    char       *data,               // buffer destinación para post-data
    size_t     len)                 // longitud data (sin terminador)
{
    // set valor de return de default
    int retval = false;

    // lee post-data (en el stream de chunks)
    size_t rpos = 0;
    chunkqueue *cq = con->read_queue;
    for (chunk *mychunk = cq->first; mychunk; mychunk = cq->first) {
        // calcula el size del buffer correspondiente al chunk de post-data corriente
        size_t n_tocopy = buffer_string_length(mychunk->mem) - mychunk->offset;

        // test si hay datos a copiar
        if (n_tocopy <= (len - rpos)) {
            // copia un chunk y set de la posición de copia del proximo chunk
            memcpy(data + rpos, mychunk->mem->ptr + mychunk->offset, n_tocopy);
            rpos += n_tocopy;

            // ha sido leído (almeno) un chunk de post-data: set retval=true
            retval = true;
        }
        else {
            // buffer overflow: salida forzada del loop
            break;
        }

        // indica como leído el chunk de post-data corriente
        chunkqueue_mark_written(cq, chunkqueue_length(cq));
    }

    // añade el terminadore de string (el buffer es largo len+1)
    data[len] = 0;

    // sale con retval
    return retval;
}
Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciónes y/o grupos de instrucciones (¡leer los comentarios! ¡Están ahí para eso!), pero voy a añadir, solamente, algunos detalles estructurales.

la función original mod_helloworld_uri_handler() sigue siendo una especie de función "Helloworld", que ahora distingue los tipos de petición y, en el caso POST, llama a la función readPost() y escribe  como respuesta "Hello, world!" mas los datos. En el caso de peticiones GET se limita a escribir "Hello, world!" y, para otros tipos de peticiones o con peticiones POST sin datos, sale con error.

La función readPost() es simple, pero no es inmediata: dada la casi total falta de ejemplos disponibles en la red, he tenido que hacer, prácticamente,   reverse engineering sobre los (excelentes) módulos integrados en la distribución (¡tener el código fuente original disponible siempre es una gran cosa!) de manera de deducir cómo hacer lo que tenia como objetivo. El resultado final (modestia aparte) es bueno, tanto en apariencia (estilo) como en rendimiento (hice algunas pruebas y funciona bien).

¡Ah, el test, se me olvidaba! Testear con peticiones POST no es simple cómo hacerlo con las GET, adonde es suficiente (como dicho en el post anterior) escribir la URI del modulo en la barra de direcciones de Firefox o Chrome (o cualquier otro navegador menos IE, por favor...) y esperar la respuesta. Para probar el nuestro nuevo módulo es mejor usar un buen plugin (como Poster, por ejemplo) que nos facilitará mucho la tarea.

Vale, de momento, creo que podemos parar por un tiempo el tema módulos de lighttpd. Juro que en el próximo post voy a hablar de otra cosa, no me gustaría que piensen que el C se utiliza sólo para Web Servers...

¡Hasta el próximo post!

domingo, 15 de abril de 2018

Dawn of the CPU
cómo testear el uso de CPU y Memoria en C - pt.2

Aquí estamos, después de la presentación en el último post, ha llegado el momento de ver si nuestro sistema de test continuo de CPU y Memoria funciona. Y si funciona bien. Os acordais el tema, ¿no? Zombies de supermercado, Dawn of the Dead, Dawn of the CPU... sí, eso.
...¿pero realmente tenemos que ir armados para comprar algo de RAM?...
Para probar el funcionamiento de la función de test descrita en el último post, he escrito un pequeño programa de test (testsys.c) que crea un thread que puede estresar la CPU y la Memoria de un sistema y luego, directamente en el main(), llama en un loop infinito la nuestra función testSys(), enseñando los resultados del test cada dos segundos. En el siguiente código solo falta la función de test: podéis ir a buscarla en el último post y aprovechar para volver a leer las partes más importantes (¡bravo quien lo hace!). ¡Vamos con el código!
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <linux/kernel.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sysinfo.h>

// struct para los resultados
typedef struct {
    ...
} Results;

// prototipos locales
void testSys(Results *results);
void *tMyThread(void *arg);

// función main()
int main(int argc, char *argv[])
{
    // init thread
    pthread_t tid;
    int error;
    if ((error = pthread_create(&tid, NULL, &tMyThread, NULL)) != 0)
        printf("%s: no puedo crear el thread (%s)\n", argv[0], strerror(error));

    // llama testSys() para primer set valores estáticos
    Results results;
    testSys(&results);
    sleep(2);

    // testSys() loop para testear repetidamente el sistema
    for (;;) {
        // get valores
        testSys(&results);
        printf("cpu: total usage = %.1f\n", results.total_cpu / results.prec);
        printf("mem: total usage = %.1f\n", results.mem_system / results.prec);
        printf("cpu: proc  usage = %.1f\n", results.proc_cpu / results.prec);
        printf("mem: proc  usage = %.1f\n", results.mem_proc / results.prec);
        printf("load average: %.2f , %.2f , %.2f\n", 
                results.loads[0] / results.loads_prec,
                results.loads[1] / results.loads_prec, 
                results.loads[2] / results.loads_prec);

        // sleep 2 segundos
        sleep(2);
    }

    // exit
    exit(EXIT_SUCCESS);
}

// función de test del sistema
void testSys(
    Results *results)   // destinación de los resultados
{
    ...
}

// thread routine
void *tMyThread(void *arg)
{
    // alloc memoria
    unsigned long mem = 1024 * 1024 * 512;  // 512 mb
    char *ptr = malloc(mem);

    // thread loop infinito
    for (;;) {
        // usa memoria
        memset(ptr, 0, mem);

        // thread sleep
        usleep(10);    // NOTA: sleep muy pequeña para forzar mucha actividad de cpu
    }

    return NULL;
}
Como podeis ver, el programa es de una simplicidad desarmante (y con excelentes comentarios, como de costumbre). Para estresar la CPU y la Memoria he utilizado algunos simples trucos: asigno (con malloc()) un megabúfer de 512MB, y luego en un loop infinito lo uso intensamente (con una memset() completa). El ciclo del thread  una usleep muuuuy pequeña (10 us) que carga mucho la CPU (que nos odiará un poco, pero es el objetivo del nuestro test, ¿no?).

Ahora viene lo bueno: como se ha mencionado varias veces en el post anterior, nuestra referencia es el comando top de la familia UNIX así que tenemos que abrir dos terminales, y en una ejecutamos nuestro programa de test, y en la otra ejecutamos top para poder comparar en tiempo real si los resultados coinciden (recordaros de activar la opción "I" de top, como descrito en el otro post). Veamos qué ha pasado en mi ordenador:
en la terminal con testsys
...
load average: 0.85 , 0.42 , 0.32
cpu: total usage = 12.9
mem: total usage = 47.8
cpu: proc  usage = 12.5
mem: proc  usage = 6.9
...

en la terminal con top
top - 18:45:16 up 39 min,  2 users,  load average: 0,85, 0,42, 0,32
Tasks: 236 total,   1 running, 235 sleeping,   0 stopped,   0 zombie
%Cpu(s): 12,5 us,  0,1 sy,  0,0 ni, 87,2 id,  0,0 wa,  0,0 hi,  0,1 si,  0,0 st
KiB Mem :  7600656 total,  3962420 free,  1705536 used,  1932700 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  5384092 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND            
 5266 aldo      20   0  604548 524744    628 S 12,5  6,9   0:43.01 testpstat          
 1380 root      20   0  559772  94056  82400 S  0,1  1,2   0:43.89 Xorg               
 1012 root      20   0    4396   1276   1196 S  0,0  0,0   0:00.07 acpid              
 2628 aldo      20   0 2542528 358152 128056 S  0,0  4,7   3:18.25 firefox            
 ...
dado que la salida es continua, he seleccionado solo una parte y he agregado las elipsis, en cualquier caso podéis repetir fácilmente el test en el vuestro ordenador para verificar la consistencia de los resultados: yo diría que el resultado es más que satisfactorio, ¿no? ¡Misión cumplida!

¿Qué falta? Ah, sí, prometí algunas notas sobre los resultados que muestra top (y, consecuentemente, también el nuestro testsys): en lo que concierne a la CPU, los comentarios en el codigo de la testSys() son suficientes (descripción de las cargas medias, opción "I", etc.). Además, podemos agregar que la línea %Cpu(s) mostrada arriba confirma las fórmulas contenidas (y comentadas) en testSys(): por ejemplo, la suma de los diversos componentes (user, idle, sys, etc.) vale, como esperado, 100.

Para la memoria, sin embargo, el discurso es un poco más complejo, y habria que dedicar un post dedicado (tal vez lo haré en el futuro): por el momento os paso un enlace interesante: understanding-memory-usage-on-linux, y os añado sólo una explicación muy simple de algo (extraño) que a veces ocurre en sistemas embedded echos con BusyBox: en algunos casos parece que algunos procesos usan más del 100% de la memoria (en la columna %MEM): esto se debe al hecho de que está utilizando una versión antigua de top proporcionada por BusyBox que calcula %MEM como VSZ/MemTotal en lugar de RSS/MemTotal: debemos tener en cuenta que RSS es un valor residente mientras VSZ es un valor virtual, que por lo tanto está influenciado, por ejemplo, de eventuales shared library que se cargan y se comparten entre varias aplicaciones, de páginas de memoria no utilizadas en ese momento, etc., por lo que no es sorprendente que el valor exceda el 100%. Sin embargo, las nuevas versiones de top para BusyBox redefinen la columna %MEM en %VSZ solucionando así cualquier malentendido (oops... el significado de las siglas extrañas anteriores lo vais a encontrar en los comentarios de testSys(), en el manual de top y en el enlace que os he pasado sobre la memoria de Linux).

Bueno, yo diría que para este post es suficiente. Os dejo con una pequeña nota: dado que he utilizado solamente loop infinitos en el programa de test, podéis modificarlo a vuestro gusto añadiendo condiciones de salida. O, en alternativa, no tengáis miedo de detenerlo con CTL-C, no creo que Linux se enfade mucho...

¡Hasta el próximo post!