kernel/services/data_passing/message_queues.html · kernel/services/data_passing/message_queues.rst 공식 원문 ↗

Zephyr 3.7.0 · Kernel · Data Passing

Message Queue

고정 크기 item을 복사해 비동기로 전달하며 full·empty 상태의 대기와 purge·peek operation을 제공합니다.

Official pathkernel/services/data_passing/message_queues.html
Source filekernel/services/data_passing/message_queues.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

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

Part 1

요약·해설

Queue item 크기 계약, priority와 대기 시간에 따른 waiter 선택, ISR no-wait 규칙과 대용량 복사의 latency 비용을 다룹니다.

Items

fixed-size copies

Queue

bounded ring buffer

ISR

no-wait receive

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 65483B909C7303ECB7B97035D7C7C89BF1E9D2EF815AF4E9C420F3C9BD6C832A
.. _message_queues_v2:

Message Queues
##############

A :dfn:`message queue` is a kernel object that implements a simple
message queue, allowing threads and ISRs to asynchronously send and receive
fixed-size data items.

.. contents::
    :local:
    :depth: 2

Concepts
********

Any number of message queues can be defined (limited only by available RAM).
Each message queue is referenced by its memory address.

A message queue has the following key properties:

* A **ring buffer** of data items that have been sent but not yet received.

* A **data item size**, measured in bytes.

* A **maximum quantity** of data items that can be queued in the ring buffer.

A message queue must be initialized before it can be used.
This sets its ring buffer to empty.

A data item can be **sent** to a message queue by a thread or an ISR.
The data item pointed at by the sending thread is copied to a waiting thread,
if one exists; otherwise the item is copied to the message queue's ring buffer,
if space is available. In either case, the size of the data area being sent
*must* equal the message queue's data item size.

If a thread attempts to send a data item when the ring buffer is full,
the sending thread may choose to wait for space to become available.
Any number of sending threads may wait simultaneously when the ring buffer
is full; when space becomes available
it is given to the highest priority sending thread that has waited the longest.

A data item can be **received** from a message queue by a thread.
The data item is copied to the area specified by the receiving thread;
the size of the receiving area *must* equal the message queue's data item size.

If a thread attempts to receive a data item when the ring buffer is empty,
the receiving thread may choose to wait for a data item to be sent.
Any number of receiving threads may wait simultaneously when the ring buffer
is empty; when a data item becomes available it is given to
the highest priority receiving thread that has waited the longest.

A thread can also **peek** at the message on the head of a message queue without
removing it from the queue.
The data item is copied to the area specified by the receiving thread;
the size of the receiving area *must* equal the message queue's data item size.

.. note::
    The kernel does allow an ISR to receive an item from a message queue,
    however the ISR must not attempt to wait if the message queue is empty.

.. note::
    Alignment of the message queue's ring buffer is not necessary.
    The underlying implementation uses :c:func:`memcpy` (which is
    alignment-agnostic) and does not expose any internal pointers.

Implementation
**************

Defining a Message Queue
========================

A message queue is defined using a variable of type :c:struct:`k_msgq`.
It must then be initialized by calling :c:func:`k_msgq_init`.

The following code defines and initializes an empty message queue
that is capable of holding 10 items, each of which is 12 bytes long.

.. code-block:: c

    struct data_item_type {
        uint32_t field1;
	uint32_t field2;
	uint32_t field3;
    };

    char my_msgq_buffer[10 * sizeof(struct data_item_type)];
    struct k_msgq my_msgq;

    k_msgq_init(&my_msgq, my_msgq_buffer, sizeof(struct data_item_type), 10);

Alternatively, a message queue can be defined and initialized at compile time
by calling :c:macro:`K_MSGQ_DEFINE`.

The following code has the same effect as the code segment above. Observe
that the macro defines both the message queue and its buffer.

.. code-block:: c

    K_MSGQ_DEFINE(my_msgq, sizeof(struct data_item_type), 10, 1);

Writing to a Message Queue
==========================

A data item is added to a message queue by calling :c:func:`k_msgq_put`.

