virtio-mem: Paravirtualized memory hotunplug part 1
Unplugging subblocks of memory blocks that are offline is easy. All we have to do is watch out for concurrent onlining activity. Tested-by: Pankaj Gupta <pankaj.gupta.linux@gmail.com> Cc: "Michael S. Tsirkin" <mst@redhat.com> Cc: Jason Wang <jasowang@redhat.com> Cc: Oscar Salvador <osalvador@suse.de> Cc: Michal Hocko <mhocko@kernel.org> Cc: Igor Mammedov <imammedo@redhat.com> Cc: Dave Young <dyoung@redhat.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Dan Williams <dan.j.williams@intel.com> Cc: Pavel Tatashin <pasha.tatashin@soleen.com> Cc: Stefan Hajnoczi <stefanha@redhat.com> Cc: Vlastimil Babka <vbabka@suse.cz> Signed-off-by: David Hildenbrand <david@redhat.com> Link: https://lore.kernel.org/r/20200507140139.17083-5-david@redhat.com Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
Родитель
f2af6d3978
Коммит
c627ff5d98
|
@ -123,7 +123,7 @@ struct virtio_mem {
|
|||
*
|
||||
* When this lock is held the pointers can't change, ONLINE and
|
||||
* OFFLINE blocks can't change the state and no subblocks will get
|
||||
* plugged.
|
||||
* plugged/unplugged.
|
||||
*/
|
||||
struct mutex hotplug_mutex;
|
||||
bool hotplug_active;
|
||||
|
@ -280,6 +280,12 @@ static int virtio_mem_mb_state_prepare_next_mb(struct virtio_mem *vm)
|
|||
_mb_id++) \
|
||||
if (virtio_mem_mb_get_state(_vm, _mb_id) == _state)
|
||||
|
||||
#define virtio_mem_for_each_mb_state_rev(_vm, _mb_id, _state) \
|
||||
for (_mb_id = _vm->next_mb_id - 1; \
|
||||
_mb_id >= _vm->first_mb_id && _vm->nb_mb_state[_state]; \
|
||||
_mb_id--) \
|
||||
if (virtio_mem_mb_get_state(_vm, _mb_id) == _state)
|
||||
|
||||
/*
|
||||
* Mark all selected subblocks plugged.
|
||||
*
|
||||
|
@ -325,6 +331,19 @@ static bool virtio_mem_mb_test_sb_plugged(struct virtio_mem *vm,
|
|||
bit + count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test if all selected subblocks are unplugged.
|
||||
*/
|
||||
static bool virtio_mem_mb_test_sb_unplugged(struct virtio_mem *vm,
|
||||
unsigned long mb_id, int sb_id,
|
||||
int count)
|
||||
{
|
||||
const int bit = (mb_id - vm->first_mb_id) * vm->nb_sb_per_mb + sb_id;
|
||||
|
||||
/* TODO: Helper similar to bitmap_set() */
|
||||
return find_next_bit(vm->sb_bitmap, bit + count, bit) >= bit + count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the first plugged subblock. Returns vm->nb_sb_per_mb in case there is
|
||||
* none.
|
||||
|
@ -513,6 +532,9 @@ static void virtio_mem_notify_offline(struct virtio_mem *vm,
|
|||
BUG();
|
||||
break;
|
||||
}
|
||||
|
||||
/* trigger the workqueue, maybe we can now unplug memory. */
|
||||
virtio_mem_retry(vm);
|
||||
}
|
||||
|
||||
static void virtio_mem_notify_online(struct virtio_mem *vm, unsigned long mb_id,
|
||||
|
@ -1122,6 +1144,94 @@ out_unlock:
|
|||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unplug the desired number of plugged subblocks of an offline memory block.
|
||||
* Will fail if any subblock cannot get unplugged (instead of skipping it).
|
||||
*
|
||||
* Will modify the state of the memory block. Might temporarily drop the
|
||||
* hotplug_mutex.
|
||||
*
|
||||
* Note: Can fail after some subblocks were successfully unplugged.
|
||||
*/
|
||||
static int virtio_mem_mb_unplug_any_sb_offline(struct virtio_mem *vm,
|
||||
unsigned long mb_id,
|
||||
uint64_t *nb_sb)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = virtio_mem_mb_unplug_any_sb(vm, mb_id, nb_sb);
|
||||
|
||||
/* some subblocks might have been unplugged even on failure */
|
||||
if (!virtio_mem_mb_test_sb_plugged(vm, mb_id, 0, vm->nb_sb_per_mb))
|
||||
virtio_mem_mb_set_state(vm, mb_id,
|
||||
VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (virtio_mem_mb_test_sb_unplugged(vm, mb_id, 0, vm->nb_sb_per_mb)) {
|
||||
/*
|
||||
* Remove the block from Linux - this should never fail.
|
||||
* Hinder the block from getting onlined by marking it
|
||||
* unplugged. Temporarily drop the mutex, so
|
||||
* any pending GOING_ONLINE requests can be serviced/rejected.
|
||||
*/
|
||||
virtio_mem_mb_set_state(vm, mb_id,
|
||||
VIRTIO_MEM_MB_STATE_UNUSED);
|
||||
|
||||
mutex_unlock(&vm->hotplug_mutex);
|
||||
rc = virtio_mem_mb_remove(vm, mb_id);
|
||||
BUG_ON(rc);
|
||||
mutex_lock(&vm->hotplug_mutex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to unplug the requested amount of memory.
|
||||
*/
|
||||
static int virtio_mem_unplug_request(struct virtio_mem *vm, uint64_t diff)
|
||||
{
|
||||
uint64_t nb_sb = diff / vm->subblock_size;
|
||||
unsigned long mb_id;
|
||||
int rc;
|
||||
|
||||
if (!nb_sb)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* We'll drop the mutex a couple of times when it is safe to do so.
|
||||
* This might result in some blocks switching the state (online/offline)
|
||||
* and we could miss them in this run - we will retry again later.
|
||||
*/
|
||||
mutex_lock(&vm->hotplug_mutex);
|
||||
|
||||
/* Try to unplug subblocks of partially plugged offline blocks. */
|
||||
virtio_mem_for_each_mb_state_rev(vm, mb_id,
|
||||
VIRTIO_MEM_MB_STATE_OFFLINE_PARTIAL) {
|
||||
rc = virtio_mem_mb_unplug_any_sb_offline(vm, mb_id,
|
||||
&nb_sb);
|
||||
if (rc || !nb_sb)
|
||||
goto out_unlock;
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
/* Try to unplug subblocks of plugged offline blocks. */
|
||||
virtio_mem_for_each_mb_state_rev(vm, mb_id,
|
||||
VIRTIO_MEM_MB_STATE_OFFLINE) {
|
||||
rc = virtio_mem_mb_unplug_any_sb_offline(vm, mb_id,
|
||||
&nb_sb);
|
||||
if (rc || !nb_sb)
|
||||
goto out_unlock;
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
mutex_unlock(&vm->hotplug_mutex);
|
||||
return 0;
|
||||
out_unlock:
|
||||
mutex_unlock(&vm->hotplug_mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to unplug all blocks that couldn't be unplugged before, for example,
|
||||
* because the hypervisor was busy.
|
||||
|
@ -1204,8 +1314,10 @@ retry:
|
|||
if (vm->requested_size > vm->plugged_size) {
|
||||
diff = vm->requested_size - vm->plugged_size;
|
||||
rc = virtio_mem_plug_request(vm, diff);
|
||||
} else {
|
||||
diff = vm->plugged_size - vm->requested_size;
|
||||
rc = virtio_mem_unplug_request(vm, diff);
|
||||
}
|
||||
/* TODO: try to unplug memory */
|
||||
}
|
||||
|
||||
switch (rc) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче