요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
==============================
General notification mechanism
==============================
The general notification mechanism is built on top of the standard pipe driver
whereby it effectively splices notification messages from the kernel into pipes
opened by userspace. This can be used in conjunction with::
* Key/keyring notifications
The notifications buffers can be enabled by:
"General setup"/"General notification queue"
(CONFIG_WATCH_QUEUE)
This document has the following sections:
.. contents:: :local:
Overview
========
This facility appears as a pipe that is opened in a special mode. The pipe's
internal ring buffer is used to hold messages that are generated by the kernel.
These messages are then read out by read(). Splice and similar are disabled on
such pipes due to them wanting to, under some circumstances, revert their
additions to the ring - which might end up interleaved with notification
messages.
The owner of the pipe has to tell the kernel which sources it would like to
watch through that pipe. Only sources that have been connected to a pipe will
insert messages into it. Note that a source may be bound to multiple pipes and
insert messages into all of them simultaneously.
Filters may also be emplaced on a pipe so that certain source types and
subevents can be ignored if they're not of interest.
A message will be discarded if there isn't a slot available in the ring or if
no preallocated message buffer is available. In both of these cases, read()
will insert a WATCH_META_LOSS_NOTIFICATION message into the output buffer after
the last message currently in the buffer has been read.
Note that when producing a notification, the kernel does not wait for the
consumers to collect it, but rather just continues on. This means that
notifications can be generated whilst spinlocks are held and also protects the
kernel from being held up indefinitely by a userspace malfunction.
Message Structure
=================
Notification messages begin with a short header::
struct watch_notification {
__u32 type:24;
__u32 subtype:8;
__u32 info;
};
"type" indicates the source of the notification record and "subtype" indicates
the type of record from that source (see the Watch Sources section below). The
type may also be "WATCH_TYPE_META". This is a special record type generated
internally by the watch queue itself. There are two subtypes:
* WATCH_META_REMOVAL_NOTIFICATION
* WATCH_META_LOSS_NOTIFICATION
The first indicates that an object on which a watch was installed was removed
or destroyed and the second indicates that some messages have been lost.
"info" indicates a bunch of things, including:
* The length of the message in bytes, including the header (mask with
WATCH_INFO_LENGTH and shift by WATCH_INFO_LENGTH__SHIFT). This indicates
the size of the record, which may be between 8 and 127 bytes.
* The watch ID (mask with WATCH_INFO_ID and shift by WATCH_INFO_ID__SHIFT).
This indicates that caller's ID of the watch, which may be between 0
and 255. Multiple watches may share a queue, and this provides a means to
distinguish them.
* A type-specific field (WATCH_INFO_TYPE_INFO). This is set by the
notification producer to indicate some meaning specific to the type and
subtype.
Everything in info apart from the length can be used for filtering.
The header can be followed by supplementary information. The format of this is
at the discretion is defined by the type and subtype.
Watch List (Notification Source) API
====================================
A "watch list" is a list of watchers that are subscribed to a source of
notifications. A list may be attached to an object (say a key or a superblock)
or may be global (say for device events). From a userspace perspective, a
non-global watch list is typically referred to by reference to the object it
belongs to (such as using KEYCTL_NOTIFY and giving it a key serial number to
watch that specific key).
To manage a watch list, the following functions are provided:
* ::
void init_watch_list(struct watch_list *wlist,
void (*release_watch)(struct watch *wlist));
Initialise a watch list. If ``release_watch`` is not NULL, then this
indicates a function that should be called when the watch_list object is
destroyed to discard any references the watch list holds on the watched
object.
* ``void remove_watch_list(struct watch_list *wlist);``
This removes all of the watches subscribed to a watch_list and frees them
and then destroys the watch_list object itself.
Watch Queue (Notification Output) API
=====================================
A "watch queue" is the buffer allocated by an application that notification
records will be written into. The workings of this are hidden entirely inside
of the pipe device driver, but it is necessary to gain a reference to it to set
a watch. These can be managed with:
* ``struct watch_queue *get_watch_queue(int fd);``
Since watch queues are indicated to the kernel by the fd of the pipe that
implements the buffer, userspace must hand that fd through a system call.
This can be used to look up an opaque pointer to the watch queue from the
system call.
* ``void put_watch_queue(struct watch_queue *wqueue);``
This discards the reference obtained from ``get_watch_queue()``.
Watch Subscription API
======================
A "watch" is a subscription on a watch list, indicating the watch queue, and
thus the buffer, into which notification records should be written. The watch
queue object may also carry filtering rules for that object, as set by
userspace. Some parts of the watch struct can be set by the driver::
struct watch {
union {
u32 info_id; /* ID to be OR'd in to info field */
...
};
void *private; /* Private data for the watched object */
u64 id; /* Internal identifier */
...
};
The ``info_id`` value should be an 8-bit number obtained from userspace and
shifted by WATCH_INFO_ID__SHIFT. This is OR'd into the WATCH_INFO_ID field of
struct watch_notification::info when and if the notification is written into
the associated watch queue buffer.
The ``private`` field is the driver's data associated with the watch_list and
is cleaned up by the ``watch_list::release_watch()`` method.
The ``id`` field is the source's ID. Notifications that are posted with a
different ID are ignored.
The following functions are provided to manage watches:
* ``void init_watch(struct watch *watch, struct watch_queue *wqueue);``
Initialise a watch object, setting its pointer to the watch queue, using
appropriate barriering to avoid lockdep complaints.
* ``int add_watch_to_object(struct watch *watch, struct watch_list *wlist);``
Subscribe a watch to a watch list (notification source). The
driver-settable fields in the watch struct must have been set before this
is called.
* ::
int remove_watch_from_object(struct watch_list *wlist,
struct watch_queue *wqueue,
u64 id, false);
Remove a watch from a watch list, where the watch must match the specified
watch queue (``wqueue``) and object identifier (``id``). A notification
(``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue to
indicate that the watch got removed.
* ``int remove_watch_from_object(struct watch_list *wlist, NULL, 0, true);``
Remove all the watches from a watch list. It is expected that this will be
called preparatory to destruction and that the watch list will be
inaccessible to new watches by this point. A notification
(``WATCH_META_REMOVAL_NOTIFICATION``) is sent to the watch queue of each
subscribed watch to indicate that the watch got removed.
Notification Posting API
========================
To post a notification to watch list so that the subscribed watches can see it,
the following function should be used::
void post_watch_notification(struct watch_list *wlist,
struct watch_notification *n,
const struct cred *cred,
u64 id);
The notification should be preformatted and a pointer to the header (``n``)
should be passed in. The notification may be larger than this and the size in
units of buffer slots is noted in ``n->info & WATCH_INFO_LENGTH``.
The ``cred`` struct indicates the credentials of the source (subject) and is
passed to the LSMs, such as SELinux, to allow or suppress the recording of the
note in each individual queue according to the credentials of that queue
(object).
The ``id`` is the ID of the source object (such as the serial number on a key).
Only watches that have the same ID set in them will see this notification.
Watch Sources
=============
Any particular buffer can be fed from multiple sources. Sources include:
* WATCH_TYPE_KEY_NOTIFY
Notifications of this type indicate changes to keys and keyrings, including
the changes of keyring contents or the attributes of keys.
See Documentation/security/keys/core.rst for more information.
Event Filtering
===============
Once a watch queue has been created, a set of filters can be applied to limit
the events that are received using::
struct watch_notification_filter filter = {
...
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter)
The filter description is a variable of type::
struct watch_notification_filter {
__u32 nr_filters;
__u32 __reserved;
struct watch_notification_type_filter filters[];
};
Where "nr_filters" is the number of filters in filters[] and "__reserved"
should be 0. The "filters" array has elements of the following type::
struct watch_notification_type_filter {
__u32 type;
__u32 info_filter;
__u32 info_mask;
__u32 subtype_filter[8];
};
Where:
* ``type`` is the event type to filter for and should be something like
"WATCH_TYPE_KEY_NOTIFY"
* ``info_filter`` and ``info_mask`` act as a filter on the info field of the
notification record. The notification is only written into the buffer if::
(watch.info & info_mask) == info_filter
This could be used, for example, to ignore events that are not exactly on
the watched point in a mount tree.
* ``subtype_filter`` is a bitmask indicating the subtypes that are of
interest. Bit 0 of subtype_filter[0] corresponds to subtype 0, bit 1 to
subtype 1, and so on.
If the argument to the ioctl() is NULL, then the filters will be removed and
all events from the watched sources will come through.
Userspace Code Example
======================
A buffer is created with something like the following::
pipe2(fds, O_TMPFILE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
It can then be set to receive keyring change notifications::
keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);
The notifications can then be consumed by something like the following::
static void consumer(int rfd, struct watch_queue_buffer *buf)
{
unsigned char buffer[128];
ssize_t buf_len;
while (buf_len = read(rfd, buffer, sizeof(buffer)),
buf_len > 0
) {
void *p = buffer;
void *end = buffer + buf_len;
while (p < end) {
union {
struct watch_notification n;
unsigned char buf1[128];
} n;
size_t largest, len;
largest = end - p;
if (largest > 128)
largest = 128;
memcpy(&n, p, largest);
len = (n->info & WATCH_INFO_LENGTH) >>
WATCH_INFO_LENGTH__SHIFT;
if (len == 0 || len > largest)
return;
switch (n.n.type) {
case WATCH_TYPE_META:
got_meta(&n.n);
case WATCH_TYPE_KEY_NOTIFY:
saw_key_change(&n.n);
break;
}
p += len;
}
}
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
일반 notification mechanism
1-21일반 notification mechanism
일반 notification mechanism은 standard pipe driver 위에 구축되며, kernel의 notification message를 userspace가 연 pipe에 실질적으로 splice합니다. 다음 기능과 함께 사용할 수 있습니다.
- Key 및 keyring notification
Notification buffer는 다음 항목으로 활성화할 수 있습니다.
"General setup"/"General notification queue"
(CONFIG_WATCH_QUEUE)
이 문서는 다음 section으로 구성됩니다.
.. contents:: :local:
개요
22-50개요
이 facility는 특별한 mode로 연 pipe로 나타납니다. Pipe의 내부 ring buffer는 kernel이 생성한 message를 보관하며, 이 message는 `read()`로 읽습니다.
이러한 pipe에서는 splice와 비슷한 operation을 비활성화합니다. 특정 상황에서 ring에 추가한 항목을 되돌리려다가 notification message와 뒤섞일 수 있기 때문입니다.
Pipe owner는 어떤 source를 그 pipe로 watch할지 kernel에 알려야 합니다. Pipe에 연결된 source만 message를 삽입합니다. Source 하나를 여러 pipe에 bind하여 모든 pipe에 동시에 message를 넣을 수도 있습니다.
관심 없는 특정 source type과 subevent를 무시하도록 pipe에 filter를 설치할 수도 있습니다.
Ring에 빈 slot이 없거나 미리 할당된 message buffer가 없으면 message를 버립니다. 두 경우 모두 현재 buffer의 마지막 message를 읽은 뒤 `read()`가 output buffer에 `WATCH_META_LOSS_NOTIFICATION` message를 삽입합니다.
Kernel은 notification을 생성할 때 consumer가 수집하기를 기다리지 않고 계속 진행합니다. 따라서 spinlock을 잡은 동안에도 notification을 생성할 수 있으며, userspace malfunction이 kernel을 무기한 지연시키는 것도 막습니다.
Message structure
51-93Message structure
Notification message는 짧은 header로 시작합니다.
struct watch_notification {
__u32 type:24;
__u32 subtype:8;
__u32 info;
};
`type`은 notification record의 source를 나타내고 `subtype`은 그 source에서 온 record type을 나타냅니다. 아래 Watch Sources section을 참조하십시오. Type은 watch queue 자체가 내부적으로 생성하는 특별한 record type인 `WATCH_TYPE_META`일 수도 있습니다. Subtype은 두 가지입니다.
- `WATCH_META_REMOVAL_NOTIFICATION`
- `WATCH_META_LOSS_NOTIFICATION`
첫 번째는 watch를 설치한 object가 제거되거나 파괴되었음을 나타내고, 두 번째는 일부 message가 손실되었음을 나타냅니다.
`info`는 다음을 포함한 여러 정보를 나타냅니다.
- Header를 포함한 message 길이(byte). `WATCH_INFO_LENGTH`로 mask하고 `WATCH_INFO_LENGTH__SHIFT`만큼 shift합니다. Record 크기는 8에서 127 byte 사이입니다.
- Watch ID. `WATCH_INFO_ID`로 mask하고 `WATCH_INFO_ID__SHIFT`만큼 shift합니다. Watch에 대한 caller ID이며 0에서 255 사이입니다. 여러 watch가 queue 하나를 공유할 때 이를 구분합니다.
- Type별 field인 `WATCH_INFO_TYPE_INFO`. Notification producer가 type과 subtype에 고유한 의미를 나타내도록 설정합니다.
`info`에서 길이를 제외한 모든 항목을 filtering에 사용할 수 있습니다.
Header 뒤에는 supplementary information이 올 수 있습니다. 그 형식은 type과 subtype이 정의합니다.
Watch list notification source API
94-121Watch List, 즉 notification source API
Watch list는 notification source를 subscribe한 watcher의 목록입니다. List는 key나 superblock 같은 object에 붙을 수도 있고, device event처럼 global일 수도 있습니다.
Userspace 관점에서 non-global watch list는 보통 자신이 속한 object를 참조해 가리킵니다. 예를 들어 `KEYCTL_NOTIFY`에 특정 key를 watch할 key serial number를 전달합니다.
Watch list를 관리하는 function은 다음과 같습니다.
void init_watch_list(struct watch_list *wlist,
void (*release_watch)(struct watch *wlist));
Watch list를 초기화합니다. `release_watch`가 `NULL`이 아니면 watch_list object를 파괴할 때 호출해야 하는 function을 뜻하며, watch list가 watched object에 보유한 reference를 버립니다.
* ``void remove_watch_list(struct watch_list *wlist);``
Watch_list를 subscribe한 모든 watch를 제거하고 free한 뒤 watch_list object 자체를 파괴합니다.
Watch queue notification output API
122-141Watch Queue, 즉 notification output API
Watch queue는 notification record가 기록될 application 할당 buffer입니다. 동작은 pipe device driver 내부에 완전히 숨겨져 있지만 watch를 설정하려면 reference를 얻어야 합니다. 다음 function으로 관리합니다.
* ``struct watch_queue *get_watch_queue(int fd);``
Watch queue는 buffer를 구현하는 pipe의 fd로 kernel에 지정되므로 userspace는 system call을 통해 그 fd를 전달해야 합니다. 이 function은 system call에서 watch queue의 opaque pointer를 찾는 데 사용할 수 있습니다.
* ``void put_watch_queue(struct watch_queue *wqueue);``
`get_watch_queue()`에서 얻은 reference를 버립니다.
Watch subscription API
142-203Watch subscription API
Watch는 watch list에 대한 subscription이며 notification record를 기록할 watch queue, 즉 buffer를 지정합니다. Watch queue object는 userspace가 설정한 해당 object용 filtering rule도 보유할 수 있습니다. Watch struct의 일부는 driver가 설정할 수 있습니다.
struct watch {
union {
u32 info_id; /* ID to be OR'd in to info field */
...
};
void *private; /* Private data for the watched object */
u64 id; /* Internal identifier */
...
};
`info_id` 값은 userspace에서 받은 8-bit number를 `WATCH_INFO_ID__SHIFT`만큼 shift한 것이어야 합니다. Notification이 연결된 watch queue buffer에 기록될 때 이 값은 `struct watch_notification::info`의 `WATCH_INFO_ID` field에 OR됩니다.
`private` field는 watch_list와 연결된 driver data이며 `watch_list::release_watch()` method가 정리합니다.
`id` field는 source ID입니다. 다른 ID로 post된 notification은 무시합니다.
Watch를 관리하는 function은 다음과 같습니다.
* ``void init_watch(struct watch *watch, struct watch_queue *wqueue);``
Watch object를 초기화하고 watch queue를 가리키는 pointer를 설정합니다. Lockdep complaint를 피하도록 적절한 barrier를 사용합니다.
* ``int add_watch_to_object(struct watch *watch, struct watch_list *wlist);``
Watch를 notification source인 watch list에 subscribe합니다. 호출 전에 watch struct에서 driver가 설정할 수 있는 field를 설정해야 합니다.
int remove_watch_from_object(struct watch_list *wlist,
struct watch_queue *wqueue,
u64 id, false);
지정한 watch queue인 `wqueue`와 object identifier인 `id`가 일치하는 watch를 watch list에서 제거합니다. Watch가 제거되었음을 나타내는 `WATCH_META_REMOVAL_NOTIFICATION`을 watch queue로 보냅니다.
* ``int remove_watch_from_object(struct watch_list *wlist, NULL, 0, true);``
Watch list에서 모든 watch를 제거합니다. 파괴 준비 과정에서 호출하며, 이 시점에는 새 watch가 watch list에 접근할 수 없어야 합니다. Subscribe된 각 watch의 watch queue에 `WATCH_META_REMOVAL_NOTIFICATION`을 보내 제거 사실을 알립니다.
Notification posting API
204-227Notification posting API
Subscribe된 watch가 notification을 볼 수 있도록 watch list에 post하려면 다음 function을 사용합니다.
void post_watch_notification(struct watch_list *wlist,
struct watch_notification *n,
const struct cred *cred,
u64 id);
Notification은 미리 format해야 하며 header pointer인 `n`을 전달합니다. Notification은 header보다 클 수 있고, buffer slot 단위 크기는 `n->info & WATCH_INFO_LENGTH`에 기록됩니다.
`cred` struct는 source, 즉 subject의 credential을 나타냅니다. SELinux 같은 LSM에 전달되어 각 queue의 credential, 즉 object에 따라 개별 queue에 note를 기록하도록 허용하거나 억제합니다.
`id`는 key serial number 같은 source object의 ID입니다. 동일한 ID가 설정된 watch만 이 notification을 받습니다.
Watch source
228-240Watch source
특정 buffer 하나는 여러 source에서 data를 받을 수 있습니다. Source에는 다음 항목이 있습니다.
- `WATCH_TYPE_KEY_NOTIFY`
이 type의 notification은 keyring content 또는 key attribute 변경을 포함하여 key와 keyring의 변경을 나타냅니다.
자세한 내용은 `Documentation/security/keys/core.rst`를 참조하십시오.
Event filtering
241-290Event filtering
Watch queue를 만든 뒤 다음과 같이 filter 집합을 적용하여 수신하는 event를 제한할 수 있습니다.
struct watch_notification_filter filter = {
...
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter)
Filter description은 다음 type의 variable입니다.
struct watch_notification_filter {
__u32 nr_filters;
__u32 __reserved;
struct watch_notification_type_filter filters[];
};
`nr_filters`는 `filters[]` 안의 filter 수이고 `__reserved`는 0이어야 합니다. `filters` array element의 type은 다음과 같습니다.
struct watch_notification_type_filter {
__u32 type;
__u32 info_filter;
__u32 info_mask;
__u32 subtype_filter[8];
};
각 field의 의미는 다음과 같습니다.
- `type`은 filtering할 event type이며 `WATCH_TYPE_KEY_NOTIFY` 같은 값이어야 합니다.
- `info_filter`와 `info_mask`는 notification record의 info field에 대한 filter로 동작합니다. 아래 조건을 만족할 때만 notification을 buffer에 기록합니다.
- `subtype_filter`는 관심 있는 subtype을 나타내는 bitmask입니다. `subtype_filter[0]`의 bit 0은 subtype 0, bit 1은 subtype 1에 해당하며 이후도 같은 방식입니다.
(watch.info & info_mask) == info_filter
예를 들어 이 조건을 사용해 mount tree에서 정확히 watched point에 있지 않은 event를 무시할 수 있습니다.
`ioctl()` argument가 `NULL`이면 filter를 제거하며 watched source의 모든 event가 전달됩니다.
Userspace code 예제
291-343Userspace code 예제
다음과 같은 code로 buffer를 만듭니다.
pipe2(fds, O_TMPFILE);
ioctl(fds[1], IOC_WATCH_QUEUE_SET_SIZE, 256);
그런 다음 keyring change notification을 받도록 설정할 수 있습니다.
keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fds[1], 0x01);
Notification은 다음과 같은 code로 consume할 수 있습니다.
static void consumer(int rfd, struct watch_queue_buffer *buf)
{
unsigned char buffer[128];
ssize_t buf_len;
while (buf_len = read(rfd, buffer, sizeof(buffer)),
buf_len > 0
) {
void *p = buffer;
void *end = buffer + buf_len;
while (p < end) {
union {
struct watch_notification n;
unsigned char buf1[128];
} n;
size_t largest, len;
largest = end - p;
if (largest > 128)
largest = 128;
memcpy(&n, p, largest);
len = (n->info & WATCH_INFO_LENGTH) >>
WATCH_INFO_LENGTH__SHIFT;
if (len == 0 || len > largest)
return;
switch (n.n.type) {
case WATCH_TYPE_META:
got_meta(&n.n);
case WATCH_TYPE_KEY_NOTIFY:
saw_key_change(&n.n);
break;
}
p += len;
}
}
}
요약과 해설
watch_queue.rst:1-343Watch queue는 특별한 pipe의 ring buffer에 kernel notification을 기록하고 userspace가 `read()`로 소비하게 합니다. Producer는 consumer를 기다리지 않으며 overflow나 buffer 부족은 loss meta notification으로 알립니다.
Watch list는 notification source의 subscriber 집합이고, watch는 source와 output watch queue를 연결합니다. Source object ID와 userspace watch ID를 별도로 사용해 대상과 subscription을 구분합니다.
Message header의 `type`, `subtype`, `info`에는 source 종류, record 종류, 길이, watch ID 및 type별 정보가 들어갑니다. Queue filter는 이 값과 subtype bitmask를 기준으로 event를 선택합니다.
Userspace는 pipe를 만든 뒤 queue 크기와 filter를 ioctl로 설정하고, keyctl 같은 source별 API로 subscription을 연결한 다음 variable-length record를 순서대로 검증하며 읽습니다.