kernel/services/timing/clocks.html · kernel/services/timing/clocks.rst 공식 원문 ↗

Zephyr 3.7.0 · Kernel · Timing

Kernel Timing

Real-time 단위, hardware cycle과 kernel tick을 연결해 portable timeout과 정밀 event scheduling을 제공합니다.

Official pathkernel/services/timing/clocks.html
Source filekernel/services/timing/clocks.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

Relative·absolute timeout의 기준 시각, timer-driver announce contract, SMP serialization과 migration-safe timepoint 사용법을 설명합니다.

Units

real · cycle · tick

Timeout

relative · absolute

Driver

announce · elapsed · tickless

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 7868336720049150FFAC2F6A086B6C7A30FE91B271E0DBECF1DC1971C7AA52E0
.. _kernel_timing:

Kernel Timing
#############

Zephyr provides a robust and scalable timing framework to enable
reporting and tracking of timed events from hardware timing sources of
arbitrary precision.

Time Units
==========

Kernel time is tracked in several units which are used for different
purposes.

Real time values, typically specified in milliseconds or microseconds,
are the default presentation of time to application code.  They have
the advantages of being universally portable and pervasively
understood, though they may not match the precision of the underlying
hardware perfectly.

The kernel presents a "cycle" count via the :c:func:`k_cycle_get_32`
and :c:func:`k_cycle_get_64` APIs.  The intent is that this counter
represents the fastest cycle counter that the operating system is able
to present to the user (for example, a CPU cycle counter) and that the
read operation is very fast.  The expectation is that very sensitive
application code might use this in a polling manner to achieve maximal
precision.  The frequency of this counter is required to be steady
over time, and is available from
:c:func:`sys_clock_hw_cycles_per_sec` (which on almost all
platforms is a runtime constant that evaluates to
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC).

For asynchronous timekeeping, the kernel defines a "ticks" concept.  A
"tick" is the internal count in which the kernel does all its internal
uptime and timeout bookkeeping.  Interrupts are expected to be
delivered on tick boundaries to the extent practical, and no
fractional ticks are tracked.  The choice of tick rate is configurable
via :kconfig:option:`CONFIG_SYS_CLOCK_TICKS_PER_SEC`.  Defaults on most
hardware platforms (ones that support setting arbitrary interrupt
timeouts) are expected to be in the range of 10 kHz, with software
emulation platforms and legacy drivers using a more traditional 100 Hz
value.

Conversion
----------

Zephyr provides an extensively enumerated conversion library with
rounding control for all time units.  Any unit of "ms" (milliseconds),
"us" (microseconds), "tick", or "cyc" can be converted to any other.
Control of rounding is provided, and each conversion is available in
"floor" (round down to nearest output unit), "ceil" (round up) and
"near" (round to nearest).  Finally the output precision can be
specified as either 32 or 64 bits.

For example: :c:func:`k_ms_to_ticks_ceil32` will convert a
millisecond input value to the next higher number of ticks, returning
a result truncated to 32 bits of precision; and
:c:func:`k_cyc_to_us_floor64` will convert a measured cycle count
to an elapsed number of microseconds in a full 64 bits of precision.
See the reference documentation for the full enumeration of conversion
routines.

On most platforms, where the various counter rates are integral
multiples of each other and where the output fits within a single
word, these conversions expand to a 2-4 operation sequence, requiring
full precision only where actually required and requested.

.. _kernel_timing_uptime:

Uptime
======

The kernel tracks a system uptime count on behalf of the application.
This is available at all times via :c:func:`k_uptime_get`, which
provides an uptime value in milliseconds since system boot.  This is
expected to be the utility used by most portable application code.

The internal tracking, however, is as a 64 bit integer count of ticks.
Apps with precise timing requirements (that are willing to do their
own conversions to portable real time units) may access this with
:c:func:`k_uptime_ticks`.

Timeouts
========

The Zephyr kernel provides many APIs with a "timeout" parameter.
Conceptually, this indicates the time at which an event will occur.
For example:

* Kernel blocking operations like :c:func:`k_sem_take` or
  :c:func:`k_queue_get` may provide a timeout after which the
  routine will return with an error code if no data is available.

* Kernel :c:struct:`k_timer` objects must specify delays for
  their duration and period.

* The kernel :c:struct:`k_work_delayable` API provides a timeout parameter
  indicating when a work queue item will be added to the system queue.

