Part 1
요약·해설
Creation부터 termination·abort·suspend, stack object·priority space·userspace constraint와 runtime statistics를 연결합니다.
spawn · join · abort
coop · preempt · meta-IRQ
kernel · user-capable · pinned
Part 2
접을 수 있는 영어 원문 전체
영어 원문 전체 펼치기
D6674818ABED4B44A086D1F1E004C5B7B63E54F65941E14647F0D074381FC374
.. _threads_v2:
Threads
#######
.. note::
There is also limited support for using :ref:`nothread`.
.. contents::
:local:
:depth: 2
This section describes kernel services for creating, scheduling, and deleting
independently executable threads of instructions.
A :dfn:`thread` is a kernel object that is used for application processing
that is too lengthy or too complex to be performed by an ISR.
Any number of threads can be defined by an application (limited only by
available RAM). Each thread is referenced by a :dfn:`thread id` that is assigned
when the thread is spawned.
A thread has the following key properties:
* A **stack area**, which is a region of memory used for the thread's stack.
The **size** of the stack area can be tailored to conform to the actual needs
of the thread's processing. Special macros exist to create and work with
stack memory regions.
* A **thread control block** for private kernel bookkeeping of the thread's
metadata. This is an instance of type :c:struct:`k_thread`.
* An **entry point function**, which is invoked when the thread is started.
Up to 3 **argument values** can be passed to this function.
* A **scheduling priority**, which instructs the kernel's scheduler how to
allocate CPU time to the thread. (See :ref:`scheduling_v2`.)
* A set of **thread options**, which allow the thread to receive special
treatment by the kernel under specific circumstances.
(See :ref:`thread_options_v2`.)
* A **start delay**, which specifies how long the kernel should wait before
starting the thread.
* An **execution mode**, which can either be supervisor or user mode.
By default, threads run in supervisor mode and allow access to
privileged CPU instructions, the entire memory address space, and
peripherals. User mode threads have a reduced set of privileges.
This depends on the :kconfig:option:`CONFIG_USERSPACE` option. See :ref:`usermode_api`.
.. _lifecycle_v2:
Lifecycle
***********
.. _spawning_thread:
Thread Creation
===============
A thread must be created before it can be used. The kernel initializes
the thread control block as well as one end of the stack portion. The remainder
of the thread's stack is typically left uninitialized.
Specifying a start delay of :c:macro:`K_NO_WAIT` instructs the kernel
to start thread execution immediately. Alternatively, the kernel can be
instructed to delay execution of the thread by specifying a timeout
value -- for example, to allow device hardware used by the thread
to become available.
The kernel allows a delayed start to be canceled before the thread begins
executing. A cancellation request has no effect if the thread has already
started. A thread whose delayed start was successfully canceled must be
re-spawned before it can be used.
Thread Termination
===================
Once a thread is started it typically executes forever. However, a thread may
synchronously end its execution by returning from its entry point function.
This is known as **termination**.
A thread that terminates is responsible for releasing any shared resources
it may own (such as mutexes and dynamically allocated memory)
prior to returning, since the kernel does *not* reclaim them automatically.
In some cases a thread may want to sleep until another thread terminates.
This can be accomplished with the :c:func:`k_thread_join` API. This
will block the calling thread until either the timeout expires, the target
thread self-exits, or the target thread aborts (either due to a
:c:func:`k_thread_abort` call or triggering a fatal error).
Once a thread has terminated, the kernel guarantees that no use will
be made of the thread struct. The memory of such a struct can then be
re-used for any purpose, including spawning a new thread. Note that
the thread must be fully terminated, which presents race conditions
where a thread's own logic signals completion which is seen by another
thread before the kernel processing is complete. Under normal
circumstances, application code should use :c:func:`k_thread_join` or
:c:func:`k_thread_abort` to synchronize on thread termination state
and not rely on signaling from within application logic.
Thread Aborting
===============
A thread may asynchronously end its execution by **aborting**. The kernel
automatically aborts a thread if the thread triggers a fatal error condition,
such as dereferencing a null pointer.
A thread can also be aborted by another thread (or by itself)
by calling :c:func:`k_thread_abort`. However, it is typically preferable
to signal a thread to terminate itself gracefully, rather than aborting it.
As with thread termination, the kernel does not reclaim shared resources
owned by an aborted thread.
.. note::
The kernel does not currently make any claims regarding an application's
ability to respawn a thread that aborts.
Thread Suspension
==================
A thread can be prevented from executing for an indefinite period of time
if it becomes **suspended**. The function :c:func:`k_thread_suspend`
can be used to suspend any thread, including the calling thread.
Suspending a thread that is already suspended has no additional effect.
Once suspended, a thread cannot be scheduled until another thread calls
:c:func:`k_thread_resume` to remove the suspension.
.. note::
A thread can prevent itself from executing for a specified period of time
using :c:func:`k_sleep`. However, this is different from suspending
a thread since a sleeping thread becomes executable automatically when the
time limit is reached.
.. _thread_states:
Thread States
*************
A thread that has no factors that prevent its execution is deemed
to be **ready**, and is eligible to be selected as the current thread.
A thread that has one or more factors that prevent its execution
is deemed to be **unready**, and cannot be selected as the current thread.
The following factors make a thread unready:
* The thread has not been started.
* The thread is waiting for a kernel object to complete an operation.
(For example, the thread is taking a semaphore that is unavailable.)
* The thread is waiting for a timeout to occur.
* The thread has been suspended.
* The thread has terminated or aborted.
.. image:: ../../../../../kernel/services/threads/thread_states.svg
:align: center
.. note::
Although the diagram above may appear to suggest that both **Ready** and
**Running** are distinct thread states, that is not the correct
interpretation. **Ready** is a thread state, and **Running** is a
schedule state that only applies to **Ready** threads.
Thread Stack objects
********************
Every thread requires its own stack buffer for the CPU to push context.
Depending on configuration, there are several constraints that must be
met:
- There may need to be additional memory reserved for memory management
structures
- If guard-based stack overflow detection is enabled, a small write-
protected memory management region must immediately precede the stack buffer
to catch overflows.
- If userspace is enabled, a separate fixed-size privilege elevation stack must
be reserved to serve as a private kernel stack for handling system calls.
- If userspace is enabled, the thread's stack buffer must be appropriately
sized and aligned such that a memory protection region may be programmed
to exactly fit.
The alignment constraints can be quite restrictive, for example some MPUs
require their regions to be of some power of two in size, and aligned to its
own size.
Because of this, portable code can't simply pass an arbitrary character buffer
to :c:func:`k_thread_create`. Special macros exist to instantiate stacks,
prefixed with ``K_KERNEL_STACK`` and ``K_THREAD_STACK``.
Kernel-only Stacks
==================
If it is known that a thread will never run in user mode, or the stack is
being used for special contexts like handling interrupts, it is best to
define stacks using the ``K_KERNEL_STACK`` macros.
These stacks save memory because an MPU region will never need to be
programmed to cover the stack buffer itself, and the kernel will not need
to reserve additional room for the privilege elevation stack, or memory
management data structures which only pertain to user mode threads.
Attempts from user mode to use stacks declared in this way will result in
a fatal error for the caller.
If ``CONFIG_USERSPACE`` is not enabled, the set of ``K_THREAD_STACK`` macros
have an identical effect to the ``K_KERNEL_STACK`` macros.
Thread stacks
=============
If it is known that a stack will need to host user threads, or if this
cannot be determined, define the stack with ``K_THREAD_STACK`` macros.
This may use more memory but the stack object is suitable for hosting
user threads.
If ``CONFIG_USERSPACE`` is not enabled, the set of ``K_THREAD_STACK`` macros
have an identical effect to the ``K_KERNEL_STACK`` macros.
.. _thread_priorities:
Thread Priorities
******************
A thread's priority is an integer value, and can be either negative or
non-negative.
Numerically lower priorities takes precedence over numerically higher values.
For example, the scheduler gives thread A of priority 4 *higher* priority
over thread B of priority 7; likewise thread C of priority -2 has higher
priority than both thread A and thread B.
The scheduler distinguishes between two classes of threads,
based on each thread's priority.
* A :dfn:`cooperative thread` has a negative priority value.
Once it becomes the current thread, a cooperative thread remains
the current thread until it performs an action that makes it unready.
* A :dfn:`preemptible thread` has a non-negative priority value.
Once it becomes the current thread, a preemptible thread may be supplanted
at any time if a cooperative thread, or a preemptible thread of higher
or equal priority, becomes ready.
A thread's initial priority value can be altered up or down after the thread
has been started. Thus it is possible for a preemptible thread to become
a cooperative thread, and vice versa, by changing its priority.
.. note::
The scheduler does not make heuristic decisions to re-prioritize threads.
Thread priorities are set and changed only at the application's request.
The kernel supports a virtually unlimited number of thread priority levels.
The configuration options :kconfig:option:`CONFIG_NUM_COOP_PRIORITIES` and
:kconfig:option:`CONFIG_NUM_PREEMPT_PRIORITIES` specify the number of priority
levels for each class of thread, resulting in the following usable priority
ranges:
* cooperative threads: (-:kconfig:option:`CONFIG_NUM_COOP_PRIORITIES`) to -1
* preemptive threads: 0 to (:kconfig:option:`CONFIG_NUM_PREEMPT_PRIORITIES` - 1)
.. image:: ../../../../../kernel/services/threads/priorities.svg
:align: center
For example, configuring 5 cooperative priorities and 10 preemptive priorities
results in the ranges -5 to -1 and 0 to 9, respectively.
.. _metairq_priorities:
Meta-IRQ Priorities
===================
When enabled (see :kconfig:option:`CONFIG_NUM_METAIRQ_PRIORITIES`), there is a special
subclass of cooperative priorities at the highest (numerically lowest)
end of the priority space: meta-IRQ threads. These are scheduled
according to their normal priority, but also have the special ability
to preempt all other threads (and other meta-IRQ threads) at lower
priorities, even if those threads are cooperative and/or have taken a
scheduler lock. Meta-IRQ threads are still threads, however,
and can still be interrupted by any hardware interrupt.
This behavior makes the act of unblocking a meta-IRQ thread (by any
means, e.g. creating it, calling k_sem_give(), etc.) into the
equivalent of a synchronous system call when done by a lower
priority thread, or an ARM-like "pended IRQ" when done from true
interrupt context. The intent is that this feature will be used to
implement interrupt "bottom half" processing and/or "tasklet" features
in driver subsystems. The thread, once woken, will be guaranteed to
run before the current CPU returns into application code.
Unlike similar features in other OSes, meta-IRQ threads are true
threads and run on their own stack (which must be allocated normally),
not the per-CPU interrupt stack. Design work to enable the use of the
IRQ stack on supported architectures is pending.
Note that because this breaks the promise made to cooperative
threads by the Zephyr API (namely that the OS won't schedule other
thread until the current thread deliberately blocks), it should be
used only with great care from application code. These are not simply
very high priority threads and should not be used as such.
.. _thread_options_v2:
Thread Options
***************
The kernel supports a small set of :dfn:`thread options` that allow a thread
to receive special treatment under specific circumstances. The set of options
associated with a thread are specified when the thread is spawned.
A thread that does not require any thread option has an option value of zero.
A thread that requires a thread option specifies it by name, using the
:literal:`|` character as a separator if multiple options are needed
(i.e. combine options using the bitwise OR operator).
The following thread options are supported.
:c:macro:`K_ESSENTIAL`
This option tags the thread as an :dfn:`essential thread`. This instructs
the kernel to treat the termination or aborting of the thread as a fatal
system error.
By default, the thread is not considered to be an essential thread.
:c:macro:`K_SSE_REGS`
This x86-specific option indicate that the thread uses the CPU's
SSE registers. Also see :c:macro:`K_FP_REGS`.
By default, the kernel does not attempt to save and restore the contents
of these registers when scheduling the thread.
:c:macro:`K_FP_REGS`
This option indicate that the thread uses the CPU's floating point
registers. This instructs the kernel to take additional steps to save
and restore the contents of these registers when scheduling the thread.
(For more information see :ref:`float_v2`.)
By default, the kernel does not attempt to save and restore the contents
of this register when scheduling the thread.
:c:macro:`K_USER`
If :kconfig:option:`CONFIG_USERSPACE` is enabled, this thread will be created in
user mode and will have reduced privileges. See :ref:`usermode_api`. Otherwise
this flag does nothing.
:c:macro:`K_INHERIT_PERMS`
If :kconfig:option:`CONFIG_USERSPACE` is enabled, this thread will inherit all
kernel object permissions that the parent thread had, except the parent
thread object. See :ref:`usermode_api`.
.. _custom_data_v2:
Thread Custom Data
******************
Every thread has a 32-bit :dfn:`custom data` area, accessible only by
the thread itself, and may be used by the application for any purpose
it chooses. The default custom data value for a thread is zero.
.. note::
Custom data support is not available to ISRs because they operate
within a single shared kernel interrupt handling context.
By default, thread custom data support is disabled. The configuration option
:kconfig:option:`CONFIG_THREAD_CUSTOM_DATA` can be used to enable support.
The :c:func:`k_thread_custom_data_set` and
:c:func:`k_thread_custom_data_get` functions are used to write and read
a thread's custom data, respectively. A thread can only access its own
custom data, and not that of another thread.
The following code uses the custom data feature to record the number of times
each thread calls a specific routine.
.. note::
Obviously, only a single routine can use this technique,
since it monopolizes the use of the custom data feature.
.. code-block:: c
int call_tracking_routine(void)
{
uint32_t call_count;
if (k_is_in_isr()) {
/* ignore any call made by an ISR */
} else {
call_count = (uint32_t)k_thread_custom_data_get();
call_count++;
k_thread_custom_data_set((void *)call_count);
}
/* do rest of routine's processing */
...
}
Use thread custom data to allow a routine to access thread-specific information,
by using the custom data as a pointer to a data structure owned by the thread.
Implementation
**************
Spawning a Thread
=================
A thread is spawned by defining its stack area and its thread control block,
and then calling :c:func:`k_thread_create`.
The stack area must be defined using :c:macro:`K_THREAD_STACK_DEFINE` or
:c:macro:`K_KERNEL_STACK_DEFINE` to ensure it is properly set up in memory.
The size parameter for the stack must be one of three values:
- The original requested stack size passed to
``K_THREAD_STACK`` or ``K_KERNEL_STACK`` family of stack instantiation
macros.
- For a stack object defined with the ``K_THREAD_STACK`` family of
macros, the return value of :c:macro:`K_THREAD_STACK_SIZEOF()` for that'
object.
- For a stack object defined with the ``K_KERNEL_STACK`` family of
macros, the return value of :c:macro:`K_KERNEL_STACK_SIZEOF()` for that
object.
The thread spawning function returns its thread id, which can be used
to reference the thread.
The following code spawns a thread that starts immediately.
.. code-block:: c
#define MY_STACK_SIZE 500
#define MY_PRIORITY 5
extern void my_entry_point(void *, void *, void *);
K_THREAD_STACK_DEFINE(my_stack_area, MY_STACK_SIZE);
struct k_thread my_thread_data;
k_tid_t my_tid = k_thread_create(&my_thread_data, my_stack_area,
K_THREAD_STACK_SIZEOF(my_stack_area),
my_entry_point,
NULL, NULL, NULL,
MY_PRIORITY, 0, K_NO_WAIT);
Alternatively, a thread can be declared at compile time by calling
:c:macro:`K_THREAD_DEFINE`. Observe that the macro defines
the stack area, control block, and thread id variables automatically.
The following code has the same effect as the code segment above.
.. code-block:: c
#define MY_STACK_SIZE 500
#define MY_PRIORITY 5
extern void my_entry_point(void *, void *, void *);
K_THREAD_DEFINE(my_tid, MY_STACK_SIZE,
my_entry_point, NULL, NULL, NULL,
MY_PRIORITY, 0, 0);
.. note::
The delay parameter to :c:func:`k_thread_create` is a
:c:type:`k_timeout_t` value, so :c:macro:`K_NO_WAIT` means to start the
thread immediately. The corresponding parameter to :c:macro:`K_THREAD_DEFINE`
is a duration in integral milliseconds, so the equivalent argument is 0.
User Mode Constraints
---------------------
This section only applies if :kconfig:option:`CONFIG_USERSPACE` is enabled, and a user
thread tries to create a new thread. The :c:func:`k_thread_create` API is
still used, but there are additional constraints which must be met or the
calling thread will be terminated:
* The calling thread must have permissions granted on both the child thread
and stack parameters; both are tracked by the kernel as kernel objects.
* The child thread and stack objects must be in an uninitialized state,
i.e. it is not currently running and the stack memory is unused.
* The stack size parameter passed in must be equal to or less than the
bounds of the stack object when it was declared.
* The :c:macro:`K_USER` option must be used, as user threads can only create
other user threads.
* The :c:macro:`K_ESSENTIAL` option must not be used, user threads may not be
considered essential threads.
* The priority of the child thread must be a valid priority value, and equal to
or lower than the parent thread.
Dropping Permissions
====================
If :kconfig:option:`CONFIG_USERSPACE` is enabled, a thread running in supervisor mode
may perform a one-way transition to user mode using the
:c:func:`k_thread_user_mode_enter` API. This is a one-way operation which
will reset and zero the thread's stack memory. The thread will be marked
as non-essential.
Terminating a Thread
====================
A thread terminates itself by returning from its entry point function.
The following code illustrates the ways a thread can terminate.
.. code-block:: c
void my_entry_point(int unused1, int unused2, int unused3)
{
while (1) {
...
if (<some condition>) {
return; /* thread terminates from mid-entry point function */
}
...
}
/* thread terminates at end of entry point function */
}
If :kconfig:option:`CONFIG_USERSPACE` is enabled, aborting a thread will additionally
mark the thread and stack objects as uninitialized so that they may be re-used.
Runtime Statistics
******************
Thread runtime statistics can be gathered and retrieved if
:kconfig:option:`CONFIG_THREAD_RUNTIME_STATS` is enabled, for example, total number of
execution cycles of a thread.
By default, the runtime statistics are gathered using the default kernel
timer. For some architectures, SoCs or boards, there are timers with higher
resolution available via timing functions. Using of these timers can be
enabled via :kconfig:option:`CONFIG_THREAD_RUNTIME_STATS_USE_TIMING_FUNCTIONS`.
Here is an example:
.. code-block:: c
k_thread_runtime_stats_t rt_stats_thread;
k_thread_runtime_stats_get(k_current_get(), &rt_stats_thread);
printk("Cycles: %llu\n", rt_stats_thread.execution_cycles);
Suggested Uses
**************
Use threads to handle processing that cannot be handled in an ISR.
Use separate threads to handle logically distinct processing operations
that can execute in parallel.
Configuration Options
**********************
Related configuration options:
* :kconfig:option:`CONFIG_MAIN_THREAD_PRIORITY`
* :kconfig:option:`CONFIG_MAIN_STACK_SIZE`
* :kconfig:option:`CONFIG_IDLE_STACK_SIZE`
* :kconfig:option:`CONFIG_THREAD_CUSTOM_DATA`
* :kconfig:option:`CONFIG_NUM_COOP_PRIORITIES`
* :kconfig:option:`CONFIG_NUM_PREEMPT_PRIORITIES`
* :kconfig:option:`CONFIG_TIMESLICING`
* :kconfig:option:`CONFIG_TIMESLICE_SIZE`
* :kconfig:option:`CONFIG_TIMESLICE_PRIORITY`
* :kconfig:option:`CONFIG_USERSPACE`
API Reference
**************
.. doxygengroup:: thread_apis
.. doxygengroup:: thread_stack_api
Part 3
한국어 전문 번역
이 문서는 독립적으로 실행되는 instruction thread를 생성·schedule·삭제하는 kernel service를 설명합니다. ISR에서 처리하기에 너무 길거나 복잡한 application work는 thread로 실행합니다.
RAM이 허용하는 만큼 thread를 정의하고 spawn 때 할당된 thread ID로 참조합니다. Thread에는 stack area와 size, k_thread control block, 최대 3개 argument를 받는 entry, priority, options, start delay와 supervisor/user execution mode가 있습니다. CONFIG_USERSPACE의 user mode는 privilege가 줄어듭니다.
Lifecycle
Thread creation
Kernel은 control block과 stack 한쪽 끝을 초기화하며 나머지 stack은 보통 uninitialized입니다. K_NO_WAIT는 즉시 시작하고 timeout delay는 hardware 준비 등을 기다립니다. Delayed start는 실행 전 cancel할 수 있지만 이미 시작했으면 효과가 없고 성공적으로 cancel된 thread는 다시 spawn해야 합니다.
Thread termination
Entry에서 return하면 synchronous termination입니다. Kernel은 mutex·dynamic allocation 같은 shared resource를 자동 회수하지 않으므로 return 전에 thread가 정리해야 합니다.
k_thread_join()은 timeout, target self-exit, abort 또는 fatal error까지 caller를 block합니다. 완전히 terminated되면 kernel이 thread struct를 더 쓰지 않아 memory를 재사용할 수 있습니다. Application completion signal이 kernel termination 처리보다 먼저 관찰되는 race가 있으므로 내부 signal 대신 join 또는 abort로 동기화합니다.
Thread abort와 suspension
Fatal error는 자동 abort하며 다른 thread 또는 자신이 k_thread_abort()를 호출할 수도 있지만 graceful self-termination을 권장합니다. Abort도 shared resource를 회수하지 않으며 현재 kernel은 aborted thread respawn 가능성을 보장하지 않습니다.
k_thread_suspend()는 caller 자신을 포함한 thread를 indefinite하게 schedule되지 않게 합니다. 이미 suspended면 추가 효과가 없고 k_thread_resume()이 제거할 때까지 실행할 수 없습니다. k_sleep()은 time limit 뒤 자동 executable이 된다는 점이 다릅니다.
Thread states
실행을 막는 factor가 없으면 ready이고 current thread 후보입니다. 시작 전, kernel object operation 대기, timeout 대기, suspended, terminated/aborted 중 하나라도 해당하면 unready입니다.
Unready
Unready
Unready
Unready
실행 방해 factor 없음 · scheduler 후보
별도 thread state가 아니라 Ready thread에만 적용되는 schedule state
Thread stack objects
CPU context용 stack buffer 외에 memory-management structure, guard-based overflow protection region, userspace system call용 privilege-elevation stack, MPU/MMU region alignment가 필요할 수 있습니다. 일부 MPU는 power-of-two size와 self-size alignment를 요구하므로 arbitrary char buffer를 k_thread_create()에 넘길 수 없고 K_KERNEL_STACK 또는 K_THREAD_STACK macro를 써야 합니다.
Kernel-only stack
User mode를 절대 실행하지 않거나 interrupt 같은 special context에는 K_KERNEL_STACK 계열을 사용합니다. MPU user region, privilege stack, user memory-management data가 필요 없어 memory를 절약합니다. User mode에서 사용하면 fatal error입니다. CONFIG_USERSPACE가 없으면 K_THREAD_STACK과 효과가 같습니다.
Thread stack
User thread를 host할 가능성이 있으면 더 많은 memory를 쓰더라도 K_THREAD_STACK 계열로 정의합니다. CONFIG_USERSPACE가 없으면 kernel-only macro와 동일합니다.
Thread priorities
Priority는 negative 또는 non-negative integer이며 숫자가 낮을수록 우선합니다. Priority 4는 7보다 높고 -2는 둘보다 높습니다. Negative cooperative thread는 스스로 unready가 될 때까지 current를 유지합니다. Non-negative preemptible thread는 cooperative 또는 더 높거나 같은 priority thread가 ready되면 대체될 수 있습니다.
Runtime에 priority를 바꿔 두 class 사이를 이동할 수 있지만 scheduler가 heuristic으로 조정하지는 않습니다. CONFIG_NUM_COOP_PRIORITIES와 CONFIG_NUM_PREEMPT_PRIORITIES가 각각 usable range를 정합니다.
선택적 · numerically lowest cooperative subclass
-CONFIG_NUM_COOP_PRIORITIES ... -1
0 ... CONFIG_NUM_PREEMPT_PRIORITIES-1
예: cooperative 5개와 preemptive 10개면 -5..-1, 0..9입니다.
Meta-IRQ priorities
CONFIG_NUM_METAIRQ_PRIORITIES가 있으면 cooperative space의 가장 높은 부분을 meta-IRQ thread가 사용합니다. Lower-priority cooperative thread가 scheduler lock을 잡아도 선점하며 hardware interrupt에는 여전히 interrupt됩니다.
Lower-priority thread가 meta-IRQ를 unblock하면 synchronous system call처럼, true interrupt context에서 unblock하면 ARM pended IRQ처럼 동작해 application으로 돌아가기 전에 반드시 실행됩니다. Driver bottom half/tasklet 구현 목적이며 per-CPU IRQ stack이 아니라 normal하게 할당한 자신의 stack을 씁니다.
Thread options
Spawn 때 option을 지정하고 여러 개면 |로 OR합니다. K_ESSENTIAL은 종료/abort를 fatal로 만들고, K_SSE_REGS와 K_FP_REGS는 x86 SSE 또는 floating-point context 보존을 요청합니다. K_USER는 userspace thread, K_INHERIT_PERMS는 parent의 object permission을 child thread object 자체를 제외하고 상속합니다.
Thread custom data
각 thread에는 자신만 접근하는 32-bit custom data가 있고 default는 0입니다. ISR은 shared kernel interrupt context에서 실행해 지원하지 않습니다. CONFIG_THREAD_CUSTOM_DATA로 켜고 set/get을 사용합니다. 다음 routine은 thread별 call count를 기록하지만 custom data를 독점하므로 한 routine만 이 기법을 쓸 수 있습니다.
int call_tracking_routine(void)
{
uint32_t call_count;
if (k_is_in_isr()) {
/* ignore any call made by an ISR */
} else {
call_count = (uint32_t)k_thread_custom_data_get();
call_count++;
k_thread_custom_data_set((void *)call_count);
}
/* do rest of routine's processing */
...
}Custom data에 thread-owned structure pointer를 넣으면 routine이 thread-specific information에 접근할 수 있습니다.
구현
Thread spawn
Stack area와 control block을 정의하고 k_thread_create()을 호출합니다. Stack은 K_THREAD_STACK_DEFINE 또는 K_KERNEL_STACK_DEFINE로 만들고 size는 original requested size, K_THREAD_STACK_SIZEOF(), K_KERNEL_STACK_SIZEOF() 중 맞는 값을 사용합니다.
#define MY_STACK_SIZE 500
#define MY_PRIORITY 5
extern void my_entry_point(void *, void *, void *);
K_THREAD_STACK_DEFINE(my_stack_area, MY_STACK_SIZE);
struct k_thread my_thread_data;
k_tid_t my_tid = k_thread_create(&my_thread_data, my_stack_area,
K_THREAD_STACK_SIZEOF(my_stack_area),
my_entry_point,
NULL, NULL, NULL,
MY_PRIORITY, 0, K_NO_WAIT);K_THREAD_DEFINE는 stack, control block과 ID를 compile time에 자동 정의합니다.
#define MY_STACK_SIZE 500
#define MY_PRIORITY 5
extern void my_entry_point(void *, void *, void *);
K_THREAD_DEFINE(my_tid, MY_STACK_SIZE,
my_entry_point, NULL, NULL, NULL,
MY_PRIORITY, 0, 0);User mode constraints
CONFIG_USERSPACE에서 user thread가 child를 만들 때 caller는 child control block과 stack permission을 가져야 하고 두 object는 uninitialized여야 합니다. Stack size는 선언 bounds 이하여야 하며 K_USER를 반드시 사용하고 K_ESSENTIAL은 금지됩니다. Child priority는 valid하면서 parent와 같거나 낮아야 하며 위반하면 caller가 terminate됩니다.
Permission drop과 termination
Supervisor thread는 k_thread_user_mode_enter()로 stack을 zero/reset하고 non-essential user thread로 one-way 전환할 수 있습니다. Thread는 entry에서 return해 종료합니다.
void my_entry_point(int unused1, int unused2, int unused3)
{
while (1) {
...
if (<some condition>) {
return; /* thread terminates from mid-entry point function */
}
...
}
/* thread terminates at end of entry point function */
}CONFIG_USERSPACE에서 abort하면 thread와 stack object도 uninitialized로 표시해 재사용할 수 있습니다.
Runtime statistics
CONFIG_THREAD_RUNTIME_STATS는 thread execution cycle 같은 통계를 수집합니다. 기본 kernel timer 대신 더 높은 resolution timing function을 쓰려면 CONFIG_THREAD_RUNTIME_STATS_USE_TIMING_FUNCTIONS를 켭니다.
k_thread_runtime_stats_t rt_stats_thread;
k_thread_runtime_stats_get(k_current_get(), &rt_stats_thread);
printk("Cycles: %llu\n", rt_stats_thread.execution_cycles);권장 용도와 설정
ISR에서 처리할 수 없는 work와 병렬 실행 가능한 논리적으로 distinct operation에 thread를 사용합니다. 관련 option은 CONFIG_MAIN_THREAD_PRIORITY, CONFIG_MAIN_STACK_SIZE, CONFIG_IDLE_STACK_SIZE, CONFIG_THREAD_CUSTOM_DATA, CONFIG_NUM_COOP_PRIORITIES, CONFIG_NUM_PREEMPT_PRIORITIES, CONFIG_TIMESLICING, CONFIG_TIMESLICE_SIZE, CONFIG_TIMESLICE_PRIORITY, CONFIG_USERSPACE입니다.
API 참고: thread_apis
K_THREAD_DEFINE과 K_KERNEL_THREAD_DEFINE의 외부 ID declaration입니다.
extern const k_tid_t <name>;extern const k_tid_t <name>;Options·definitions·callback
K_ESSENTIALAbort 또는 termination을 fatal system error로 취급해야 하는 essential thread option입니다.
K_FP_IDXContext switch가 FPU register를 관리하는 option index이며 CONFIG_FPU_SHARING이 없으면 효과가 없습니다.
K_FP_REGSThread가 floating-point register를 사용하므로 scheduling 때 save·restore하도록 표시합니다.
K_USERCONFIG_USERSPACE에서 reduced privilege user-mode thread로 생성합니다.
K_INHERIT_PERMSCONFIG_USERSPACE에서 parent thread가 가진 kernel object permission을 child가 상속하게 합니다.
K_CALLBACK_STATECallback manager가 user callback 안에서 호출된 operation을 추적하는 zero-overhead TLS 성격의 1-bit state입니다.
K_DSP_IDXCONFIG_DSP_SHARING에서 context switch가 DSP register를 관리하는 option index입니다.
K_DSP_REGSThread의 DSP register context를 save·restore하도록 표시합니다.
K_AGU_IDXARC XY memory·DSP feature용 AGU context option index이며 CONFIG_ARC_AGU_SHARING이 없으면 효과가 없습니다.
K_AGU_REGSThread의 ARC AGU register context를 관리하도록 표시합니다.
K_SSE_REGSCONFIG_X86_SSE에서 x86 FP와 SSE register를 context switch가 save·restore하도록 표시합니다.
k_thread_access_grant(thread, ...)Initialized thread에 나머지 argument로 전달한 kernel object pointer의 access를 부여하는 convenience macro입니다. Object 자체는 아직 initialized일 필요가 없고 NULL은 전달하면 안 됩니다.
매개변수
thread: permission을 받을 running thread...: kernel object pointer 목록
K_THREAD_DEFINE(name, stack_size, entry, p1, p2, p3, prio, options, delay)Stack, control block과 thread ID를 static하게 정의하고 immediate 또는 delayed start로 초기화합니다. MetaIRQ priority의 zero-delay static thread는 system initialization 순서를 의외로 선점할 수 있으므로 SYS_INIT()을 권장합니다.
매개변수
name: thread 이름stack_size: byte 단위 stack sizeentry: entry functionp1/p2/p3: entry argumentprio: priorityoptions: bitwise OR thread optionsdelay: millisecond delay, 0이면 즉시
K_KERNEL_THREAD_DEFINE(name, stack_size, entry, p1, p2, p3, prio, options, delay)Kernel mode 전용 static thread를 정의합니다. k_thread_user_mode_enter()로 user thread가 될 수 없고 architecture에 따라 stack size가 MMU page multiple 또는 MPU power-of-two여야 합니다.
매개변수
name: thread 이름stack_size: byte 단위 stack sizeentry: entry functionp1/p2/p3: entry argumentprio: priorityoptions: optionsdelay: millisecond delay
k_thread_user_cb_tThread enumeration callback type: void (*)(const struct k_thread *thread, void *user_data)입니다.
Functions
k_thread_foreach(user_cb, user_data)CONFIG_THREAD_MONITOR에서 system의 모든 thread에 callback을 호출합니다. _kernel.threads list를 k_spin_lock으로 보호하므로 API가 끝날 때까지 thread creation·termination도 막습니다.
매개변수
user_cb: callbackuser_data: callback data
k_thread_foreach_unlocked(user_cb, user_data)List element 접근 때만 spinlock을 잡고 callback 중에는 풉니다. Enumeration 중 생성된 thread는 빠질 수 있고 abort된 thread가 포함될 수 있습니다. Abort 뒤 k_thread memory를 재사용하면 list traversal이 잘못된 pointer를 따라 system이 unstable하거나 반환하지 않을 수 있으므로 재사용하면 안 됩니다.
매개변수
user_cb: callbackuser_data: callback data
k_thread_stack_alloc(size, flags)CONFIG_DYNAMIC_THREAD에서 stack을 동적 할당합니다. K_USER flag는 userspace stack을 요청합니다.
매개변수
size: byte 단위 stack sizeflags: K_USER 또는 0
반환값
k_thread_stack_t *: 성공한 stackNULL: 실패
k_thread_stack_free(stack)동적 할당한 thread stack을 해제합니다.
매개변수
stack: stack pointer
반환값
0: 성공-EBUSY: 사용 중-EINVAL: invalid stack-ENOSYS: dynamic allocation disabled
k_thread_create(new_thread, stack, stack_size, entry, p1, p2, p3, prio, options, delay)Thread를 초기화하고 immediate 또는 delayed execution으로 schedule합니다. No-delay child가 current thread를 즉시 preempt할 수 있습니다. Stack은 K_THREAD_STACK_DEFINE 또는 K_KERNEL_STACK_DEFINE 계열로 만들어야 하며 sizeof(stack)은 undefined behavior를 만들 수 있으므로 해당 SIZEOF macro나 original requested size를 씁니다.
매개변수
new_thread: uninitialized k_threadstack: stack spacestack_size: 허용된 size 값entry: entry functionp1/p2/p3: entry argumentprio: priorityoptions: thread optionsdelay: delay 또는 K_NO_WAIT
반환값
k_tid_t: 새 thread ID
k_thread_user_mode_enter(entry, p1, p2, p3)Supervisor thread를 user mode로 영구 전환하며 반환하지 않습니다. Stack buffer와 TLS를 pristine state로 지우고 entry에서 새 user thread처럼 실행합니다. Memory domain, resource pool, object permission, priority, options는 유지합니다.
매개변수
entry: 새 실행 entryp1/p2/p3: entry argument
k_thread_heap_assign(thread, heap)Thread의 resource memory pool을 지정합니다. Parent pool은 기본 상속되며 여러 thread가 같은 pool을 쓸 수 있지만 변경해도 기존 allocation은 migrate하지 않습니다.
매개변수
thread: targetheap: heap 또는 pool 제거용 NULL
k_thread_join(thread, timeout)Target이 abort, self-exit 또는 fatal error로 종료될 때까지 sleep합니다. 이미 running이 아니면 즉시 성공합니다. ISR에서는 K_NO_WAIT predicate로만 호출할 수 있습니다.
매개변수
thread: targettimeout: 최대 대기 시간
반환값
0: 종료했거나 running이 아님-EBUSY: no-wait 실패-EAGAIN: timeout-EDEADLK: 상호 join 또는 self join
k_sleep(timeout)Current thread를 k_timeout_t 기간 sleep시킵니다. K_FOREVER면 suspend됩니다.
매개변수
timeout: sleep 기간
반환값
0: 시간이 지났거나 k_wakeup으로 깨어남positive: 남은 시간을 올림한 millisecond
k_msleep(ms)Current thread를 millisecond 단위로 sleep시킵니다.
매개변수
ms: sleep milliseconds
반환값
int32_t: 0 또는 올림한 남은 milliseconds
k_usleep(us)Microsecond resolution sleep입니다. Tick이 최소 sleep 단위이므로 CONFIG_SYS_CLOCK_TICKS_PER_SEC tuning 없이는 기대와 다를 수 있어 주의해야 합니다.
매개변수
us: sleep microseconds
반환값
int32_t: 0 또는 올림한 남은 microseconds
k_busy_wait(usec_to_wait)Current thread가 지정 microseconds 동안 do-nothing loop를 실행합니다. Clock tolerance 때문에 k_sleep clock과 어긋날 수 있고 CONFIG_SYSTEM_CLOCK_SLOPPY_IDLE+CONFIG_PM에서는 delay clock이 inactive여서 동작하지 않을 수 있습니다.
매개변수
usec_to_wait: busy-wait microseconds
k_can_yield(void)PRE_KERNEL init이나 idle thread처럼 yield/blocking API를 못 쓰는 context인지 검사합니다.
반환값
true: yield 가능false: 불가능
k_yield(void)같거나 높은 priority의 다른 ready thread에 execution을 양보하며 그런 thread가 없으면 즉시 반환합니다.
k_wakeup(thread)Sleeping thread를 조기에 깨웁니다. Sleeping 중이 아니면 효과가 없습니다.
매개변수
thread: thread ID
k_sched_current_thread_query(void)System call로 kernel에 current thread ID를 무조건 질의합니다. TLS가 invalid할 수 있는 특수 경우 외에는 k_current_get()을 사용해야 하며 TLS가 없으면 둘은 같습니다.
반환값
k_tid_t: current thread ID
k_current_get(void)Current thread ID를 얻습니다.
반환값
k_tid_t: current thread ID
k_thread_abort(thread)Thread를 영구 중단하고 ready·timeout·object wait queue에서 제거하지만 보유 mutex나 memory는 해제하지 않습니다. 반환 뒤 system 어디에서도 running/runnable이 아님을 보장하며 SMP interrupt context에서는 다른 CPU의 target이 멈출 때까지 spin할 수 있습니다.
매개변수
thread: abort할 ID
k_thread_start(thread)Delay K_FOREVER로 만든 inactive thread를 scheduling queue에 넣습니다.
매개변수
thread: 시작할 thread
k_thread_timeout_expires_ticks(thread)Waiting thread가 다음 실행할 system uptime을 tick으로 계산하며 waiting이 아니면 current system time을 반환합니다.
k_thread_timeout_remaining_ticks(thread)Waiting thread가 실행하기까지 남은 tick을 계산하며 waiting이 아니면 0입니다.
k_thread_priority_get(thread)Thread priority를 반환합니다.
매개변수
thread: ID
반환값
int: priority
k_thread_priority_set(thread, prio)Priority를 즉시 바꾸며 caller/target priority와 preemptibility에 따라 즉시 reschedule할 수 있습니다. 허용 범위는 -CONFIG_NUM_COOP_PRIORITIES부터 CONFIG_NUM_PREEMPT_PRIORITIES-1입니다.
매개변수
thread: IDprio: new priority
k_thread_deadline_set(thread, deadline)Current time에서 cycle 단위 delta로 deadline을 설정합니다. Deadline scheduler는 static priority가 같은 thread끼리 next expiration을 우선합니다. 내부 32-bit 값에서 first·last 차이는 2^31 미만이어야 하며 deadline 안 실행을 보장하거나 runtime/period feasibility를 검증하지 않습니다. CONFIG_SCHED_DEADLINE이 필요합니다.
매개변수
thread: targetdeadline: cycle 단위 delta
k_thread_cpu_mask_clear(thread)모든 CPU enable mask를 0으로 만들어 어느 CPU에도 schedule되지 않게 합니다. Thread는 runnable이면 안 되고 CONFIG_SCHED_CPU_MASK가 필요합니다.
매개변수
thread: target
반환값
0: 성공other: error
k_thread_cpu_mask_enable_all(thread)모든 CPU mask를 1로 만들어 어느 CPU에서도 실행 가능하게 합니다. Thread는 runnable이면 안 됩니다.
매개변수
thread: target
반환값
0: 성공
k_thread_cpu_mask_enable(thread, cpu)지정 CPU에서 thread 실행을 허용합니다. Thread는 runnable이면 안 됩니다.
매개변수
thread: targetcpu: CPU index
반환값
0: 성공
k_thread_cpu_mask_disable(thread, cpu)지정 CPU에서 thread 실행을 막습니다. Thread는 runnable이면 안 됩니다.
매개변수
thread: targetcpu: CPU index
반환값
0: 성공
k_thread_cpu_pin(thread, cpu)CPU mask를 먼저 clear하고 선택 CPU만 enable해 thread를 한 CPU에 pin합니다.
매개변수
thread: targetcpu: CPU index
반환값
0: 성공
k_thread_suspend(thread)Scheduler가 target을 current로 만들지 못하게 합니다. 기존 timeout은 cancel되고 resume 뒤 blocked call에서 즉시 반환합니다. 다른 CPU에서 active면 caller는 halt까지 block하며 interrupt context에서는 spin합니다.
매개변수
thread: suspend할 ID
k_thread_resume(thread)Suspension을 제거해 다음 eligible 시 current가 될 수 있게 합니다. Suspended 상태가 아니면 효과가 없습니다.
매개변수
thread: resume할 ID
k_sched_time_slice_set(slice, prio)Global time slicing period와 범위를 millisecond로 설정합니다. Nonzero slice로 활성화하고 prio보다 높은 priority는 면제합니다. Minimum 실행 시간은 보장하지 않으며 같은 priority eligible thread가 하나면 slice 뒤 즉시 재선택됩니다. slice=prio=0이면 disable합니다.
매개변수
slice: maximum millisecondsprio: slicing 대상의 highest priority level
k_thread_time_slice_set(th, slice_ticks, expired, data)CONFIG_TIMESLICE_PER_THREAD에서 특정 thread의 tick 단위 slice를 설정하며 nonzero 값은 global보다 우선합니다. Expiration callback은 interrupt context에서 아직 thread가 current인 시점에 timeout 처리보다 먼저 호출됩니다.
매개변수
th: initialized threadslice_ticks: maximum ticksexpired: expiration callbackdata: callback parameter
k_sched_lock(void)Current thread를 cooperative처럼 취급해 normal thread preemption을 막습니다. Recursive하며 non-userspace에서는 thread struct byte 증감으로 매우 빠릅니다. 다른 CPU, interrupt, MetaIRQ preemption은 막지 않는 historical API이므로 주의합니다.
k_sched_unlock(void)k_sched_lock() 효과를 한 단계 되돌리며 lock 횟수만큼 호출해야 preemptible이 됩니다.
k_thread_custom_data_set(value)Kernel이 사용하지 않는 current thread의 custom data를 설정해 간단한 TLS 기반으로 쓸 수 있습니다.
매개변수
value: new value
k_thread_custom_data_get(void)Current thread custom data를 반환합니다.
반환값
void *: current value
k_thread_name_set(thread, str)CONFIG_THREAD_MONITOR tracing/debug용 이름을 설정합니다. NULL thread는 current thread를 뜻합니다.
매개변수
thread: target 또는 NULLstr: name
반환값
0: 성공-EFAULT: string memory access error-ENOSYS: name option disabled-EINVAL: name이 너무 김
k_thread_name_get(thread)Thread 이름을 얻습니다.
매개변수
thread: ID
반환값
const char *: name 또는 feature disabled면 NULL
k_thread_name_copy(thread, buf, size)Thread 이름을 caller buffer로 복사합니다.
매개변수
thread: targetbuf: destinationsize: buffer size
반환값
0: 성공-ENOSPC: buffer 부족-EFAULT: memory error-ENOSYS: feature disabled
k_thread_state_str(thread_id, buf, buf_size)Human-readable thread state string을 가능한 만큼 buffer에 복사합니다.
매개변수
thread_id: IDbuf: destinationbuf_size: size
반환값
buf: data가 복사됨"": 복사할 data 없음
k_thread structure
k_threadThread Structure이며 include 경로는 thread.h입니다.
callee_savedArchitecture가 정의하는 callee-saved context입니다.
init_dataStatic thread initialization data입니다.
join_queuek_thread_join()에서 기다리는 thread queue입니다.
entryThread entry와 parameter description입니다.
next_thread전체 thread list의 next item입니다.
custom_data간단한 thread-local storage입니다.
stack_infoThread stack boundary와 정보를 담습니다.
mem_domain_infoThread memory-domain 정보입니다.
stack_objThread stack base 주소이며 CONFIG_THREAD_STACK_MEM_MAPPED이면 physical address입니다.
syscall_frameCurrent syscall frame pointer입니다.
swap_retvalz_swap() return value입니다.
switch_handlearch_switch()가 반환한 context handle입니다.
resource_poolThread resource pool인 k_heap pointer입니다.
halt_queuek_thread_suspend()에서 기다리는 thread queue입니다.
arch항상 구조체 끝에 있어야 하는 architecture-specific data입니다.
Thread stack API 참고: thread_stack_api
K_KERNEL_STACK_DECLARE(sym, size)다른 곳에 정의된 kernel thread stack symbol을 현재 scope에 선언합니다.
매개변수
sym: symbolsize: region size
K_KERNEL_STACK_ARRAY_DECLARE(sym, nmemb, size)다른 곳에 정의된 kernel stack array를 선언합니다.
매개변수
sym: symbolnmemb: stack 수size: 각 region size
K_KERNEL_PINNED_STACK_ARRAY_DECLARE(sym, nmemb, size)다른 곳의 pinned kernel stack array를 선언합니다.
매개변수
sym: symbolnmemb: 수size: size
K_KERNEL_STACK_DEFINE(sym, size)Supervisor-only thread와 interrupt/exception용 top-level kernel stack을 정의합니다. User thread는 쓸 수 없고 sizeof(sym) 대신 K_KERNEL_STACK_SIZEOF()을 써야 하며 guard 때문에 allocation이 커질 수 있습니다.
매개변수
sym: symbolsize: requested size
K_KERNEL_PINNED_STACK_DEFINE(sym, size)CONFIG_LINKER_USE_PINNED_SECTION이면 pinned noinit section에 kernel stack을 정의하며 아니면 일반 kernel stack section을 사용합니다.
매개변수
sym: symbolsize: size
K_KERNEL_STACK_ARRAY_DEFINE(sym, nmemb, size)User thread를 host할 수 없는 kernel stack array를 정의합니다.
매개변수
sym: symbolnmemb: 수size: size
K_KERNEL_PINNED_STACK_ARRAY_DEFINE(sym, nmemb, size)Pinned section에 kernel stack array를 정의합니다.
매개변수
sym: symbolnmemb: 수size: size
K_KERNEL_STACK_MEMBER(sym, size)다른 data structure 안에 embedded kernel stack region을 정의하며 user thread는 사용할 수 없습니다.
매개변수
sym: symbolsize: size
K_KERNEL_STACK_SIZEOF(sym)Kernel stack object에 k_thread_create()로 전달해도 안전한 실제 stack size를 반환합니다.
K_THREAD_STACK_DECLARE(sym, size)다른 곳에 정의된 generic thread stack을 선언합니다.
매개변수
sym: symbolsize: size
K_THREAD_STACK_ARRAY_DECLARE(sym, nmemb, size)다른 곳에 정의된 generic thread stack array를 선언합니다.
매개변수
sym: symbolnmemb: 수size: size
K_THREAD_STACK_SIZEOF(sym)Guard·alignment로 underlying allocation이 더 커질 수 있음을 반영해 k_thread_create()에 안전하게 전달할 stack buffer size를 반환합니다.
매개변수
sym: stack symbol
반환값
size_t: stack buffer size
K_THREAD_STACK_DEFINE(sym, size)User 또는 supervisor thread에 적합한 top-level stack을 정렬해 noinit section에 정의합니다. Symbol은 k_thread_stack_t이고 직접 조작하지 않으며 sizeof 대신 K_THREAD_STACK_SIZEOF()을 사용합니다.
매개변수
sym: symbolsize: requested size
K_THREAD_PINNED_STACK_DEFINE(sym, size)Generic thread stack을 pinned noinit section에 정의하며 pinned section을 쓰지 않으면 일반 thread stack section을 사용합니다.
매개변수
sym: symbolsize: size
K_THREAD_STACK_LEN(size)Stack array member가 필요한 alignment·MPU padding을 포함하도록 allocation size를 계산합니다.
매개변수
size: requested size
반환값
size_t: array member에 적합한 size
K_THREAD_STACK_ARRAY_DEFINE(sym, nmemb, size)동일 크기의 generic thread stack array를 top level에 정의합니다.
매개변수
sym: symbolnmemb: 수size: size
K_THREAD_PINNED_STACK_ARRAY_DEFINE(sym, nmemb, size)동일 크기의 generic stack array를 pinned noinit section에 정의합니다.
매개변수
sym: symbolnmemb: 수size: size
K_THREAD_STACK_MEMBER(sym, size)다른 structure 안에 generic stack을 embed하는 deprecated macro입니다. 앞 RAM을 user thread가 쓸 수 있으면 overflow가 silent corruption을 만들 수 있어 containing structure는 kernel-owned RAM이어야 합니다. User thread는 supervisor creator만 시작할 수 있으며 K_KERNEL_STACK_MEMBER 사용을 권장합니다.
매개변수
sym: symbolsize: size
Source
출처
영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.