요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
=================
KVM VCPU Requests
=================
Overview
========
KVM supports an internal API enabling threads to request a VCPU thread to
perform some activity. For example, a thread may request a VCPU to flush
its TLB with a VCPU request. The API consists of the following functions::
/* Check if any requests are pending for VCPU @vcpu. */
bool kvm_request_pending(struct kvm_vcpu *vcpu);
/* Check if VCPU @vcpu has request @req pending. */
bool kvm_test_request(int req, struct kvm_vcpu *vcpu);
/* Clear request @req for VCPU @vcpu. */
void kvm_clear_request(int req, struct kvm_vcpu *vcpu);
/*
* Check if VCPU @vcpu has request @req pending. When the request is
* pending it will be cleared and a memory barrier, which pairs with
* another in kvm_make_request(), will be issued.
*/
bool kvm_check_request(int req, struct kvm_vcpu *vcpu);
/*
* Make request @req of VCPU @vcpu. Issues a memory barrier, which pairs
* with another in kvm_check_request(), prior to setting the request.
*/
void kvm_make_request(int req, struct kvm_vcpu *vcpu);
/* Make request @req of all VCPUs of the VM with struct kvm @kvm. */
bool kvm_make_all_cpus_request(struct kvm *kvm, unsigned int req);
Typically a requester wants the VCPU to perform the activity as soon
as possible after making the request. This means most requests
(kvm_make_request() calls) are followed by a call to kvm_vcpu_kick(),
and kvm_make_all_cpus_request() has the kicking of all VCPUs built
into it.
VCPU Kicks
----------
The goal of a VCPU kick is to bring a VCPU thread out of guest mode in
order to perform some KVM maintenance. To do so, an IPI is sent, forcing
a guest mode exit. However, a VCPU thread may not be in guest mode at the
time of the kick. Therefore, depending on the mode and state of the VCPU
thread, there are two other actions a kick may take. All three actions
are listed below:
1) Send an IPI. This forces a guest mode exit.
2) Waking a sleeping VCPU. Sleeping VCPUs are VCPU threads outside guest
mode that wait on waitqueues. Waking them removes the threads from
the waitqueues, allowing the threads to run again. This behavior
may be suppressed, see KVM_REQUEST_NO_WAKEUP below.
3) Nothing. When the VCPU is not in guest mode and the VCPU thread is not
sleeping, then there is nothing to do.
VCPU Mode
---------
VCPUs have a mode state, ``vcpu->mode``, that is used to track whether the
guest is running in guest mode or not, as well as some specific
outside guest mode states. The architecture may use ``vcpu->mode`` to
ensure VCPU requests are seen by VCPUs (see "Ensuring Requests Are Seen"),
as well as to avoid sending unnecessary IPIs (see "IPI Reduction"), and
even to ensure IPI acknowledgements are waited upon (see "Waiting for
Acknowledgements"). The following modes are defined:
OUTSIDE_GUEST_MODE
The VCPU thread is outside guest mode.
IN_GUEST_MODE
The VCPU thread is in guest mode.
EXITING_GUEST_MODE
The VCPU thread is transitioning from IN_GUEST_MODE to
OUTSIDE_GUEST_MODE.
READING_SHADOW_PAGE_TABLES
The VCPU thread is outside guest mode, but it wants the sender of
certain VCPU requests, namely KVM_REQ_TLB_FLUSH, to wait until the VCPU
thread is done reading the page tables.
VCPU Request Internals
======================
VCPU requests are simply bit indices of the ``vcpu->requests`` bitmap.
This means general bitops, like those documented in [atomic-ops]_ could
also be used, e.g. ::
clear_bit(KVM_REQ_UNBLOCK & KVM_REQUEST_MASK, &vcpu->requests);
However, VCPU request users should refrain from doing so, as it would
break the abstraction. The first 8 bits are reserved for architecture
independent requests; all additional bits are available for architecture
dependent requests.
Architecture Independent Requests
---------------------------------
KVM_REQ_TLB_FLUSH
KVM's common MMU notifier may need to flush all of a guest's TLB
entries, calling kvm_flush_remote_tlbs() to do so. Architectures that
choose to use the common kvm_flush_remote_tlbs() implementation will
need to handle this VCPU request.
KVM_REQ_VM_DEAD
This request informs all VCPUs that the VM is dead and unusable, e.g. due to
fatal error or because the VM's state has been intentionally destroyed.
KVM_REQ_UNBLOCK
This request informs the vCPU to exit kvm_vcpu_block. It is used for
example from timer handlers that run on the host on behalf of a vCPU,
or in order to update the interrupt routing and ensure that assigned
devices will wake up the vCPU.
KVM_REQ_OUTSIDE_GUEST_MODE
This "request" ensures the target vCPU has exited guest mode prior to the
sender of the request continuing on. No action needs be taken by the target,
and so no request is actually logged for the target. This request is similar
to a "kick", but unlike a kick it guarantees the vCPU has actually exited
guest mode. A kick only guarantees the vCPU will exit at some point in the
future, e.g. a previous kick may have started the process, but there's no
guarantee the to-be-kicked vCPU has fully exited guest mode.
KVM_REQUEST_MASK
----------------
VCPU requests should be masked by KVM_REQUEST_MASK before using them with
bitops. This is because only the lower 8 bits are used to represent the
request's number. The upper bits are used as flags. Currently only two
flags are defined.
VCPU Request Flags
------------------
KVM_REQUEST_NO_WAKEUP
This flag is applied to requests that only need immediate attention
from VCPUs running in guest mode. That is, sleeping VCPUs do not need
to be awakened for these requests. Sleeping VCPUs will handle the
requests when they are awakened later for some other reason.
KVM_REQUEST_WAIT
When requests with this flag are made with kvm_make_all_cpus_request(),
then the caller will wait for each VCPU to acknowledge its IPI before
proceeding. This flag only applies to VCPUs that would receive IPIs.
If, for example, the VCPU is sleeping, so no IPI is necessary, then
the requesting thread does not wait. This means that this flag may be
safely combined with KVM_REQUEST_NO_WAKEUP. See "Waiting for
Acknowledgements" for more information about requests with
KVM_REQUEST_WAIT.
VCPU Requests with Associated State
===================================
Requesters that want the receiving VCPU to handle new state need to ensure
the newly written state is observable to the receiving VCPU thread's CPU
by the time it observes the request. This means a write memory barrier
must be inserted after writing the new state and before setting the VCPU
request bit. Additionally, on the receiving VCPU thread's side, a
corresponding read barrier must be inserted after reading the request bit
and before proceeding to read the new state associated with it. See
scenario 3, Message and Flag, of [lwn-mb]_ and the kernel documentation
[memory-barriers]_.
The pair of functions, kvm_check_request() and kvm_make_request(), provide
the memory barriers, allowing this requirement to be handled internally by
the API.
Ensuring Requests Are Seen
==========================
When making requests to VCPUs, we want to avoid the receiving VCPU
executing in guest mode for an arbitrary long time without handling the
request. We can be sure this won't happen as long as we ensure the VCPU
thread checks kvm_request_pending() before entering guest mode and that a
kick will send an IPI to force an exit from guest mode when necessary.
Extra care must be taken to cover the period after the VCPU thread's last
kvm_request_pending() check and before it has entered guest mode, as kick
IPIs will only trigger guest mode exits for VCPU threads that are in guest
mode or at least have already disabled interrupts in order to prepare to
enter guest mode. This means that an optimized implementation (see "IPI
Reduction") must be certain when it's safe to not send the IPI. One
solution, which all architectures except s390 apply, is to:
- set ``vcpu->mode`` to IN_GUEST_MODE between disabling the interrupts and
the last kvm_request_pending() check;
- enable interrupts atomically when entering the guest.
This solution also requires memory barriers to be placed carefully in both
the requesting thread and the receiving VCPU. With the memory barriers we
can exclude the possibility of a VCPU thread observing
!kvm_request_pending() on its last check and then not receiving an IPI for
the next request made of it, even if the request is made immediately after
the check. This is done by way of the Dekker memory barrier pattern
(scenario 10 of [lwn-mb]_). As the Dekker pattern requires two variables,
this solution pairs ``vcpu->mode`` with ``vcpu->requests``. Substituting
them into the pattern gives::
CPU1 CPU2
================= =================
local_irq_disable();
WRITE_ONCE(vcpu->mode, IN_GUEST_MODE); kvm_make_request(REQ, vcpu);
smp_mb(); smp_mb();
if (kvm_request_pending(vcpu)) { if (READ_ONCE(vcpu->mode) ==
IN_GUEST_MODE) {
...abort guest entry... ...send IPI...
} }
As stated above, the IPI is only useful for VCPU threads in guest mode or
that have already disabled interrupts. This is why this specific case of
the Dekker pattern has been extended to disable interrupts before setting
``vcpu->mode`` to IN_GUEST_MODE. WRITE_ONCE() and READ_ONCE() are used to
pedantically implement the memory barrier pattern, guaranteeing the
compiler doesn't interfere with ``vcpu->mode``'s carefully planned
accesses.
IPI Reduction
-------------
As only one IPI is needed to get a VCPU to check for any/all requests,
then they may be coalesced. This is easily done by having the first IPI
sending kick also change the VCPU mode to something !IN_GUEST_MODE. The
transitional state, EXITING_GUEST_MODE, is used for this purpose.
Waiting for Acknowledgements
----------------------------
Some requests, those with the KVM_REQUEST_WAIT flag set, require IPIs to
be sent, and the acknowledgements to be waited upon, even when the target
VCPU threads are in modes other than IN_GUEST_MODE. For example, one case
is when a target VCPU thread is in READING_SHADOW_PAGE_TABLES mode, which
is set after disabling interrupts. To support these cases, the
KVM_REQUEST_WAIT flag changes the condition for sending an IPI from
checking that the VCPU is IN_GUEST_MODE to checking that it is not
OUTSIDE_GUEST_MODE.
Request-less VCPU Kicks
-----------------------
As the determination of whether or not to send an IPI depends on the
two-variable Dekker memory barrier pattern, then it's clear that
request-less VCPU kicks are almost never correct. Without the assurance
that a non-IPI generating kick will still result in an action by the
receiving VCPU, as the final kvm_request_pending() check does for
request-accompanying kicks, then the kick may not do anything useful at
all. If, for instance, a request-less kick was made to a VCPU that was
just about to set its mode to IN_GUEST_MODE, meaning no IPI is sent, then
the VCPU thread may continue its entry without actually having done
whatever it was the kick was meant to initiate.
One exception is x86's posted interrupt mechanism. In this case, however,
even the request-less VCPU kick is coupled with the same
local_irq_disable() + smp_mb() pattern described above; the ON bit
(Outstanding Notification) in the posted interrupt descriptor takes the
role of ``vcpu->requests``. When sending a posted interrupt, PIR.ON is
set before reading ``vcpu->mode``; dually, in the VCPU thread,
vmx_sync_pir_to_irr() reads PIR after setting ``vcpu->mode`` to
IN_GUEST_MODE.
Additional Considerations
=========================
Sleeping VCPUs
--------------
VCPU threads may need to consider requests before and/or after calling
functions that may put them to sleep, e.g. kvm_vcpu_block(). Whether they
do or not, and, if they do, which requests need consideration, is
architecture dependent. kvm_vcpu_block() calls kvm_arch_vcpu_runnable()
to check if it should awaken. One reason to do so is to provide
architectures a function where requests may be checked if necessary.
References
==========
.. [atomic-ops] Documentation/atomic_bitops.txt and Documentation/atomic_t.txt
.. [memory-barriers] Documentation/memory-barriers.txt
.. [lwn-mb] https://lwn.net/Articles/573436/
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
내부 요청 API
1-44KVM vCPU request는 한 스레드가 특정 vCPU 스레드에 TLB flush 같은 유지보수 작업을 수행하도록 요청하는 내부 API입니다. 요청자는 보통 가능한 빨리 처리되기를 원하므로 `kvm_make_request()` 뒤에 `kvm_vcpu_kick()`을 호출합니다. `kvm_make_all_cpus_request()`는 모든 vCPU kick을 자체적으로 수행합니다.
요청 상태 조회, 삭제, 생성의 핵심 함수입니다.
`kvm_test_request()`는 상태를 바꾸지 않는 단순 검사이고 `kvm_check_request()`는 확인과 동시에 bit를 지우며 필요한 barrier까지 실행한다는 차이가 있습니다. 수신 경로가 연관 상태를 소비한다면 후자를 써야 합니다.
단일 vCPU 요청은 request bit 설정과 kick이 별도 단계이지만 all-CPU helper는 전체 vCPU에 대한 두 동작을 하나의 인터페이스로 묶습니다.
.. SPDX-License-Identifier: GPL-2.0
=================
KVM VCPU Requests
=================
Overview
========
KVM supports an internal API enabling threads to request a VCPU thread to
perform some activity. For example, a thread may request a VCPU to flush
its TLB with a VCPU request. The API consists of the following functions::
/* Check if any requests are pending for VCPU @vcpu. */
bool kvm_request_pending(struct kvm_vcpu *vcpu);
/* Check if VCPU @vcpu has request @req pending. */
bool kvm_test_request(int req, struct kvm_vcpu *vcpu);
/* Clear request @req for VCPU @vcpu. */
void kvm_clear_request(int req, struct kvm_vcpu *vcpu);
/*
* Check if VCPU @vcpu has request @req pending. When the request is
* pending it will be cleared and a memory barrier, which pairs with
* another in kvm_make_request(), will be issued.
*/
bool kvm_check_request(int req, struct kvm_vcpu *vcpu);
/*
* Make request @req of VCPU @vcpu. Issues a memory barrier, which pairs
* with another in kvm_check_request(), prior to setting the request.
*/
void kvm_make_request(int req, struct kvm_vcpu *vcpu);
/* Make request @req of all VCPUs of the VM with struct kvm @kvm. */
bool kvm_make_all_cpus_request(struct kvm *kvm, unsigned int req);
Typically a requester wants the VCPU to perform the activity as soon
as possible after making the request. This means most requests
(kvm_make_request() calls) are followed by a call to kvm_vcpu_kick(),
and kvm_make_all_cpus_request() has the kicking of all VCPUs built
into it.
vCPU kick
45-62vCPU kick의 목적은 vCPU 스레드를 guest mode에서 꺼내 KVM 유지보수를 수행하게 하는 것입니다. vCPU가 guest mode이면 IPI를 보내 강제로 exit시킵니다.
kick 시점의 vCPU mode와 sleep 상태에 따라 결과가 달라집니다.
sleep vCPU를 깨우는 것은 guest exit가 아니라 waitqueue에서 runnable 상태로 되돌리는 작업입니다. 반면 이미 실행 중이면서 guest 밖에 있는 vCPU에는 별도의 강제 동작이 필요하지 않습니다.
VCPU Kicks
----------
The goal of a VCPU kick is to bring a VCPU thread out of guest mode in
order to perform some KVM maintenance. To do so, an IPI is sent, forcing
a guest mode exit. However, a VCPU thread may not be in guest mode at the
time of the kick. Therefore, depending on the mode and state of the VCPU
thread, there are two other actions a kick may take. All three actions
are listed below:
1) Send an IPI. This forces a guest mode exit.
2) Waking a sleeping VCPU. Sleeping VCPUs are VCPU threads outside guest
mode that wait on waitqueues. Waking them removes the threads from
the waitqueues, allowing the threads to run again. This behavior
may be suppressed, see KVM_REQUEST_NO_WAKEUP below.
3) Nothing. When the VCPU is not in guest mode and the VCPU thread is not
sleeping, then there is nothing to do.
vcpu->mode 상태
63-92`vcpu->mode`는 guest mode 실행 여부와 guest 밖의 특수 상태를 추적합니다. 아키텍처는 이 상태로 request 관측을 보장하고 불필요한 IPI를 줄이며 필요한 경우 IPI acknowledgement를 기다립니다.
요청자와 대상 vCPU가 동기화할 때 사용하는 네 상태입니다.
`EXITING_GUEST_MODE`는 단순한 상태 설명뿐 아니라 첫 kick 뒤 추가 IPI를 합치기 위한 전이 표지입니다. `READING_SHADOW_PAGE_TABLES`는 guest 밖이라는 사실만으로 안전하지 않은 TLB flush의 완료 경계를 나타냅니다.
VCPU Mode
---------
VCPUs have a mode state, ``vcpu->mode``, that is used to track whether the
guest is running in guest mode or not, as well as some specific
outside guest mode states. The architecture may use ``vcpu->mode`` to
ensure VCPU requests are seen by VCPUs (see "Ensuring Requests Are Seen"),
as well as to avoid sending unnecessary IPIs (see "IPI Reduction"), and
even to ensure IPI acknowledgements are waited upon (see "Waiting for
Acknowledgements"). The following modes are defined:
OUTSIDE_GUEST_MODE
The VCPU thread is outside guest mode.
IN_GUEST_MODE
The VCPU thread is in guest mode.
EXITING_GUEST_MODE
The VCPU thread is transitioning from IN_GUEST_MODE to
OUTSIDE_GUEST_MODE.
READING_SHADOW_PAGE_TABLES
The VCPU thread is outside guest mode, but it wants the sender of
certain VCPU requests, namely KVM_REQ_TLB_FLUSH, to wait until the VCPU
thread is done reading the page tables.
request 비트맵 내부 표현
93-106vCPU request는 `vcpu->requests` 비트맵의 bit index입니다. `clear_bit()` 같은 일반 bit operation으로 직접 다룰 수도 있지만 abstraction을 깨므로 request API를 사용해야 합니다.
처음 8개 비트는 architecture-independent request에 예약되고 나머지 비트는 아키텍처별 request에 사용할 수 있습니다.
원문의 `clear_bit(KVM_REQ_UNBLOCK & KVM_REQUEST_MASK, &vcpu->requests)` 예시는 직접 bit operation이 기술적으로 가능함을 보여줄 뿐 권장 사용법이 아닙니다. 상위 flag bit를 제거하는 마스크 규칙과 API barrier 의미를 우회하기 때문입니다.
VCPU Request Internals
======================
VCPU requests are simply bit indices of the ``vcpu->requests`` bitmap.
This means general bitops, like those documented in [atomic-ops]_ could
also be used, e.g. ::
clear_bit(KVM_REQ_UNBLOCK & KVM_REQUEST_MASK, &vcpu->requests);
However, VCPU request users should refrain from doing so, as it would
break the abstraction. The first 8 bits are reserved for architecture
independent requests; all additional bits are available for architecture
dependent requests.
아키텍처 독립 request
107-138모든 아키텍처가 공유할 수 있는 요청입니다.
`KVM_REQ_OUTSIDE_GUEST_MODE`는 대상이 수행할 작업을 기록하지 않는 특별한 request입니다. kick은 vCPU가 미래 어느 시점에 exit할 것만 보장하지만 이 request는 이미 완전히 exit했음을 보장합니다.
`KVM_REQ_UNBLOCK`은 호스트 timer가 vCPU 대신 실행된 뒤 차단을 풀거나 interrupt routing 변경 후 assigned device가 vCPU를 깨울 수 있게 할 때 사용됩니다. `KVM_REQ_VM_DEAD`는 복구 가능한 유지보수가 아니라 전체 VM을 사용할 수 없게 된 상태를 알립니다.
Architecture Independent Requests
---------------------------------
KVM_REQ_TLB_FLUSH
KVM's common MMU notifier may need to flush all of a guest's TLB
entries, calling kvm_flush_remote_tlbs() to do so. Architectures that
choose to use the common kvm_flush_remote_tlbs() implementation will
need to handle this VCPU request.
KVM_REQ_VM_DEAD
This request informs all VCPUs that the VM is dead and unusable, e.g. due to
fatal error or because the VM's state has been intentionally destroyed.
KVM_REQ_UNBLOCK
This request informs the vCPU to exit kvm_vcpu_block. It is used for
example from timer handlers that run on the host on behalf of a vCPU,
or in order to update the interrupt routing and ensure that assigned
devices will wake up the vCPU.
KVM_REQ_OUTSIDE_GUEST_MODE
This "request" ensures the target vCPU has exited guest mode prior to the
sender of the request continuing on. No action needs be taken by the target,
and so no request is actually logged for the target. This request is similar
to a "kick", but unlike a kick it guarantees the vCPU has actually exited
guest mode. A kick only guarantees the vCPU will exit at some point in the
future, e.g. a previous kick may have started the process, but there's no
guarantee the to-be-kicked vCPU has fully exited guest mode.
KVM_REQUEST_MASK와 플래그
139-167request 번호는 하위 8비트에 있고 상위 비트는 flag이므로 bit operation 전에 `KVM_REQUEST_MASK`로 마스킹해야 합니다.
즉시 wakeup과 IPI 확인 대기를 제어합니다.
sleep 중이라 IPI가 필요 없는 vCPU에 대해서는 `KVM_REQUEST_WAIT` 요청자도 기다리지 않습니다. 따라서 `KVM_REQUEST_WAIT`와 `KVM_REQUEST_NO_WAKEUP`을 안전하게 함께 사용할 수 있습니다.
`KVM_REQUEST_WAIT`의 대기 대상은 request를 받은 모든 vCPU가 아니라 실제 IPI를 받아 acknowledgement 의무가 생긴 vCPU입니다. 이 구분 덕분에 sleep vCPU를 깨우지 않는 요청과 조합해도 불필요한 대기가 발생하지 않습니다.
KVM_REQUEST_MASK
----------------
VCPU requests should be masked by KVM_REQUEST_MASK before using them with
bitops. This is because only the lower 8 bits are used to represent the
request's number. The upper bits are used as flags. Currently only two
flags are defined.
VCPU Request Flags
------------------
KVM_REQUEST_NO_WAKEUP
This flag is applied to requests that only need immediate attention
from VCPUs running in guest mode. That is, sleeping VCPUs do not need
to be awakened for these requests. Sleeping VCPUs will handle the
requests when they are awakened later for some other reason.
KVM_REQUEST_WAIT
When requests with this flag are made with kvm_make_all_cpus_request(),
then the caller will wait for each VCPU to acknowledge its IPI before
proceeding. This flag only applies to VCPUs that would receive IPIs.
If, for example, the VCPU is sleeping, so no IPI is necessary, then
the requesting thread does not wait. This means that this flag may be
safely combined with KVM_REQUEST_NO_WAKEUP. See "Waiting for
Acknowledgements" for more information about requests with
KVM_REQUEST_WAIT.
연관 상태의 memory ordering
168-184request와 함께 새 상태를 전달하는 요청자는 대상 CPU가 request를 관측할 때 새 상태도 볼 수 있게 해야 합니다. 새 상태를 쓴 뒤 request bit를 세우기 전에 write memory barrier가 필요합니다. 수신 vCPU는 request bit를 읽은 뒤 새 상태를 읽기 전에 대응하는 read barrier를 실행해야 합니다.
message-and-flag 패턴의 쓰기와 읽기 순서입니다.
`kvm_make_request()`와 `kvm_check_request()` 함수 쌍이 이 memory barrier를 내부에서 제공하므로 사용자가 직접 순서를 구현하지 않아도 됩니다.
이 규칙은 새 상태가 message이고 request bit가 flag인 message-and-flag 패턴입니다. bit만 먼저 보이거나 이전 상태를 읽는 일을 막으려면 송신 write barrier와 수신 read barrier가 반드시 쌍을 이뤄야 합니다.
VCPU Requests with Associated State
===================================
Requesters that want the receiving VCPU to handle new state need to ensure
the newly written state is observable to the receiving VCPU thread's CPU
by the time it observes the request. This means a write memory barrier
must be inserted after writing the new state and before setting the VCPU
request bit. Additionally, on the receiving VCPU thread's side, a
corresponding read barrier must be inserted after reading the request bit
and before proceeding to read the new state associated with it. See
scenario 3, Message and Flag, of [lwn-mb]_ and the kernel documentation
[memory-barriers]_.
The pair of functions, kvm_check_request() and kvm_make_request(), provide
the memory barriers, allowing this requirement to be handled internally by
the API.
request 관측 보장
185-232vCPU가 request를 처리하지 않은 채 guest mode에서 임의로 오래 실행되지 않게 하려면 guest 진입 전 `kvm_request_pending()`을 확인하고, 필요할 때 kick IPI가 guest mode exit를 강제해야 합니다.
특히 마지막 pending 확인 뒤 실제 guest 진입 전의 틈을 덮어야 합니다. kick IPI는 이미 guest mode에 있거나 guest 진입을 위해 interrupt를 끈 vCPU에서만 exit를 일으킬 수 있으므로 최적화된 구현은 IPI를 생략해도 되는 시점을 정확히 판단해야 합니다.
s390을 제외한 모든 아키텍처는 interrupt를 끈 뒤 마지막 request 확인 전에 `vcpu->mode`를 `IN_GUEST_MODE`로 설정하고 guest 진입 시 interrupt를 원자적으로 켭니다.
두 CPU가 서로의 상태를 하나 이상 관측하도록 `vcpu->mode`와 `vcpu->requests`를 짝짓습니다.
memory barrier 때문에 vCPU가 마지막 검사에서 request 없음만 보고 곧바로 만들어진 다음 request의 IPI까지 놓치는 상황을 배제할 수 있습니다. `WRITE_ONCE()`와 `READ_ONCE()`는 컴파일러가 세심하게 설계한 `vcpu->mode` 접근을 바꾸지 못하게 합니다.
이 KVM 적용은 `vcpu->mode`를 `IN_GUEST_MODE`로 쓰기 전에 interrupt를 끈다는 점에서 일반 Dekker 패턴을 확장합니다. 그래야 상태를 읽은 요청자가 보낸 IPI가 guest 진입 준비 구간에서도 유효합니다.
Dekker 패턴의 보장은 CPU1이 새 request를 보거나 CPU2가 `IN_GUEST_MODE`를 보는 둘 중 적어도 하나입니다. 전자면 vCPU가 guest 진입을 중단하고, 후자면 요청자가 IPI를 보내므로 request가 장시간 미처리되는 경로가 사라집니다.
interrupt를 먼저 끄지 않으면 요청자가 mode를 보고 IPI를 보내더라도 vCPU가 guest 진입 준비 전에 이를 처리하고 다시 진입해 request를 놓칠 수 있습니다. 따라서 interrupt 상태 변경도 barrier 패턴의 일부입니다.
Ensuring Requests Are Seen
==========================
When making requests to VCPUs, we want to avoid the receiving VCPU
executing in guest mode for an arbitrary long time without handling the
request. We can be sure this won't happen as long as we ensure the VCPU
thread checks kvm_request_pending() before entering guest mode and that a
kick will send an IPI to force an exit from guest mode when necessary.
Extra care must be taken to cover the period after the VCPU thread's last
kvm_request_pending() check and before it has entered guest mode, as kick
IPIs will only trigger guest mode exits for VCPU threads that are in guest
mode or at least have already disabled interrupts in order to prepare to
enter guest mode. This means that an optimized implementation (see "IPI
Reduction") must be certain when it's safe to not send the IPI. One
solution, which all architectures except s390 apply, is to:
- set ``vcpu->mode`` to IN_GUEST_MODE between disabling the interrupts and
the last kvm_request_pending() check;
- enable interrupts atomically when entering the guest.
This solution also requires memory barriers to be placed carefully in both
the requesting thread and the receiving VCPU. With the memory barriers we
can exclude the possibility of a VCPU thread observing
!kvm_request_pending() on its last check and then not receiving an IPI for
the next request made of it, even if the request is made immediately after
the check. This is done by way of the Dekker memory barrier pattern
(scenario 10 of [lwn-mb]_). As the Dekker pattern requires two variables,
this solution pairs ``vcpu->mode`` with ``vcpu->requests``. Substituting
them into the pattern gives::
CPU1 CPU2
================= =================
local_irq_disable();
WRITE_ONCE(vcpu->mode, IN_GUEST_MODE); kvm_make_request(REQ, vcpu);
smp_mb(); smp_mb();
if (kvm_request_pending(vcpu)) { if (READ_ONCE(vcpu->mode) ==
IN_GUEST_MODE) {
...abort guest entry... ...send IPI...
} }
As stated above, the IPI is only useful for VCPU threads in guest mode or
that have already disabled interrupts. This is why this specific case of
the Dekker pattern has been extended to disable interrupts before setting
``vcpu->mode`` to IN_GUEST_MODE. WRITE_ONCE() and READ_ONCE() are used to
pedantically implement the memory barrier pattern, guaranteeing the
compiler doesn't interfere with ``vcpu->mode``'s carefully planned
accesses.
IPI 축약
233-240하나의 IPI만으로 vCPU가 모든 pending request를 검사하게 할 수 있으므로 여러 request의 IPI를 합칠 수 있습니다. 첫 IPI를 보내는 kick이 mode를 `IN_GUEST_MODE`가 아닌 `EXITING_GUEST_MODE`로 바꾸면 이후 요청자는 추가 IPI를 생략할 수 있습니다.
mode 전환은 첫 IPI가 이미 guest exit를 시작했다는 신호입니다. 뒤따르는 요청은 같은 exit 뒤 한 번의 pending bitmap 검사에서 함께 처리되므로 별도의 IPI가 필요하지 않습니다.
IPI Reduction
-------------
As only one IPI is needed to get a VCPU to check for any/all requests,
then they may be coalesced. This is easily done by having the first IPI
sending kick also change the VCPU mode to something !IN_GUEST_MODE. The
transitional state, EXITING_GUEST_MODE, is used for this purpose.
acknowledgement 대기
241-252`KVM_REQUEST_WAIT` request는 대상 mode가 `IN_GUEST_MODE`가 아니더라도 IPI를 보내고 acknowledgement를 기다려야 할 수 있습니다. 예를 들어 `READING_SHADOW_PAGE_TABLES`는 interrupt를 끈 뒤 설정되는 상태이므로 TLB 관련 요청자가 완료를 기다려야 합니다.
따라서 이 flag는 IPI 전송 조건을 `mode == IN_GUEST_MODE`에서 `mode != OUTSIDE_GUEST_MODE`로 넓힙니다.
`READING_SHADOW_PAGE_TABLES`처럼 interrupt가 꺼진 guest 밖 상태에도 IPI acknowledgement가 동기화 지점 역할을 합니다. 완전히 `OUTSIDE_GUEST_MODE`인 경우에만 별도 IPI와 대기가 필요 없습니다.
Waiting for Acknowledgements
----------------------------
Some requests, those with the KVM_REQUEST_WAIT flag set, require IPIs to
be sent, and the acknowledgements to be waited upon, even when the target
VCPU threads are in modes other than IN_GUEST_MODE. For example, one case
is when a target VCPU thread is in READING_SHADOW_PAGE_TABLES mode, which
is set after disabling interrupts. To support these cases, the
KVM_REQUEST_WAIT flag changes the condition for sending an IPI from
checking that the VCPU is IN_GUEST_MODE to checking that it is not
OUTSIDE_GUEST_MODE.
request 없는 kick의 위험
253-275IPI 전송 여부가 `vcpu->mode`와 request bit의 Dekker 패턴에 의존하므로 request 없는 vCPU kick은 거의 항상 잘못된 설계입니다. IPI가 생략된 kick 뒤 대상이 어떤 작업을 수행하리라는 보장이 없기 때문입니다.
예를 들어 vCPU가 막 `IN_GUEST_MODE`로 mode를 바꾸려는 순간 request 없는 kick이 오면 IPI가 전송되지 않고, vCPU는 kick이 의도한 작업을 하지 않은 채 guest 진입을 계속할 수 있습니다.
예외는 x86 posted interrupt입니다. 이 경우에도 request 없는 kick이 같은 `local_irq_disable()`과 `smp_mb()` 패턴을 사용합니다. posted interrupt descriptor의 ON(Outstanding Notification) bit가 `vcpu->requests` 역할을 합니다.
송신자는 `vcpu->mode`를 읽기 전에 `PIR.ON`을 세우고, vCPU는 mode를 `IN_GUEST_MODE`로 설정한 뒤 `vmx_sync_pir_to_irr()`에서 PIR을 읽어 쌍을 이룹니다.
Request-less VCPU Kicks
-----------------------
As the determination of whether or not to send an IPI depends on the
two-variable Dekker memory barrier pattern, then it's clear that
request-less VCPU kicks are almost never correct. Without the assurance
that a non-IPI generating kick will still result in an action by the
receiving VCPU, as the final kvm_request_pending() check does for
request-accompanying kicks, then the kick may not do anything useful at
all. If, for instance, a request-less kick was made to a VCPU that was
just about to set its mode to IN_GUEST_MODE, meaning no IPI is sent, then
the VCPU thread may continue its entry without actually having done
whatever it was the kick was meant to initiate.
One exception is x86's posted interrupt mechanism. In this case, however,
even the request-less VCPU kick is coupled with the same
local_irq_disable() + smp_mb() pattern described above; the ON bit
(Outstanding Notification) in the posted interrupt descriptor takes the
role of ``vcpu->requests``. When sending a posted interrupt, PIR.ON is
set before reading ``vcpu->mode``; dually, in the VCPU thread,
vmx_sync_pir_to_irr() reads PIR after setting ``vcpu->mode`` to
IN_GUEST_MODE.
sleep vCPU 고려사항
276-288vCPU 스레드는 `kvm_vcpu_block()`처럼 sleep할 수 있는 함수를 호출하기 전이나 후에 request를 확인해야 할 수 있습니다. 확인 시점과 대상 request는 아키텍처별입니다.
`kvm_vcpu_block()`은 `kvm_arch_vcpu_runnable()`로 깨워야 하는지 검사합니다. 이 hook은 필요한 아키텍처가 request도 함께 확인할 수 있는 지점을 제공합니다.
따라서 공통 차단 함수가 모든 request의 처리 시점을 고정하지는 않습니다. 각 아키텍처는 sleep 진입 전 처리해야 할 요청과 wakeup 판정 중 확인할 요청을 자신의 실행 모델에 맞게 선택합니다.
Additional Considerations
=========================
Sleeping VCPUs
--------------
VCPU threads may need to consider requests before and/or after calling
functions that may put them to sleep, e.g. kvm_vcpu_block(). Whether they
do or not, and, if they do, which requests need consideration, is
architecture dependent. kvm_vcpu_block() calls kvm_arch_vcpu_runnable()
to check if it should awaken. One reason to do so is to provide
architectures a function where requests may be checked if necessary.
참고 문서
289-294비트 연산과 memory ordering의 배경 문서입니다.
References
==========
.. [atomic-ops] Documentation/atomic_bitops.txt and Documentation/atomic_t.txt
.. [memory-barriers] Documentation/memory-barriers.txt
.. [lwn-mb] https://lwn.net/Articles/573436/
요약·해설
vcpu-requests.rst:1-294vCPU request 비트맵, kick, mode, memory barrier와 IPI 동기화 규칙을 설명합니다.
요청 생성부터 guest exit, acknowledgement와 sleep 처리까지 함수·상태·코드 순서를 보존했습니다.