The following code builds on the example above, and uses the message queue
to pass data items from a producing thread to one or more consuming threads.
If the message queue fills up because the consumers can't keep up, the
producing thread throws away all existing data so the newer data can be saved.

.. code-block:: c

    void producer_thread(void)
    {
        struct data_item_type data;

        while (1) {
            /* create data item to send (e.g. measurement, timestamp, ...) */
            data = ...

            /* send data to consumers */
            while (k_msgq_put(&my_msgq, &data, K_NO_WAIT) != 0) {
                /* message queue is full: purge old data & try again */
                k_msgq_purge(&my_msgq);
            }

            /* data item was successfully added to message queue */
        }
    }

Reading from a Message Queue
============================

A data item is taken from a message queue by calling :c:func:`k_msgq_get`.

The following code builds on the example above, and uses the message queue
to process data items generated by one or more producing threads. Note that
the return value of :c:func:`k_msgq_get` should be tested as ``-ENOMSG``
can be returned due to :c:func:`k_msgq_purge`.

.. code-block:: c

    void consumer_thread(void)
    {
        struct data_item_type data;

        while (1) {
            /* get a data item */
            k_msgq_get(&my_msgq, &data, K_FOREVER);

            /* process data item */
            ...
        }
    }


Peeking into a Message Queue
============================

A data item is read from a message queue by calling :c:func:`k_msgq_peek`.

The following code peeks into the message queue to read the data item at the
head of the queue that is generated by one or more producing threads.

.. code-block:: c

    void consumer_thread(void)
    {
        struct data_item_type data;

        while (1) {
            /* read a data item by peeking into the queue */
            k_msgq_peek(&my_msgq, &data);

            /* process data item */
            ...
        }
    }

Suggested Uses
**************

Use a message queue to transfer small data items between threads
in an asynchronous manner.

.. note::
    A message queue can be used to transfer large data items, if desired.
    However, this can increase interrupt latency as interrupts are locked
    while a data item is written or read. The time to write or read a data item
    increases linearly with its size since the item is copied in its entirety
    to or from the buffer in memory. For this reason, it is usually preferable
    to transfer large data items by exchanging a pointer to the data item,
    rather than the data item itself.

    A synchronous transfer can be achieved by using the kernel's mailbox
    object type.

Configuration Options
*********************

Related configuration options:

* None.

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

.. doxygengroup:: msgq_apis

Part 3

한국어 전문 번역

Message queue는 thread와 ISR이 고정 크기 data item을 비동기로 보내고 받는 단순 queue를 구현한 kernel object입니다.

개념

사용 가능한 RAM이 허용하는 만큼 message queue를 정의할 수 있고 memory 주소로 참조합니다. 각 queue는 아직 수신되지 않은 item의 ring buffer, byte 단위 item 크기, ring buffer가 담을 최대 item 수를 가집니다. 첫 사용 전 초기화하면 ring buffer가 비게 됩니다.

Thread나 ISR이 item을 보내면 기다리는 수신 thread가 있을 때 그 thread의 영역으로 곧바로 복사합니다. 대기자가 없으면 공간이 있는 경우 ring buffer로 복사합니다. 어느 경우든 송신 data 영역 크기는 queue의 고정 item 크기와 정확히 같아야 합니다.

Ring buffer가 찼을 때 thread는 공간이 날 때까지 기다릴 수 있습니다. 여러 송신 thread가 기다리면 priority가 가장 높은 thread 중 가장 오래 기다린 thread에 먼저 공간을 줍니다.

Thread가 item을 받으면 지정한 영역으로 복사하며 수신 영역 크기도 queue item 크기와 정확히 같아야 합니다. Ring buffer가 비었으면 item이 올 때까지 기다릴 수 있고, 여러 수신자가 기다리면 priority가 가장 높고 가장 오래 기다린 thread가 먼저 받습니다.

Thread는 queue head message를 제거하지 않고 peek할 수도 있습니다. 이때도 item 전체를 지정 영역으로 복사하므로 영역 크기는 고정 item 크기와 같아야 합니다.

구현

Message queue 정의

struct k_msgq variable과 충분한 buffer를 만들고 k_msgq_init()으로 초기화합니다. 다음 queue는 12-byte item 10개를 담습니다.