All these values are specified using a :c:type:`k_timeout_t` value.  This is
an opaque struct type that must be initialized using one of a family
of kernel timeout macros.  The most common, :c:macro:`K_MSEC`, defines
a time in milliseconds after the current time.

What is meant by "current time" for relative timeouts depends on the context:

* When scheduling a relative timeout from within a timeout callback (e.g. from
  within the expiry function passed to :c:func:`k_timer_init` or the work handler
  passed to :c:func:`k_work_init_delayable`), "current time" is the exact time at
  which the currently firing timeout was originally scheduled even if the "real
  time" will already have advanced. This is to ensure that timers scheduled from
  within another timer's callback will always be calculated with a precise offset
  to the firing timer. It is thereby possible to fire at regular intervals without
  introducing systematic clock drift over time.

* When scheduling a timeout from application context, "current time" means the
  value returned by :c:func:`k_uptime_ticks` at the time at which the kernel
  receives the timeout value.

Other options for timeout initialization follow the unit conventions
described above: :c:macro:`K_NSEC()`, :c:macro:`K_USEC`, :c:macro:`K_TICKS` and
:c:macro:`K_CYC()` specify timeout values that will expire after specified
numbers of nanoseconds, microseconds, ticks and cycles, respectively.

Precision of :c:type:`k_timeout_t` values is configurable, with the default
being 32 bits.  Large uptime counts in non-tick units will experience
complicated rollover semantics, so it is expected that
timing-sensitive applications with long uptimes will be configured to
use a 64 bit timeout type.

Finally, it is possible to specify timeouts as absolute times since
system boot.  A timeout initialized with :c:macro:`K_TIMEOUT_ABS_MS`
indicates a timeout that will expire after the system uptime reaches
the specified value.  There are likewise nanosecond, microsecond,
cycles and ticks variants of this API.

Timing Internals
================

Timeout Queue
-------------

All Zephyr :c:type:`k_timeout_t` events specified using the API above are
managed in a single, global queue of events.  Each event is stored in
a double-linked list, with an attendant delta count in ticks from the
previous event.  The action to take on an event is specified as a
callback function pointer provided by the subsystem requesting the
event, along with a :c:struct:`_timeout` tracking struct that is
expected to be embedded within subsystem-defined data structures (for
example: a :c:struct:`wait_q` struct, or a :c:type:`k_tid_t` thread struct).

Note that all variant units passed via a :c:type:`k_timeout_t` are converted
to ticks once on insertion into the list.  There no
multiple-conversion steps internal to the kernel, so precision is
guaranteed at the tick level no matter how many events exist or how
long a timeout might be.

Note that the list structure means that the CPU work involved in
managing large numbers of timeouts is quadratic in the number of
active timeouts.  The API design of the timeout queue was intended to
permit a more scalable backend data structure, but no such
implementation exists currently.

Timer Drivers
-------------

Kernel timing at the tick level is driven by a timer driver with a
comparatively simple API.

* The driver is expected to be able to "announce" new ticks to the
  kernel via the :c:func:`sys_clock_announce` call, which passes an integer
  number of ticks that have elapsed since the last announce call (or
  system boot).  These calls can occur at any time, but the driver is
  expected to attempt to ensure (to the extent practical given
  interrupt latency interactions) that they occur near tick boundaries
  (i.e. not "halfway through" a tick), and most importantly that they
  be correct over time and subject to minimal skew vs. other counters
  and real world time.

* The driver is expected to provide a :c:func:`sys_clock_set_timeout` call
  to the kernel which indicates how many ticks may elapse before the
  kernel must receive an announce call to trigger registered timeouts.
  It is legal to announce new ticks before that moment (though they
  must be correct) but delay after that will cause events to be
  missed.  Note that the timeout value passed here is in a delta from
  current time, but that does not absolve the driver of the
  requirement to provide ticks at a steady rate over time.  Naive
  implementations of this function are subject to bugs where the
  fractional tick gets "reset" incorrectly and causes clock skew.

* The driver is expected to provide a :c:func:`sys_clock_elapsed` call which
  provides a current indication of how many ticks have elapsed (as
  compared to a real world clock) since the last call to
  :c:func:`sys_clock_announce`, which the kernel needs to test newly
  arriving timeouts for expiration.

Note that a natural implementation of this API results in a "tickless"
kernel, which receives and processes timer interrupts only for
registered events, relying on programmable hardware counters to
provide irregular interrupts.  But a traditional, "ticked" or "dumb"
counter driver can be trivially implemented also:

