Warning: many of the functions in here are for internal use of the kernel.
There are 32 priority levels. DEFAULT_PRIORITY is appropriate for typical threads. LOW_PRIORITY and HIGH_PRIORITY are relative to default priority. Threads that are set to real time will not be preempted by other threads of the same or lower priority when they are running. Use with caution.
Threads do not begin running until thread_resume() is called on them. Typically calls to thread_create() are followed immediately by thread_resume(). Threads that exit will wait, consuming resources like stack space until thread_join() is called to obtain their exit status, unless thread_detach() is called on them prior to exit (in which case they will silently exit and clean up). Threads may give up their quantum voluntarily by calling thread_yield().
1
int a_thread_start_routine(void *arg);
Functions to create, start, and obtain exit status from a thread:
/** * @brief Create a new thread * * This function creates a new thread. The thread is initially suspended, so you * need to call thread_resume() to execute it. * * @param name Name of thread * @param entry Entry point of thread * @param arg Arbitrary argument passed to entry() * @param priority Execution priority for the thread. * @param stack_size Stack size for the thread. * * Thread priority is an integer from 0 (lowest) to 31 (highest). Some standard * prioritys are defined in <kernel/thread.h>: * * HIGHEST_PRIORITY * DPC_PRIORITY * HIGH_PRIORITY * DEFAULT_PRIORITY * LOW_PRIORITY * IDLE_PRIORITY * LOWEST_PRIORITY * * Stack size is typically set to DEFAULT_STACK_SIZE * * @return Pointer to thread object, or NULL on failure. */ thread_t *thread_create_etc(thread_t *t, const char *name, thread_start_routine entry, void *arg, int priority, void *stack, size_t stack_size) { unsigned int flags = 0;
if (!t) { t = malloc(sizeof(thread_t)); if (!t) return NULL; flags |= THREAD_FLAG_FREE_STRUCT; }
/* save whether or not we need to free the thread struct and/or stack */ t->flags = flags;
/* inheirit thread local storage from the parent */ thread_t *current_thread = get_current_thread(); int i; for (i=0; i < MAX_TLS_ENTRY; i++) t->tls[i] = current_thread->tls[i];
/* set up the initial stack frame */ arch_thread_initialize(t);
/* add it to the global thread list */ THREAD_LOCK(state); list_add_head(&thread_list, &t->thread_list_node); THREAD_UNLOCK(state);
/** * @brief Make a suspended thread executable. * * This function is typically called to start a thread which has just been * created with thread_create() * * @param t Thread to resume * * @return NO_ERROR on success, ERR_NOT_SUSPENDED if thread was not suspended. */ status_t thread_resume(thread_t *t) { DEBUG_ASSERT(t->magic == THREAD_MAGIC); DEBUG_ASSERT(t->state != THREAD_DEATH);
bool resched = false; bool ints_disabled = arch_ints_disabled(); THREAD_LOCK(state); if (t->state == THREAD_SUSPENDED) { t->state = THREAD_READY; insert_in_run_queue_head(t); if (!ints_disabled) /* HACK, don't resced into bootstrap thread before idle thread is set up */ resched = true; }
/** * @brief Yield the cpu to another thread * * This function places the current thread at the end of the run queue * and yields the cpu to another waiting thread (if any.) * * This function will return at some later time. Possibly immediately if * no other threads are waiting to execute. */ void thread_yield(void) { thread_t *current_thread = get_current_thread();
/* we are yielding the cpu, so stick ourselves into the tail of the run queue and reschedule */ current_thread->state = THREAD_READY; current_thread->remaining_quantum = 0; if (likely(!thread_is_idle(current_thread))) { /* idle thread doesn't go in the run queue */ insert_in_run_queue_tail(current_thread); } thread_resched();
/** * @brief Cause another thread to be executed. * * Internal reschedule routine. The current thread needs to already be in whatever * state and queues it needs to be in. This routine simply picks the next thread and * switches to it. * * This is probably not the function you're looking for. See * thread_yield() instead. */ void thread_resched(void) { thread_t *oldthread; thread_t *newthread;
thread_t *current_thread = get_current_thread(); uint cpu = arch_curr_cpu_num();
/* set up quantum for the new thread if it was consumed */ if (newthread->remaining_quantum <= 0) { newthread->remaining_quantum = 5; // XXX make this smarter }
/* mark the cpu ownership of the threads */ thread_set_curr_cpu(oldthread, -1); thread_set_curr_cpu(newthread, cpu);
if (thread_is_idle(oldthread)) { lk_bigtime_t now = current_time_hires(); thread_stats[cpu].idle_time += now - thread_stats[cpu].last_idle_timestamp; } if (thread_is_idle(newthread)) { thread_stats[cpu].last_idle_timestamp = current_time_hires(); } #endif
KEVLOG_THREAD_SWITCH(oldthread, newthread);
#if PLATFORM_HAS_DYNAMIC_TIMER if (thread_is_real_time_or_idle(newthread)) { if (!thread_is_real_time_or_idle(oldthread)) { /* if we're switching from a non real time to a real time, cancel * the preemption timer. */ #if DEBUG_THREAD_CONTEXT_SWITCH dprintf(ALWAYS, "arch_context_switch: stop preempt, cpu %d, old %p (%s), new %p (%s)\n", cpu, oldthread, oldthread->name, newthread, newthread->name); #endif timer_cancel(&preempt_timer[cpu]); } } else if (thread_is_real_time_or_idle(oldthread)) { /* if we're switching from a real time (or idle thread) to a regular one, * set up a periodic timer to run our preemption tick. */ #if DEBUG_THREAD_CONTEXT_SWITCH dprintf(ALWAYS, "arch_context_switch: start preempt, cpu %d, old %p (%s), new %p (%s)\n", cpu, oldthread, oldthread->name, newthread, newthread->name); #endif timer_set_periodic(&preempt_timer[cpu], 10, (timer_callback)thread_timer_tick, NULL); } #endif
/* set some optional target debug leds */ target_set_debug_led(0, !thread_is_idle(newthread));
/* do the switch */ set_current_thread(newthread);
#if DEBUG_THREAD_CONTEXT_SWITCH dprintf(ALWAYS, "arch_context_switch: cpu %d, old %p (%s, pri %d, flags 0x%x), new %p (%s, pri %d, flags 0x%x)\n", cpu, oldthread, oldthread->name, oldthread->priority, oldthread->flags, newthread, newthread->name, newthread->priority, newthread->flags); #endif
#if THREAD_STACK_BOUNDS_CHECK /* check that the old thread has not blown its stack just before pushing its context */ if (oldthread->flags & THREAD_FLAG_DEBUG_STACK_BOUNDS_CHECK) { STATIC_ASSERT((THREAD_STACK_PADDING_SIZE % sizeof(uint32_t)) == 0); uint32_t *s = (uint32_t *)oldthread->stack; for (size_t i = 0; i < THREAD_STACK_PADDING_SIZE / sizeof(uint32_t); i++) { if (unlikely(s[i] != STACK_DEBUG_WORD)) { /* NOTE: will probably blow the stack harder here, but hopefully enough * state exists to at least get some sort of debugging done. */ panic("stack overrun at %p: thread %p (%s), stack %p\n", &s[i], oldthread, oldthread->name, oldthread->stack); } } } #endif
#if WITH_KERNEL_VM /* see if we need to swap mmu context */ if (newthread->aspace != oldthread->aspace) { vmm_context_switch(oldthread->aspace, newthread->aspace); } #endif
/* do the low level context switch */ arch_context_switch(oldthread, newthread); }
/* context switch frame is as follows: * lr * r11 * r10 * r9 * r8 * r7 * r6 * r5 * r4 */ /* arm_context_switch(addr_t *old_sp, addr_t new_sp) */ FUNCTION(arm_context_switch) /* save non callee trashed supervisor registers */ /* spsr and user mode registers are saved and restored in the iframe by exceptions.S */ push { r4-r11, lr }
/* save old sp */ str sp, [r0]
/* clear any exlusive locks that the old thread holds */ #if ARM_ARCH_LEVEL >= 7 /* can clear it directly */ clrex #elif ARM_ARCH_LEVEL == 6 /* have to do a fake strex to clear it */ ldr r0, =strex_spot strex r3, r2, [r0] #endif
/* load new regs */ mov sp, r1 pop { r4-r11, lr } bx lr