LK kernel thread

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:

1
2
3
4
5
thread_t *thread_create(const char *name, thread_start_routine entry, void *arg, int priority, size_t stack_size);
status_t thread_resume(thread_t *);
status_t thread_detach(thread_t *t);
status_t thread_detach_and_resume(thread_t *t);
status_t thread_join(thread_t *t, int *retcode, lk_time_t timeout);

Functions to temporarily or permanently stop executing a thread:

1
2
3
void thread_yield(void);
void thread_sleep(lk_time_t delay);
void thread_exit(int retcode);

Functions for a thread to modify its state:

1
2
3
void thread_set_name(const char *name);
void thread_set_priority(int priority);
status_t thread_set_real_time(thread_t *t);

thread_init

thread_init 函数位于 kernel/thread.c 文件中,关于线程的初始化在 thread_init_early 中已经完成。前一篇文章中已对thread_init_early做了解析。

thread_create

1
2
3
4
thread_t *thread_create(const char *name, thread_start_routine entry, void *arg, int priority, size_t stack_size)
{
return thread_create_etc(NULL, name, entry, arg, priority, NULL, stack_size);
}

thread_create_etc代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
* @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;
}

init_thread_struct(t, name);

t->entry = entry;
t->arg = arg;
t->priority = priority;
t->state = THREAD_SUSPENDED;
t->blocking_wait_queue = NULL;
t->wait_queue_block_ret = NO_ERROR;
thread_set_curr_cpu(t, -1);

t->retcode = 0;
wait_queue_init(&t->retcode_wait_queue);

#if WITH_KERNEL_VM
t->aspace = NULL;
#endif

/* create the stack */
if (!stack) {
#if THREAD_STACK_BOUNDS_CHECK
stack_size += THREAD_STACK_PADDING_SIZE;
flags |= THREAD_FLAG_DEBUG_STACK_BOUNDS_CHECK;
#endif
t->stack = malloc(stack_size);
if (!t->stack) {
if (flags & THREAD_FLAG_FREE_STRUCT)
free(t);
return NULL;
}
flags |= THREAD_FLAG_FREE_STACK;
#if THREAD_STACK_BOUNDS_CHECK
memset(t->stack, STACK_DEBUG_BYTE, THREAD_STACK_PADDING_SIZE);
#endif
} else {
t->stack = stack;
}
#if THREAD_STACK_HIGHWATER
if (flags & THREAD_FLAG_DEBUG_STACK_BOUNDS_CHECK) {
memset(t->stack + THREAD_STACK_PADDING_SIZE, STACK_DEBUG_BYTE,
stack_size - THREAD_STACK_PADDING_SIZE);
} else {
memset(t->stack, STACK_DEBUG_BYTE, stack_size);
}
#endif

t->stack_size = stack_size;