* The driver can receive interrupts at a regular rate corresponding to
  the OS tick rate, calling :c:func:`sys_clock_announce` with an argument of one
  each time.

* The driver can ignore calls to :c:func:`sys_clock_set_timeout`, as every
  tick will be announced regardless of timeout status.

* The driver can return zero for every call to :c:func:`sys_clock_elapsed`
  as no more than one tick can be detected as having elapsed (because
  otherwise an interrupt would have been received).


SMP Details
-----------

In general, the timer API described above does not change when run in
a multiprocessor context.  The kernel will internally synchronize all
access appropriately, and ensure that all critical sections are small
and minimal.  But some notes are important to detail:

* Zephyr is agnostic about which CPU services timer interrupts.  It is
  not illegal (though probably undesirable in some circumstances) to
  have every timer interrupt handled on a single processor.  Existing
  SMP architectures implement symmetric timer drivers.

* The :c:func:`sys_clock_announce` call is expected to be globally
  synchronized at the driver level.  The kernel does not do any
  per-CPU tracking, and expects that if two timer interrupts fire near
  simultaneously, that only one will provide the current tick count to
  the timing subsystem.  The other may legally provide a tick count of
  zero if no ticks have elapsed.  It should not "skip" the announce
  call because of timeslicing requirements (see below).

* Some SMP hardware uses a single, global timer device, others use a
  per-CPU counter.  The complexity here (for example: ensuring counter
  synchronization between CPUs) is expected to be managed by the
  driver, not the kernel.

* The next timeout value passed back to the driver via
  :c:func:`sys_clock_set_timeout` is done identically for every CPU.
  So by default, every CPU will see simultaneous timer interrupts for
  every event, even though by definition only one of them should see a
  non-zero ticks argument to :c:func:`sys_clock_announce`.  This is probably
  a correct default for timing sensitive applications (because it
  minimizes the chance that an errant ISR or interrupt lock will delay
  a timeout), but may be a performance problem in some cases.  The
  current design expects that any such optimization is the
  responsibility of the timer driver.

Time Slicing
------------

An auxiliary job of the timing subsystem is to provide tick counters
to the scheduler that allow implementation of time slicing of threads.
A thread time-slice cannot be a timeout value, as it does not reflect
a global expiration but instead a per-CPU value that needs to be
tracked independently on each CPU in an SMP context.

Because there may be no other hardware available to drive timeslicing,
Zephyr multiplexes the existing timer driver.  This means that the
value passed to :c:func:`sys_clock_set_timeout` may be clamped to a
smaller value than the current next timeout when a time sliced thread
is currently scheduled.

Subsystems that keep millisecond APIs
-------------------------------------

In general, code like this will port just like applications code will.
Millisecond values from the user may be treated any way the subsystem
likes, and then converted into kernel timeouts using
:c:macro:`K_MSEC()` at the point where they are presented to the
kernel.

Obviously this comes at the cost of not being able to use new
features, like the higher precision timeout constructors or absolute
timeouts.  But for many subsystems with simple needs, this may be
acceptable.

One complexity is :c:macro:`K_FOREVER`.  Subsystems that might have
been able to accept this value to their millisecond API in the past no
longer can, because it is no longer an integral type.  Such code
will need to use a different, integer-valued token to represent
"forever".  :c:macro:`K_NO_WAIT` has the same typesafety concern too,
of course, but as it is (and has always been) simply a numerical zero,
it has a natural porting path.

Subsystems using ``k_timeout_t``
--------------------------------

Ideally, code that takes a "timeout" parameter specifying a time to
wait should be using the kernel native abstraction where possible.
But :c:type:`k_timeout_t` is opaque, and needs to be converted before
it can be inspected by an application.

Some conversions are simple.  Code that needs to test for
:c:macro:`K_FOREVER` can simply use the :c:macro:`K_TIMEOUT_EQ()`
macro to test the opaque struct for equality and take special action.

The more complicated case is when the subsystem needs to take a
timeout and loop, waiting for it to finish while doing some processing
that may require multiple blocking operations on underlying kernel
code.  For example, consider this design:

.. code-block:: c

    void my_wait_for_event(struct my_subsys *obj, int32_t timeout_in_ms)
    {
        while (true) {
            uint32_t start = k_uptime_get_32();

            if (is_event_complete(obj)) {
                return;
            }

            /* Wait for notification of state change */
            k_sem_take(obj->sem, timeout_in_ms);

            /* Subtract elapsed time */
            timeout_in_ms -= (k_uptime_get_32() - start);
        }
    }

This code requires that the timeout value be inspected, which is no
longer possible.  For situations like this, the new API provides the
internal :c:func:`sys_timepoint_calc` and :c:func:`sys_timepoint_timeout` routines
that converts an arbitrary timeout to and from a timepoint value based on
an uptime tick at which it will expire.  So such a loop might look like:


.. code-block:: c

    void my_wait_for_event(struct my_subsys *obj, k_timeout_t timeout)
    {
        /* Compute the end time from the timeout */
        k_timepoint_t end = sys_timepoint_calc(timeout);

        do {
            if (is_event_complete(obj)) {
                return;
            }

            /* Update timeout with remaining time */
            timeout = sys_timepoint_timeout(end);

            /* Wait for notification of state change */
            k_sem_take(obj->sem, timeout);
        } while (!K_TIMEOUT_EQ(timeout, K_NO_WAIT));
    }

Note that :c:func:`sys_timepoint_calc` accepts special values :c:macro:`K_FOREVER`
and :c:macro:`K_NO_WAIT`, and works identically for absolute timeouts as well
as conventional ones. Conversely, :c:func:`sys_timepoint_timeout` may return
:c:macro:`K_FOREVER` or :c:macro:`K_NO_WAIT` if those were used to create
the timepoint, the later also being returned if the timepoint is now in the
past. For simple cases, :c:func:`sys_timepoint_expired` can be used as well.

But some care is still required for subsystems that use those.  Note that
delta timeouts need to be interpreted relative to a "current time",
and obviously that time is the time of the call to
:c:func:`sys_timepoint_calc`.  But the user expects that the time is
the time they passed the timeout to you.  Care must be taken to call
this function just once, as synchronously as possible to the timeout
creation in user code.  It should not be used on a "stored" timeout
value, and should never be called iteratively in a loop.


API Reference
*************

.. doxygengroup:: clock_apis

Part 3

한국어 전문 번역

Zephyr는 임의 precision의 hardware timing source에서 timed event를 보고·추적하는 견고하고 확장 가능한 timing framework를 제공합니다.

Time units

Kernel time은 목적에 따라 여러 unit으로 추적됩니다. Application에는 보통 millisecond·microsecond real-time value를 제시합니다. 이 단위는 portable하고 익숙하지만 underlying hardware precision과 정확히 일치하지 않을 수 있습니다.

k_cycle_get_32()·k_cycle_get_64()는 OS가 user에게 빠르게 제공할 수 있는 가장 빠른 cycle counter, 예를 들어 CPU cycle counter를 나타냅니다. Timing-sensitive code가 polling해 최대 precision을 얻을 수 있으며 frequency는 시간에 따라 안정적이어야 합니다. sys_clock_hw_cycles_per_sec()에서 rate를 얻고 대부분 platform에서는 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC runtime constant입니다.

Asynchronous timekeeping에는 tick을 사용합니다. Kernel의 uptime과 timeout bookkeeping은 모두 integer tick으로 이루어지고 fractional tick은 추적하지 않습니다. 가능한 한 interrupt도 tick boundary에 맞춥니다. CONFIG_SYS_CLOCK_TICKS_PER_SEC가 rate를 정하며 programmable timeout hardware는 보통 10 kHz 부근, emulation·legacy driver는 전통적인 100 Hz를 사용합니다.

Zephyr time unit 계층
Real time

ms · us · ns · application 표현

Cycle

가장 빠른 stable hardware counter · polling precision

Tick

kernel uptime·timeout의 integer bookkeeping 단위

모든 timeout unit은 global queue 삽입 시 한 번 tick으로 변환됩니다.

Conversion

Zephyr conversion library는 ms·us·tick·cyc 사이 모든 방향 변환을 제공합니다. Floor는 내림, ceil은 올림, near는 nearest rounding이며 output은 32 또는 64-bit precision을 선택합니다.

예를 들어 k_ms_to_ticks_ceil32()는 millisecond를 다음 높은 tick으로 올림해 32-bit 결과를 내고, k_cyc_to_us_floor64()는 cycle 측정값을 64-bit microsecond 경과 시간으로 내림합니다. Counter rate가 서로 integer multiple이고 결과가 word 안에 드는 대부분 platform에서는 2~4 operation 정도로 확장됩니다.