struct data_item_type {
    uint32_t field1;
    uint32_t field2;
    uint32_t field3;
};

char my_msgq_buffer[10 * sizeof(struct data_item_type)];
struct k_msgq my_msgq;

k_msgq_init(&my_msgq, my_msgq_buffer, sizeof(struct data_item_type), 10);

K_MSGQ_DEFINE macro는 queue와 buffer를 함께 compile time에 정의하고 초기화합니다.

K_MSGQ_DEFINE(my_msgq, sizeof(struct data_item_type), 10, 1);

Message queue에 쓰기

k_msgq_put()으로 item을 추가합니다. 다음 producer는 consumer가 따라오지 못해 queue가 차면 기존 data를 모두 purge하고 최신 data를 다시 저장합니다.

void producer_thread(void)
{
    struct data_item_type data;

    while (1) {
        /* create data item to send (e.g. measurement, timestamp, ...) */
        data = ...

        /* send data to consumers */
        while (k_msgq_put(&my_msgq, &data, K_NO_WAIT) != 0) {
            /* message queue is full: purge old data & try again */
            k_msgq_purge(&my_msgq);
        }

        /* data item was successfully added to message queue */
    }
}

Message queue에서 읽기

k_msgq_get()으로 item을 꺼냅니다. k_msgq_purge() 때문에 -ENOMSG가 반환될 수 있으므로 실제 code에서는 반환값을 검사해야 합니다.

void consumer_thread(void)
{
    struct data_item_type data;

    while (1) {
        /* get a data item */
        k_msgq_get(&my_msgq, &data, K_FOREVER);

        /* process data item */
        ...
    }
}

Message queue 들여다보기

k_msgq_peek()은 head item을 제거하지 않고 복사해 읽습니다.

void consumer_thread(void)
{
    struct data_item_type data;

    while (1) {
        /* read a data item by peeking into the queue */
        k_msgq_peek(&my_msgq, &data);

        /* process data item */
        ...
    }
}

권장 용도

작은 data item을 thread 사이에서 비동기로 전달할 때 사용합니다.

동기 전송이 필요하면 kernel mailbox object를 사용할 수 있습니다.

설정 옵션

이 문서에 관련 configuration option은 없습니다.

API 참고: msgq_apis

K_MSGQ_FLAG_ALLOC

Message queue ring buffer가 동적으로 할당되었음을 표시하는 내부 flag입니다.

K_MSGQ_DEFINE(q_name, q_msg_size, q_max_msgs, q_align)

고정 크기 message를 담는 queue와 ring buffer를 정적으로 정의하고 초기화합니다. Alignment가 필요하지 않으므로 q_align은 1이면 충분합니다.

extern struct k_msgq <name>;

매개변수

  • q_name: message queue 이름
  • q_msg_size: message 크기(byte)
  • q_max_msgs: 최대 queue message 수
  • q_align: ring buffer 정렬값, 2의 거듭제곱
k_msgq_init(msgq, buffer, msg_size, max_msgs)

기존 buffer를 사용하는 message queue를 첫 사용 전에 초기화합니다. Buffer는 msg_size byte message max_msgs개를 담을 수 있어야 합니다.

매개변수

  • msgq: message queue 주소
  • buffer: queued message를 담는 ring buffer
  • msg_size: message 크기(byte)
  • max_msgs: 최대 message 수
k_msgq_alloc_init(msgq, msg_size, max_msgs)

호출 thread의 resource pool에서 내부 ring buffer를 할당하여 message queue를 초기화합니다. k_msgq_cleanup() 또는 userspace에서 마지막 참조 소멸 시 memory가 해제됩니다.

매개변수

  • msgq: message queue 주소
  • msg_size: message 크기(byte)
  • max_msgs: 최대 message 수

반환값

  • 0: 성공
  • -ENOMEM: resource pool memory 부족
  • -EINVAL: 크기 계산의 integer overflow
k_msgq_cleanup(msgq)

동적으로 할당한 ring buffer를 해제합니다.

매개변수

  • msgq: 정리할 message queue

