From dcb809e824dcde7abd8118ed83f0ba0bf824d5c1 Mon Sep 17 00:00:00 2001 From: Conghui Chen Date: Thu, 30 Apr 2020 15:39:37 +0000 Subject: [PATCH] doc: update cpu-sharing doc change IORR to BVT, update affinity related info. Signed-off-by: Conghui Chen --- doc/tutorials/cpu_sharing.rst | 264 +++++++----------- .../images/cpu_sharing_framework.png | Bin 8109 -> 8705 bytes 2 files changed, 102 insertions(+), 162 deletions(-) diff --git a/doc/tutorials/cpu_sharing.rst b/doc/tutorials/cpu_sharing.rst index 66e10ca90..040867aa1 100644 --- a/doc/tutorials/cpu_sharing.rst +++ b/doc/tutorials/cpu_sharing.rst @@ -6,17 +6,33 @@ ACRN CPU Sharing Introduction ************ -The goal of CPU Sharing is to fully utilize the physical CPU resource to support more virtual machines. Currently, ACRN only supports 1 to 1 mapping mode between virtual CPUs (vCPUs) and physical CPUs (pCPUs). Because of the lack of CPU sharing ability, the number of VMs is limited. To support CPU Sharing, we have introduced a scheduling framework and implemented two simple small scheduling algorithms to satisfy embedded device requirements. Note that, CPU Sharing is not available for VMs with local APIC passthrough (``--lapic_pt`` option). +The goal of CPU Sharing is to fully utilize the physical CPU resource to +support more virtual machines. Currently, ACRN only supports 1 to 1 +mapping mode between virtual CPUs (vCPUs) and physical CPUs (pCPUs). +Because of the lack of CPU sharing ability, the number of VMs is +limited. To support CPU Sharing, we have introduced a scheduling +framework and implemented two simple small scheduling algorithms to +satisfy embedded device requirements. Note that, CPU Sharing is not +available for VMs with local APIC passthrough (``--lapic_pt`` option). Scheduling Framework ******************** -To satisfy the modularization design concept, the scheduling framework layer isolates the vCPU layer and scheduler algorithm. It does not have a vCPU concept so it is only aware of the thread object instance. The thread object state machine is maintained in the framework. The framework abstracts the scheduler algorithm object, so this architecture can easily extend to new scheduler algorithms. +To satisfy the modularization design concept, the scheduling framework +layer isolates the vCPU layer and scheduler algorithm. It does not have +a vCPU concept so it is only aware of the thread object instance. The +thread object state machine is maintained in the framework. The +framework abstracts the scheduler algorithm object, so this architecture +can easily extend to new scheduler algorithms. .. figure:: images/cpu_sharing_framework.png :align: center -The below diagram shows that the vCPU layer invokes APIs provided by scheduling framework for vCPU scheduling. The scheduling framework also provides some APIs for schedulers. The scheduler mainly implements some callbacks in an ``acrn_scheduler`` instance for scheduling framework. Scheduling initialization is invoked in the hardware management layer. +The below diagram shows that the vCPU layer invokes APIs provided by +scheduling framework for vCPU scheduling. The scheduling framework also +provides some APIs for schedulers. The scheduler mainly implements some +callbacks in an ``acrn_scheduler`` instance for scheduling framework. +Scheduling initialization is invoked in the hardware management layer. .. figure:: images/cpu_sharing_api.png :align: center @@ -24,7 +40,9 @@ The below diagram shows that the vCPU layer invokes APIs provided by scheduling vCPU affinity ************* -Currently, we do not support vCPU migration; the assignment of vCPU mapping to pCPU is statically configured in the VM configuration via a vcpu_affinity array. The item number of the array matches the vCPU number of this VM. Each item has one bit to indicate the assigned pCPU of the corresponding vCPU. Use these rules to configure the vCPU affinity: +Currently, we do not support vCPU migration; the assignment of vCPU +mapping to pCPU is statically configured by acrn-dm through +``--cpu_affinity``. Use these rules to configure the vCPU affinity: - Only one bit can be set for each affinity item of vCPU. - vCPUs in the same VM cannot be assigned to the same pCPU. @@ -46,26 +64,59 @@ The thread object contains three states: RUNNING, RUNNABLE, and BLOCKED. .. figure:: images/cpu_sharing_state.png :align: center -After a new vCPU is created, the corresponding thread object is initiated. The vCPU layer invokes a wakeup operation. After wakeup, the state for the new thread object is set to RUNNABLE, and then follows its algorithm to determine whether or not to preempt the current running thread object. If yes, it turns to the RUNNING state. In RUNNING state, the thread object may turn back to the RUNNABLE state when it runs out of its timeslice, or it might yield the pCPU by itself, or be preempted. The thread object under RUNNING state may trigger sleep to transfer to BLOCKED state. +After a new vCPU is created, the corresponding thread object is +initiated. The vCPU layer invokes a wakeup operation. After wakeup, the +state for the new thread object is set to RUNNABLE, and then follows its +algorithm to determine whether or not to preempt the current running +thread object. If yes, it turns to the RUNNING state. In RUNNING state, +the thread object may turn back to the RUNNABLE state when it runs out +of its timeslice, or it might yield the pCPU by itself, or be preempted. +The thread object under RUNNING state may trigger sleep to transfer to +BLOCKED state. Scheduler ********* -The below block diagram shows the basic concept for the scheduler. There are two kinds of scheduler in the diagram: NOOP (No-Operation) scheduler and IORR (IO sensitive Round-Robin) scheduler. +The below block diagram shows the basic concept for the scheduler. There +are two kinds of schedulers in the diagram: NOOP (No-Operation) scheduler +and BVT (Borrowed Virtual Time) scheduler. - **No-Operation scheduler**: - The NOOP (No-operation) scheduler has the same policy as the original 1-1 mapping previously used; every pCPU can run only two thread objects: one is the idle thread, and another is the thread of the assigned vCPU. With this scheduler, vCPU works in Work-Conserving mode, which always try to keep resource busy, and will run once it is ready. Idle thread can run when the vCPU thread is blocked. + The NOOP (No-operation) scheduler has the same policy as the original + 1-1 mapping previously used; every pCPU can run only two thread objects: + one is the idle thread, and another is the thread of the assigned vCPU. + With this scheduler, vCPU works in Work-Conserving mode, which always + try to keep resource busy, and will run once it is ready. Idle thread + can run when the vCPU thread is blocked. -- **IO sensitive round-robin scheduler**: +- **Borrowed Virtual Time scheduler**: - The IORR (IO sensitive round-robin) scheduler is implemented with the per-pCPU runqueue and the per-pCPU tick timer; it supports more than one vCPU running on a pCPU. It basically schedules thread objects in a round-robin policy and supports preemption by timeslice counting. + BVT (Borrowed Virtual time) is a virtual time based scheduling + algorithm, it dispatching the runnable thread with the earliest + effective virtual time. + + TODO: BVT scheduler will be built on top of prioritized scheduling + mechanism, i.e. higher priority threads get scheduled first, and same + priority tasks are scheduled per BVT. + + - **Virtual time**: The thread with the earliest effective virtual + time (EVT) is dispatched first. + - **Warp**: a latency-sensitive thread is allowed to warp back in + virtual time to make it appear earlier. It borrows virtual time from + its future CPU allocation and thus does not disrupt long-term CPU + sharing + - **MCU**: minimum charging unit, the scheduler account for running time + in units of MCU. + - **Weighted fair sharing**: each runnable thread receives a share of + the processor in proportion to its weight over a scheduling + window of some number of MCU. + - **C**: context switch allowance. Real time by which the current + thread is allowed to advance beyond another runnable thread with + equal claim on the CPU. C is similar to the quantum in conventional + timesharing. - - Every thread object has an initial timeslice (ex: 10ms) - - The timeslice is consumed with time and be counted in the context switch and tick handler - - If the timeslice is positive or zero, then switch out the current thread object and put it to tail of runqueue. Then, pick the next runnable one from runqueue to run. - - Threads with an IO request will preempt current running threads on the same pCPU. Scheduler configuration *********************** @@ -75,169 +126,58 @@ Two places in the code decide the usage for the scheduler. * The option in Kconfig decides the only scheduler used in runtime. ``hypervisor/arch/x86/Kconfig`` - .. literalinclude:: ../../../../hypervisor/arch/x86/Kconfig - :name: Kconfig for Scheduler - :caption: Kconfig for Scheduler - :linenos: - :lines: 40-58 - :emphasize-lines: 3 - :language: c +.. code-block:: none -The default scheduler is **SCHED_NOOP**. To use the IORR, change it to **SCHED_IORR** in the **ACRN Scheduler**. + config SCHED_BVT + bool "BVT scheduler" + help + BVT (Borrowed Virtual time) is virtual time based scheduling algorithm. It + dispatches the runnable thread with the earliest effective virtual time. + TODO: BVT scheduler will be built on top of prioritized scheduling mechanism, + i.e. higher priority threads get scheduled first, and same priority tasks are + scheduled per BVT. -* The affinity for VMs are set in ``hypervisor/scenarios//vm_configurations.h`` +The default scheduler is **SCHED_NOOP**. To use the BVT, change it to +**SCHED_BVT** in the **ACRN Scheduler**. - .. literalinclude:: ../../../..//hypervisor/scenarios/industry/vm_configurations.h - :name: Affinity for VMs - :caption: Affinity for VMs - :linenos: - :lines: 31-32 - :language: c +* The cpu_affinity is configured by acrn-dm command. + + For example, assign physical CPUs (pCPUs) 1 and 3 to this VM using:: + + --cpu_affinity 1,3 -* vCPU number corresponding to affinity is set in ``hypervisor/scenarios//vm_configurations.c`` by the **vcpu_num** Example ******* -To support below configuration in industry scenario: +Use the following settings to support this configuration in the industry scenario: -+-----------------+-------+-------+--------+ -|pCPU0 |pCPU1 |pCPU2 |pCPU3 | -+=================+=======+=======+========+ -|Service VM WaaG |RT Linux |vxWorks | -+-----------------+---------------+--------+ ++---------+-------+-------+-------+ +|pCPU0 |pCPU1 |pCPU2 |pCPU3 | ++=========+=======+=======+=======+ +|SOS + WaaG |RT Linux | ++-----------------+---------------+ -Change the following three files: +- offline pcpu2-3 in SOS. -1. ``hypervisor/arch/x86/Kconfig`` +- launch guests. -.. code-block:: none + - launch WaaG with "--cpu_affinity=0,1" + - launch RT with "--cpu_affinity=2,3" - choice - prompt "ACRN Scheduler" - -default SCHED_NOOP - +default SCHED_IORR - help - Select the CPU scheduler to be used by the hypervisor -2. ``hypervisor/scenarios/industry/vm_configurations.h`` +After you start all VMs, check the vCPU affinities from the Hypervisor +console with the ``vcpu_list`` command: -.. code-block:: none +.. code-block:: console - #define CONFIG_MAX_VM_NUM (4U) + ACRN:\>vcpu_list - #define DM_OWNED_GUEST_FLAG_MASK (GUEST_FLAG_SECURE_WORLD_ENABLED | GUEST_FLAG_LAPIC_PASSTHROUGH | \ - GUEST_FLAG_RT | GUEST_FLAG_IO_COMPLETION_POLLING) - - #define SOS_VM_BOOTARGS SOS_ROOTFS \ - "rw rootwait " \ - "console=tty0 " \ - SOS_CONSOLE \ - "consoleblank=0 " \ - "no_timer_check " \ - "quiet loglevel=3 " \ - "i915.nuclear_pageflip=1 " \ - "i915.avail_planes_per_pipe=0x01010F " \ - "i915.domain_plane_owners=0x011111110000 " \ - "i915.enable_gvt=1 " \ - SOS_BOOTARGS_DIFF - - #define VM1_CONFIG_CPU_AFFINITY {AFFINITY_CPU(0U)} - #define VM2_CONFIG_CPU_AFFINITY {AFFINITY_CPU(1U), AFFINITY_CPU(2U)} - #define VM3_CONFIG_CPU_AFFINITY {AFFINITY_CPU(3U)} - -3. ``hypervisor/scenarios/industry/vm_configurations.c`` - -.. code-block:: none - - struct acrn_vm_config vm_configs[CONFIG_MAX_VM_NUM] = { - { - CONFIG_SOS_VM, - .name = "ACRN SOS VM", - .guest_flags = 0UL, - .clos = 0U, - .memory = { - .start_hpa = 0UL, - .size = CONFIG_SOS_RAM_SIZE, - }, - .os_config = { - .name = "ACRN Service OS", - .kernel_type = KERNEL_BZIMAGE, - .kernel_mod_tag = "Linux_bzImage", - .bootargs = SOS_VM_BOOTARGS - }, - .vuart[0] = { - .type = VUART_LEGACY_PIO, - .addr.port_base = SOS_COM1_BASE, - .irq = SOS_COM1_IRQ, - }, - .vuart[1] = { - .type = VUART_LEGACY_PIO, - .addr.port_base = SOS_COM2_BASE, - .irq = SOS_COM2_IRQ, - .t_vuart.vm_id = 2U, - .t_vuart.vuart_id = 1U, - }, - .pci_dev_num = SOS_EMULATED_PCI_DEV_NUM, - .pci_devs = sos_pci_devs, - }, - { - CONFIG_POST_STD_VM(1), - .cpu_affinity_bitmap = VM1_CONFIG_CPU_AFFINITY, - .vuart[0] = { - .type = VUART_LEGACY_PIO, - .addr.port_base = COM1_BASE, - .irq = COM1_IRQ, - }, - .vuart[1] = { - .type = VUART_LEGACY_PIO, - .addr.port_base = INVALID_COM_BASE, - } - - }, - { - CONFIG_POST_RT_VM(1), - .cpu_affinity_bitmap = VM2_CONFIG_CPU_AFFINITY, - .vuart[0] = { - .type = VUART_LEGACY_PIO, - .addr.port_base = COM1_BASE, - .irq = COM1_IRQ, - }, - .vuart[1] = { - .type = VUART_LEGACY_PIO, - .addr.port_base = COM2_BASE, - .irq = COM2_IRQ, - .t_vuart.vm_id = 0U, - .t_vuart.vuart_id = 1U, - }, - }, - { - CONFIG_POST_STD_VM(2), - .cpu_affinity_bitmap = VM3_CONFIG_CPU_AFFINITY, - .vuart[0] = { - .type = VUART_LEGACY_PIO, - .addr.port_base = COM1_BASE, - .irq = COM1_IRQ, - }, - .vuart[1] = { - .type = VUART_LEGACY_PIO, - .addr.port_base = INVALID_COM_BASE, - } - - }, - - }; - -After you start all VMs, check the vCPU affinities from the Hypervisor console: - -.. code-block:: none - - ACRN:\>vcpu_list - - VM ID PCPU ID VCPU ID VCPU ROLE VCPU STATE - ===== ======= ======= ========= ========== - 0 0 0 PRIMARY Running - 1 0 0 PRIMARY Running - 2 1 0 PRIMARY Running - 2 2 1 SECONDARY Running - 3 3 0 PRIMARY Running + VM ID PCPU ID VCPU ID VCPU ROLE VCPU STATE THREAD STATE + ===== ======= ======= ========= ========== ========== + 0 0 0 PRIMARY Running BLOCKED + 0 1 0 SECONDARY Running BLOCKED + 1 0 0 PRIMARY Running RUNNING + 1 1 0 SECONDARY Running RUNNING + 2 2 0 PRIMARY Running RUNNING + 2 3 1 SECONDARY Running RUNNING diff --git a/doc/tutorials/images/cpu_sharing_framework.png b/doc/tutorials/images/cpu_sharing_framework.png index ad18d23c8c3716734a571844f85eb31534964ef1..8dce87e5425720278fcb75193e5fe393f95905ba 100644 GIT binary patch literal 8705 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