Uptime

Kernel은 system boot 뒤 uptime을 추적합니다. 대부분 portable application은 k_uptime_get()으로 millisecond 값을 얻습니다. 내부 표현은 64-bit tick count이며, 정밀 timing이 필요하고 직접 real-time unit으로 변환할 application은 k_uptime_ticks()를 사용할 수 있습니다.

Timeouts

Semaphore take·queue get 같은 blocking operation의 failure deadline, k_timer duration·period, k_work_delayable queue 제출 시점은 모두 k_timeout_t로 지정합니다. 이 opaque struct는 K_MSEC() 같은 timeout macro로 초기화합니다.

Relative timeout에서 current time은 context에 따라 다릅니다. Timer expiry나 delayable-work handler 같은 timeout callback 안에서 새 timeout을 schedule하면, 실제 시간이 이미 더 진행했어도 현재 firing timeout이 원래 schedule된 정확한 시각을 기준으로 합니다. 이 규칙 덕분에 callback에서 반복 timer를 만들어도 systematic drift가 누적되지 않습니다. Application context에서는 kernel이 timeout을 받는 순간의 k_uptime_ticks()가 current time입니다.

K_NSEC(), K_USEC(), K_TICKS(), K_CYC()는 각 unit의 relative timeout을 만듭니다. k_timeout_t precision은 기본 32-bit이며 long uptime과 non-tick unit rollover가 복잡해질 수 있으므로 long-running timing-sensitive application은 64-bit timeout type을 사용해야 합니다.

K_TIMEOUT_ABS_MS 계열은 boot 이후 absolute uptime에 도달할 때 만료합니다. Nanosecond·microsecond·cycle·tick variant도 있습니다.

Timing internals

Timeout queue

모든 timeout event는 single global queue에서 관리됩니다. Event는 double-linked list에 저장되고 앞 event부터의 tick delta를 가집니다. Subsystem이 callback pointer와 embedded _timeout tracking structure를 제공합니다. Wait queue나 thread structure가 이를 포함할 수 있습니다.

어떤 unit으로 전달됐든 list 삽입 시 한 번만 tick으로 변환되므로 event 수나 timeout 길이에 관계없이 tick level precision을 보장합니다.

Timer drivers

Driver는 sys_clock_announce()로 지난 tick 수를 kernel에 알리고, 가능한 한 tick boundary 가까이 정확하고 다른 counter·real-world time과 skew가 적게 전달해야 합니다.

Kernel은 sys_clock_set_timeout()으로 다음 registered timeout을 trigger하려면 몇 tick 안에 announce가 필요한지 알립니다. 일찍 정확한 tick을 announce하는 것은 허용되지만 늦으면 event를 놓칩니다. Delta timeout을 받는다고 fractional tick을 매번 reset해서는 안 되며, 그러면 clock skew가 누적됩니다.

sys_clock_elapsed()는 마지막 announce 뒤 real-world clock 기준으로 지난 tick을 알려 새 timeout이 이미 expired인지 검사하게 합니다.

이 contract의 자연스러운 구현은 registered event가 있을 때만 programmable hardware interrupt를 받는 tickless kernel입니다. Traditional ticked driver도 tick마다 announce(1)를 호출하고 set_timeout은 무시하며 elapsed는 항상 0을 반환해 쉽게 구현할 수 있습니다.

SMP details

Multiprocessor에서도 API는 같고 kernel이 access를 synchronize합니다. 어느 CPU가 timer interrupt를 처리할지는 driver 선택이며 한 CPU에 몰아도 illegal하지 않습니다. 기존 SMP architecture는 symmetric timer driver를 구현합니다.

sys_clock_announce()는 driver level에서 globally synchronized되어야 합니다. 거의 동시에 두 interrupt가 나도 하나만 non-zero current tick을 제공하고 다른 하나는 elapsed가 없다면 0을 announce할 수 있습니다. Timeslicing 때문에 announce call 자체를 생략하면 안 됩니다.

Global timer든 per-CPU counter든 CPU 사이 synchronization 복잡성은 driver 책임입니다. sys_clock_set_timeout()의 next timeout은 모든 CPU에 동일하게 전달되어 기본적으로 모든 CPU가 같은 event interrupt를 볼 수 있습니다. 하나만 non-zero tick을 announce해야 하지만 이 기본 동작은 latency를 낮춥니다. Performance optimization도 driver 책임입니다.

