next up previous contents
Nächste Seite: Nonblocking I/O Aufwärts: User Mode Threads Vorherige Seite: Stammfunktionen (primitives) für Threads   Inhalt

Implementation des Windischer Threads Package

Die Threads bauen auf der Abstraktion 'Coroutine' auf. Das Umschalten von einer 'Coroutine' zu einer andern erfolgt mit den Systemaufrufen setjump und longjump. Der nachstehende Code zeigt den Sourcecode von coroutin.c, die Funktionen cor_create und transfer enthält. Für die Funktion cor_create wird die maschinenabhängige Routine wta__newcoroutin benötigt die das Initialisieren der Datenstruktur jmp_buf übernimmt (siehe man-pages von setjmp/longjmp).
#include <setjmp.h>
 /*
 * GLOBAL DATA
 */
jmp_buf      cor_main;
cor_t_descr  cor_main_descr;

int cor_create (cor, start_adress, workspace_size, exit_adress)
    cor_t_descr   *cor;
    void          (*start_adress)();
    unsigned int  workspace_size;
    void          (*exit_adress)();
{
    *cor = (cor_t_descr) wta__newcoroutin(start_adress, workspace_size,
                                         exit_adress);
    return  *cor == 0 ? COR_C_NO_SPACE : COR_C_SUCCESS;
}

void cor_transfer (old_cor, new_cor)
    cor_t_descr  old_cor;
    cor_t_descr  new_cor;
{
    if (setjmp(old_cor) == 0) {
        longjmp(new_cor, 1);
    }
}
Wie kann man nun die Funktion wta__newcoroutin() erstellen? Die Antwort ist man muss wissen wie der jmp_buf aufgebaut ist. Dazu muss man die notwendige Information suchen:
Mit dem Debugger:
Zu diesem Zweck erstellt man ein ganz einfaches c-Programm:
$ cat analyze_setjmp.c
#include <setjmp.h>
main(){
    jmp_buf my_buffer1;
    jmp_buf my_buffer2;
    setjmp(my_buffer1);
    longjmp(my_buffer2,1);
}
Dieses Programm wird compiliert und mit dem Debugger untersucht:
 $ gcc -g -o mytest analyze_setjmp.c
 $ gdb mytest
GNU gdb 5.3.92
Copyright 2003 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 "i586-suse-linux"...
(gdb) list
1       #include <setjmp.h>
2       main(){
3           jmp_buf my_buffer1;
4           jmp_buf my_buffer2;
5           setjmp(my_buffer1);
6           longjmp(my_buffer2,1);
7       }
8
(gdb) break 5
Breakpoint 1 at 0x8048382: file analyze_setjmp.c, line 5.
(gdb) run
Starting program: /home/oser/wthreads/temp/mytest

