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:
$ 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.
/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.
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: