LA PILA DE EJECUCIÓN Y DEBBUGGING THREADS

Documentos relacionados
Procesos e Hilos en C

Tutorial de GDB. Algoritmos y Estructuras de Datos II. Algoritmos y Estructuras de Datos II () Tutorial de GDB 1 / 1

UNIVERSIDAD AUTÓNOMA DE BAJA CALIFORNIA FACULTAD DE CIENCIAS PRACTICA DE PROCESOS HERRAMIENTAS

Herramienta de depuración GNU Debugger (GDB)

Concurrencia y paralelismo

DEPURADOR GDB. Debugging de programas complejos con múltiples archivos.

Sistemas Operativos Práctica 3

Manipulación de procesos

Guía práctica de estudio 11: Funciones

Tema: Clases y Objetos en C#. Parte II.

Manual de Usuario. Aplicación de Autoevaluación de Centros

Tema 13: Apuntadores en C

Programación en C. Algoritmo y Estructura de Datos. Ing. M. Laura López. Programación en C

La última versión disponible cuando se redactó este manual era la 5 Beta (versión ), y sobre ella versa este manual.

Threads, SMP y Microkernels. Proceso

Qué es un programa informático?

En este artículo vamos a conocer los tipos de datos que podemos manejar programando en C.

Taller de Sistemas Operativos. Módulos del kernel

Clases y Objetos en Java. ELO329: Diseño y Programación Orientados a Objetos

T5-multithreading. Indice

Programación Avanzada

Elementos esenciales de Word

Principios de Computadoras II

Fundamentos de Ordenadores. Depurar programas usando Nemiver

Tema ADQUISICIÓN Y TRATAMIENTO DE DATOS. Departamento de Ciencias de la Computación e IA. Subprogramas en C

TADs en C. Matías Bordese Algoritmos y Estructuras de Datos II - Laboratorio 2013

Generar Temas de Colores y Cambiarlos Dinámicamente en ZK

Tema 3. Aplicaciones de Tipo Consola

Estructuras de Datos Declaraciones Tipos de Datos

Elementos de un programa en C

Tema 6. Gestión dinámica de memoria

C# para no Programadores

El lenguaje de Programación C. Fernando J. Pereda

Sistemas Operativos: Programación de Sistemas. Curso Oscar Déniz Suárez Alexis Quesada Arencibia Francisco J.

PRÁCTICA DE LABORATORIO 4 Programación Orientada a Objetos

Modelos Comprimidos. Modelo de Bloque 3D Standard

Manual de Usuario/a sobre el uso de firma electrónica avanzada

Introducción a C++ y Code::Blocks

ING. JONATHAN QUIROGA TINOCO. Desarrollado por Ing. Jonathan Quiroga T.

PADRÓN GENERAL DE IMPORTADORES Y SECTORES ESPECIFICOS (PGIySE)

Aprendiendo a depurar código

Generador de analizadores léxicos FLEX

Conceptos a tratar. Fundamentos de la Programación Orientada a Objetos Ampliación sobre clases y objetos

Depuración de Node.js (práctica)

Caracteres y Cadenas Conversión de Datos Funciones y procedimientos Archivos cabecera. Fundamentos de programación

Acceso a Datos con Visual Basic

Serialización de datos en C# en Binario, Soap y Xml

Mi primer programa en Code::Blocks

Aplicativos: Cómo se realiza la descarga e instalación de Aplicativos AFIP?

Programación. Test Autoevaluación Tema 3

Lenguaje de Programación: C++ GLUT (como instalarlo)

Métodos que devuelven valor Dado el siguiente triángulo rectángulo:

DESCRIPCIÓN ESPECÍFICA NÚCLEO: COMERCIO Y SERVICIOS SUBSECTOR: INFORMÁTICA

Avance - Soluciones Informáticas Página 1 de 17

Guía de uso del programa AVR-Studio

Guía rápida de uso de Visual Sueldos

Decenio de las personas con Discapacidad en el Perú Año de la consolidación del Mar de Grau