Time slicing

Scheduler time slice는 global expiration이 아니라 SMP의 각 CPU가 독립 추적하는 값이라 일반 timeout으로 표현할 수 없습니다. 별도 hardware가 없을 수 있어 Zephyr는 timer driver를 multiplex하고, time-sliced thread가 실행 중이면 sys_clock_set_timeout() 값이 next global timeout보다 짧게 clamp될 수 있습니다.

Subsystem timeout migration

Millisecond API를 유지하는 subsystem

User의 integer millisecond를 subsystem 내부에서 처리한 뒤 kernel에 넘길 때 K_MSEC()로 변환할 수 있습니다. Higher precision constructor와 absolute timeout은 못 쓰지만 단순 요구에는 충분할 수 있습니다.

K_FOREVER는 더 이상 integer type이 아니므로 과거 millisecond API가 이를 받았다면 별도 integer token으로 forever를 표현해야 합니다. K_NO_WAIT도 type-safety 이슈가 있지만 항상 numeric zero여서 자연스럽게 이식할 수 있습니다.

k_timeout_t를 사용하는 subsystem

Wait timeout은 가능하면 native k_timeout_t를 받아야 합니다. Opaque value에서 K_FOREVER를 검사하려면 K_TIMEOUT_EQ()를 사용합니다.

여러 blocking operation을 반복하면서 전체 timeout을 줄여야 하는 옛 integer-millisecond 코드는 다음처럼 uptime 차이를 직접 뺍니다.

void my_wait_for_event(struct my_subsys *obj, int32_t timeout_in_ms)
{
    while (true) {
        uint32_t start = k_uptime_get_32();

        if (is_event_complete(obj)) {
            return;
        }

        /* Wait for notification of state change */
        k_sem_take(obj->sem, timeout_in_ms);

        /* Subtract elapsed time */
        timeout_in_ms -= (k_uptime_get_32() - start);
    }
}

Opaque timeout에서는 sys_timepoint_calc()으로 end time을 한 번 계산하고 매 iteration마다 sys_timepoint_timeout()으로 remaining timeout을 얻습니다.

void my_wait_for_event(struct my_subsys *obj, k_timeout_t timeout)
{
    /* Compute the end time from the timeout */
    k_timepoint_t end = sys_timepoint_calc(timeout);

    do {
        if (is_event_complete(obj)) {
            return;
        }

        /* Update timeout with remaining time */
        timeout = sys_timepoint_timeout(end);

        /* Wait for notification of state change */
        k_sem_take(obj->sem, timeout);
    } while (!K_TIMEOUT_EQ(timeout, K_NO_WAIT));
}

calc는 K_FOREVER·K_NO_WAIT와 absolute timeout을 모두 처리합니다. timeout은 past면 K_NO_WAIT, infinite point면 K_FOREVER를 돌려주며 간단한 경우 sys_timepoint_expired()를 사용할 수 있습니다.

API 참고: clock_apis

Timeout constructor

K_NO_WAIT

요청한 operation을 즉시 수행할 수 없으면 기다리지 않게 하는 null timeout입니다.

반환값

  • k_timeout_t: timeout delay
K_NSEC(t)

최대 t nanosecond를 기다리는 timeout을 만듭니다. 실제 precision은 요청값이 아니라 tick rate로 제한됩니다.

매개변수

  • t: nanosecond duration

반환값

  • k_timeout_t: timeout delay
K_USEC(t)

최대 t microsecond를 기다리는 timeout을 만듭니다. 실제 precision은 tick rate로 제한됩니다.

매개변수

  • t: microsecond duration

반환값

  • k_timeout_t: timeout delay
K_CYC(t)

최대 t hardware cycle을 기다리는 timeout을 만듭니다.

매개변수

  • t: cycle duration

반환값

  • k_timeout_t: timeout delay
K_TICKS(t)

최대 t system tick을 기다리는 timeout을 만듭니다.

매개변수

  • t: tick duration

반환값

  • k_timeout_t: timeout delay
K_MSEC(ms)

최대 ms millisecond를 기다리는 timeout을 만듭니다.

매개변수

  • ms: millisecond duration

반환값

  • k_timeout_t: timeout delay
K_SECONDS(s)

최대 s second를 기다리는 timeout을 만듭니다.

매개변수

  • s: second duration

반환값

  • k_timeout_t: timeout delay