/* 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);

return t;
}

thread_create 的逻辑比较简单,总体上来说只有 3 个步骤:
申请并填充 thread_t 结构体
初始化线程栈空间
添加线程到 thread_list 头部

arch_thread_initialize(t)

lk 的线程栈初始化。
线程栈的初始化由 arch_thread_initialize 完成,这个函数位于 arch/arm/thread.c 文件中,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct context_switch_frame {
vaddr_t r4;
vaddr_t r5;
vaddr_t r6;
vaddr_t r7;
vaddr_t r8;
vaddr_t r9;
vaddr_t r10;
vaddr_t r11;
vaddr_t lr;
};

void arch_thread_initialize(thread_t *t)
{
// create a default stack frame on the stack
vaddr_t stack_top = (vaddr_t)t->stack + t->stack_size;

// make sure the top of the stack is 8 byte aligned for EABI compliance
stack_top = ROUNDDOWN(stack_top, 8);

struct context_switch_frame *frame = (struct context_switch_frame *)(stack_top);
frame--;

// fill it in
memset(frame, 0, sizeof(*frame));
frame->lr = (vaddr_t)&initial_thread_func;

// set the stack pointer
t->arch.sp = (vaddr_t)frame;

#if ARM_WITH_VFP
arm_fpu_thread_initialize(t);
#endif
}

初始化线程栈空间其实就是在栈顶使用一块空间用于后续保存寄存器信息以方便线程切换。需要保存的寄存器信息定义在 context_switch_frame 结构体中,其中 lr 用于保存线程函数入口。 当这块内存空间设置好以后,线程栈的初始化工作基本就完成了,剩下的就是通过 thread_resume 来启动线程。

thread_resume

thread_resume 函数位于 kernel/thread.c 文件中,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* @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;
}

mp_reschedule(MP_CPU_ALL_BUT_LOCAL, 0);

THREAD_UNLOCK(state);

if (resched)
thread_yield();

return NO_ERROR;
}

代码逻辑简单明了,只有以下两个步骤:
修改 thread 的状态为 THREAD_READY, 然后添加到 run_queue 中。

insert_in_run_queue_head

insert_in_run_queue_head 函数位于同一文件中,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
static void insert_in_run_queue_tail(thread_t *t)
{
DEBUG_ASSERT(t->magic == THREAD_MAGIC);
DEBUG_ASSERT(t->state == THREAD_READY);
DEBUG_ASSERT(!list_in_list(&t->queue_node));
DEBUG_ASSERT(arch_ints_disabled());
DEBUG_ASSERT(spin_lock_held(&thread_lock));

list_add_tail(&run_queue[t->priority], &t->queue_node);
run_queue_bitmap |= (1<<t->priority);
}

run_queue 在 thread_init_early 中已经有过了解,就是一个大小为 32 的全局链表数组,这个数组每一项对应一个线程优先级,下标越大的数组项优先级越大。比较有趣的是使用了一个uint32类型的全局变量 run_queue_bitmap 来作为优先级的索引。这个变量的每一位对应数组的每一项, 通过检查对应位的值是 0 或 1 就可以知道优先级的使用情况。

thread_yield

调用 thread_yield 来获取 cpu 执行。thread_yield 函数位于同一文件中,主要是修改current_thread 的状态的 THREAD_READY并插入对应优先级项链表的尾部,然后调用 thread_resched 函数来切换线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* @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();

DEBUG_ASSERT(current_thread->magic == THREAD_MAGIC);
DEBUG_ASSERT(current_thread->state == THREAD_RUNNING);

THREAD_LOCK(state);

THREAD_STATS_INC(yields);

/* 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();

THREAD_UNLOCK(state);
}
thread_resched

切换线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/**
* @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();

DEBUG_ASSERT(arch_ints_disabled());
DEBUG_ASSERT(spin_lock_held(&thread_lock));
DEBUG_ASSERT(current_thread->state != THREAD_RUNNING);

THREAD_STATS_INC(reschedules);

newthread = get_top_thread(cpu);

DEBUG_ASSERT(newthread);

newthread->state = THREAD_RUNNING;

oldthread = current_thread;

if (newthread == oldthread)
return;

/* 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 WITH_SMP
if (thread_is_idle(newthread)) {
mp_set_cpu_idle(cpu);
} else {
mp_set_cpu_busy(cpu);
}

if (thread_is_realtime(newthread)) {
mp_set_cpu_realtime(cpu);
} else {
mp_set_cpu_non_realtime(cpu);
}
#endif

#if THREAD_STATS
THREAD_STATS_INC(context_switches);

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

#ifdef WITH_LIB_UTHREAD
uthread_context_switch(oldthread, newthread);
#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);
}

整体流程如下:

通过get_top_thread(cpu)函数从 run_queue_bitmap 中获取优先级最高的线程。
设置线程状态为 THREAD_RUNNING, 如果新线程不等于老线程则调用 arch_context_switch 切换线程。arch_context_switch 函数位于 arch/arm/thread.c 文件中,只是作为转接 arm_context_switch 的媒介。

1
2
3
4
5
6
7
8
9
void arch_context_switch(thread_t *oldthread, thread_t *newthread)
{
// TRACEF("arch_context_switch: cpu %u old %p (%s), new %p (%s)\n", arch_curr_cpu_num(), oldthread, oldthread->name, newthread, newthread->name);
#if ARM_WITH_VFP
arm_fpu_thread_swap(oldthread, newthread);
#endif

arm_context_switch(&oldthread->arch.sp, newthread->arch.sp);
}

arm_context_switch 函数位于 arch/arm/asm.S 文件中,是汇编代码,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    /* 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

函数的功能很简单,保存 old_thread 的寄存器环境到内存,从内存加载 new_thread 的寄存器环境,跳转到线程入口,到这里新的线程就会执行起来。