Changes in / [0614d14:1ce2189]
- Location:
- src
- Files:
-
- 5 deleted
- 18 edited
-
benchmark/CorCtxSwitch.c (modified) (1 diff)
-
benchmark/csv-data.c (modified) (1 diff)
-
benchmark/interrupt_linux.c (deleted)
-
libcfa/concurrency/alarm.c (modified) (7 diffs)
-
libcfa/concurrency/coroutine (modified) (4 diffs)
-
libcfa/concurrency/coroutine.c (modified) (5 diffs)
-
libcfa/concurrency/invoke.c (modified) (5 diffs)
-
libcfa/concurrency/invoke.h (modified) (3 diffs)
-
libcfa/concurrency/kernel (modified) (4 diffs)
-
libcfa/concurrency/kernel.c (modified) (35 diffs)
-
libcfa/concurrency/kernel_private.h (modified) (3 diffs)
-
libcfa/concurrency/monitor (modified) (1 diff)
-
libcfa/concurrency/monitor.c (modified) (23 diffs)
-
libcfa/concurrency/preemption.c (modified) (8 diffs)
-
libcfa/concurrency/thread (modified) (1 diff)
-
libcfa/concurrency/thread.c (modified) (4 diffs)
-
libcfa/libhdr/libalign.h (modified) (3 diffs)
-
libcfa/libhdr/libdebug.h (modified) (2 diffs)
-
tests/.expect/concurrent/preempt.txt (deleted)
-
tests/preempt.c (deleted)
-
tests/preempt_longrun/enter.c (deleted)
-
tests/preempt_longrun/enter3.c (deleted)
-
tests/sched-int-block.c (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/benchmark/CorCtxSwitch.c
r0614d14 r1ce2189 31 31 32 32 StartTime = Time(); 33 // for ( volatile unsigned int i = 0; i < NoOfTimes; i += 1 ) { 34 // resume( this_coroutine() ); 35 // // resume( &s ); 36 // } 33 37 resumer( &s, NoOfTimes ); 34 38 EndTime = Time(); -
src/benchmark/csv-data.c
r0614d14 r1ce2189 38 38 39 39 StartTime = Time(); 40 // for ( volatile unsigned int i = 0; i < NoOfTimes; i += 1 ) { 41 // resume( this_coroutine() ); 42 // // resume( &s ); 43 // } 40 44 resumer( &s, NoOfTimes ); 41 45 EndTime = Time(); -
src/libcfa/concurrency/alarm.c
r0614d14 r1ce2189 16 16 17 17 extern "C" { 18 #include <errno.h>19 #include <stdio.h>20 #include <string.h>21 18 #include <time.h> 22 #include <unistd.h>23 19 #include <sys/time.h> 24 20 } 25 26 #include "libhdr.h"27 21 28 22 #include "alarm.h" … … 37 31 timespec curr; 38 32 clock_gettime( CLOCK_REALTIME, &curr ); 39 __cfa_time_t curr_time = ((__cfa_time_t)curr.tv_sec * TIMEGRAN) + curr.tv_nsec; 40 // LIB_DEBUG_PRINT_BUFFER_DECL( STDERR_FILENO, "Kernel : current time is %lu\n", curr_time ); 41 return curr_time; 33 return ((__cfa_time_t)curr.tv_sec * TIMEGRAN) + curr.tv_nsec; 42 34 } 43 35 44 36 void __kernel_set_timer( __cfa_time_t alarm ) { 45 LIB_DEBUG_PRINT_BUFFER_DECL( STDERR_FILENO, "Kernel : set timer to %lu\n", (__cfa_time_t)alarm );46 37 itimerval val; 47 38 val.it_value.tv_sec = alarm / TIMEGRAN; // seconds … … 80 71 } 81 72 82 LIB_DEBUG_DO( bool validate( alarm_list_t * this ) {83 alarm_node_t ** it = &this->head;84 while( (*it) ) {85 it = &(*it)->next;86 }87 88 return it == this->tail;89 })90 91 73 static inline void insert_at( alarm_list_t * this, alarm_node_t * n, __alarm_it_t p ) { 92 verify( !n->next );74 assert( !n->next ); 93 75 if( p == this->tail ) { 94 76 this->tail = &n->next; … … 98 80 } 99 81 *p = n; 100 101 verify( validate( this ) );102 82 } 103 83 … … 109 89 110 90 insert_at( this, n, it ); 111 112 verify( validate( this ) );113 91 } 114 92 … … 122 100 head->next = NULL; 123 101 } 124 verify( validate( this ) );125 102 return head; 126 103 } … … 128 105 static inline void remove_at( alarm_list_t * this, alarm_node_t * n, __alarm_it_t it ) { 129 106 verify( it ); 130 verify( (*it) == n );107 verify( (*it)->next == n ); 131 108 132 (*it) = n->next;109 (*it)->next = n->next; 133 110 if( !n-> next ) { 134 111 this->tail = it; 135 112 } 136 113 n->next = NULL; 137 138 verify( validate( this ) );139 114 } 140 115 141 116 static inline void remove( alarm_list_t * this, alarm_node_t * n ) { 142 117 alarm_node_t ** it = &this->head; 143 while( (*it) && (*it) != n ) {118 while( (*it) && (*it)->next != n ) { 144 119 it = &(*it)->next; 145 120 } 146 121 147 verify( validate( this ) );148 149 122 if( *it ) { remove_at( this, n, it ); } 150 151 verify( validate( this ) );152 123 } 153 124 154 125 void register_self( alarm_node_t * this ) { 155 126 disable_interrupts(); 156 verify( !systemProcessor->pending_alarm );157 lock( &systemProcessor->alarm_lock DEBUG_CTX2);127 assert( !systemProcessor->pending_alarm ); 128 lock( &systemProcessor->alarm_lock ); 158 129 { 159 verify( validate( &systemProcessor->alarms ) );160 bool first = !systemProcessor->alarms.head;161 162 130 insert( &systemProcessor->alarms, this ); 163 131 if( systemProcessor->pending_alarm ) { 164 132 tick_preemption(); 165 133 } 166 if( first ) {167 __kernel_set_timer( systemProcessor->alarms.head->alarm - __kernel_get_time() );168 }169 134 } 170 135 unlock( &systemProcessor->alarm_lock ); 171 136 this->set = true; 172 enable_interrupts( DEBUG_CTX);137 enable_interrupts(); 173 138 } 174 139 175 140 void unregister_self( alarm_node_t * this ) { 176 // LIB_DEBUG_PRINT_BUFFER_DECL( STDERR_FILENO, "Kernel : unregister %p start\n", this );177 141 disable_interrupts(); 178 lock( &systemProcessor->alarm_lock DEBUG_CTX2 ); 179 { 180 verify( validate( &systemProcessor->alarms ) ); 181 remove( &systemProcessor->alarms, this ); 182 } 142 lock( &systemProcessor->alarm_lock ); 143 remove( &systemProcessor->alarms, this ); 183 144 unlock( &systemProcessor->alarm_lock ); 184 enable_interrupts( DEBUG_CTX);145 disable_interrupts(); 185 146 this->set = false; 186 // LIB_DEBUG_PRINT_BUFFER_LOCAL( STDERR_FILENO, "Kernel : unregister %p end\n", this );187 147 } -
src/libcfa/concurrency/coroutine
r0614d14 r1ce2189 63 63 64 64 // Get current coroutine 65 extern volatile thread_local coroutine_desc * this_coroutine;65 coroutine_desc * this_coroutine(void); 66 66 67 67 // Private wrappers for context switch and stack creation … … 71 71 // Suspend implementation inlined for performance 72 72 static inline void suspend() { 73 coroutine_desc * src = this_coroutine ; // optimization73 coroutine_desc * src = this_coroutine(); // optimization 74 74 75 75 assertf( src->last != 0, … … 88 88 forall(dtype T | is_coroutine(T)) 89 89 static inline void resume(T * cor) { 90 coroutine_desc * src = this_coroutine ; // optimization90 coroutine_desc * src = this_coroutine(); // optimization 91 91 coroutine_desc * dst = get_coroutine(cor); 92 92 … … 112 112 113 113 static inline void resume(coroutine_desc * dst) { 114 coroutine_desc * src = this_coroutine ; // optimization114 coroutine_desc * src = this_coroutine(); // optimization 115 115 116 116 // not resuming self ? -
src/libcfa/concurrency/coroutine.c
r0614d14 r1ce2189 32 32 #include "invoke.h" 33 33 34 extern volatilethread_local processor * this_processor;34 extern thread_local processor * this_processor; 35 35 36 36 //----------------------------------------------------------------------------- … … 44 44 // Coroutine ctors and dtors 45 45 void ?{}(coStack_t* this) { 46 this->size = 65000; // size of stack46 this->size = 10240; // size of stack 47 47 this->storage = NULL; // pointer to stack 48 48 this->limit = NULL; // stack grows towards stack limit … … 50 50 this->context = NULL; // address of cfa_context_t 51 51 this->top = NULL; // address of top of storage 52 this->userStack = false; 52 this->userStack = false; 53 53 } 54 54 … … 106 106 107 107 // set state of current coroutine to inactive 108 src->state = src->state == Halted ? Halted :Inactive;108 src->state = Inactive; 109 109 110 110 // set new coroutine that task is executing 111 this_ coroutine = dst;111 this_processor->current_coroutine = dst; 112 112 113 113 // context switch to specified coroutine 114 assert( src->stack.context );115 114 CtxSwitch( src->stack.context, dst->stack.context ); 116 // when CtxSwitch returns we are back in the src coroutine 115 // when CtxSwitch returns we are back in the src coroutine 117 116 118 117 // set state of new coroutine to active … … 132 131 this->size = libCeiling( storageSize, 16 ); 133 132 // use malloc/memalign because "new" raises an exception for out-of-memory 134 133 135 134 // assume malloc has 8 byte alignment so add 8 to allow rounding up to 16 byte alignment 136 135 LIB_DEBUG_DO( this->storage = memalign( pageSize, cxtSize + this->size + pageSize ) ); -
src/libcfa/concurrency/invoke.c
r0614d14 r1ce2189 29 29 30 30 extern void __suspend_internal(void); 31 extern void __leave_thread_monitor( struct thread_desc * this ); 32 extern void disable_interrupts(); 33 extern void enable_interrupts( DEBUG_CTX_PARAM ); 31 extern void __leave_monitor_desc( struct monitor_desc * this ); 34 32 35 33 void CtxInvokeCoroutine( 36 void (*main)(void *), 37 struct coroutine_desc *(*get_coroutine)(void *), 34 void (*main)(void *), 35 struct coroutine_desc *(*get_coroutine)(void *), 38 36 void *this 39 37 ) { … … 58 56 59 57 void CtxInvokeThread( 60 void (*dtor)(void *), 61 void (*main)(void *), 62 struct thread_desc *(*get_thread)(void *), 58 void (*dtor)(void *), 59 void (*main)(void *), 60 struct thread_desc *(*get_thread)(void *), 63 61 void *this 64 62 ) { 65 // First suspend, once the thread arrives here,66 // the function pointer to main can be invalidated without risk67 63 __suspend_internal(); 68 64 69 // Fetch the thread handle from the user defined thread structure70 65 struct thread_desc* thrd = get_thread( this ); 66 struct coroutine_desc* cor = &thrd->cor; 67 struct monitor_desc* mon = &thrd->mon; 68 cor->state = Active; 71 69 72 // Officially start the thread by enabling preemption 73 enable_interrupts( DEBUG_CTX ); 74 75 // Call the main of the thread 70 // LIB_DEBUG_PRINTF("Invoke Thread : invoking main %p (args %p)\n", main, this); 76 71 main( this ); 77 72 78 // To exit a thread we must : 79 // 1 - Mark it as halted 80 // 2 - Leave its monitor 81 // 3 - Disable the interupts 82 // The order of these 3 operations is very important 83 __leave_thread_monitor( thrd ); 73 __leave_monitor_desc( mon ); 84 74 85 75 //Final suspend, should never return … … 90 80 91 81 void CtxStart( 92 void (*main)(void *), 93 struct coroutine_desc *(*get_coroutine)(void *), 94 void *this, 82 void (*main)(void *), 83 struct coroutine_desc *(*get_coroutine)(void *), 84 void *this, 95 85 void (*invoke)(void *) 96 86 ) { … … 118 108 ((struct FakeStack *)(((struct machine_context_t *)stack->context)->SP))->rturn = invoke; 119 109 ((struct FakeStack *)(((struct machine_context_t *)stack->context)->SP))->mxcr = 0x1F80; //Vol. 2A 3-520 120 ((struct FakeStack *)(((struct machine_context_t *)stack->context)->SP))->fcw = 0x037F; //Vol. 1 8-7 110 ((struct FakeStack *)(((struct machine_context_t *)stack->context)->SP))->fcw = 0x037F; //Vol. 1 8-7 121 111 122 112 #elif defined( __x86_64__ ) … … 138 128 ((struct FakeStack *)(((struct machine_context_t *)stack->context)->SP))->fixedRegisters[1] = invoke; 139 129 ((struct FakeStack *)(((struct machine_context_t *)stack->context)->SP))->mxcr = 0x1F80; //Vol. 2A 3-520 140 ((struct FakeStack *)(((struct machine_context_t *)stack->context)->SP))->fcw = 0x037F; //Vol. 1 8-7 130 ((struct FakeStack *)(((struct machine_context_t *)stack->context)->SP))->fcw = 0x037F; //Vol. 1 8-7 141 131 #else 142 132 #error Only __i386__ and __x86_64__ is supported for threads in cfa -
src/libcfa/concurrency/invoke.h
r0614d14 r1ce2189 31 31 struct spinlock { 32 32 volatile int lock; 33 #ifdef __CFA_DEBUG__34 const char * prev_name;35 void* prev_thrd;36 #endif37 33 }; 38 34 … … 87 83 struct __thread_queue_t entry_queue; // queue of threads that are blocked waiting for the monitor 88 84 struct __condition_stack_t signal_stack; // stack of conditions to run next once we exit the monitor 85 struct monitor_desc * stack_owner; // if bulk acquiring was used we need to synchronize signals with an other monitor 89 86 unsigned int recursion; // monitor routines can be called recursively, we need to keep track of that 90 87 }; … … 102 99 #ifndef _INVOKE_PRIVATE_H_ 103 100 #define _INVOKE_PRIVATE_H_ 104 101 105 102 struct machine_context_t { 106 103 void *SP; -
src/libcfa/concurrency/kernel
r0614d14 r1ce2189 28 28 //----------------------------------------------------------------------------- 29 29 // Locks 30 bool try_lock ( spinlock * DEBUG_CTX_PARAM2 ); 31 void lock ( spinlock * DEBUG_CTX_PARAM2 ); 32 void lock_yield( spinlock * DEBUG_CTX_PARAM2 ); 33 void unlock ( spinlock * ); 30 bool try_lock( spinlock * ); 31 void lock( spinlock * ); 32 void unlock( spinlock * ); 34 33 35 34 struct signal_once { … … 69 68 unsigned short thrd_count; 70 69 }; 71 static inline void ?{}(FinishAction * this) { 70 static inline void ?{}(FinishAction * this) { 72 71 this->action_code = No_Action; 73 72 this->thrd = NULL; … … 79 78 struct processorCtx_t * runner; 80 79 cluster * cltr; 80 coroutine_desc * current_coroutine; 81 thread_desc * current_thread; 81 82 pthread_t kernel_thread; 82 83 83 84 signal_once terminated; 84 85 volatile bool is_terminated; … … 89 90 unsigned int preemption; 90 91 92 unsigned short disable_preempt_count; 93 91 94 bool pending_preemption; 92 93 char * last_enable;94 95 }; 95 96 -
src/libcfa/concurrency/kernel.c
r0614d14 r1ce2189 15 15 // 16 16 17 #include "libhdr.h" 17 #include "startup.h" 18 19 //Start and stop routine for the kernel, declared first to make sure they run first 20 void kernel_startup(void) __attribute__(( constructor( STARTUP_PRIORITY_KERNEL ) )); 21 void kernel_shutdown(void) __attribute__(( destructor ( STARTUP_PRIORITY_KERNEL ) )); 22 23 //Header 24 #include "kernel_private.h" 18 25 19 26 //C Includes … … 28 35 29 36 //CFA Includes 30 #include " kernel_private.h"37 #include "libhdr.h" 31 38 #include "preemption.h" 32 #include "startup.h"33 39 34 40 //Private includes 35 41 #define __CFA_INVOKE_PRIVATE__ 36 42 #include "invoke.h" 37 38 //Start and stop routine for the kernel, declared first to make sure they run first39 void kernel_startup(void) __attribute__(( constructor( STARTUP_PRIORITY_KERNEL ) ));40 void kernel_shutdown(void) __attribute__(( destructor ( STARTUP_PRIORITY_KERNEL ) ));41 43 42 44 //----------------------------------------------------------------------------- … … 57 59 // Global state 58 60 59 volatile thread_local processor * this_processor; 60 volatile thread_local coroutine_desc * this_coroutine; 61 volatile thread_local thread_desc * this_thread; 62 volatile thread_local unsigned short disable_preempt_count = 1; 61 thread_local processor * this_processor; 62 63 coroutine_desc * this_coroutine(void) { 64 return this_processor->current_coroutine; 65 } 66 67 thread_desc * this_thread(void) { 68 return this_processor->current_thread; 69 } 63 70 64 71 //----------------------------------------------------------------------------- 65 72 // Main thread construction 66 73 struct current_stack_info_t { 67 machine_context_t ctx; 74 machine_context_t ctx; 68 75 unsigned int size; // size of stack 69 76 void *base; // base of stack … … 99 106 100 107 void ?{}( coroutine_desc * this, current_stack_info_t * info) { 101 (&this->stack){ info }; 108 (&this->stack){ info }; 102 109 this->name = "Main Thread"; 103 110 this->errno_ = 0; … … 129 136 void ?{}(processor * this, cluster * cltr) { 130 137 this->cltr = cltr; 138 this->current_coroutine = NULL; 139 this->current_thread = NULL; 131 140 (&this->terminated){}; 132 141 this->is_terminated = false; 133 142 this->preemption_alarm = NULL; 134 143 this->preemption = default_preemption(); 144 this->disable_preempt_count = 1; //Start with interrupts disabled 135 145 this->pending_preemption = false; 136 146 … … 140 150 void ?{}(processor * this, cluster * cltr, processorCtx_t * runner) { 141 151 this->cltr = cltr; 152 this->current_coroutine = NULL; 153 this->current_thread = NULL; 142 154 (&this->terminated){}; 143 155 this->is_terminated = false; 144 this->preemption_alarm = NULL; 145 this->preemption = default_preemption(); 156 this->disable_preempt_count = 0; 146 157 this->pending_preemption = false; 147 this->kernel_thread = pthread_self();148 158 149 159 this->runner = runner; 150 LIB_DEBUG_PRINT_SAFE("Kernel : constructing systemprocessor context %p\n", runner);160 LIB_DEBUG_PRINT_SAFE("Kernel : constructing processor context %p\n", runner); 151 161 runner{ this }; 152 162 } 153 154 LIB_DEBUG_DO( bool validate( alarm_list_t * this ); )155 163 156 164 void ?{}(system_proc_t * this, cluster * cltr, processorCtx_t * runner) { … … 160 168 161 169 (&this->proc){ cltr, runner }; 162 163 verify( validate( &this->alarms ) );164 170 } 165 171 … … 178 184 179 185 void ^?{}(cluster * this) { 180 186 181 187 } 182 188 … … 197 203 198 204 thread_desc * readyThread = NULL; 199 for( unsigned int spin_count = 0; ! this->is_terminated; spin_count++ ) 205 for( unsigned int spin_count = 0; ! this->is_terminated; spin_count++ ) 200 206 { 201 207 readyThread = nextThread( this->cltr ); … … 203 209 if(readyThread) 204 210 { 205 verify( disable_preempt_count > 0 );206 207 211 runThread(this, readyThread); 208 209 verify( disable_preempt_count > 0 );210 212 211 213 //Some actions need to be taken from the kernel … … 227 229 } 228 230 229 // runThread runs a thread by context switching 230 // from the processor coroutine to the target thread 231 // runThread runs a thread by context switching 232 // from the processor coroutine to the target thread 231 233 void runThread(processor * this, thread_desc * dst) { 232 234 coroutine_desc * proc_cor = get_coroutine(this->runner); 233 235 coroutine_desc * thrd_cor = get_coroutine(dst); 234 236 235 237 //Reset the terminating actions here 236 238 this->finish.action_code = No_Action; 237 239 238 240 //Update global state 239 this _thread = dst;241 this->current_thread = dst; 240 242 241 243 // Context Switch to the thread … … 244 246 } 245 247 246 // Once a thread has finished running, some of 248 // Once a thread has finished running, some of 247 249 // its final actions must be executed from the kernel 248 250 void finishRunning(processor * this) { … … 254 256 } 255 257 else if( this->finish.action_code == Release_Schedule ) { 256 unlock( this->finish.lock ); 258 unlock( this->finish.lock ); 257 259 ScheduleThread( this->finish.thrd ); 258 260 } … … 287 289 processor * proc = (processor *) arg; 288 290 this_processor = proc; 289 this_coroutine = NULL;290 this_thread = NULL;291 disable_preempt_count = 1;292 291 // SKULLDUGGERY: We want to create a context for the processor coroutine 293 292 // which is needed for the 2-step context switch. However, there is no reason 294 // to waste the perfectly valid stack create by pthread. 293 // to waste the perfectly valid stack create by pthread. 295 294 current_stack_info_t info; 296 295 machine_context_t ctx; … … 301 300 302 301 //Set global state 303 this_coroutine = &proc->runner->__cor;304 this_thread = NULL;302 proc->current_coroutine = &proc->runner->__cor; 303 proc->current_thread = NULL; 305 304 306 305 //We now have a proper context from which to schedule threads 307 306 LIB_DEBUG_PRINT_SAFE("Kernel : core %p created (%p, %p)\n", proc, proc->runner, &ctx); 308 307 309 // SKULLDUGGERY: Since the coroutine doesn't have its own stack, we can't 310 // resume it to start it like it normally would, it will just context switch 311 // back to here. Instead directly call the main since we already are on the 308 // SKULLDUGGERY: Since the coroutine doesn't have its own stack, we can't 309 // resume it to start it like it normally would, it will just context switch 310 // back to here. Instead directly call the main since we already are on the 312 311 // appropriate stack. 313 312 proc_cor_storage.__cor.state = Active; … … 316 315 317 316 // Main routine of the core returned, the core is now fully terminated 318 LIB_DEBUG_PRINT_SAFE("Kernel : core %p main ended (%p)\n", proc, proc->runner); 317 LIB_DEBUG_PRINT_SAFE("Kernel : core %p main ended (%p)\n", proc, proc->runner); 319 318 320 319 return NULL; … … 323 322 void start(processor * this) { 324 323 LIB_DEBUG_PRINT_SAFE("Kernel : Starting core %p\n", this); 325 324 326 325 pthread_create( &this->kernel_thread, NULL, CtxInvokeProcessor, (void*)this ); 327 326 328 LIB_DEBUG_PRINT_SAFE("Kernel : core %p started\n", this); 327 LIB_DEBUG_PRINT_SAFE("Kernel : core %p started\n", this); 329 328 } 330 329 … … 332 331 // Scheduler routines 333 332 void ScheduleThread( thread_desc * thrd ) { 334 // if( !thrd ) return; 335 assert( thrd ); 336 assert( thrd->cor.state != Halted ); 337 338 verify( disable_preempt_count > 0 ); 333 if( !thrd ) return; 339 334 340 335 verifyf( thrd->next == NULL, "Expected null got %p", thrd->next ); 341 342 lock( &systemProcessor->proc.cltr->lock DEBUG_CTX2);336 337 lock( &systemProcessor->proc.cltr->lock ); 343 338 append( &systemProcessor->proc.cltr->ready_queue, thrd ); 344 339 unlock( &systemProcessor->proc.cltr->lock ); 345 346 verify( disable_preempt_count > 0 );347 340 } 348 341 349 342 thread_desc * nextThread(cluster * this) { 350 verify( disable_preempt_count > 0 ); 351 lock( &this->lock DEBUG_CTX2 ); 343 lock( &this->lock ); 352 344 thread_desc * head = pop_head( &this->ready_queue ); 353 345 unlock( &this->lock ); 354 verify( disable_preempt_count > 0 );355 346 return head; 356 347 } 357 348 358 void BlockInternal() { 359 disable_interrupts(); 360 verify( disable_preempt_count > 0 ); 349 void ScheduleInternal() { 361 350 suspend(); 362 verify( disable_preempt_count > 0 ); 363 enable_interrupts( DEBUG_CTX ); 364 } 365 366 void BlockInternal( spinlock * lock ) { 367 disable_interrupts(); 351 } 352 353 void ScheduleInternal( spinlock * lock ) { 368 354 this_processor->finish.action_code = Release; 369 355 this_processor->finish.lock = lock; 370 371 verify( disable_preempt_count > 0 );372 356 suspend(); 373 verify( disable_preempt_count > 0 ); 374 375 enable_interrupts( DEBUG_CTX ); 376 } 377 378 void BlockInternal( thread_desc * thrd ) { 379 disable_interrupts(); 380 assert( thrd->cor.state != Halted ); 357 } 358 359 void ScheduleInternal( thread_desc * thrd ) { 381 360 this_processor->finish.action_code = Schedule; 382 361 this_processor->finish.thrd = thrd; 383 384 verify( disable_preempt_count > 0 );385 362 suspend(); 386 verify( disable_preempt_count > 0 ); 387 388 enable_interrupts( DEBUG_CTX ); 389 } 390 391 void BlockInternal( spinlock * lock, thread_desc * thrd ) { 392 disable_interrupts(); 363 } 364 365 void ScheduleInternal( spinlock * lock, thread_desc * thrd ) { 393 366 this_processor->finish.action_code = Release_Schedule; 394 367 this_processor->finish.lock = lock; 395 368 this_processor->finish.thrd = thrd; 396 397 verify( disable_preempt_count > 0 );398 369 suspend(); 399 verify( disable_preempt_count > 0 ); 400 401 enable_interrupts( DEBUG_CTX ); 402 } 403 404 void BlockInternal(spinlock ** locks, unsigned short count) { 405 disable_interrupts(); 370 } 371 372 void ScheduleInternal(spinlock ** locks, unsigned short count) { 406 373 this_processor->finish.action_code = Release_Multi; 407 374 this_processor->finish.locks = locks; 408 375 this_processor->finish.lock_count = count; 409 410 verify( disable_preempt_count > 0 );411 376 suspend(); 412 verify( disable_preempt_count > 0 ); 413 414 enable_interrupts( DEBUG_CTX ); 415 } 416 417 void BlockInternal(spinlock ** locks, unsigned short lock_count, thread_desc ** thrds, unsigned short thrd_count) { 418 disable_interrupts(); 377 } 378 379 void ScheduleInternal(spinlock ** locks, unsigned short lock_count, thread_desc ** thrds, unsigned short thrd_count) { 419 380 this_processor->finish.action_code = Release_Multi_Schedule; 420 381 this_processor->finish.locks = locks; … … 422 383 this_processor->finish.thrds = thrds; 423 384 this_processor->finish.thrd_count = thrd_count; 424 425 verify( disable_preempt_count > 0 );426 385 suspend(); 427 verify( disable_preempt_count > 0 );428 429 enable_interrupts( DEBUG_CTX );430 386 } 431 387 … … 436 392 // Kernel boot procedures 437 393 void kernel_startup(void) { 438 LIB_DEBUG_PRINT_SAFE("Kernel : Starting\n"); 394 LIB_DEBUG_PRINT_SAFE("Kernel : Starting\n"); 439 395 440 396 // Start by initializing the main thread 441 // SKULLDUGGERY: the mainThread steals the process main thread 397 // SKULLDUGGERY: the mainThread steals the process main thread 442 398 // which will then be scheduled by the systemProcessor normally 443 399 mainThread = (thread_desc *)&mainThread_storage; … … 447 403 LIB_DEBUG_PRINT_SAFE("Kernel : Main thread ready\n"); 448 404 405 // Enable preemption 406 kernel_start_preemption(); 407 449 408 // Initialize the system cluster 450 409 systemCluster = (cluster *)&systemCluster_storage; … … 458 417 systemProcessor{ systemCluster, (processorCtx_t *)&systemProcessorCtx_storage }; 459 418 460 // Add the main thread to the ready queue 419 // Add the main thread to the ready queue 461 420 // once resume is called on systemProcessor->runner the mainThread needs to be scheduled like any normal thread 462 421 ScheduleThread(mainThread); … … 464 423 //initialize the global state variables 465 424 this_processor = &systemProcessor->proc; 466 this_thread = mainThread; 467 this_coroutine = &mainThread->cor; 468 disable_preempt_count = 1; 469 470 // Enable preemption 471 kernel_start_preemption(); 425 this_processor->current_thread = mainThread; 426 this_processor->current_coroutine = &mainThread->cor; 472 427 473 428 // SKULLDUGGERY: Force a context switch to the system processor to set the main thread's context to the current UNIX 474 429 // context. Hence, the main thread does not begin through CtxInvokeThread, like all other threads. The trick here is that 475 // mainThread is on the ready queue when this call is made. 430 // mainThread is on the ready queue when this call is made. 476 431 resume( systemProcessor->proc.runner ); 477 432 … … 480 435 // THE SYSTEM IS NOW COMPLETELY RUNNING 481 436 LIB_DEBUG_PRINT_SAFE("Kernel : Started\n--------------------------------------------------\n\n"); 482 483 enable_interrupts( DEBUG_CTX );484 437 } 485 438 486 439 void kernel_shutdown(void) { 487 440 LIB_DEBUG_PRINT_SAFE("\n--------------------------------------------------\nKernel : Shutting down\n"); 488 489 disable_interrupts();490 441 491 442 // SKULLDUGGERY: Notify the systemProcessor it needs to terminates. … … 497 448 // THE SYSTEM IS NOW COMPLETELY STOPPED 498 449 499 // Disable preemption500 kernel_stop_preemption();501 502 450 // Destroy the system processor and its context in reverse order of construction 503 451 // These were manually constructed so we need manually destroy them … … 509 457 ^(mainThread){}; 510 458 511 LIB_DEBUG_PRINT_SAFE("Kernel : Shutdown complete\n"); 459 LIB_DEBUG_PRINT_SAFE("Kernel : Shutdown complete\n"); 512 460 } 513 461 … … 519 467 // abort cannot be recursively entered by the same or different processors because all signal handlers return when 520 468 // the globalAbort flag is true. 521 lock( &kernel_abort_lock DEBUG_CTX2);469 lock( &kernel_abort_lock ); 522 470 523 471 // first task to abort ? … … 525 473 kernel_abort_called = true; 526 474 unlock( &kernel_abort_lock ); 527 } 475 } 528 476 else { 529 477 unlock( &kernel_abort_lock ); 530 478 531 479 sigset_t mask; 532 480 sigemptyset( &mask ); … … 534 482 sigaddset( &mask, SIGUSR1 ); // block SIGUSR1 signals 535 483 sigsuspend( &mask ); // block the processor to prevent further damage during abort 536 _exit( EXIT_FAILURE ); // if processor unblocks before it is killed, terminate it 537 } 538 539 return this_thread ;484 _exit( EXIT_FAILURE ); // if processor unblocks before it is killed, terminate it 485 } 486 487 return this_thread(); 540 488 } 541 489 … … 546 494 __lib_debug_write( STDERR_FILENO, abort_text, len ); 547 495 548 if ( thrd != this_coroutine ) {549 len = snprintf( abort_text, abort_text_size, " in coroutine %.256s (%p).\n", this_coroutine ->name, this_coroutine);496 if ( thrd != this_coroutine() ) { 497 len = snprintf( abort_text, abort_text_size, " in coroutine %.256s (%p).\n", this_coroutine()->name, this_coroutine() ); 550 498 __lib_debug_write( STDERR_FILENO, abort_text, len ); 551 } 499 } 552 500 else { 553 501 __lib_debug_write( STDERR_FILENO, ".\n", 2 ); … … 557 505 extern "C" { 558 506 void __lib_debug_acquire() { 559 lock( &kernel_debug_lock DEBUG_CTX2);507 lock(&kernel_debug_lock); 560 508 } 561 509 562 510 void __lib_debug_release() { 563 unlock( &kernel_debug_lock);511 unlock(&kernel_debug_lock); 564 512 } 565 513 } … … 577 525 } 578 526 579 bool try_lock( spinlock * this DEBUG_CTX_PARAM2) {527 bool try_lock( spinlock * this ) { 580 528 return this->lock == 0 && __sync_lock_test_and_set_4( &this->lock, 1 ) == 0; 581 529 } 582 530 583 void lock( spinlock * this DEBUG_CTX_PARAM2) {531 void lock( spinlock * this ) { 584 532 for ( unsigned int i = 1;; i += 1 ) { 585 if ( this->lock == 0 && __sync_lock_test_and_set_4( &this->lock, 1 ) == 0 ) { break; } 586 } 587 LIB_DEBUG_DO( 588 this->prev_name = caller; 589 this->prev_thrd = this_thread; 590 ) 591 } 592 593 void lock_yield( spinlock * this DEBUG_CTX_PARAM2 ) { 594 for ( unsigned int i = 1;; i += 1 ) { 595 if ( this->lock == 0 && __sync_lock_test_and_set_4( &this->lock, 1 ) == 0 ) { break; } 596 yield(); 597 } 598 LIB_DEBUG_DO( 599 this->prev_name = caller; 600 this->prev_thrd = this_thread; 601 ) 602 } 603 533 if ( this->lock == 0 && __sync_lock_test_and_set_4( &this->lock, 1 ) == 0 ) break; 534 } 535 } 604 536 605 537 void unlock( spinlock * this ) { … … 615 547 616 548 void wait( signal_once * this ) { 617 lock( &this->lock DEBUG_CTX2);549 lock( &this->lock ); 618 550 if( !this->cond ) { 619 append( &this->blocked, (thread_desc*)this_thread ); 620 BlockInternal( &this->lock ); 621 } 622 else { 623 unlock( &this->lock ); 624 } 551 append( &this->blocked, this_thread() ); 552 ScheduleInternal( &this->lock ); 553 lock( &this->lock ); 554 } 555 unlock( &this->lock ); 625 556 } 626 557 627 558 void signal( signal_once * this ) { 628 lock( &this->lock DEBUG_CTX2);559 lock( &this->lock ); 629 560 { 630 561 this->cond = true; 631 562 632 disable_interrupts();633 563 thread_desc * it; 634 564 while( it = pop_head( &this->blocked) ) { 635 565 ScheduleThread( it ); 636 566 } 637 enable_interrupts( DEBUG_CTX );638 567 } 639 568 unlock( &this->lock ); … … 661 590 } 662 591 head->next = NULL; 663 } 592 } 664 593 return head; 665 594 } … … 680 609 this->top = top->next; 681 610 top->next = NULL; 682 } 611 } 683 612 return top; 684 613 } -
src/libcfa/concurrency/kernel_private.h
r0614d14 r1ce2189 18 18 #define KERNEL_PRIVATE_H 19 19 20 #include "libhdr.h"21 22 20 #include "kernel" 23 21 #include "thread" … … 25 23 #include "alarm.h" 26 24 25 #include "libhdr.h" 27 26 28 27 //----------------------------------------------------------------------------- 29 28 // Scheduler 30 31 extern "C" {32 void disable_interrupts();33 void enable_interrupts_noRF();34 void enable_interrupts( DEBUG_CTX_PARAM );35 }36 37 29 void ScheduleThread( thread_desc * ); 38 static inline void WakeThread( thread_desc * thrd ) {39 if( !thrd ) return;40 41 disable_interrupts();42 ScheduleThread( thrd );43 enable_interrupts( DEBUG_CTX );44 }45 30 thread_desc * nextThread(cluster * this); 46 31 47 void BlockInternal(void);48 void BlockInternal(spinlock * lock);49 void BlockInternal(thread_desc * thrd);50 void BlockInternal(spinlock * lock, thread_desc * thrd);51 void BlockInternal(spinlock ** locks, unsigned short count);52 void BlockInternal(spinlock ** locks, unsigned short count, thread_desc ** thrds, unsigned short thrd_count);32 void ScheduleInternal(void); 33 void ScheduleInternal(spinlock * lock); 34 void ScheduleInternal(thread_desc * thrd); 35 void ScheduleInternal(spinlock * lock, thread_desc * thrd); 36 void ScheduleInternal(spinlock ** locks, unsigned short count); 37 void ScheduleInternal(spinlock ** locks, unsigned short count, thread_desc ** thrds, unsigned short thrd_count); 53 38 54 39 //----------------------------------------------------------------------------- … … 75 60 extern cluster * systemCluster; 76 61 extern system_proc_t * systemProcessor; 77 extern volatile thread_local processor * this_processor; 78 extern volatile thread_local coroutine_desc * this_coroutine; 79 extern volatile thread_local thread_desc * this_thread; 80 extern volatile thread_local unsigned short disable_preempt_count; 62 extern thread_local processor * this_processor; 63 64 static inline void disable_interrupts() { 65 __attribute__((unused)) unsigned short prev = __atomic_fetch_add_2( &this_processor->disable_preempt_count, 1, __ATOMIC_SEQ_CST ); 66 assert( prev != (unsigned short) -1 ); 67 } 68 69 static inline void enable_interrupts_noRF() { 70 __attribute__((unused)) unsigned short prev = __atomic_fetch_add_2( &this_processor->disable_preempt_count, -1, __ATOMIC_SEQ_CST ); 71 verify( prev != (unsigned short) 0 ); 72 } 73 74 static inline void enable_interrupts() { 75 __attribute__((unused)) unsigned short prev = __atomic_fetch_add_2( &this_processor->disable_preempt_count, -1, __ATOMIC_SEQ_CST ); 76 verify( prev != (unsigned short) 0 ); 77 if( prev == 1 && this_processor->pending_preemption ) { 78 ScheduleInternal( this_processor->current_thread ); 79 this_processor->pending_preemption = false; 80 } 81 } 81 82 82 83 //----------------------------------------------------------------------------- -
src/libcfa/concurrency/monitor
r0614d14 r1ce2189 26 26 static inline void ?{}(monitor_desc * this) { 27 27 this->owner = NULL; 28 this->stack_owner = NULL; 28 29 this->recursion = 0; 29 30 } -
src/libcfa/concurrency/monitor.c
r0614d14 r1ce2189 19 19 #include <stdlib> 20 20 21 #include "kernel_private.h" 21 22 #include "libhdr.h" 22 #include "kernel_private.h"23 23 24 24 //----------------------------------------------------------------------------- … … 44 44 45 45 extern "C" { 46 void __enter_monitor_desc( monitor_desc * this) {47 lock _yield( &this->lock DEBUG_CTX2);48 thread_desc * thrd = this_thread ;49 50 //LIB_DEBUG_PRINT_SAFE("%p Entering %p (o: %p, r: %i)\n", thrd, this, this->owner, this->recursion);46 void __enter_monitor_desc(monitor_desc * this) { 47 lock( &this->lock ); 48 thread_desc * thrd = this_thread(); 49 50 LIB_DEBUG_PRINT_SAFE("%p Entering %p (o: %p, r: %i)\n", thrd, this, this->owner, this->recursion); 51 51 52 52 if( !this->owner ) { … … 62 62 //Some one else has the monitor, wait in line for it 63 63 append( &this->entry_queue, thrd ); 64 //LIB_DEBUG_PRINT_SAFE("%p Blocking on entry\n", thrd);65 BlockInternal( &this->lock );66 67 // BlockInternal will unlock spinlock, no need to unlock ourselves68 return; 64 LIB_DEBUG_PRINT_SAFE("%p Blocking on entry\n", thrd); 65 ScheduleInternal( &this->lock ); 66 67 //ScheduleInternal will unlock spinlock, no need to unlock ourselves 68 return; 69 69 } 70 70 … … 75 75 // leave pseudo code : 76 76 // TODO 77 void __leave_monitor_desc( monitor_desc * this) {78 lock _yield( &this->lock DEBUG_CTX2);79 80 // LIB_DEBUG_PRINT_SAFE("%p Leaving %p (o: %p, r: %i). ", this_thread, this, this->owner, this->recursion);81 verifyf( this_thread == this->owner, "Expected owner to be %p, got %p (r: %i)", this_thread, this->owner, this->recursion );77 void __leave_monitor_desc(monitor_desc * this) { 78 lock( &this->lock ); 79 80 LIB_DEBUG_PRINT_SAFE("%p Leaving %p (o: %p, r: %i)\n", thrd, this, this->owner, this->recursion); 81 verifyf( this_thread() == this->owner, "Expected owner to be %p, got %p (r: %i)", this_thread(), this->owner, this->recursion ); 82 82 83 83 //Leaving a recursion level, decrement the counter … … 96 96 unlock( &this->lock ); 97 97 98 //LIB_DEBUG_PRINT_SAFE("Next owner is %p\n", new_owner);98 LIB_DEBUG_PRINT_SAFE("Next owner is %p\n", new_owner); 99 99 100 100 //We need to wake-up the thread 101 WakeThread( new_owner ); 102 } 103 104 void __leave_thread_monitor( thread_desc * thrd ) { 105 monitor_desc * this = &thrd->mon; 106 lock_yield( &this->lock DEBUG_CTX2 ); 107 108 disable_interrupts(); 109 110 thrd->cor.state = Halted; 111 112 verifyf( thrd == this->owner, "Expected owner to be %p, got %p (r: %i)", thrd, this->owner, this->recursion ); 113 114 //Leaving a recursion level, decrement the counter 115 this->recursion -= 1; 116 117 //If we haven't left the last level of recursion 118 //it means we don't need to do anything 119 if( this->recursion != 0) { 120 unlock( &this->lock ); 121 return; 122 } 123 124 thread_desc * new_owner = next_thread( this ); 125 126 //We can now let other threads in safely 127 unlock( &this->lock ); 128 129 //We need to wake-up the thread 130 if( new_owner) ScheduleThread( new_owner ); 101 ScheduleThread( new_owner ); 131 102 } 132 103 } … … 150 121 enter( this->m, this->count ); 151 122 152 this->prev_mntrs = this_thread ->current_monitors;153 this->prev_count = this_thread ->current_monitor_count;154 155 this_thread ->current_monitors = m;156 this_thread ->current_monitor_count = count;123 this->prev_mntrs = this_thread()->current_monitors; 124 this->prev_count = this_thread()->current_monitor_count; 125 126 this_thread()->current_monitors = m; 127 this_thread()->current_monitor_count = count; 157 128 } 158 129 … … 160 131 leave( this->m, this->count ); 161 132 162 this_thread ->current_monitors = this->prev_mntrs;163 this_thread ->current_monitor_count = this->prev_count;133 this_thread()->current_monitors = this->prev_mntrs; 134 this_thread()->current_monitor_count = this->prev_count; 164 135 } 165 136 … … 188 159 // Internal scheduling 189 160 void wait( condition * this, uintptr_t user_info = 0 ) { 190 //LIB_DEBUG_PRINT_SAFE("Waiting\n");161 LIB_DEBUG_PRINT_SAFE("Waiting\n"); 191 162 192 163 brand_condition( this ); … … 199 170 unsigned short count = this->monitor_count; 200 171 unsigned int recursions[ count ]; //Save the current recursion levels to restore them later 201 spinlock * locks [ count ]; //We need to pass-in an array of locks to BlockInternal202 203 //LIB_DEBUG_PRINT_SAFE("count %i\n", count);204 205 __condition_node_t waiter = { (thread_desc*)this_thread, count, user_info };172 spinlock * locks [ count ]; //We need to pass-in an array of locks to ScheduleInternal 173 174 LIB_DEBUG_PRINT_SAFE("count %i\n", count); 175 176 __condition_node_t waiter = { this_thread(), count, user_info }; 206 177 207 178 __condition_criterion_t criteria[count]; 208 179 for(int i = 0; i < count; i++) { 209 180 (&criteria[i]){ this->monitors[i], &waiter }; 210 //LIB_DEBUG_PRINT_SAFE( "Criterion %p\n", &criteria[i] );181 LIB_DEBUG_PRINT_SAFE( "Criterion %p\n", &criteria[i] ); 211 182 } 212 183 … … 230 201 } 231 202 232 //LIB_DEBUG_PRINT_SAFE("Will unblock: ");203 LIB_DEBUG_PRINT_SAFE("Will unblock: "); 233 204 for(int i = 0; i < thread_count; i++) { 234 //LIB_DEBUG_PRINT_SAFE("%p ", threads[i]);235 } 236 //LIB_DEBUG_PRINT_SAFE("\n");205 LIB_DEBUG_PRINT_SAFE("%p ", threads[i]); 206 } 207 LIB_DEBUG_PRINT_SAFE("\n"); 237 208 238 209 // Everything is ready to go to sleep 239 BlockInternal( locks, count, threads, thread_count );210 ScheduleInternal( locks, count, threads, thread_count ); 240 211 241 212 … … 251 222 bool signal( condition * this ) { 252 223 if( is_empty( this ) ) { 253 //LIB_DEBUG_PRINT_SAFE("Nothing to signal\n");224 LIB_DEBUG_PRINT_SAFE("Nothing to signal\n"); 254 225 return false; 255 226 } … … 260 231 261 232 unsigned short count = this->monitor_count; 262 233 263 234 //Some more checking in debug 264 235 LIB_DEBUG_DO( 265 thread_desc * this_thrd = this_thread ;236 thread_desc * this_thrd = this_thread(); 266 237 if ( this->monitor_count != this_thrd->current_monitor_count ) { 267 238 abortf( "Signal on condition %p made with different number of monitor(s), expected %i got %i", this, this->monitor_count, this_thrd->current_monitor_count ); … … 277 248 //Lock all the monitors 278 249 lock_all( this->monitors, NULL, count ); 279 //LIB_DEBUG_PRINT_SAFE("Signalling");250 LIB_DEBUG_PRINT_SAFE("Signalling"); 280 251 281 252 //Pop the head of the waiting queue … … 285 256 for(int i = 0; i < count; i++) { 286 257 __condition_criterion_t * crit = &node->criteria[i]; 287 //LIB_DEBUG_PRINT_SAFE(" %p", crit->target);258 LIB_DEBUG_PRINT_SAFE(" %p", crit->target); 288 259 assert( !crit->ready ); 289 260 push( &crit->target->signal_stack, crit ); 290 261 } 291 262 292 //LIB_DEBUG_PRINT_SAFE("\n");263 LIB_DEBUG_PRINT_SAFE("\n"); 293 264 294 265 //Release … … 310 281 unsigned short count = this->monitor_count; 311 282 unsigned int recursions[ count ]; //Save the current recursion levels to restore them later 312 spinlock * locks [ count ]; //We need to pass-in an array of locks to BlockInternal283 spinlock * locks [ count ]; //We need to pass-in an array of locks to ScheduleInternal 313 284 314 285 lock_all( this->monitors, locks, count ); 315 286 316 287 //create creteria 317 __condition_node_t waiter = { (thread_desc*)this_thread, count, 0 };288 __condition_node_t waiter = { this_thread(), count, 0 }; 318 289 319 290 __condition_criterion_t criteria[count]; 320 291 for(int i = 0; i < count; i++) { 321 292 (&criteria[i]){ this->monitors[i], &waiter }; 322 //LIB_DEBUG_PRINT_SAFE( "Criterion %p\n", &criteria[i] );293 LIB_DEBUG_PRINT_SAFE( "Criterion %p\n", &criteria[i] ); 323 294 push( &criteria[i].target->signal_stack, &criteria[i] ); 324 295 } … … 338 309 339 310 //Everything is ready to go to sleep 340 BlockInternal( locks, count, &signallee, 1 );311 ScheduleInternal( locks, count, &signallee, 1 ); 341 312 342 313 … … 354 325 355 326 uintptr_t front( condition * this ) { 356 verifyf( !is_empty(this), 327 verifyf( !is_empty(this), 357 328 "Attempt to access user data on an empty condition.\n" 358 329 "Possible cause is not checking if the condition is empty before reading stored data." … … 364 335 // Internal scheduling 365 336 void __accept_internal( unsigned short count, __acceptable_t * acceptables, void (*func)(void) ) { 366 // thread_desc * this = this_thread ;337 // thread_desc * this = this_thread(); 367 338 368 339 // unsigned short count = this->current_monitor_count; 369 340 // unsigned int recursions[ count ]; //Save the current recursion levels to restore them later 370 // spinlock * locks [ count ]; //We need to pass-in an array of locks to BlockInternal341 // spinlock * locks [ count ]; //We need to pass-in an array of locks to ScheduleInternal 371 342 372 343 // lock_all( this->current_monitors, locks, count ); … … 377 348 378 349 // // // Everything is ready to go to sleep 379 // // BlockInternal( locks, count, threads, thread_count );350 // // ScheduleInternal( locks, count, threads, thread_count ); 380 351 381 352 … … 422 393 static inline void lock_all( spinlock ** locks, unsigned short count ) { 423 394 for( int i = 0; i < count; i++ ) { 424 lock _yield( locks[i] DEBUG_CTX2);395 lock( locks[i] ); 425 396 } 426 397 } … … 429 400 for( int i = 0; i < count; i++ ) { 430 401 spinlock * l = &source[i]->lock; 431 lock _yield( l DEBUG_CTX2);402 lock( l ); 432 403 if(locks) locks[i] = l; 433 404 } … … 472 443 for( int i = 0; i < count; i++ ) { 473 444 474 //LIB_DEBUG_PRINT_SAFE( "Checking %p for %p\n", &criteria[i], target );445 LIB_DEBUG_PRINT_SAFE( "Checking %p for %p\n", &criteria[i], target ); 475 446 if( &criteria[i] == target ) { 476 447 criteria[i].ready = true; 477 //LIB_DEBUG_PRINT_SAFE( "True\n" );448 LIB_DEBUG_PRINT_SAFE( "True\n" ); 478 449 } 479 450 … … 481 452 } 482 453 483 //LIB_DEBUG_PRINT_SAFE( "Runing %i\n", ready2run );454 LIB_DEBUG_PRINT_SAFE( "Runing %i\n", ready2run ); 484 455 return ready2run ? node->waiting_thread : NULL; 485 456 } 486 457 487 458 static inline void brand_condition( condition * this ) { 488 thread_desc * thrd = this_thread ;459 thread_desc * thrd = this_thread(); 489 460 if( !this->monitors ) { 490 //LIB_DEBUG_PRINT_SAFE("Branding\n");461 LIB_DEBUG_PRINT_SAFE("Branding\n"); 491 462 assertf( thrd->current_monitors != NULL, "No current monitor to brand condition", thrd->current_monitors ); 492 463 this->monitor_count = thrd->current_monitor_count; -
src/libcfa/concurrency/preemption.c
r0614d14 r1ce2189 15 15 // 16 16 17 #include "libhdr.h"18 17 #include "preemption.h" 19 18 20 19 extern "C" { 21 #include <errno.h>22 #include <execinfo.h>23 #define __USE_GNU24 20 #include <signal.h> 25 #undef __USE_GNU26 #include <stdio.h>27 #include <string.h>28 #include <unistd.h>29 21 } 30 22 31 32 #ifdef __USE_STREAM__ 33 #include "fstream" 34 #endif 35 36 #define __CFA_DEFAULT_PREEMPTION__ 10000 23 #define __CFA_DEFAULT_PREEMPTION__ 10 37 24 38 25 __attribute__((weak)) unsigned int default_preemption() { … … 40 27 } 41 28 42 #define __CFA_SIGCXT__ ucontext_t *43 #define __CFA_SIGPARMS__ __attribute__((unused)) int sig, __attribute__((unused)) siginfo_t *sfp, __attribute__((unused)) __CFA_SIGCXT__ cxt44 45 29 static void preempt( processor * this ); 46 30 static void timeout( thread_desc * this ); 47 48 void sigHandler_ctxSwitch( __CFA_SIGPARMS__ );49 void sigHandler_alarm ( __CFA_SIGPARMS__ );50 void sigHandler_segv ( __CFA_SIGPARMS__ );51 void sigHandler_abort ( __CFA_SIGPARMS__ );52 53 static void __kernel_sigaction( int sig, void (*handler)(__CFA_SIGPARMS__), int flags );54 LIB_DEBUG_DO( bool validate( alarm_list_t * this ); )55 56 #ifdef __x86_64__57 #define CFA_REG_IP REG_RIP58 #else59 #define CFA_REG_IP REG_EIP60 #endif61 62 31 63 32 //============================================================================================= … … 65 34 //============================================================================================= 66 35 36 void kernel_start_preemption() { 37 38 } 39 67 40 void tick_preemption() { 68 // LIB_DEBUG_PRINT_BUFFER_DECL( STDERR_FILENO, "Ticking preemption\n" );69 70 41 alarm_list_t * alarms = &systemProcessor->alarms; 71 42 __cfa_time_t currtime = __kernel_get_time(); 72 43 while( alarms->head && alarms->head->alarm < currtime ) { 73 44 alarm_node_t * node = pop(alarms); 74 // LIB_DEBUG_PRINT_BUFFER_LOCAL( STDERR_FILENO, "Ticking %p\n", node );75 76 45 if( node->kernel_alarm ) { 77 46 preempt( node->proc ); … … 81 50 } 82 51 83 verify( validate( alarms ) );84 85 52 if( node->period > 0 ) { 86 node->alarm = currtime +node->period;53 node->alarm += node->period; 87 54 insert( alarms, node ); 88 55 } … … 95 62 __kernel_set_timer( alarms->head->alarm - currtime ); 96 63 } 97 98 verify( validate( alarms ) );99 // LIB_DEBUG_PRINT_BUFFER_LOCAL( STDERR_FILENO, "Ticking preemption done\n" );100 64 } 101 65 102 66 void update_preemption( processor * this, __cfa_time_t duration ) { 103 LIB_DEBUG_PRINT_BUFFER_DECL( STDERR_FILENO, "Processor : %p updating preemption to %lu\n", this, duration ); 104 67 // assert( THREAD_GETMEM( disableInt ) && THREAD_GETMEM( disableIntCnt ) == 1 ); 105 68 alarm_node_t * alarm = this->preemption_alarm; 106 duration *= 1000;107 69 108 70 // Alarms need to be enabled … … 127 89 } 128 90 91 void ?{}( preemption_scope * this, processor * proc ) { 92 (&this->alarm){ proc }; 93 this->proc = proc; 94 this->proc->preemption_alarm = &this->alarm; 95 update_preemption( this->proc, this->proc->preemption ); 96 } 97 98 void ^?{}( preemption_scope * this ) { 99 update_preemption( this->proc, 0 ); 100 } 101 129 102 //============================================================================================= 130 // Kernel Signal Tools103 // Kernel Signal logic 131 104 //============================================================================================= 132 105 133 LIB_DEBUG_DO( static thread_local void * last_interrupt = 0; )134 135 extern "C" {136 void disable_interrupts() {137 __attribute__((unused)) unsigned short new_val = __atomic_add_fetch_2( &disable_preempt_count, 1, __ATOMIC_SEQ_CST );138 verify( new_val < (unsigned short)65_000 );139 verify( new_val != (unsigned short) 0 );140 }141 142 void enable_interrupts_noRF() {143 __attribute__((unused)) unsigned short prev = __atomic_fetch_add_2( &disable_preempt_count, -1, __ATOMIC_SEQ_CST );144 verify( prev != (unsigned short) 0 );145 }146 147 void enable_interrupts( DEBUG_CTX_PARAM ) {148 processor * proc = this_processor;149 thread_desc * thrd = this_thread;150 unsigned short prev = __atomic_fetch_add_2( &disable_preempt_count, -1, __ATOMIC_SEQ_CST );151 verify( prev != (unsigned short) 0 );152 if( prev == 1 && proc->pending_preemption ) {153 proc->pending_preemption = false;154 BlockInternal( thrd );155 }156 157 LIB_DEBUG_DO( proc->last_enable = caller; )158 }159 }160 161 static inline void signal_unblock( int sig ) {162 sigset_t mask;163 sigemptyset( &mask );164 sigaddset( &mask, sig );165 166 if ( pthread_sigmask( SIG_UNBLOCK, &mask, NULL ) == -1 ) {167 abortf( "internal error, pthread_sigmask" );168 }169 }170 171 static inline void signal_block( int sig ) {172 sigset_t mask;173 sigemptyset( &mask );174 sigaddset( &mask, sig );175 176 if ( pthread_sigmask( SIG_BLOCK, &mask, NULL ) == -1 ) {177 abortf( "internal error, pthread_sigmask" );178 }179 }180 181 106 static inline bool preemption_ready() { 182 return disable_preempt_count == 0;107 return this_processor->disable_preempt_count == 0; 183 108 } 184 109 … … 191 116 } 192 117 118 void sigHandler_ctxSwitch( __attribute__((unused)) int sig ) { 119 if( preemption_ready() ) { 120 ScheduleInternal( this_processor->current_thread ); 121 } 122 else { 123 defer_ctxSwitch(); 124 } 125 } 126 127 void sigHandler_alarm( __attribute__((unused)) int sig ) { 128 if( try_lock( &systemProcessor->alarm_lock ) ) { 129 tick_preemption(); 130 unlock( &systemProcessor->alarm_lock ); 131 } 132 else { 133 defer_alarm(); 134 } 135 } 136 193 137 static void preempt( processor * this ) { 194 138 pthread_kill( this->kernel_thread, SIGUSR1 ); … … 198 142 //TODO : implement waking threads 199 143 } 200 201 //=============================================================================================202 // Kernel Signal Startup/Shutdown logic203 //=============================================================================================204 205 static pthread_t alarm_thread;206 void * alarm_loop( __attribute__((unused)) void * args );207 208 void kernel_start_preemption() {209 LIB_DEBUG_PRINT_SAFE("Kernel : Starting preemption\n");210 __kernel_sigaction( SIGUSR1, sigHandler_ctxSwitch, SA_SIGINFO );211 __kernel_sigaction( SIGSEGV, sigHandler_segv , SA_SIGINFO );212 __kernel_sigaction( SIGBUS , sigHandler_segv , SA_SIGINFO );213 214 signal_block( SIGALRM );215 216 pthread_create( &alarm_thread, NULL, alarm_loop, NULL );217 }218 219 void kernel_stop_preemption() {220 sigset_t mask;221 sigfillset( &mask );222 sigprocmask( SIG_BLOCK, &mask, NULL );223 224 pthread_kill( alarm_thread, SIGINT );225 pthread_join( alarm_thread, NULL );226 LIB_DEBUG_PRINT_SAFE("Kernel : Preemption stopped\n");227 }228 229 void ?{}( preemption_scope * this, processor * proc ) {230 (&this->alarm){ proc };231 this->proc = proc;232 this->proc->preemption_alarm = &this->alarm;233 update_preemption( this->proc, this->proc->preemption );234 }235 236 void ^?{}( preemption_scope * this ) {237 disable_interrupts();238 239 update_preemption( this->proc, 0 );240 }241 242 //=============================================================================================243 // Kernel Signal Handlers244 //=============================================================================================245 246 void sigHandler_ctxSwitch( __CFA_SIGPARMS__ ) {247 LIB_DEBUG_DO( last_interrupt = (void *)(cxt->uc_mcontext.gregs[CFA_REG_IP]); )248 if( preemption_ready() ) {249 signal_unblock( SIGUSR1 );250 BlockInternal( (thread_desc*)this_thread );251 }252 else {253 defer_ctxSwitch();254 }255 }256 257 // void sigHandler_alarm( __CFA_SIGPARMS__ ) {258 // LIB_DEBUG_DO( last_interrupt = (void *)(cxt->uc_mcontext.gregs[CFA_REG_IP]); )259 // verify( this_processor == systemProcessor );260 261 // if( try_lock( &systemProcessor->alarm_lock DEBUG_CTX2 ) ) {262 // tick_preemption();263 // systemProcessor->pending_alarm = false;264 // unlock( &systemProcessor->alarm_lock );265 // }266 // else {267 // defer_alarm();268 // }269 270 // signal_unblock( SIGALRM );271 272 // if( preemption_ready() && this_processor->pending_preemption ) {273 274 // this_processor->pending_preemption = false;275 // BlockInternal( (thread_desc*)this_thread );276 // }277 // }278 279 void * alarm_loop( __attribute__((unused)) void * args ) {280 sigset_t mask;281 sigemptyset( &mask );282 sigaddset( &mask, SIGALRM );283 sigaddset( &mask, SIGUSR2 );284 sigaddset( &mask, SIGINT );285 286 if ( pthread_sigmask( SIG_BLOCK, &mask, NULL ) == -1 ) {287 abortf( "internal error, pthread_sigmask" );288 }289 290 while( true ) {291 int sig;292 if( sigwait( &mask, &sig ) != 0 ) {293 abortf( "internal error, sigwait" );294 }295 296 switch( sig) {297 case SIGALRM:298 LIB_DEBUG_PRINT_SAFE("Kernel : Preemption thread tick\n");299 lock( &systemProcessor->alarm_lock DEBUG_CTX2 );300 tick_preemption();301 unlock( &systemProcessor->alarm_lock );302 break;303 case SIGUSR2:304 //TODO other actions305 break;306 case SIGINT:307 LIB_DEBUG_PRINT_SAFE("Kernel : Preemption thread stopping\n");308 return NULL;309 default:310 abortf( "internal error, sigwait returned sig %d", sig );311 break;312 }313 }314 }315 316 static void __kernel_sigaction( int sig, void (*handler)(__CFA_SIGPARMS__), int flags ) {317 struct sigaction act;318 319 act.sa_sigaction = (void (*)(int, siginfo_t *, void *))handler;320 act.sa_flags = flags;321 322 if ( sigaction( sig, &act, NULL ) == -1 ) {323 LIB_DEBUG_PRINT_BUFFER_DECL( STDERR_FILENO,324 " __kernel_sigaction( sig:%d, handler:%p, flags:%d ), problem installing signal handler, error(%d) %s.\n",325 sig, handler, flags, errno, strerror( errno )326 );327 _exit( EXIT_FAILURE );328 }329 }330 331 typedef void (*sa_handler_t)(int);332 333 static void __kernel_sigdefault( int sig ) {334 struct sigaction act;335 336 // act.sa_handler = SIG_DFL;337 act.sa_flags = 0;338 sigemptyset( &act.sa_mask );339 340 if ( sigaction( sig, &act, NULL ) == -1 ) {341 LIB_DEBUG_PRINT_BUFFER_DECL( STDERR_FILENO,342 " __kernel_sigdefault( sig:%d ), problem reseting signal handler, error(%d) %s.\n",343 sig, errno, strerror( errno )344 );345 _exit( EXIT_FAILURE );346 }347 }348 349 //=============================================================================================350 // Terminating Signals logic351 //=============================================================================================352 353 LIB_DEBUG_DO(354 static void __kernel_backtrace( int start ) {355 // skip first N stack frames356 357 enum { Frames = 50 };358 void * array[Frames];359 int size = backtrace( array, Frames );360 char ** messages = backtrace_symbols( array, size );361 362 // find executable name363 *index( messages[0], '(' ) = '\0';364 #ifdef __USE_STREAM__365 serr | "Stack back trace for:" | messages[0] | endl;366 #else367 fprintf( stderr, "Stack back trace for: %s\n", messages[0]);368 #endif369 370 // skip last 2 stack frames after main371 for ( int i = start; i < size && messages != NULL; i += 1 ) {372 char * name = NULL;373 char * offset_begin = NULL;374 char * offset_end = NULL;375 376 for ( char *p = messages[i]; *p; ++p ) {377 // find parantheses and +offset378 if ( *p == '(' ) {379 name = p;380 }381 else if ( *p == '+' ) {382 offset_begin = p;383 }384 else if ( *p == ')' ) {385 offset_end = p;386 break;387 }388 }389 390 // if line contains symbol print it391 int frameNo = i - start;392 if ( name && offset_begin && offset_end && name < offset_begin ) {393 // delimit strings394 *name++ = '\0';395 *offset_begin++ = '\0';396 *offset_end++ = '\0';397 398 #ifdef __USE_STREAM__399 serr | "(" | frameNo | ")" | messages[i] | ":"400 | name | "+" | offset_begin | offset_end | endl;401 #else402 fprintf( stderr, "(%i) %s : %s + %s %s\n", frameNo, messages[i], name, offset_begin, offset_end);403 #endif404 }405 // otherwise, print the whole line406 else {407 #ifdef __USE_STREAM__408 serr | "(" | frameNo | ")" | messages[i] | endl;409 #else410 fprintf( stderr, "(%i) %s\n", frameNo, messages[i] );411 #endif412 }413 }414 415 free( messages );416 }417 )418 419 void sigHandler_segv( __CFA_SIGPARMS__ ) {420 LIB_DEBUG_DO(421 #ifdef __USE_STREAM__422 serr | "*CFA runtime error* program cfa-cpp terminated with"423 | (sig == SIGSEGV ? "segment fault." : "bus error.")424 | endl;425 #else426 fprintf( stderr, "*CFA runtime error* program cfa-cpp terminated with %s\n", sig == SIGSEGV ? "segment fault." : "bus error." );427 #endif428 429 // skip first 2 stack frames430 __kernel_backtrace( 1 );431 )432 exit( EXIT_FAILURE );433 }434 435 // void sigHandler_abort( __CFA_SIGPARMS__ ) {436 // // skip first 6 stack frames437 // LIB_DEBUG_DO( __kernel_backtrace( 6 ); )438 439 // // reset default signal handler440 // __kernel_sigdefault( SIGABRT );441 442 // raise( SIGABRT );443 // } -
src/libcfa/concurrency/thread
r0614d14 r1ce2189 54 54 } 55 55 56 extern volatile thread_local thread_desc * this_thread;56 thread_desc * this_thread(void); 57 57 58 58 forall( dtype T | is_thread(T) ) -
src/libcfa/concurrency/thread.c
r0614d14 r1ce2189 28 28 } 29 29 30 extern volatilethread_local processor * this_processor;30 extern thread_local processor * this_processor; 31 31 32 32 //----------------------------------------------------------------------------- … … 71 71 coroutine_desc* thrd_c = get_coroutine(this); 72 72 thread_desc* thrd_h = get_thread (this); 73 thrd_c->last = this_coroutine; 73 thrd_c->last = this_coroutine(); 74 this_processor->current_coroutine = thrd_c; 74 75 75 //LIB_DEBUG_PRINT_SAFE("Thread start : %p (t %p, c %p)\n", this, thrd_c, thrd_h);76 LIB_DEBUG_PRINT_SAFE("Thread start : %p (t %p, c %p)\n", this, thrd_c, thrd_h); 76 77 77 disable_interrupts();78 78 create_stack(&thrd_c->stack, thrd_c->stack.size); 79 this_coroutine = thrd_c;80 79 CtxStart(this, CtxInvokeThread); 81 assert( thrd_c->last->stack.context );82 80 CtxSwitch( thrd_c->last->stack.context, thrd_c->stack.context ); 83 81 84 82 ScheduleThread(thrd_h); 85 enable_interrupts( DEBUG_CTX );86 83 } 87 84 88 85 void yield( void ) { 89 BlockInternal( (thread_desc *)this_thread );86 ScheduleInternal( this_processor->current_thread ); 90 87 } 91 88 … … 98 95 void ThreadCtxSwitch(coroutine_desc* src, coroutine_desc* dst) { 99 96 // set state of current coroutine to inactive 100 src->state = src->state == Halted ? Halted :Inactive;97 src->state = Inactive; 101 98 dst->state = Active; 102 99 … … 106 103 // set new coroutine that the processor is executing 107 104 // and context switch to it 108 this_coroutine = dst; 109 assert( src->stack.context ); 105 this_processor->current_coroutine = dst; 110 106 CtxSwitch( src->stack.context, dst->stack.context ); 111 this_ coroutine = src;107 this_processor->current_coroutine = src; 112 108 113 109 // set state of new coroutine to active 114 dst->state = dst->state == Halted ? Halted :Inactive;110 dst->state = Inactive; 115 111 src->state = Active; 116 112 } -
src/libcfa/libhdr/libalign.h
r0614d14 r1ce2189 1 // -*- Mode: C++ -*- 1 // -*- Mode: C++ -*- 2 2 // 3 3 // Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo … … 18 18 // Free Software Foundation; either version 2.1 of the License, or (at your 19 19 // option) any later version. 20 // 20 // 21 21 // This library is distributed in the hope that it will be useful, but WITHOUT 22 22 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 23 23 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 24 24 // for more details. 25 // 25 // 26 26 // You should have received a copy of the GNU Lesser General Public License 27 27 // along with this library. 28 // 28 // 29 29 30 30 … … 33 33 34 34 #include "assert" 35 #include <stdbool.h>36 35 37 // Minimum size used to align memory boundaries for memory allocations. 36 // Minimum size used to align memory boundaries for memory allocations. 38 37 #define libAlign() (sizeof(double)) 39 38 -
src/libcfa/libhdr/libdebug.h
r0614d14 r1ce2189 18 18 19 19 #ifdef __CFA_DEBUG__ 20 #define LIB_DEBUG_DO(...) __VA_ARGS__ 21 #define LIB_NO_DEBUG_DO(...) 22 #define DEBUG_CTX __PRETTY_FUNCTION__ 23 #define DEBUG_CTX2 , __PRETTY_FUNCTION__ 24 #define DEBUG_CTX_PARAM const char * caller 25 #define DEBUG_CTX_PARAM2 , const char * caller 20 #define LIB_DEBUG_DO(x) x 21 #define LIB_NO_DEBUG_DO(x) ((void)0) 26 22 #else 27 #define LIB_DEBUG_DO(...) 28 #define LIB_NO_DEBUG_DO(...) __VA_ARGS__ 29 #define DEBUG_CTX 30 #define DEBUG_CTX2 31 #define DEBUG_CTX_PARAM 32 #define DEBUG_CTX_PARAM2 23 #define LIB_DEBUG_DO(x) ((void)0) 24 #define LIB_NO_DEBUG_DO(x) x 33 25 #endif 34 26 … … 59 51 60 52 #ifdef __CFA_DEBUG_PRINT__ 61 #define LIB_DEBUG_WRITE( fd, buffer, len ) __lib_debug_write( fd, buffer, len ) 62 #define LIB_DEBUG_ACQUIRE() __lib_debug_acquire() 63 #define LIB_DEBUG_RELEASE() __lib_debug_release() 64 #define LIB_DEBUG_PRINT_SAFE(...) __lib_debug_print_safe (__VA_ARGS__) 65 #define LIB_DEBUG_PRINT_NOLOCK(...) __lib_debug_print_nolock (__VA_ARGS__) 66 #define LIB_DEBUG_PRINT_BUFFER(...) __lib_debug_print_buffer (__VA_ARGS__) 67 #define LIB_DEBUG_PRINT_BUFFER_DECL(fd, ...) char text[256]; int len = snprintf( text, 256, __VA_ARGS__ ); __lib_debug_write( fd, text, len ); 68 #define LIB_DEBUG_PRINT_BUFFER_LOCAL(fd, ...) len = snprintf( text, 256, __VA_ARGS__ ); __lib_debug_write( fd, text, len ); 53 #define LIB_DEBUG_WRITE( fd, buffer, len ) __lib_debug_write( fd, buffer, len ) 54 #define LIB_DEBUG_ACQUIRE() __lib_debug_acquire() 55 #define LIB_DEBUG_RELEASE() __lib_debug_release() 56 #define LIB_DEBUG_PRINT_SAFE(...) __lib_debug_print_safe (__VA_ARGS__) 57 #define LIB_DEBUG_PRINT_NOLOCK(...) __lib_debug_print_nolock (__VA_ARGS__) 58 #define LIB_DEBUG_PRINT_BUFFER(...) __lib_debug_print_buffer (__VA_ARGS__) 69 59 #else 70 #define LIB_DEBUG_WRITE(...) ((void)0) 71 #define LIB_DEBUG_ACQUIRE() ((void)0) 72 #define LIB_DEBUG_RELEASE() ((void)0) 73 #define LIB_DEBUG_PRINT_SAFE(...) ((void)0) 74 #define LIB_DEBUG_PRINT_NOLOCK(...) ((void)0) 75 #define LIB_DEBUG_PRINT_BUFFER(...) ((void)0) 76 #define LIB_DEBUG_PRINT_BUFFER_DECL(...) ((void)0) 77 #define LIB_DEBUG_PRINT_BUFFER_LOCAL(...) ((void)0) 60 #define LIB_DEBUG_WRITE(...) ((void)0) 61 #define LIB_DEBUG_ACQUIRE() ((void)0) 62 #define LIB_DEBUG_RELEASE() ((void)0) 63 #define LIB_DEBUG_PRINT_SAFE(...) ((void)0) 64 #define LIB_DEBUG_PRINT_NOLOCK(...) ((void)0) 65 #define LIB_DEBUG_PRINT_BUFFER(...) ((void)0) 78 66 #endif 79 67 -
src/tests/sched-int-block.c
r0614d14 r1ce2189 31 31 //------------------------------------------------------------------------------ 32 32 void wait_op( global_data_t * mutex a, global_data_t * mutex b, unsigned i ) { 33 wait( &cond, (uintptr_t)this_thread );33 wait( &cond, (uintptr_t)this_thread() ); 34 34 35 35 yield( ((unsigned)rand48()) % 10 ); … … 40 40 } 41 41 42 a->last_thread = b->last_thread = this_thread ;42 a->last_thread = b->last_thread = this_thread(); 43 43 44 44 yield( ((unsigned)rand48()) % 10 ); … … 56 56 yield( ((unsigned)rand48()) % 10 ); 57 57 58 a->last_thread = b->last_thread = a->last_signaller = b->last_signaller = this_thread ;58 a->last_thread = b->last_thread = a->last_signaller = b->last_signaller = this_thread(); 59 59 60 60 if( !is_empty( &cond ) ) { … … 86 86 //------------------------------------------------------------------------------ 87 87 void barge_op( global_data_t * mutex a ) { 88 a->last_thread = this_thread ;88 a->last_thread = this_thread(); 89 89 } 90 90
Note:
See TracChangeset
for help on using the changeset viewer.