Lab1: Observando el Comportamiento de Linux

Objetivos

Iniciar los laboratorios con un proyecto sencillo, donde se muestre como a través del sistema de archivos virtual /proc de Linux, podemos  inspeccionar  información interna del kernel. Se deberá generar la utilidad ksamp que muestra de diversas formas algún subconjunto de la información disponible en /proc.

Introducción

Podemos pensar el kernel de Linux como una colección de funciones y estructuras de datos. Estas estructuras de datos (o variables de kernel) contienen la visión del kernel respecto al estado del sistema, donde cada interrupción,  cada llamada al sistema, cada fallo de protección hacen que este estado cambie. Inspeccionando las variables del kernel podemos obtener información relevante a los procesos, interrupciones, dispositivos, sistemas de archivos, capacidades del hardware, etc.

Muchas variables conforman el estado del kernel y estas pueden estar alojadas de manera estática o dinámica en un stack frame de la memoria del kernel. Dado que el kernel de Linux es un programa "C" de código abierto,  es posible inspeccionar el código fuente y encontrar algunos ejemplos relevantes de estas variables como por ejemplo xtime definido en include/linux/sched.h que mantiene la hora del sistema, la estructura task_struct definida en include/linux/sched.h que contiene la descripción completa de un proceso, o los valores nr_threads y nr_running definidos en kernel/fork.c los cuales indican cuantos procesos existen y cuantos de estos están corriendo.

El código fuente de Linux lo podemos encontrar en el directorio /usr/src/linux de la mayoría de las distribuciones, pero también existen páginas donde podemos navegar el código de varias versiones de kernel, como por ejemplo Linux Cross Reference. También existen utilidades para navegación de código local como Source Navigator.

Tareas

Parte A

Buscar información acerca de la estructura del directorio /proc, y averiguar los siguientes datos Estos datos se tomaron del servidor de alumnos:

Type: GenuineIntel
Model: Intel(R) Xeon(TM) CPU 2.40GHz
Kernel: 2.4.18-custom.1.3
UpTime: 1D 5:32:47.43
UserTime: 1:49.9
SysTime: 6:55.32
IdleTime: 1D 5:24:03.02
TotalMem: 922587136
FreeMem: 15532032
Disk: 667810
Context: 16336100
Processes: 18224

Parte B

Escriba una versión inicial del programa que inspeccione las variables del kernel a través del /proc e informe por stdout: También se pide incluir una cabecera donde se indique el nombre de la máquina y la fecha y hora actuales.

Parte C

Escriba una segunda versión del programa que imprima la misma información que la versión por defecto, pero en caso de invocarse con la opción -s, agrega la siguiente información:

Parte D

La tercera parte involucra imprimir todo lo de las versiones anteriores, pero cuando se invoca con la opción -l interval duration imprime además: Asi por ejemplo ksamp -l 2 100 mostrará el promedio de carga de 1 minuto por 100 segundos tomando muestras  en intervalos de 2 segundos.

Notar que cada opción incluye a la otra, por lo que ksamp -s -l 2 100 no debería ser aceptado y en tales casos resulta útil imprimir por la salida standard un resumen de las opciones aceptadas.

Cómo atacar los problemas

La página del manual de Linux man 5 proc contiene suficiente información al respecto, también se pueden ver artículos de la revista Linux Magazine " An Overview of the Proc Filesystem" o The Official Red Hat Linux Reference Guide en su capítulo "The /proc Filesystem". En general basta con realizar una búsqueda de "/proc filesystem" en cualquier buscador de la Web para encontrar información en cualquier idioma.

Una vez encontrados los archivos de /proc donde está la información, es necesario abrirlos, leer la información y cerrarlos. Esto se puede lograr con las funciones de la biblioteca "C" fopen, fgets (o fscanf) y fclose. Un ejemplo de uso sería el siguiente.
 
#include <stdio.h>
#define BUFFSIZE 256

int
main(int argc, char *argv[]) {

FILE *fd;
char buffer[BUFFSIZE+1];
fd = fopen("/proc/sys/kernel/hostname","r");
fgets(buffer, BUFFSIZE+1, fd);
printf("Hostname: %s\n",buffer);
fclose(fd);

}

Para leer los argumentos de entrada e interpretarlos (proceso de parsing) hay que hacer uso de las variables int argc y char *argv[]. La primera indica cuantos argumentos se pasaron y argv es el arreglo de tamaño argc con cada uno de las cadenas de los argumentos. Notar que como el propio comando se incluye en la lista de argumentos una llamada ksamp -l 10 100 implica que al inicio del main se cumple

{argc=4 & argv[]={"ksamp", "-l", "10", "100"}}.

Pueden utilizar la función getopt de la glibc para no volver a inventar la rueda. Pueden encontrar información en man 3 getopt o bien en las Infopages invocando pinfo libc y buscado este tema. Este último comando muestra la GNU C Library reference manual.

El ejemplo anterior resulta sencillo en cuanto a que no tenemos que "interpretar" la secuencia de caracteres, sólo la tomamos y la imprimimos. Sin embargo, muchos de los parámetros que se necesitan imprimir requieren de cierto tratamiento. Este proceso de transformar una secuencia de caracteres en alguna representación más adecuada para su tratamiento se denomina parsing. Por ejemplo el tiempo transcurrido desde que el sistema inició se expresa en segundos, y se encuentra en cierta parte del archivo, entonces tenemos que extraerlo y convertirlo a un entero sin signo. Cuando ya tenemos un entero sin signo, resulta sencillo operar matemáticamente y generar una secuencia de caracteres (imprimir) con el formato adecuado. Esto último se denomina pretty printing.

Funciones como atoi y sus relacionadas pueden ser útiles. También resulta útil sscanf.

Para obtener la fecha y hora que va en el encabezado del informe que brinda el programa, se pueden utilizar la funciones de la glibc gettimeofday y ctime, o bien leer el tiempo de inicio del sistema y sumarlo al transcurrido para luego convertirlo a fecha.

Cuando se necesite realizar las muestra del promedio de carga de 1 minuto, puede ser útil la función sleep que duerme un proceso por un intervalo determinado de tiempo.

Viéndolo de manera general podrímos decir que Lab1 es básicamente un parser/pretty printer, un unserializer/serializer, un demarshaller/marshaller u otra de las tantas denominaciones que tiene este procedimiento tan usual.

Qué se debe Entregar

Tips

No intenten hacer todo de golpe, vayan de a partes, y sobre todo discutan, analicen y trabajen en ideas de forma grupal. Una hora pensando en papel libremente suele ahorrar muchos problemas en el momento de codificar.

Utilicen debuggers de línea de comandos como gdb o interfaces gráficas para estos como ddd. También pueden realizar compilación condicional para hacer debugging de la siguente manera.
 

#define STDOUT 0
#define DEBUG
.
.
#ifdef DEBUG
fprintf(STDOUT,"DEBUG> argc: %d\n",argc);
#endif
.
.

Tareas Adicionales

Si les sobra tiempo pueden hacer las siguientes mejoras:

 

Nicolás Wolovick, 21 de Agosto de 2002