반환값

  • 0: 성공
  • -EBUSY: queue가 비어 있지 않음
k_msgq_put(msgq, data, timeout)

Message 내용을 data에서 queue로 복사합니다. Data pointer는 보관하지 않으므로 이 함수가 원본 내용을 변경하지 않습니다. ISR에서도 호출할 수 있습니다.

매개변수

  • msgq: message queue 주소
  • data: message pointer
  • timeout: 추가할 공간을 기다릴 시간, K_NO_WAIT 또는 K_FOREVER

반환값

  • 0: message 전송
  • -ENOMSG: 기다리지 않았거나 queue가 purge됨
  • -EAGAIN: 대기 시간 만료
k_msgq_get(msgq, data, timeout)

Queue에서 first-in, first-out 순서로 message를 제거해 data 영역으로 복사합니다. ISR에서는 timeout을 반드시 K_NO_WAIT로 설정해야 합니다.

매개변수

  • msgq: message queue 주소
  • data: 수신 message를 담을 영역
  • timeout: 수신 대기 시간, K_NO_WAIT 또는 K_FOREVER

반환값

  • 0: message 수신
  • -ENOMSG: 기다리지 않고 반환
  • -EAGAIN: 대기 시간 만료
k_msgq_peek(msgq, data)

Head message를 제거하지 않고 FIFO 순서로 읽어 data 영역에 복사합니다. ISR에서도 호출할 수 있습니다.

매개변수

  • msgq: message queue 주소
  • data: 읽은 message를 담을 영역

반환값

  • 0: message 읽음
  • -ENOMSG: queue가 비어 있음
k_msgq_peek_at(msgq, data, idx)

지정 index의 message를 제거하지 않고 읽습니다. Index 0은 k_msgq_peek()과 같습니다. ISR에서도 호출할 수 있습니다.

매개변수

  • msgq: message queue 주소
  • data: 읽은 message를 담을 영역
  • idx: peek할 queue index

반환값

  • 0: message 읽음
  • -ENOMSG: 해당 index에 message가 없음
k_msgq_purge(msgq)

Ring buffer의 미수신 message를 모두 버립니다. Put에서 공간을 기다리던 thread는 모두 깨어나 -ENOMSG를 받습니다.

매개변수

  • msgq: message queue 주소
k_msgq_num_free_get(msgq)

Ring buffer에서 사용하지 않은 entry 수를 반환합니다.

매개변수

  • msgq: message queue 주소

반환값

  • entry 수: 비어 있는 ring buffer slot 수
k_msgq_get_attrs(msgq, attrs)

Message queue의 기본 속성을 attrs에 채웁니다.

매개변수

  • msgq: message queue 주소
  • attrs: attribute 구조체 pointer
k_msgq_num_used_get(msgq)

Ring buffer에 들어 있는 message 수를 반환합니다.

매개변수

  • msgq: message queue 주소

반환값

  • message 수: 사용 중인 entry 수
struct k_msgq

Message queue의 상태와 ring buffer pointer를 나타내는 kernel 구조체입니다.

k_msgq::wait_q

Message queue의 송수신 대기 queue입니다.

k_msgq::lock

Queue 상태를 보호하는 spinlock입니다.

k_msgq::msg_size

각 message의 고정 크기입니다.

k_msgq::max_msgs

Queue가 담을 수 있는 최대 message 수입니다.

k_msgq::buffer_start

Message ring buffer 시작 주소입니다.

k_msgq::buffer_end

Message ring buffer 끝 주소입니다.

k_msgq::read_ptr

다음 message를 읽을 위치입니다.

k_msgq::write_ptr

다음 message를 쓸 위치입니다.

k_msgq::used_msgs

현재 사용 중인 message 수입니다.

k_msgq::flags

동적 할당 등 message queue 상태 flag입니다.

struct k_msgq_attrs

Message queue의 공개 기본 속성을 담는 구조체입니다.

k_msgq_attrs::msg_size

Message 크기입니다.

k_msgq_attrs::max_msgs

최대 message 수입니다.

k_msgq_attrs::used_msgs

현재 사용 중인 message 수입니다.

Source

출처

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