Breakpoint 1, main () at analyze_setjmp.c:5
5           setjmp(my_buffer1);
(gdb) disass
Dump of assembler code for function main:
0x0804836c <main+0>:    push   %ebp
0x0804836d <main+1>:    mov    %esp,%ebp
0x0804836f <main+3>:    push   %edi
0x08048370 <main+4>:    push   %esi
0x08048371 <main+5>:    push   %ebx
0x08048372 <main+6>:    sub    $0x14c,%esp
0x08048378 <main+12>:   and    $0xfffffff0,%esp
0x0804837b <main+15>:   mov    $0x0,%eax
0x08048380 <main+20>:   sub    %eax,%esp
0x08048382 <main+22>:   sub    $0xc,%esp
0x08048385 <main+25>:   lea    0xffffff48(%ebp),%eax
0x0804838b <main+31>:   push   %eax
0x0804838c <main+32>:   call   0x80482a0
0x08048391 <main+37>:   add    $0x10,%esp
0x08048394 <main+40>:   sub    $0x8,%esp
0x08048397 <main+43>:   push   $0x1
0x08048399 <main+45>:   lea    0xfffffea8(%ebp),%eax
0x0804839f <main+51>:   push   %eax
0x080483a0 <main+52>:   call   0x8048280
End of assembler dump.
(gdb) disass setjmp
Dump of assembler code for function setjmp:
0x400586b0 <setjmp+0>:  mov    0x4(%esp,1),%eax
0x400586b4 <setjmp+4>:  mov    %ebx,0x0(%eax)
0x400586b7 <setjmp+7>:  mov    %esi,0x4(%eax)
0x400586ba <setjmp+10>: mov    %edi,0x8(%eax)
0x400586bd <setjmp+13>: lea    0x4(%esp,1),%ecx
0x400586c1 <setjmp+17>: mov    %ecx,0x10(%eax)
0x400586c4 <setjmp+20>: mov    0x0(%esp,1),%ecx
0x400586c8 <setjmp+24>: mov    %ecx,0x14(%eax)
0x400586cb <setjmp+27>: mov    %ebp,0xc(%eax)
0x400586ce <setjmp+30>: push   $0x1
0x400586d0 <setjmp+32>: pushl  0x8(%esp,1)
0x400586d4 <setjmp+36>: call   0x400586ea <setjmp+58>
0x400586d9 <setjmp+41>: add    $0x1068ff,%ecx
0x400586df <setjmp+47>: lea    0xffef9668(%ecx),%ecx
0x400586e5 <setjmp+53>: call   *%ecx
0x400586e7 <setjmp+55>: pop    %ecx
0x400586e8 <setjmp+56>: pop    %edx
0x400586e9 <setjmp+57>: ret
0x400586ea <setjmp+58>: mov    (%esp,1),%ecx
0x400586ed <setjmp+61>: ret
0x400586ee <setjmp+62>: nop
0x400586ef <setjmp+63>: nop
End of assembler dump.
Unter Verwendung der C Header Dateien:
Ausschnitt aus /usr/include/setjmp.h:
#include <bits/setjmp.h>
typedef struct __jmp_buf_tag    /* C++ doesn't like tagless structs.  */
  {
    /* NOTE: The machine-dependent definitions of `__sigsetjmp'
       assume that a `jmp_buf' begins with a `__jmp_buf' and that
       `__mask_was_saved' follows it.  Do not move these members
       or add others before it.  */
    __jmp_buf __jmpbuf;         /* Calling environment.  */
    int __mask_was_saved;       /* Saved the signal mask?  */
    __sigset_t __saved_mask;    /* Saved signal mask.  */
  } jmp_buf[1];
Und in der Datei /usr/include/bits/setjmp.h findet man:
#if defined __USE_MISC || defined _ASM
# define JB_BX  0
# define JB_SI  1
# define JB_DI  2
# define JB_BP  3
# define JB_SP  4
# define JB_PC  5
# define JB_SIZE 24
#endif

#ifndef _ASM
typedef int __jmp_buf[6];
#endif

Die Corouinen bilden die unterste Abstraktionsebene. Unter der Verwendung von Coroutinen werden mit der Funktion wta_create Threads erzeugt:

/*
 * GLOBAL DATA
 */
list_t_node *ready_list;           /* Liste der ablauffaehigen Threads  */
list_t_node *exit_list;            /* Liste der beendeten Threads       */

unsigned    thread_counter;        /* aktuelle Anzahl Threads */

