]> git.kernelconcepts.de Git - karo-tx-redboot.git/blob - packages/kernel/v2_0/include/thread.inl
unified MX27, MX25, MX37 trees
[karo-tx-redboot.git] / packages / kernel / v2_0 / include / thread.inl
1 #ifndef CYGONCE_KERNEL_THREAD_INL
2 #define CYGONCE_KERNEL_THREAD_INL
3
4 //==========================================================================
5 //
6 //      thread.inl
7 //
8 //      Thread class inlines
9 //
10 //==========================================================================
11 //####ECOSGPLCOPYRIGHTBEGIN####
12 // -------------------------------------------
13 // This file is part of eCos, the Embedded Configurable Operating System.
14 // Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
15 // Copyright (C) 2003 Gary Thomas
16 //
17 // eCos is free software; you can redistribute it and/or modify it under
18 // the terms of the GNU General Public License as published by the Free
19 // Software Foundation; either version 2 or (at your option) any later version.
20 //
21 // eCos is distributed in the hope that it will be useful, but WITHOUT ANY
22 // WARRANTY; without even the implied warranty of MERCHANTABILITY or
23 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
24 // for more details.
25 //
26 // You should have received a copy of the GNU General Public License along
27 // with eCos; if not, write to the Free Software Foundation, Inc.,
28 // 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
29 //
30 // As a special exception, if other files instantiate templates or use macros
31 // or inline functions from this file, or you compile this file and link it
32 // with other works to produce a work based on this file, this file does not
33 // by itself cause the resulting work to be covered by the GNU General Public
34 // License. However the source code for this file must still be made available
35 // in accordance with section (3) of the GNU General Public License.
36 //
37 // This exception does not invalidate any other reasons why a work based on
38 // this file might be covered by the GNU General Public License.
39 //
40 // Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
41 // at http://sources.redhat.com/ecos/ecos-license/
42 // -------------------------------------------
43 //####ECOSGPLCOPYRIGHTEND####
44 //==========================================================================
45 //#####DESCRIPTIONBEGIN####
46 //
47 // Author(s):   nickg
48 // Contributors:        nickg
49 // Date:        1997-09-09
50 // Purpose:     Define inlines for thread classes
51 // Description: Inline implementations of various member functions defined
52 //              in various Thread classes. 
53 // Usage:
54 //              #include <cyg/kernel/thread.hxx>
55 //              ...
56 //              #include <cyg/kernel/thread.inl>
57 //              ...
58
59 //
60 //####DESCRIPTIONEND####
61 //
62 //==========================================================================
63
64 #include <cyg/kernel/thread.hxx>
65 #include <cyg/hal/hal_arch.h>
66
67 #include <cyg/kernel/clock.inl>
68 #include <cyg/infra/diag.h>
69
70 #ifndef CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE
71 #define CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE (0)
72 #endif
73
74 //==========================================================================
75 // Inlines for Cyg_HardwareThread
76
77 // -------------------------------------------------------------------------
78 // get the size/base of this thread's stack
79
80 inline CYG_ADDRESS
81 Cyg_HardwareThread::get_stack_base()
82 {
83     return stack_base - CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE;
84 }
85
86 inline cyg_uint32
87 Cyg_HardwareThread::get_stack_size()
88 {
89     return stack_size + 2 * CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE;
90 }
91
92 // -------------------------------------------------------------------------
93 // Check the stack bounds of this thread:
94 #ifdef CYGFUN_KERNEL_THREADS_STACK_CHECKING
95 inline void Cyg_HardwareThread::check_stack(void)
96 {
97     cyg_uint32 sig = (cyg_uint32)this;
98     cyg_uint32 *base = (cyg_uint32 *)get_stack_base();
99     cyg_uint32 *top =  (cyg_uint32 *)(stack_base + stack_size);
100     cyg_ucount32 i;
101
102     CYG_INSTRUMENT_THREAD(CHECK_STACK, base, top );
103     
104     CYG_ASSERT( 0 == ((sizeof(CYG_WORD)-1) & (cyg_uint32)base), "stack base not word aligned" );
105     CYG_ASSERT( 0 == ((sizeof(CYG_WORD)-1) & (cyg_uint32)top),  "stack  top not word aligned" );
106
107     CYG_ASSERT( (cyg_uint32)stack_ptr > (cyg_uint32)stack_base,
108                 "Stack_ptr below base" );
109     CYG_ASSERT( (cyg_uint32)stack_ptr <= ((cyg_uint32)stack_base + stack_size),
110                 "Stack_ptr above top" );
111
112     for ( i = 0;
113           i < CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE/sizeof(cyg_uint32);
114           i++ ) {
115         if ((sig ^ (i * 0x01010101)) != base[i]) {
116             char *reason = "Stack base corrupt";
117             diag_printf("%s - i: %d\n", reason, i);
118             diag_dump_buf(base, CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE);
119             CYG_FAIL(reason);
120         }
121         if ((sig ^ (i * 0x10101010)) != top[i]) {
122             char *reason = "Stack top corrupt";
123             diag_printf("%s - i: %d\n", reason, i);
124             diag_dump_buf(top, CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE);
125             CYG_FAIL(reason);
126         }
127     }            
128
129 #ifdef CYGFUN_KERNEL_THREADS_STACK_LIMIT
130     // we won't have added check data above the stack limit if it hasn't
131     // been incremented
132     if (stack_limit != stack_base) {
133         CYG_ADDRESS limit = stack_limit;
134         // the limit will be off by the check data size, so lets correct it
135         limit -= CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE;
136         
137         // determine base of check data by rounding up to nearest word aligned
138         // address if not already aligned
139         cyg_uint32 *p = (cyg_uint32 *)((limit + 3) & ~3);
140         // i.e. + sizeof(cyg_uint32)-1) & ~(sizeof(cyg_uint32)-1);
141         
142         for ( i = 0;
143               i < CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE/sizeof(cyg_uint32);
144               i++ ) {
145             if ((sig ^ (i * 0x01010101)) != p[i]) {
146                 char *reason = "Gap between stack limit and base corrupt";
147                 diag_printf("%s - i: %d\n", reason, i);
148                 diag_dump_buf(p, CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE);
149                 CYG_FAIL(reason);
150             }
151         }
152     }
153 #endif
154 }
155 #endif
156
157 // -------------------------------------------------------------------------
158 // Measure the stack usage of the thread
159 #ifdef CYGFUN_KERNEL_THREADS_STACK_MEASUREMENT
160 inline cyg_uint32 Cyg_HardwareThread::measure_stack_usage(void)
161 {
162 #ifdef CYGFUN_KERNEL_THREADS_STACK_LIMIT
163     CYG_WORD *base = (CYG_WORD *)stack_limit;
164     cyg_uint32 size = (stack_size - (stack_limit-stack_base))/sizeof(CYG_WORD);
165 #else
166     CYG_WORD *base = (CYG_WORD *)stack_base;
167     cyg_uint32 size = stack_size/sizeof(CYG_WORD);
168 #endif
169     cyg_ucount32 i;
170
171     // Work up the stack comparing with the preset value
172     // We assume the stack grows downwards, hmm...
173     for (i=0; i<size; i++) {
174         if (base[i] != 0xDEADBEEF)
175           break;
176     }
177     return (size - i)*sizeof(CYG_WORD);
178 }
179 #endif
180
181 // -------------------------------------------------------------------------
182 // Attach a stack to this thread. If there is a HAL defined macro to
183 // do this, then we use that, otherwise assume a falling stack.
184 inline void Cyg_HardwareThread::attach_stack(CYG_ADDRESS s_base, cyg_uint32 s_size)
185 {
186 #ifdef CYGNUM_HAL_STACK_SIZE_MINIMUM
187     CYG_ASSERT( s_size >= CYGNUM_HAL_STACK_SIZE_MINIMUM,
188                 "Stack size too small");
189 #endif
190
191 #ifdef CYGFUN_KERNEL_THREADS_STACK_CHECKING
192     {
193         cyg_uint32 sig = (cyg_uint32)this;
194         cyg_uint32 *base = (cyg_uint32 *)s_base;
195         cyg_uint32 *top =  (cyg_uint32 *)(s_base + s_size -
196             CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE);
197
198         unsigned int i;
199
200         CYG_INSTRUMENT_THREAD(ATTACH_STACK, base, top );
201         
202         CYG_ASSERT( NULL != base, "stack base non-NULL" );
203         CYG_ASSERT( 0 == ((sizeof(CYG_WORD)-1) & (cyg_uint32)base), "stack base alignment" );
204         CYG_ASSERT( 0 == ((sizeof(CYG_WORD)-1) & (cyg_uint32)top),  "stack  top alignment" );
205
206         for ( i = 0;
207               i < CYGNUM_KERNEL_THREADS_STACK_CHECK_DATA_SIZE/sizeof(cyg_uint32);
208               i++ ) {
209             base[i] = (sig ^ (i * 0x01010101));
210              top[i] = (sig ^ (i * 0x10101010));
211         }            
212         // This check for overlap of the two signature areas also detects
213         // wrap round zero of the size in the unsigned subtraction below.
214         CYG_ASSERT( &base[i] < &top[0], "Stack is so small size wrapped" );
215         // Use this 'i' expression to round correctly to whole words.
216         s_base += i * sizeof(cyg_uint32);
217         s_size -= i * sizeof(cyg_uint32) * 2;
218         // This is a complete guess, the 256; the point is to assert early that
219         // this might go badly wrong.  It would not detect wrap of unsigned size.
220         CYG_ASSERT( s_size >= 256,
221                     "Stack size too small after allocating checking buffer");
222     }
223 #endif
224 #ifdef CYGFUN_KERNEL_THREADS_STACK_MEASUREMENT
225     {
226         CYG_WORD *base = (CYG_WORD *)s_base;
227         cyg_uint32 size = s_size/sizeof(CYG_WORD);
228         cyg_ucount32 i;
229
230         // initialize all of stack with known value - don't choose 0
231         // could do with pseudo value as above, but this way, checking
232         // is faster
233         for (i=0; i<size; i++) {
234                 base[i] = 0xDEADBEEF;
235         }
236         // Don't bother about the case when the stack isn't a multiple of
237         // CYG_WORD in size. Since it's at the top of the stack, it will
238         // almost certainly be overwritten the instant the thread starts
239         // anyway.
240     }
241 #endif
242     stack_base = s_base;
243     stack_size = s_size;
244 #ifdef CYGFUN_KERNEL_THREADS_STACK_LIMIT
245     stack_limit = s_base;
246 #endif
247     
248 #ifdef HAL_THREAD_ATTACH_STACK
249
250     HAL_THREAD_ATTACH_STACK(stack_ptr, stack_base, stack_size);
251     
252 #else
253
254     stack_ptr = stack_base + stack_size;
255
256 #endif
257
258 #ifdef CYGFUN_KERNEL_THREADS_STACK_CHECKING
259     check_stack();
260 #endif
261 }
262
263 // -------------------------------------------------------------------------
264
265 inline Cyg_HardwareThread::Cyg_HardwareThread(
266     cyg_thread_entry        *e_point,   // entry point function
267     CYG_ADDRWORD            e_data,     // entry data
268     cyg_ucount32            s_size,     // stack size, 0 = use default
269     CYG_ADDRESS             s_base      // stack base, NULL = allocate
270 )
271 {
272     entry_point = e_point;
273     entry_data  = e_data;
274 #ifdef CYGDBG_KERNEL_DEBUG_GDB_THREAD_SUPPORT
275     saved_context = 0;
276 #endif
277     
278     attach_stack( s_base, s_size );
279 };
280
281 // -------------------------------------------------------------------------
282
283 #ifdef CYGDBG_KERNEL_DEBUG_GDB_THREAD_SUPPORT
284
285 // Return the current saved state for this thread.
286 inline HAL_SavedRegisters *Cyg_HardwareThread::get_saved_context()
287 {
288     HAL_SavedRegisters *regs;
289     if( saved_context != 0 ) regs = saved_context;
290     else HAL_THREAD_GET_SAVED_REGISTERS( stack_ptr, regs );
291     return regs;
292 }
293
294 inline void Cyg_HardwareThread::set_saved_context(HAL_SavedRegisters *ctx)
295 {
296     saved_context = ctx;
297 }
298
299 #endif
300
301 // -------------------------------------------------------------------------
302 // (declare this inline before its first use)
303
304 inline cyg_uint16 Cyg_Thread::get_unique_id()
305 {
306     return unique_id;
307 }
308
309 // -------------------------------------------------------------------------
310 // Initialize the context of this thread.
311
312 inline void Cyg_HardwareThread::init_context(Cyg_Thread *thread)
313 {
314 #ifdef CYGPKG_INFRA_DEBUG
315     cyg_uint32 threadid = thread->get_unique_id()*0x01010000;
316 #else
317     cyg_uint32 threadid = 0x11110000;
318 #endif
319     HAL_THREAD_INIT_CONTEXT( stack_ptr, thread, thread_entry, threadid );
320 }
321
322
323
324 // -------------------------------------------------------------------------
325 // Save current thread's context and load that of the given next thread.
326 // This function is only really here for completeness, the
327 // kernel generally calls the HAL macros directly.
328
329 inline void Cyg_HardwareThread::switch_context(Cyg_HardwareThread *next)
330 {
331     HAL_THREAD_SWITCH_CONTEXT( &stack_ptr, &next->stack_ptr );
332 }
333
334 // -------------------------------------------------------------------------
335 // Get and set entry_data.
336
337 inline void Cyg_HardwareThread::set_entry_data( CYG_ADDRWORD data )
338 {
339     entry_data = data;
340 }
341
342 inline CYG_ADDRWORD Cyg_HardwareThread::get_entry_data()
343 {
344     return entry_data;
345 }
346
347 // -------------------------------------------------------------------------
348 // Allocate some memory at the lower end of the stack
349 // by moving the stack limit pointer.
350
351 #ifdef CYGFUN_KERNEL_THREADS_STACK_LIMIT
352
353 #ifndef CYGFUN_KERNEL_THREADS_STACK_CHECKING
354 // if stack checking, implementation is in thread.cxx
355 inline void *Cyg_HardwareThread::increment_stack_limit( cyg_ucount32 size )
356 {
357     void *ret = (void *)stack_limit;
358     stack_limit += size;
359     return ret;
360 }
361 #endif
362     
363 inline CYG_ADDRESS
364 Cyg_HardwareThread::get_stack_limit()
365 {
366     return stack_limit;
367 }
368
369 #endif    
370
371 //==========================================================================
372 // Inlines for Cyg_Thread class
373
374 inline Cyg_Thread *Cyg_Thread::self()
375 {
376     return Cyg_Scheduler::get_current_thread();
377 }
378
379 // -------------------------------------------------------------------------
380
381 inline void Cyg_Thread::yield()
382 {
383     self()->Cyg_SchedThread::yield();
384 }
385
386 // -------------------------------------------------------------------------
387
388 inline void
389 Cyg_Thread::rotate_queue( cyg_priority pri )
390 {
391     self()->Cyg_SchedThread::rotate_queue( pri );
392 }
393
394 // -------------------------------------------------------------------------
395
396 inline void
397 Cyg_Thread::to_queue_head( void )
398 {
399     this->Cyg_SchedThread::to_queue_head();
400 }
401
402 // -------------------------------------------------------------------------
403
404 #ifdef CYGIMP_THREAD_PRIORITY
405
406 inline cyg_priority Cyg_Thread::get_priority()
407 {
408 #ifdef CYGSEM_KERNEL_SYNCH_MUTEX_PRIORITY_INVERSION_PROTOCOL_SIMPLE
409
410     // If we have an inherited priority, return our original
411     // priority rather than the current one.
412     
413     if( priority_inherited ) return original_priority;
414
415 #endif
416
417     return priority;
418 }
419
420 // Return the actual dispatching priority of the thread
421 // regardless of inheritance or scheduling concerns.
422 inline cyg_priority Cyg_Thread::get_current_priority()
423 {
424     return priority;
425 }
426
427 #endif
428
429 // -------------------------------------------------------------------------
430
431 inline void Cyg_Thread::set_sleep_reason( cyg_reason reason)
432 {
433     self()->sleep_reason = reason;
434     self()->wake_reason = NONE;
435 }
436
437 // -------------------------------------------------------------------------
438
439 inline Cyg_Thread::cyg_reason Cyg_Thread::get_sleep_reason()
440 {
441     return sleep_reason;
442 }
443
444 // -------------------------------------------------------------------------
445
446 inline void Cyg_Thread::set_wake_reason( cyg_reason reason )
447 {
448     sleep_reason = NONE;
449     wake_reason = reason;
450 }
451
452 // -------------------------------------------------------------------------
453
454 inline Cyg_Thread::cyg_reason Cyg_Thread::get_wake_reason()
455 {
456     return wake_reason;
457 }
458
459 // -------------------------------------------------------------------------
460
461 inline void Cyg_Thread::set_timer(
462     cyg_tick_count      trigger,
463     cyg_reason          reason
464 )
465 {
466 #ifdef CYGFUN_KERNEL_THREADS_TIMER
467     self()->sleep_reason = reason;
468     self()->wake_reason = NONE;
469     self()->timer.initialize( trigger);
470 #endif
471 }
472
473 // -------------------------------------------------------------------------
474
475 inline void Cyg_Thread::clear_timer()
476 {
477 #ifdef CYGFUN_KERNEL_THREADS_TIMER
478     self()->timer.disable();
479 #endif
480 }
481
482 // -------------------------------------------------------------------------
483
484 #ifdef CYGVAR_KERNEL_THREADS_DATA
485
486 inline CYG_ADDRWORD Cyg_Thread::get_data( Cyg_Thread::cyg_data_index index )
487 {
488     CYG_ASSERT( index < CYGNUM_KERNEL_THREADS_DATA_MAX,
489                 "Per thread data index out of bounds");
490     CYG_ASSERT( (thread_data_map & (1<<index)) == 0,
491                 "Unallocated index used");
492     
493     return self()->thread_data[index];
494 }
495
496 inline CYG_ADDRWORD *Cyg_Thread::get_data_ptr( Cyg_Thread::cyg_data_index index )
497 {
498     CYG_ASSERT( index < CYGNUM_KERNEL_THREADS_DATA_MAX,
499                 "Per thread data index out of bounds");
500     CYG_ASSERT( (thread_data_map & (1<<index)) == 0,
501                 "Unallocated index used");
502     
503     return &(self()->thread_data[index]);
504 }
505
506 inline void Cyg_Thread::set_data( Cyg_Thread::cyg_data_index index,
507                                   CYG_ADDRWORD data )
508 {
509     CYG_ASSERT( index < CYGNUM_KERNEL_THREADS_DATA_MAX,
510                 "Per thread data index out of bounds");
511     CYG_ASSERT( (thread_data_map & (1<<index)) == 0,
512                 "Unallocated index used");
513
514     thread_data[index] = data;
515 }
516
517 #endif
518
519 // -------------------------------------------------------------------------
520
521 #ifdef CYGVAR_KERNEL_THREADS_NAME
522
523 inline char *Cyg_Thread::get_name()
524 {
525     return name;
526 }
527
528 #endif
529
530 // -------------------------------------------------------------------------
531
532 #ifdef CYGVAR_KERNEL_THREADS_LIST
533
534 inline Cyg_Thread *Cyg_Thread::get_list_head()
535 {
536     return thread_list?thread_list->list_next:0;
537 }
538     
539 inline Cyg_Thread *Cyg_Thread::get_list_next()
540 {
541     return (this==thread_list)?0:list_next;
542 }
543
544 #endif
545
546
547 // -------------------------------------------------------------------------
548
549 #ifdef CYGPKG_KERNEL_EXCEPTIONS
550
551 inline void Cyg_Thread::register_exception(
552     cyg_code                exception_number,       // exception number
553     cyg_exception_handler   handler,                // handler function
554     CYG_ADDRWORD            data,                   // data argument
555     cyg_exception_handler   **old_handler,          // handler function
556     CYG_ADDRWORD            *old_data               // data argument
557     )
558 {
559     self()->exception_control.register_exception(
560         exception_number,
561         handler,
562         data,
563         old_handler,
564         old_data
565         );
566 }
567
568 inline void Cyg_Thread::deregister_exception(
569     cyg_code                exception_number        // exception number
570     )
571 {
572     self()->exception_control.deregister_exception(
573         exception_number
574         );
575 }
576
577 #endif
578
579 //==========================================================================
580 // Inlines for Cyg_ThreadTimer class
581
582 // -------------------------------------------------------------------------
583 #if defined(CYGFUN_KERNEL_THREADS_TIMER) && defined(CYGVAR_KERNEL_COUNTERS_CLOCK)
584
585 inline Cyg_ThreadTimer::Cyg_ThreadTimer(
586     Cyg_Thread  *th
587     )
588     : Cyg_Alarm(Cyg_Clock::real_time_clock,
589                 &alarm,
590                 CYG_ADDRWORD(this)
591                 )
592 {
593     thread = th;
594 }
595
596 #endif
597
598 //==========================================================================
599 // Inlines for Cyg_ThreadQueue class
600
601
602 inline void Cyg_ThreadQueue::enqueue(Cyg_Thread *thread)
603 {
604     Cyg_ThreadQueue_Implementation::enqueue(thread);
605 }
606
607 // -------------------------------------------------------------------------
608
609 inline Cyg_Thread *Cyg_ThreadQueue::highpri()
610 {
611     return Cyg_ThreadQueue_Implementation::highpri();
612 }
613
614 // -------------------------------------------------------------------------
615
616 inline Cyg_Thread *Cyg_ThreadQueue::dequeue()
617 {
618     return Cyg_ThreadQueue_Implementation::dequeue();
619 }
620
621 // -------------------------------------------------------------------------
622
623 inline void Cyg_ThreadQueue::remove(Cyg_Thread *thread)
624 {
625     Cyg_ThreadQueue_Implementation::remove(thread);
626 }
627
628 // -------------------------------------------------------------------------
629
630 inline cyg_bool Cyg_ThreadQueue::empty()
631 {
632     return Cyg_ThreadQueue_Implementation::empty();
633 }
634
635 // -------------------------------------------------------------------------
636
637 #ifdef CYGPKG_KERNEL_THREADS_DESTRUCTORS
638
639 #ifndef CYGSEM_KERNEL_THREADS_DESTRUCTORS_PER_THREAD
640 # include <cyg/kernel/sched.inl>
641 #endif
642
643 // Add and remove destructors. Returns true on success, false on failure.
644 inline cyg_bool
645 Cyg_Thread::add_destructor( destructor_fn fn, CYG_ADDRWORD data )
646 {
647     cyg_ucount16 i;
648 #ifndef CYGSEM_KERNEL_THREADS_DESTRUCTORS_PER_THREAD
649     Cyg_Scheduler::lock();
650 #endif
651     for (i=0; i<CYGNUM_KERNEL_THREADS_DESTRUCTORS; i++) {
652         if (NULL == destructors[i].fn) {
653             destructors[i].data = data;
654             destructors[i].fn = fn;
655 #ifndef CYGSEM_KERNEL_THREADS_DESTRUCTORS_PER_THREAD
656             Cyg_Scheduler::unlock();
657 #endif
658             return true;
659         }
660     }
661 #ifndef CYGSEM_KERNEL_THREADS_DESTRUCTORS_PER_THREAD
662     Cyg_Scheduler::unlock();
663 #endif
664     return false;
665 }
666
667 inline cyg_bool
668 Cyg_Thread::rem_destructor( destructor_fn fn, CYG_ADDRWORD data )
669 {
670     cyg_ucount16 i;
671 #ifndef CYGSEM_KERNEL_THREADS_DESTRUCTORS_PER_THREAD
672     Cyg_Scheduler::lock();
673 #endif
674     for (i=0; i<CYGNUM_KERNEL_THREADS_DESTRUCTORS; i++) {
675         if (destructors[i].fn == fn && destructors[i].data == data) {
676             destructors[i].fn = NULL;
677 #ifndef CYGSEM_KERNEL_THREADS_DESTRUCTORS_PER_THREAD
678             Cyg_Scheduler::unlock();
679 #endif
680             return true;
681         }
682     }
683 #ifndef CYGSEM_KERNEL_THREADS_DESTRUCTORS_PER_THREAD
684     Cyg_Scheduler::unlock();
685 #endif
686     return false;
687 }
688 #endif
689
690 // -------------------------------------------------------------------------
691
692 #endif // ifndef CYGONCE_KERNEL_THREAD_INL
693 // EOF thread.inl