K_MINUTES(m)

최대 m minute를 기다리는 timeout을 만듭니다.

매개변수

  • m: minute duration

반환값

  • k_timeout_t: timeout delay
K_HOURS(h)

최대 h hour를 기다리는 timeout을 만듭니다.

매개변수

  • h: hour duration

반환값

  • k_timeout_t: timeout delay
K_FOREVER

요청한 operation이 가능해질 때까지 무기한 기다리는 timeout입니다.

반환값

  • k_timeout_t: infinite timeout
K_TICKS_FOREVER

System clock driver에 향후 timer interrupt가 필요하지 않음을 알릴 때 쓰는 tick sentinel입니다.

K_TIMEOUT_EQ(a, b)

Opaque k_timeout_t를 K_NO_WAIT·K_FOREVER 같은 알려진 constant와 안전하게 비교합니다. Application은 내부 field를 직접 검사하면 안 됩니다.

매개변수

  • a: timeout
  • b: 비교 대상 timeout

반환값

  • true: 두 timeout object가 동일

Unit constant와 tick type

NSEC_PER_USEC

Microsecond당 nanosecond 수입니다.

NSEC_PER_MSEC

Millisecond당 nanosecond 수입니다.

USEC_PER_MSEC

Millisecond당 microsecond 수입니다.

MSEC_PER_SEC

Second당 millisecond 수입니다.

SEC_PER_MIN

Minute당 second 수입니다.

MIN_PER_HOUR

Hour당 minute 수입니다.

HOUR_PER_DAY

Day당 hour 수입니다.

USEC_PER_SEC

Second당 microsecond 수입니다.

NSEC_PER_SEC

Second당 nanosecond 수입니다.

SYS_CLOCK_HW_CYCLES_TO_NS_AVG(X, NCYCLES)

CPU clock cycle을 nanosecond로 바꾸고 average cycle time을 계산합니다.

k_ticks_t

Timeout API가 사용하는 tick precision type입니다. k_timeout_t의 word size와 maximum timeout length를 정하지만, 항상 64-bit tick count인 system uptime 크기는 바꾸지 않습니다.

System clock driver API

sys_clock_set_timeout(ticks, idle)

Current time부터 지정 tick 안에 다음 sys_clock_announce()가 필요함을 driver에 알립니다. ticks=1은 next tick, 0이나 negative는 가능한 즉시를 뜻합니다. Spurious early announce는 허용되지만 늦으면 event를 놓칩니다. K_TICKS_FOREVER는 future interrupt가 필요 없고 uptime rollover가 틀려져도 indefinite sleep을 허용합니다. Event가 없지만 uptime을 보존할 때 kernel은 관례상 INT_MAX를 넘깁니다. SMP에서는 어느 CPU에서든 호출될 수 있으며 driver가 같은 tick을 system 전체에서 정확히 한 번만 announce하도록 serialize해야 합니다.

매개변수

  • ticks: current time부터의 tick delta
  • idle: 설정 직후 system이 idle 진입 예정이라는 hint
sys_clock_idle_exit()

System이 idle에서 나옴을 timer driver에 알려 timer 복구와 elapsed tick bookkeeping을 하게 합니다. Legacy driver가 여기서 sys_clock_announce()를 호출하는 것은 호환상 허용되지만 권장하지 않습니다.

sys_clock_announce(ticks)

마지막 announce 또는 boot 뒤 지정 tick이 지났음을 kernel에 알립니다. Hardware·latency 한계 안에서 tick boundary 가까이 전달해야 합니다.

매개변수

  • ticks: elapsed ticks
sys_clock_elapsed()

마지막 sys_clock_announce() 뒤 실제 clock 기준으로 지난 tick 수를 즉시 반환합니다. Kernel이 적절히 lock하므로 driver는 순간값만 제공하면 됩니다.

반환값

  • uint32_t: elapsed ticks
sys_clock_disable()

System timer를 비활성화합니다. 모든 driver가 지원하지는 않으며 CONFIG_SYSTEM_TIMER_HAS_DISABLE_SUPPORT로 capability를 확인합니다.

sys_clock_cycle_get_32()

Timer driver가 제공하는 32-bit hardware cycle up-counter입니다. Rate는 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC와 일치해야 하고 0xffffffff 뒤 wrap합니다. Hardware precision이 더 작으면 32-bit 값을 합성합니다. 50 MHz보다 빨라 몇 초 안에 wrap하면 64-bit API도 구현하는 것을 권장합니다.