Tema 2 Introducción a la Programación en C.

de Gran Canaria Centro de Tecnología Médica Programación Concurrente

Introduccion al Lenguaje C. Omar Andrés Zapata Mesa Grupo de Fenomenología de Interacciones Fundamentales, (Gfif) Universidad de Antioquia

Laboratorio de Diseño de Robots Móviles Practica No. 2 Sistema mínimo del microcontrolador PIC16F877

Una introducción al compilador C de GNU

Práctica 2: Eclipse como Entorno Integrado de Desarrollo e Introducción al manejo de excepciones.

INSTITUTO ELECTORAL DEL ESTADO DE MÉXICO SECRETARÍA EJECUTIVA UNIDAD DE INFORMÁTICA Y ESTADÍSTICA

Lección: Lenguaje de Programación JULIA

Tema: Introducción al IDE de Microsoft Visual C#.

Manual de Usuario. HISMINSA Sistema de Gestión Asistencial (Versión Offline para XP) Ministerio de Salud del Perú Todos los Derechos Reservados

Office 365 Pro Plus ACTVACIÓN EN EQUIPOS COMPARTIDOS

Oracle Database: Programación con PL/SQL

Diagramas de secuencia

Funciones básicas del depurador

Explicación didáctica sobre comandos de Linux: Comandos de Inicio

Tutorial MPLAB v6.x PROYECTO. Creación de Proyecto

UNIVERSIDAD DE LOS ANDES NUCLEO UNIVERSITARIO RAFAEL RANGEL (NURR) DEPARTAMENTO DE FISICA Y MATEMATICA AREA COMPUTACION TRUJILLO EDO.

Escuela Politécnica Superior de Elche

1. Introducción Generalidades Configuración del Equipo Instalación de Java... 3

MIA RICARDO GONZALEZ LOZANO APLICACIONES EN LENGUAJE C

Introducción a Java LSUB. 30 de enero de 2013 GSYC

MPB Mouse por Barrido

ENTORNO DE DESARROLLO Y COMPILACIÓN DE PELLES C

SUBPROGRAMAS PL/SQL César Martínez C. Profesor Instructor de Base de Datos Sede Puente Alto DUOC

Ejecuta el modo XP sin virtualización de hardware

EJERCICIO 26 DE EXCEL

ACCIONES Photoshop. Primero explicaremos brevemente que son las Acciones de Photoshop y cómo utilizar esta interesante utilidad. Acciones Photoshop

Java desde Consola Utilizando Java sin Path

Seagate Extended Capacity Manager

ATRIBUTOS DE LOS FICHEROS EN LINUX

Introducción. Word Autor: Viviana M. Lloret Prof. de Matemática e Informática Blog: aulamatic.blogspot.com

PROYECTO 2 Parte 1 BASES DE DATOS. Curso (2 Semestre) Grupos 4F2M y 4F1M-1 (aula 5102) CONSULTAS REMOTAS EN JAVA A UNA BASE DE DATOS

Archivos Datanet. Si desea acceder directamente a un capítulo, posicionese sobre el número de página y haga clic.

Seleccione en el escritorio el programa Sucosoft S40 y darle doble click.

Manual de Instrucciones Definición de Componentes y Registro de Notas Parciales

Transcripción:

UNIVERSIDAD NACIONAL DEL CENTRO DE LA PROVINCIA DE BUENOS AIRES FACULTAD DE CIENCIAS EXACTAS APUNTE DE CÁTEDRA LA PILA DE EJECUCIÓN Y DEBBUGGING THREADS por José A. Fernández León y José Macchi ANTES DE COMENZAR El presente apunte tiene como objetivo dar un marco introductorio al manejo de un debugger o depurador para la programación multi-threaded utilizando la libreria POSIX threads (pthreads). Para ello, se mostrarán a continuación los comandos a fin de visualizar el contenido de la pila de ejecución vinculada a threads. Las explicaciones dadas aquí son específicas a la librería antes nombrada de Linux. El tutorial explica las diferentes herramientas básicas definidas por la librería, muestra como usarlas, y luego da un ejemplo de su uso para resolver problemas de programación. Cuando se hable de POSIX threads, es necesario preguntarse: Cuál implementación del estándar de POSIX threads será utilizada?. Como este estándar de threads ha sido revisado durante muchos años en varios períodos, se pueden encontrar implementaciones del estándar en diferentes etapas de desarrollo del mismo; por ejemplo, que dichas librerías diferirán en el conjunto de funciones, valores por defecto, etc. Para ello se recomienda que ante cualquier duda, se consulten las referencias de la bibliografía de este tutorial, o bien los manuales respectivos de las librerías que se utilicen. QUÉ ES UN THREAD? PORQUE UTILIZARLOS Un thread es un semi-proceso, que tiene su propia pila, y que ejecuta una porción de código dada. A diferencia de un proceso real, un thread normalmente comparte su memoria con otros threads (en la cual tal como sucede con los procesos, cada thread tendrá asignado su espacio de memoria). Un grupo de threads es un conjunto de threads que se están ejecutando todos dentro del mismo proceso. Comparten todos la misma memoria, y por ello pueden acceder a las mismas variables globales, la misma memoria de heap, los mismos descriptores de archivos, etc. Todos se ejecutan en paralelo (por ejemplo, usando porciones del tiempo asignado al proceso en general, o si están dentro de un sistema con multiprocesadores, de forma paralela realmente). La ventaja de usar un grupo de threads en lugar de un programa normal en serie es que muchas operaciones pueden ser llevadas a cabo de forma paralela, y de esta forma los eventos asociados a cada actividad pueden ser manejados inmediatamente tan pronto como llegan (por ejemplo, si tenemos un thread manejando la interface de usuario, y 1

otro manejando las consultas a una base de datos, podremos ejecutar consultas complejas realizadas por el usuario, y aun así responder a la entrada del mismo mientras la consulta está siendo ejecutada). La ventaja de usar un grupo de threads en vez de un grupo de procesos es que el cambio de contexto entre threads es realizado mucho más rápidamente que el cambio de contexto entre procesos (un cambio de contexto significa que el sistema operativo cambia la ejecución de un thread o proceso a la ejecución de otro). Por lo tanto, las comunicaciones entre dos threads son usualmente más rápidas y sencillas de implementar que las comunicaciones entre dos procesos. Por otro lado, a modo de inconveniente, debido a que los threads dentro de un grupo, comparten el mismo espacio de memoria, si uno de ellos corrompe el espacio de su memoria, los otros threads también sufrirán las consecuencias. Con un proceso, el sistema operativo normalmente protege a un proceso de otros, y si un proceso corrompe su espacio de memoria, los demás no se verán afectados. CREACIÓN Y DESTRUCCIÓN DE THREADS Cuando un programa multi-threaded comienza su ejecución, tendrá un thread corriendo, el cual ejecuta la función main() del programa. Este es ya un thread en si mismo, con su propio identificador de thread (ID). Para poder crear un nuevo thread, el programa deberá utilizar la función pthread_create(). En el siguiente código mostramos como se la utiliza. CODIGO: create.c #include <stdio.h> /* Rutinas I/O estandar */ #include <pthread.h> /* Estructuras de datos y funciones pthread */ /* Funcion a ser ejecutada por el Nuevo thread */ void* do_loop(void* data) { int i; /* Contador para imprimir valores */ int j; /* Contador para el delay */ int me = *((int*)data); /* Numero ID del Thread */ for (i=0; i<10; i++) { for (j=0; j<500000; j++); printf("thread '%d' - Posicion i en memoria '%x' - Valor i '%d'\n",me,&i,i); } } /* Salida del thread */ pthread_exit(null); /* Main del ejemplo */ int main(int argc, char* argv[]) { int thr_id; /* ID del thread Nuevo */ pthread_t p_thread; /* Estructura del thread */ int a = 1; /* thread 1 */ int b = 2; /* thread 2 */ /* Creo un nuevo thread que ejecutara 'do_loop()' */ thr_id = pthread_create(&p_thread, NULL, do_loop, (void*)&a); /* El otro thread esta dado por la function main() */ do_loop((void*)&b); 2

} /* No alcanzado */ return 0; Algunas consideraciones acerca del programa: 1. Notar que el programa main() es también un thread, el cual ejecuta la función do_loop() en paralelo con la ejecución del thread. 2. pthread_create() toma 4 parámetros. El primer parámetro es usado por pthread_create() para brindar información al programa acerca del thread. El segundo parámetro es usado para setear algunos atributos del nuevo thread. En el caso presentado, se provee de un puntero a NULL para decirle a pthread_create() que use los valores por defecto. El tercer parámetro es el nombre de la función a la cual el thread ejecutará. El cuarto parámetro es un argumento a ser pasado a la función. Es posible notar el casting al tipo 'void*'. Esto no es requerido por la sintaxis de ANSI-C, pero es mencionada para clarificar su uso. 3. El lazo del delay dentro de la función es usado sólo para demostrar que los threads son ejecutados de forma paralela. Se recomienda dar un delay mayor si la velocidad de su CPU es demasiado rápida, de esta forma podrá ver las impresiones de cada thread uno antes del otro. 4. La llamada a pthread_exit() causa que el thread en ejecución termine y libere sus recursos. No hay necesidad de utilizar esta llamada al final de la ejecución de la función que se invoca, ya que al terminar la ejecución el thread automáticamente terminará. Esta función es útil cuando deseamos terminar la ejecución de un thread a la mitad de su corrida. Para poder compilar un programa multi-threaded usando gcc, se debe linkear con la librería pthreads. Asumiendo que la librería ya está instalada en el sistema, el siguiente comando compilará nuestro primer programa: gcc create.c -o create -lpthread TERMINACIÓN Y CANCELACIÓN DE THREADS Tal como se crearon los threads, también se debe pensar en la terminación de los mismos. Existen diversas consideraciones a tener en cuenta. Se debe poder terminar la ejecución de un thread de forma clara y limpia. A diferencia de los procesos, para los cuales se utiliza un método de envíos de señales, el diseño de la librería de threads es un poco más compleja, permitiendo cancelar threads y realizar finalizaciones limpias de los mismos. CANCELANDO UN THREAD Cuando se quiere terminar un thread, se debe utilizar la función pthread_cancel. Esta función toma un ID de thread como parámetro y envía un requerimiento de cancelación 3