int 
wta_create (thread_ptr, start_routine, wsp_size, priority, slice)
    wta_t_thread      *thread_ptr;
    void              (*start_routine)();
    unsigned          wsp_size;
    unsigned          priority;
    unsigned          slice;
{
    dispatch_t_thread  new_thread;
    cor_t_descr        new_cor;
    int                old_timer_state;

    *thread_ptr = NULL;
    /*
     * Fuer neuen Thread-Handle Speicherplatz anfordern
     */
    new_thread = (dispatch_t_thread) malloc (sizeof (wta_thread_descr));
    /*
     * wenn Speicherplatz vorhanden ist, wird der Handle initialisiert
     */
    if (new_thread != NULL) {
        /*
         * Initialisierung des Listen-Elements
         */
        new_thread->list.next = NULL;
        new_thread->list.back = NULL;
        /*
         * Status ist undefiniert
         */
        new_thread->state = undefined;
        /*
         * Prioritaet bestimmen
         */
        if (priority > MIN_PRIO) {
            priority = MIN_PRIO;
        } /* endif */
        if (priority < MAX_PRIO) {
            priority = MAX_PRIO;
        } /* endif */
        new_thread->priority = priority;
        /*
         * Arbeitsspeicher
         */
        if (wsp_size < MIN_WSP_SIZE) {
            wsp_size = MIN_WSP_SIZE;
        } /* endif */
        /*
         * Zeitschlitze zuweisen
         */
        new_thread->slice_counter = slice;
        new_thread->slice_actual = slice;
        /*
         * Exception_Teil initialisieren
         */
        new_thread->exceptionStack = NULL;
        /*
         * Liste fuer wta_join initialisieren
         */
        list_Init(&(new_thread->join_list));
        /*
         * Coroutine erzeugen und Ready setzen
         */
        if (cor_create (&(new_thread->cor), start_routine, 
                        wsp_size, wta_exit) == COR_C_SUCCESS) {
            /*
             * Thread ist gueltig
             */
            *thread_ptr = (wta_t_thread) new_thread;

            timer_disable (old_timer_state);
            thread_counter++;
            dispatch_ready ((wta_t_thread) new_thread);
            timer_enable (old_timer_state);
            return SUCCESS;
        } else {
            return NOMEMORY;
        } /* endif */
    } else {
        return NOMEMORY;
    } /* endif */
}
Ein Thread kann mit der Funktion dispatch_block auf irgend einer Liste blockiert werden. Mit dispatch_ready wird der Thread aufgeweckt.
Es sind verschiedene Scheduling Strategien vorgesehen. Es gibt die Möglichkeit die Threads reihum zu bedienen (ROUNDROBIN), die Readyliste immer prioritätssortiert zu unterhalten (PRIOSORTED) oder die Readyliste periodisch zu sortieren (PERIODIC_SORTED).
void  
dispatch_ready (thread)
    wta_t_thread   thread;
{
    dispatch_t_thread  tid;
    int                old_timer_state;

    /*
     * kritischer Abschnitt --> Timer ausschalten
     */
    timer_disable (old_timer_state);    
    /*
     * Status setzen
     */
    tid = (dispatch_t_thread) thread;
    tid->state = ready_running;
    /*
     * Prioritaet setzen
     */
    tid->list.key = tid->priority;
#ifdef PERIODIC_SORTED
    list_Insert_Tail (ready_list, tid);
    /*
     * Sortieren der Threads nur nach einer gewissen Anzahl
     * von Theadwechsel (siehe SORTCOUNTER)
     */
    if (counter <= 0) {
        dispatch_resort ();
        counter = SORTCOUNTER;
    } /* endif */
#endif

#ifdef PRIOSORTED
    /*
     * sortiert nach Prioritaet einordnen
     */
    list_Insert_Sorted (ready_list, tid);
#endif
#ifdef ROUNDROBIN
    /*
     * Round-Robin-Verfahren mit Einfuegen am Ende der Liste
     * --> keine Prioritaeten
     */
    list_Insert_Tail (ready_list, tid);
#endif
    /*
     * kritischer Abschnitt beendet --> Timer einschalten
     */
    timer_enable (old_timer_state);
}

void
dispatch_block(blocked_list)
    list_t_node    *blocked_list;
{
    dispatch__block_state(blocked_list, blocked);
}

void  
dispatch__block_state (blocked_list, block_state)
    list_t_node     *blocked_list;
    thread_e_state   block_state;
{
    dispatch_t_thread  thread;
    int                old_timer_state;
    
    /*
     * kritischer Abschnitt --> Timer ausschalten
     */
    timer_disable (old_timer_state);
    /*
     *  Zustand des Threads modifizieren
     */    
    thread = running_thread;
    thread->state = block_state;
    /*
     *  in die Warteliste einfuegen
     */
    list_Insert_Tail (blocked_list, thread);
    /*
     *  naechsten Thread aus der Bereitliste holen
     */
    list_Remove_Head (ready_list, (list_t_node *) running_thread);
#ifdef PERIODIC_SORTED
    counter--;
#endif
     /*
      * Zeitschlitze setzen
      */
     running_thread->slice_actual = running_thread->slice_counter;
    /*
     *  Wechsel durchfuehren
     */
    cor_transfer(thread->cor, running_thread->cor);
    /* 
     *  Timer 'enablen'
     */
    old_timer_state = 0;
    /*
     * kritischer Abschnitt beendet --> Timer einschalten
     */
    timer_enable (old_timer_state);
}
Das eigentliche Umschalten von einem Thread zu einem andern erfolgt aus nachstehenden Gründen:


next up previous contents
Nächste Seite: Nonblocking I/O Aufwärts: User Mode Threads Vorherige Seite: Stammfunktionen (primitives) für Threads   Inhalt
Hans-Peter Oser 2007-10-30