반환값

  • uint32_t: current cycle count
sys_clock_cycle_get_64()

CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER가 설정된 hardware의 64-bit cycle up-counter입니다. 2^64-1 뒤 wrap하며 작은 counter hardware는 보통 이 API를 구현할 필요가 없습니다.

반환값

  • uint64_t: current cycle count

Uptime·cycle·tick API

k_uptime_ticks()

Boot 뒤 경과한 system uptime을 kernel timekeeping의 기본 resolution인 tick으로 반환합니다.

반환값

  • int64_t: current uptime ticks
k_uptime_get()

Boot 뒤 경과한 uptime을 millisecond로 반환합니다. Millisecond 단위 반환이 millisecond resolution을 뜻하지는 않으며 실제 resolution은 CONFIG_SYS_CLOCK_TICKS_PER_SEC에 달려 있습니다.

반환값

  • int64_t: current uptime milliseconds
k_uptime_get_32()

Millisecond uptime의 low 32 bits를 반환합니다. 변환에는 full clock precision이 필요해 64-bit math와 interrupt blocking이 생길 수 있으므로, 실행 시간이 짧다는 확신이 없다면 k_uptime_get()보다 이점이 없습니다.

반환값

  • uint32_t: low 32 bits of uptime milliseconds
k_uptime_seconds()

Boot 뒤 경과한 uptime을 second로 반환합니다.

반환값

  • uint32_t: current uptime seconds
k_uptime_delta(reftime)

현재 uptime과 이전 millisecond reference 사이 경과 시간을 계산하고 reference를 현재 uptime으로 갱신합니다.

매개변수

  • reftime: 읽고 갱신할 reference pointer

반환값

  • int64_t: elapsed milliseconds
k_cycle_get_32()

System hardware clock의 current 32-bit cycle count를 읽습니다.

반환값

  • uint32_t: hardware up-counter cycles
k_cycle_get_64()

지원될 때 system hardware clock의 current 64-bit cycle count를 읽습니다. CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER를 확인합니다.

반환값

  • uint64_t: hardware up-counter cycles
sys_clock_tick_get_32()

Current system tick count의 low 32 bits를 반환합니다.

반환값

  • uint32_t: current tick count
sys_clock_tick_get()

Current system tick count를 반환합니다.

반환값

  • int64_t: current tick count

Timepoint와 opaque type

sys_timepoint_calc(timeout)

Unlocked current time을 기준으로 timeout expiration에 해당하는 absolute timepoint를 계산합니다. User가 새 timeout을 넘긴 시점에 동기적으로 한 번만 호출하고 loop에서 반복 계산하면 안 됩니다. K_FOREVER·K_NO_WAIT와 absolute timeout도 처리합니다.

매개변수

  • timeout: current time 기준 timeout

반환값

  • k_timepoint_t: expiration timepoint
sys_timepoint_timeout(timepoint)

Current time부터 timepoint까지 남은 interval을 k_timeout_t로 반환합니다. Past 또는 K_NO_WAIT에서 만든 point면 K_NO_WAIT, K_FOREVER에서 만들었으면 K_FOREVER입니다.

매개변수

  • timepoint: target timepoint

반환값

  • k_timeout_t: remaining timeout
sys_clock_timeout_end_calc(timeout)

Backward compatibility용 deprecated API입니다. 새 코드는 sys_timepoint_calc()을 사용합니다.

sys_timepoint_cmp(a, b)

두 timepoint를 순서 비교합니다.

매개변수

  • a: left timepoint
  • b: right timepoint

반환값

  • 0: 동일
  • negative: a가 b보다 이전
  • positive: a가 b보다 이후
sys_timepoint_expired(timepoint)

Timepoint가 이미 past인지 확인합니다.

매개변수

  • timepoint: 검사할 point

반환값

  • true: expired
  • false: 아직 future
k_timeout_t

Kernel API timeout을 여러 format·unit으로 보관하는 opaque type입니다. K_MSEC(), K_TIMEOUT_ABS_TICKS() 같은 constructor 또는 K_NO_WAIT·K_FOREVER로 만들고 내부 data를 직접 검사하지 않습니다.

k_timepoint_t

Absolute timepoint를 보관하는 opaque type입니다. Content를 직접 검사하지 말고 calc·timeout·expired API를 사용합니다.

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.