al thread. Lo que hace el thread con el pedido de cancelación dependerá de su estado. Puede finalizar inmediatamente, esperar a encontrar un punto de cancelación o bien ignorarlo. Uso de la función de cancelación: Se debe asumir que 'thr_id' es una variable del tipo pthread_id conteniendo el ID de un thread que está ejecutándose: pthread_cancel(thr_id); La función pthread_cancel() retorna 0, por lo que no se sabe si terminó erróneamente o no. SETEANDO EL ESTADO DE CANCELACIÓN DE UN THREAD El estado de cancelación de un thread puede ser modificado usando varios métodos. El primero es mediante el uso de la función pthread_setcancelstate(). Esta función define cuando el thread deberá aceptar el estado de cancelación o cuando no. La función toma dos argumentos. Uno que setea el nuevo estado de cancelación, y otro con el cual se guarda el estado previo de cancelación mediante la función. Se utiliza de la siguiente forma: int old_cancel_state; pthread_setcancelstate(pthread_cancel_disable, &old_cancel_state); Esto deshabilitará la cancelación del thread. También se puede habilitar la cancelación del thread de la siguiente forma: int old_cancel_state; pthread_setcancelstate(pthread_cancel_enable, &old_cancel_state); Es posible notar que se provee de un puntero a NULL como segundo parámetro, y que por ello no se puede obtener el viejo estado de cancelación del thread. Una función similar, llamada pthread_setcanceltype() es usada para definir como un thread responde a un pedido de cancelación, asumiendo que si el estado de cancelación actual es habilitado. Una opción es tratar el pedido inmediatamente (asincrónicamente). La otra forma de tratar el pedido es esperar hasta un punto de cancelación. Para setear la primera opción (cancelación asincrónica) deberemos hacer algo como lo siguiente: int old_cancel_type; pthread_setcanceltype(pthread_cancel_asynchronous, &old_cancel_type); Mientras que para la segunda opción (cancelación diferida): int old_cancel_type; pthread_setcanceltype(pthread_cancel_deferred, &old_cancel_type); 4

