Part 1
요약·해설
ELF/DWARF object discovery, public·inherited permission, initialization state와 새 object type 등록 절차를 설명합니다.
ELF · DWARF · gperf
grant · revoke · inherit
reference · cleanup · free
Part 2
접을 수 있는 영어 원문 전체
영어 원문 전체 펼치기
BB4ADE4D97E61D1D4AF689DBED4BB6E76618D6D8B248DCAAFDCD8B4C65C264DA
.. _kernelobjects:
Kernel Objects
##############
A kernel object can be one of three classes of data:
* A core kernel object, such as a semaphore, thread, pipe, etc.
* A thread stack, which is an array of :c:struct:`z_thread_stack_element`
and declared with :c:macro:`K_THREAD_STACK_DEFINE()`
* A device driver instance (const struct device) that belongs to one of a defined
set of subsystems
The set of known kernel objects and driver subsystems is defined in
include/kernel.h as :c:enum:`k_objects`.
Kernel objects are completely opaque to user threads. User threads work
with addresses to kernel objects when making API calls, but may never
dereference these addresses, doing so will cause a memory protection fault.
All kernel objects must be placed in memory that is not accessible by
user threads.
Since user threads may not directly manipulate kernel objects, all use of
them must go through system calls. In order to perform a system call on
a kernel object, checks are performed by system call handler functions
that the kernel object address is valid and that the calling thread
has sufficient permissions to work with it.
Permission on an object also has the semantics of a reference to an object.
This is significant for certain object APIs which do temporary allocations,
or objects which themselves have been allocated from a runtime memory pool.
If an object loses all references, two events may happen:
* If the object has an associated cleanup function, the cleanup function
may be called to release any runtime-allocated buffers the object was using.
* If the object itself was dynamically allocated, the memory for the object
will be freed.
Object Placement
****************
Kernel objects that are only used by supervisor threads have no restrictions
and can be located anywhere in the binary, or even declared on stacks. However,
to prevent accidental or intentional corruption by user threads, they must
not be located in any memory that user threads have direct access to.
In order for a static kernel object to be usable by a user thread via system
call APIs, several conditions must be met on how the kernel object is declared:
* The object must be declared as a top-level global at build time, such that it
appears in the ELF symbol table. It is permitted to declare kernel objects
with static scope. The post-build script :ref:`gen_kobject_list.py` scans the
generated ELF file to find kernel objects and places their memory addresses
in a special table of kernel object metadata. Kernel objects may be members
of arrays or embedded within other data structures.
* Kernel objects must be located in memory reserved for the kernel. They
must not be located in any memory partitions that are user-accessible.
* Any memory reserved for a kernel object must be used exclusively for that
object. Kernel objects may not be members of a union data type.
Kernel objects that are found but do not meet the above conditions will not be
included in the generated table that is used to validate kernel object pointers
passed in from user mode.
The debug output of the :ref:`gen_kobject_list.py` script may be useful when
debugging why some object was unexpectedly not being tracked. This
information will be printed if the script is run with the ``--verbose`` flag,
or if the build system is invoked with verbose output.
Dynamic Objects
***************
Kernel objects may also be allocated at runtime if
:kconfig:option:`CONFIG_DYNAMIC_OBJECTS` is enabled. In this case, the
:c:func:`k_object_alloc` API may be used to instantiate an object from
the calling thread's resource pool. Such allocations may be freed in two
ways:
* Supervisor threads may call :c:func:`k_object_free` to force a dynamic
object to be released.
* If an object's references drop to zero (which happens when no threads have
permissions on it) the object will be automatically freed. User threads
may drop their own permission on an object with
:c:func:`k_object_release`, and their permissions are automatically
cleared when a thread terminates. Supervisor threads may additionally
revoke references for another thread using
:c:func:`k_object_access_revoke`.
Because permissions are also used for reference counting, it is important for
supervisor threads to acquire permissions on objects they are using even though
the access control aspects of the permission system are not enforced.
Implementation Details
======================
The :ref:`gen_kobject_list.py` script is a post-build step which finds all the
valid kernel object instances in the binary. It accomplishes this by parsing
the DWARF debug information present in the generated ELF file for the kernel.
Any instances of structs or arrays corresponding to kernel objects that meet
the object placement criteria will have their memory addresses placed in a
special perfect hash table of kernel objects generated by the 'gperf' tool.
When a system call is made and the kernel is presented with a memory address
of what may or may not be a valid kernel object, the address can be validated
with a constant-time lookup in this table.
Drivers are a special case. All drivers are instances of :c:struct:`device`, but
it is important to know what subsystem a driver belongs to so that
incorrect operations, such as calling a UART API on a sensor driver object, can
be prevented. When a device struct is found, its API pointer is examined to
determine what subsystem the driver belongs to.
The table itself maps kernel object memory addresses to instances of
:c:struct:`z_object`, which has all the metadata for that object. This
includes:
* A bitfield indicating permissions on that object. All threads have a
numerical ID assigned to them at build time, used to index the permission
bitfield for an object to see if that thread has permission on it. The size
of this bitfield is controlled by the :kconfig:option:`CONFIG_MAX_THREAD_BYTES`
option and the build system will generate an error if this value is too low.
* A type field indicating what kind of object this is, which is some
instance of :c:enum:`k_objects`.
* A set of flags for that object. This is currently used to track
initialization state and whether an object is public or not.
* An extra data field. The semantics of this field vary by object type, see
the definition of :c:union:`z_object_data`.
Dynamic objects allocated at runtime are tracked in a runtime red/black tree
which is used in parallel to the gperf table when validating object pointers.
Supervisor Thread Access Permission
***********************************
Supervisor threads can access any kernel object. However, permissions for
supervisor threads are still tracked for two reasons:
* If a supervisor thread calls :c:func:`k_thread_user_mode_enter`, the
thread will then run in user mode with any permissions it had been granted
(in many cases, by itself) when it was a supervisor thread.
* If a supervisor thread creates a user thread with the
:c:macro:`K_INHERIT_PERMS` option, the child thread will be granted the
same permissions as the parent thread, except the parent thread object.
User Thread Access Permission
*****************************
By default, when a user thread is created, it will only have access permissions
on its own thread object. Other kernel objects by default are not usable.
Access to them needs to be explicitly or implicitly granted. There are several
ways to do this.
* If a thread is created with the :c:macro:`K_INHERIT_PERMS`, that thread
will inherit all the permissions of the parent thread, except the parent
thread object.
* A thread that has permission on an object, or is running in supervisor mode,
may grant permission on that object to another thread via the
:c:func:`k_object_access_grant` API. The convenience pseudo-function
:c:func:`k_thread_access_grant` may also be used, which accepts an arbitrary
number of pointers to kernel objects and calls
:c:func:`k_object_access_grant` on each of them. The thread being granted
permission, or the object whose access is being granted, do not need to be
in an initialized state. If the caller is from user mode, the caller must
have permissions on both the kernel object and the target thread object.
* Supervisor threads may declare a particular kernel object to be a public
object, usable by all current and future threads with the
:c:func:`k_object_access_all_grant` API. You must assume that any
untrusted or exploited code will then be able to access the object. Use
this API with caution!
* If a thread was declared statically with :c:macro:`K_THREAD_DEFINE()`,
then the :c:macro:`K_THREAD_ACCESS_GRANT()` may be used to grant that thread
access to a set of kernel objects at boot time.
Once a thread has been granted access to an object, such access may be
removed with the :c:func:`k_object_access_revoke` API. This API is not
available to user threads, however user threads may use
:c:func:`k_object_release` to relinquish their own permissions on an
object.
API calls from supervisor mode to set permissions on kernel objects that are
not being tracked by the kernel will be no-ops. Doing the same from user mode
will result in a fatal error for the calling thread.
Objects allocated with :c:func:`k_object_alloc` implicitly grant
permission on the allocated object to the calling thread.
Initialization State
********************
Most operations on kernel objects will fail if the object is considered to be
in an uninitialized state. The appropriate init function for the object must
be performed first.
Some objects will be implicitly initialized at boot:
* Kernel objects that were declared with static initialization macros
(such as :c:macro:`K_SEM_DEFINE` for semaphores) will be in an initialized
state at build time.
* Device driver objects are considered initialized after their init function
is run by the kernel early in the boot process.
If a kernel object is initialized with a private static initializer, the object
must have :c:func:`k_object_init` called on it at some point by a supervisor
thread, otherwise the kernel will consider the object uninitialized if accessed
by a user thread. This is very uncommon, typically only for kernel objects that
are embedded within some larger struct and initialized statically.
.. code-block:: c
struct foo {
struct k_sem sem;
...
};
struct foo my_foo = {
.sem = Z_SEM_INITIALIZER(my_foo.sem, 0, 1),
...
};
...
k_object_init(&my_foo.sem);
...
Creating New Kernel Object Types
********************************
When implementing new kernel features or driver subsystems, it may be necessary
to define some new kernel object types. There are different steps needed
for creating core kernel objects and new driver subsystems.
Creating New Core Kernel Objects
================================
* In ``scripts/build/gen_kobject_list.py``, add the name of the struct to the
:py:data:`kobjects` list.
Instances of the new struct should now be tracked.
Creating New Driver Subsystem Kernel Objects
============================================
All driver instances are :c:struct:`device`. They are differentiated by
what API struct they are set to.
* In ``scripts/build/gen_kobject_list.py``, add the name of the API struct for the
new subsystem to the :py:data:`subsystems` list.
Driver instances of the new subsystem should now be tracked.
Configuration Options
*********************
Related configuration options:
* :kconfig:option:`CONFIG_USERSPACE`
* :kconfig:option:`CONFIG_MAX_THREAD_BYTES`
API Reference
*************
.. doxygengroup:: usermode_apis
Part 3
한국어 전문 번역
Kernel object는 semaphore·thread·pipe 같은 core object, K_THREAD_STACK_DEFINE()으로 선언한 z_thread_stack_element array인 thread stack, 정의된 subsystem 중 하나에 속한 const struct device driver instance의 세 class입니다. Known object와 driver subsystem 집합은 include/kernel.h의 k_objects enum에 정의됩니다.
Kernel object는 user thread에서 완전히 opaque합니다. System call에 address를 넘길 수 있지만 dereference하면 memory protection fault가 발생합니다. 따라서 모든 object는 user thread가 direct access할 수 없는 memory에 있어야 합니다.
User thread의 모든 object operation은 system call을 거치고 handler가 address validity와 caller permission을 검사합니다.
Object placement
Supervisor-only object는 binary 어디나 stack에도 둘 수 있지만 user-accessible memory에는 두면 안 됩니다. User thread가 system call로 사용할 static object는 다음 조건을 모두 만족해야 합니다.
- Build time top-level global로 ELF symbol table에 나타나야 합니다. Static scope도 허용되고 array member 또는 다른 structure에 embed할 수 있습니다.
- Kernel-reserved memory에 있어야 하며 user-accessible partition에 있으면 안 됩니다.
- Reserved memory는 해당 object만 독점해야 하므로 union member일 수 없습니다.
Post-build gen_kobject_list.py가 ELF를 scan해 object address를 metadata table에 넣습니다. 조건을 못 맞춘 object는 user-mode pointer validation table에 포함되지 않습니다. --verbose 또는 verbose build output은 추적되지 않은 object를 진단하는 데 유용합니다.
Dynamic objects
CONFIG_DYNAMIC_OBJECTS를 켜면 k_object_alloc()으로 caller resource pool에서 runtime object를 만들 수 있습니다. Supervisor는 k_object_free()로 강제 해제할 수 있고, permission reference가 0이 되면 자동 해제됩니다.
User thread는 k_object_release()로 자기 permission을 포기하며 종료 시 permission도 자동 clear됩니다. Supervisor는 k_object_access_revoke()로 다른 thread reference를 제거할 수 있습니다.
Implementation details
Post-build gen_kobject_list.py는 generated ELF의 DWARF debug information을 parse해 placement criteria를 만족하는 object instance와 array를 찾습니다. gperf가 object address에서 z_object metadata로 constant-time lookup하는 perfect hash table을 만듭니다.
모든 driver는 struct device이므로 API pointer를 검사해 UART·sensor 같은 subsystem을 구분합니다. 이를 통해 sensor driver object에 UART API를 호출하는 잘못을 막습니다.
z_object metadata에는 CONFIG_MAX_THREAD_BYTES가 크기를 정하는 per-thread permission bitfield, k_objects type, initialized/public 같은 flag와 object-specific z_object_data가 있습니다. Thread에는 build-time numerical ID가 있어 bitfield index로 사용되며 option이 너무 작으면 build error가 납니다.
Runtime dynamic object는 gperf static table과 병행해 조회하는 red-black tree에서 추적합니다.
ELF + DWARF scan
address → z_object constant-time lookup
runtime red-black tree
address · type · initialized · permission 검사
Supervisor thread access permission
Supervisor thread는 모든 kernel object에 접근할 수 있지만 permission을 추적합니다. k_thread_user_mode_enter() 뒤에도 이전 permission을 유지하고, K_INHERIT_PERMS로 user child를 만들면 parent thread object를 제외한 permission을 상속하기 때문입니다.
User thread access permission
새 user thread는 기본적으로 자기 thread object에만 permission이 있습니다. 그 밖의 object는 explicit 또는 implicit grant가 필요합니다.
- K_INHERIT_PERMS는 parent thread object를 제외한 parent permission을 상속합니다.
- Object permission이 있는 thread 또는 supervisor는 k_object_access_grant()로 다른 thread에 grant할 수 있습니다. k_thread_access_grant()는 여러 object를 한 번에 처리합니다. Target thread와 object가 아직 initialized일 필요는 없지만 user caller는 둘 모두 permission이 있어야 합니다.
- k_object_access_all_grant()는 현재·미래 모든 thread에 공개합니다.
- K_THREAD_DEFINE() static thread에는 K_THREAD_ACCESS_GRANT()로 boot-time grant할 수 있습니다.
k_object_access_revoke()가 특정 thread access를 제거합니다. User thread는 대신 k_object_release()로 자기 permission을 포기합니다. Supervisor가 kernel이 추적하지 않는 object에 permission API를 호출하면 no-op이지만 user mode에서 같은 호출은 caller fatal error입니다. k_object_alloc()은 caller permission을 implicit grant합니다.
Initialization state
대부분 object operation은 uninitialized object에서 실패하므로 적절한 init function을 먼저 호출합니다. K_SEM_DEFINE 같은 static initialization macro object는 build time initialized이고 device object는 early boot init function 뒤 initialized로 간주합니다.
Larger structure에 embed하고 private static initializer를 쓴 드문 object는 supervisor가 k_object_init()을 명시적으로 호출해야 user thread 접근 때 initialized로 인정됩니다.
struct foo {
struct k_sem sem;
...
};
struct foo my_foo = {
.sem = Z_SEM_INITIALIZER(my_foo.sem, 0, 1),
...
};
...
k_object_init(&my_foo.sem);
...새 kernel object type 만들기
Core kernel object
scripts/build/gen_kobject_list.py의 kobjects list에 새 structure 이름을 추가하면 instance를 추적합니다.
Driver subsystem object
모든 driver는 struct device이고 API structure로 구분됩니다. gen_kobject_list.py의 subsystems list에 새 subsystem API structure 이름을 추가하면 해당 driver instance를 추적합니다.
설정과 API 참고: usermode_apis
관련 option은 CONFIG_USERSPACE와 CONFIG_MAX_THREAD_BYTES입니다.
K_THREAD_ACCESS_GRANT(name_, ...)K_THREAD_DEFINE()으로 선언한 static thread에 kernel object 목록의 permission을 early boot initialization 때 부여합니다. Object는 아직 initialized일 필요가 없고 첫 argument 뒤는 모두 kernel-object pointer여야 합니다.
매개변수
name_: K_THREAD_DEFINE()에 사용한 thread 이름...: kernel object pointer 목록
K_OBJ_FLAG_INITIALIZEDObject가 initialized 상태임을 나타냅니다.
K_OBJ_FLAG_PUBLIC모든 thread가 접근 가능한 public object임을 나타냅니다.
K_OBJ_FLAG_ALLOCObject 자체가 runtime allocation되었음을 나타냅니다.
K_OBJ_FLAG_DRIVERDriver object임을 나타냅니다.
k_object_access_grant(object, thread)Supervisor caller이거나, user caller가 object와 target thread 둘 다 permission을 가진 경우 target thread에 object access를 부여합니다.
매개변수
object: kernel object 주소thread: permission을 받을 thread
k_object_access_revoke(object, thread)Target thread의 object access reference를 제거합니다. Supervisor mode에서 사용할 수 있으며 생성 API 설명상 user caller가 양쪽 permission을 가지는 경우도 검증됩니다. Public object permission은 revoke할 수 없습니다.
매개변수
object: kernel object 주소thread: permission을 잃을 thread
k_object_release(object)User thread가 자신의 object permission/reference를 포기합니다. Thread 종료 때도 permission이 자동 clear됩니다.
매개변수
object: release할 object
k_object_access_all_grant(object)현재와 미래 모든 thread에 permission을 부여해 object를 public으로 만듭니다. Untrusted code도 주소를 유추해 사용할 수 있고 한 번 public이 되면 permission을 revoke할 수 없으므로 신중히 사용합니다.
매개변수
object: public으로 만들 object
k_object_is_valid(obj, otype)Object가 존재하고 요청 type이며 initialized인지 확인합니다. Type 검사를 생략하려면 K_OBJ_ANY를 사용합니다.
매개변수
obj: kernel object 주소otype: expected object type
반환값
true: 존재·type·initialization 모두 validfalse: 그 밖의 경우
k_object_alloc(otype)CONFIG_DYNAMIC_OBJECTS에서 caller resource pool로 지정 type object를 runtime allocation합니다. Uninitialized 상태로 반환하고 caller에게 implicit permission을 줍니다. Variable-size thread stack에는 alloc_size를 사용합니다.
매개변수
otype: requested kernel object type
반환값
void *: allocated objectNULL: memory 부족
k_object_alloc_size(otype, size)CONFIG_DYNAMIC_OBJECTS에서 지정 type과 byte size로 object를 allocation합니다. 크기가 다양한 thread stack에 특히 적합하며 다른 object는 보통 k_object_alloc()을 사용합니다.
매개변수
otype: object typesize: requested bytes
반환값
void *: uninitialized object with caller permissionNULL: memory 부족
k_object_free(obj)Supervisor가 k_object_alloc() 계열로 만든 dynamic object memory를 원래 resource pool에 반환합니다. 호출 중과 이후 누구도 object를 사용하지 않도록 보장해야 합니다. CONFIG_DYNAMIC_OBJECTS가 필요합니다.
매개변수
obj: dynamic object pointer
Source
출처
영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.