es posible observar que existe un puntero a NULL como segundo parámetro y que por ello no es posible obtener el viejo estado de cancelación. El lector se preguntará - qué sucede si nunca seteo el estado de cancelación de un tipo thread? En tal caso, la función pthread_create() automáticamente setea el thread al estado de cancelación diferida, esto es, PTHREAD_CANCEL_ENABLE para el modo de cancelación, y PTHREAD_CANCEL_DEFERRED para el tipo de cancelación. USANDO UN THREAD-AWARE DEBUGGER Una última mención a realizar cuando estamos debuggeando una aplicación multithreaded, es necesario usar un debugger que vea los threads en el programa. La mayoría de los debuggers actuales que vienen en los ambientes de desarrollo comercial son thread-aware. En tanto que en Linux, gdb es el debugger que acompaña a la mayoría de las distribuciones, y el cual normalmente no es thread-aware. Existe un proyecto, llamado 'SmartGDB', que agrega soporte para threads al gdb. Por más información recomendamos dirigirse al sitio http://hegel.ittc.ukans.edu/projects/smartgdb/. O también a la siguiente dirección LinuxThreads homepage (http://pauillac.inria.fr/%7exleroy/linuxthreads/ ). EJEMPLO DE EJECUCIÓN DEL DEBUGGER El siguiente ejemplo muestra el debugging del archivo denominado create.c, del cual ya hemos mostrado su código fuente. Se compiló el archivo mencionado con GCC, y luego se procedió a debuggearlo con GDB bajo el entorno Linux Fedora (aunque con GDB cabe mencionar que solo podremos observar determinadas aspectos del debugging, como la pila de ejecución y la existencia de dos threads, ya que la versión utilizada no es thread-aware). En el ejemplo se visualizan las líneas de ejecución de consola para el debugging con una pequeña leyenda que va dando una explicación de la secuencia de pasos del programa. También al momento de ejecutar el ejemplo, la cual muestra las diferentes direcciones que toma la variable i de la función do_loop() para cada uno de los threads, complementando de esta forma la visión para la cual se puede apreciar que cada thread tiene su propia pila de ejecución. Corrida del ejecutable correspondiente a create.c [vmware@server1 multi-thread]$ gcc create.c -o create - lpthread [vmware@server1 multi-thread]$./create Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '0' Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '1' Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '2' Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '3' Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '4' Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '5' Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '6' Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '0' Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '1' Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '2' Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '3' Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '4' Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '5' Se compila el fuente con GCC Se ejecuta create Notemos que la variable i para el Thread 2 toma la posición de memoria bfeb1774, mientras que la misma variable para el Thread 1 toma la posición de memoria b7f92a94. Además debemos destacar que las ejecuciones de ambos threads son conjuntas (notar que en 5

Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '6' Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '7' Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '8' Thread '2' - Posicion i en memoria 'bfeb1774' - Valor i '9' Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '7' Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '8' Thread '1' - Posicion i en memoria 'b7f92a94' - Valor i '9' [vmware@server1 multi-thread]$ la salida por pantalla, existen impresiones de ambos threads alternándose) Podemos preguntarnos entonces PREGUNTA: A que se debe que la variable i asuma diferentes posiciones en memoria? Por qué ocurre esto? Veamos ahora la ejecución dentro del debugger: Nota: Por consultas a los comandos del debugger, escribir en la linea de comandos del mismo help, esto desplegará una lista de opciones a las cuales se podrá acceder para realizar consultas de los comandos disponibles. [vmware@server1 multi-thread]$ gdb create GNU gdb Red Hat Linux (6.0post-0.20040223.19rh) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux-gnu"...(no debugging symbols found)...using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) break main Breakpoint 1 at 0x8048498 (gdb) break do_loop Breakpoint 2 at 0x804843a (gdb) info break Num Type Disp Enb Address What 1 breakpoint keep y 0x08048498 <main+6> 2 breakpoint keep y 0x0804843a <do_loop+6> (gdb) run Starting program: /tmp/jose/multi-thread/create (no debugging symbols found)...[thread debugging using libthread_db enabled] [New Thread -1208400448 (LWP 9789)] (no debugging symbols found)...(no debugging symbols found)...[switching to Thread -1208400448 (LWP 9789)] Breakpoint 1, 0x08048498 in main () (gdb) where #0 0x08048498 in main () (gdb) next Single stepping until exit from function main, which has no line number information. [New Thread -1208403024 (LWP 9794)] Breakpoint 2, 0x0804843a in do_loop () (gdb) where #0 0x0804843a in do_loop () #1 0x080484d6 in main () (gdb) info threads 2 Thread -1208403024 (LWP 9794) 0x00acc15c in clone () from /lib/tls/libc.so.6 * 1 Thread -1208400448 (LWP 9789) 0x0804843a in do_loop Se ejecuta el programa de debugging para el ejemplo mencionado. Se establece un breakpoint en la función main. Se establece un breakpoint en la funcion do_loop. Se visualiza la información de los breakpoints definidos. Comienzo del proceso de debugging del ejemplo. Se detiene en la función main del Thread 1 (LWP 9789) Posición en donde se detuvo el debugger. Continuo la ejecución. Crea un nuevo Thread (2 LWP 9794) y se detiene en el breakpoint 2 a la entrada del do_loop del Thread 1. Posición de donde se detuvo el debugger. Threads activos. Veamos que el thread 1 es quien esta siendo debuggeado desde la consola, ya que aparece con 6

() (gdb) next Single stepping until exit from function do_loop, which has no line number information. Program received signal SIGTRAP, Trace/breakpoint trap. [Switching to Thread -1208403024 (LWP 9794)] 0x0804843b in do_loop () (gdb) where #0 0x0804843b in do_loop () #1 0x00c6d98c in start_thread () from /lib/tls/libpthread.so.0 #2 0x00acc16a in clone () from /lib/tls/libc.so.6 (gdb) info break Num Type Disp Enb Address What 1 breakpoint keep y 0x08048498 <main+6> breakpoint already hit 1 time 2 breakpoint keep y 0x0804843a <do_loop+6> breakpoint already hit 1 time (gdb) info threads * 2 Thread -1208403024 (LWP 9794) 0x0804843b in do_loop () 1 Thread -1208400448 (LWP 9789) 0x0804843d in do_loop () (gdb) next Single stepping until exit from function do_loop, which has no line number information. '1' '2' '3' '4' '5' '6' '7' '8' '9' Couldn't get registers: No such process. (gdb) where #0 0x0804843c in do_loop () (gdb) next Single stepping until exit from function main, which has no line number information. Program exited normally. (gdb) quit [vmware@server1 multi-thread]$ el signo * a su izquierda. Continuo la ejecución. Cambia al Thread 2. El debugger se detuvo en do_loop del Thread 2, ya que el Thread 1 esta ejecutando do_loop hasta su finalización. Muestro los pasajes por los breakpoints. Muestro el estado de los threads (thread activo y en que lugar se encuentra detenido) Continuo con la ejecución del Thread 2. Muestro donde se encuentra el debugger. Continuo la ejecución del Thread 2 hasta su finalización. Salgo del debugger (notemos que la ejecución del Thread 1 no se ha visualizado en la consola. Ello sucede debido a que la versión del GDB no es thread-aware. 7

BIBLIOGRAFÍA http://users.actcom.co.il/~choo/lupg/tutorials/debugging/debugging-with-gdb.html http://users.actcom.co.il/~choo/lupg/tutorials/multi-thread/multi-thread.html 8