mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
Merge tag 'migration-next-pull-request' of https://gitlab.com/peterx/qemu into staging
Migartion pull request for 20240304 - Bryan's fix on multifd compression level API - Fabiano's mapped-ram series (base + multifd only) - Steve's amend on cpr document in qapi/ # -----BEGIN PGP SIGNATURE----- # # iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCZeUjKhIccGV0ZXJ4QHJl # ZGhhdC5jb20ACgkQO1/MzfOr1wbv5QD/ZexBUsmZA5qyxgGvZ2yvlUBEGNOvtmKY # kRdiYPU7khMA/0N43rn4LcqKCoq4+T+EAnYizGjIyhH/7BRUyn4DUxgO # =AeEn # -----END PGP SIGNATURE----- # gpg: Signature made Mon 04 Mar 2024 01:26:02 GMT # gpg: using EDDSA key B9184DC20CC457DACF7DD1A93B5FCCCDF3ABD706 # gpg: issuer "peterx@redhat.com" # gpg: Good signature from "Peter Xu <xzpeter@gmail.com>" [marginal] # gpg: aka "Peter Xu <peterx@redhat.com>" [marginal] # gpg: WARNING: This key is not certified with sufficiently trusted signatures! # gpg: It is not certain that the signature belongs to the owner. # Primary key fingerprint: B918 4DC2 0CC4 57DA CF7D D1A9 3B5F CCCD F3AB D706 * tag 'migration-next-pull-request' of https://gitlab.com/peterx/qemu: (27 commits) migration/multifd: Document two places for mapped-ram tests/qtest/migration: Add a multifd + mapped-ram migration test migration/multifd: Add mapped-ram support to fd: URI migration/multifd: Support incoming mapped-ram stream format migration/multifd: Support outgoing mapped-ram stream format migration/multifd: Prepare multifd sync for mapped-ram migration migration/multifd: Add incoming QIOChannelFile support migration/multifd: Add outgoing QIOChannelFile support migration/multifd: Add a wrapper for channels_created migration/multifd: Allow receiving pages without packets migration/multifd: Allow multifd without packets migration/multifd: Decouple recv method from pages migration/multifd: Rename MultiFDSend|RecvParams::data to compress_data tests/qtest/migration: Add tests for mapped-ram file-based migration migration/ram: Add incoming 'mapped-ram' migration migration/ram: Add outgoing 'mapped-ram' migration migration: Add mapped-ram URI compatibility check migration/ram: Introduce 'mapped-ram' migration capability migration/qemu-file: add utility methods for working with seekable channels io: fsync before closing a file channel ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org> # Conflicts: # migration/ram.c
This commit is contained in:
@@ -10,3 +10,4 @@ Migration has plenty of features to support different use cases.
|
||||
dirty-limit
|
||||
vfio
|
||||
virtio
|
||||
mapped-ram
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
Mapped-ram
|
||||
==========
|
||||
|
||||
Mapped-ram is a new stream format for the RAM section designed to
|
||||
supplement the existing ``file:`` migration and make it compatible
|
||||
with ``multifd``. This enables parallel migration of a guest's RAM to
|
||||
a file.
|
||||
|
||||
The core of the feature is to ensure that RAM pages are mapped
|
||||
directly to offsets in the resulting migration file. This enables the
|
||||
``multifd`` threads to write exclusively to those offsets even if the
|
||||
guest is constantly dirtying pages (i.e. live migration). Another
|
||||
benefit is that the resulting file will have a bounded size, since
|
||||
pages which are dirtied multiple times will always go to a fixed
|
||||
location in the file, rather than constantly being added to a
|
||||
sequential stream. Having the pages at fixed offsets also allows the
|
||||
usage of O_DIRECT for save/restore of the migration stream as the
|
||||
pages are ensured to be written respecting O_DIRECT alignment
|
||||
restrictions (direct-io support not yet implemented).
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
On both source and destination, enable the ``multifd`` and
|
||||
``mapped-ram`` capabilities:
|
||||
|
||||
``migrate_set_capability multifd on``
|
||||
|
||||
``migrate_set_capability mapped-ram on``
|
||||
|
||||
Use a ``file:`` URL for migration:
|
||||
|
||||
``migrate file:/path/to/migration/file``
|
||||
|
||||
Mapped-ram migration is best done non-live, i.e. by stopping the VM on
|
||||
the source side before migrating.
|
||||
|
||||
Use-cases
|
||||
---------
|
||||
|
||||
The mapped-ram feature was designed for use cases where the migration
|
||||
stream will be directed to a file in the filesystem and not
|
||||
immediately restored on the destination VM [#]_. These could be
|
||||
thought of as snapshots. We can further categorize them into live and
|
||||
non-live.
|
||||
|
||||
- Non-live snapshot
|
||||
|
||||
If the use case requires a VM to be stopped before taking a snapshot,
|
||||
that's the ideal scenario for mapped-ram migration. Not having to
|
||||
track dirty pages, the migration will write the RAM pages to the disk
|
||||
as fast as it can.
|
||||
|
||||
Note: if a snapshot is taken of a running VM, but the VM will be
|
||||
stopped after the snapshot by the admin, then consider stopping it
|
||||
right before the snapshot to take benefit of the performance gains
|
||||
mentioned above.
|
||||
|
||||
- Live snapshot
|
||||
|
||||
If the use case requires that the VM keeps running during and after
|
||||
the snapshot operation, then mapped-ram migration can still be used,
|
||||
but will be less performant. Other strategies such as
|
||||
background-snapshot should be evaluated as well. One benefit of
|
||||
mapped-ram in this scenario is portability since background-snapshot
|
||||
depends on async dirty tracking (KVM_GET_DIRTY_LOG) which is not
|
||||
supported outside of Linux.
|
||||
|
||||
.. [#] While this same effect could be obtained with the usage of
|
||||
snapshots or the ``file:`` migration alone, mapped-ram provides
|
||||
a performance increase for VMs with larger RAM sizes (10s to
|
||||
100s of GiBs), specially if the VM has been stopped beforehand.
|
||||
|
||||
RAM section format
|
||||
------------------
|
||||
|
||||
Instead of having a sequential stream of pages that follow the
|
||||
RAMBlock headers, the dirty pages for a RAMBlock follow its header
|
||||
instead. This ensures that each RAM page has a fixed offset in the
|
||||
resulting migration file.
|
||||
|
||||
A bitmap is introduced to track which pages have been written in the
|
||||
migration file. Pages are written at a fixed location for every
|
||||
ramblock. Zero pages are ignored as they'd be zero in the destination
|
||||
migration as well.
|
||||
|
||||
::
|
||||
|
||||
Without mapped-ram: With mapped-ram:
|
||||
|
||||
--------------------- --------------------------------
|
||||
| ramblock 1 header | | ramblock 1 header |
|
||||
--------------------- --------------------------------
|
||||
| ramblock 2 header | | ramblock 1 mapped-ram header |
|
||||
--------------------- --------------------------------
|
||||
| ... | | padding to next 1MB boundary |
|
||||
--------------------- | ... |
|
||||
| ramblock n header | --------------------------------
|
||||
--------------------- | ramblock 1 pages |
|
||||
| RAM_SAVE_FLAG_EOS | | ... |
|
||||
--------------------- --------------------------------
|
||||
| stream of pages | | ramblock 2 header |
|
||||
| (iter 1) | --------------------------------
|
||||
| ... | | ramblock 2 mapped-ram header |
|
||||
--------------------- --------------------------------
|
||||
| RAM_SAVE_FLAG_EOS | | padding to next 1MB boundary |
|
||||
--------------------- | ... |
|
||||
| stream of pages | --------------------------------
|
||||
| (iter 2) | | ramblock 2 pages |
|
||||
| ... | | ... |
|
||||
--------------------- --------------------------------
|
||||
| ... | | ... |
|
||||
--------------------- --------------------------------
|
||||
| RAM_SAVE_FLAG_EOS |
|
||||
--------------------------------
|
||||
| ... |
|
||||
--------------------------------
|
||||
|
||||
where:
|
||||
- ramblock header: the generic information for a ramblock, such as
|
||||
idstr, used_len, etc.
|
||||
|
||||
- ramblock mapped-ram header: the information added by this feature:
|
||||
bitmap of pages written, bitmap size and offset of pages in the
|
||||
migration file.
|
||||
|
||||
Restrictions
|
||||
------------
|
||||
|
||||
Since pages are written to their relative offsets and out of order
|
||||
(due to the memory dirtying patterns), streaming channels such as
|
||||
sockets are not supported. A seekable channel such as a file is
|
||||
required. This can be verified in the QIOChannel by the presence of
|
||||
the QIO_CHANNEL_FEATURE_SEEKABLE.
|
||||
|
||||
The improvements brought by this feature apply only to guest physical
|
||||
RAM. Other types of memory such as VRAM are migrated as part of device
|
||||
states.
|
||||
@@ -44,6 +44,19 @@ struct RAMBlock {
|
||||
size_t page_size;
|
||||
/* dirty bitmap used during migration */
|
||||
unsigned long *bmap;
|
||||
|
||||
/*
|
||||
* Below fields are only used by mapped-ram migration
|
||||
*/
|
||||
/* bitmap of pages present in the migration file */
|
||||
unsigned long *file_bmap;
|
||||
/*
|
||||
* offset in the file pages belonging to this ramblock are saved,
|
||||
* used only during migration to a file.
|
||||
*/
|
||||
off_t bitmap_offset;
|
||||
uint64_t pages_offset;
|
||||
|
||||
/* bitmap of already received pages in postcopy */
|
||||
unsigned long *receivedmap;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ enum QIOChannelFeature {
|
||||
QIO_CHANNEL_FEATURE_LISTEN,
|
||||
QIO_CHANNEL_FEATURE_WRITE_ZERO_COPY,
|
||||
QIO_CHANNEL_FEATURE_READ_MSG_PEEK,
|
||||
QIO_CHANNEL_FEATURE_SEEKABLE,
|
||||
};
|
||||
|
||||
|
||||
@@ -130,6 +131,16 @@ struct QIOChannelClass {
|
||||
Error **errp);
|
||||
|
||||
/* Optional callbacks */
|
||||
ssize_t (*io_pwritev)(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
off_t offset,
|
||||
Error **errp);
|
||||
ssize_t (*io_preadv)(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
off_t offset,
|
||||
Error **errp);
|
||||
int (*io_shutdown)(QIOChannel *ioc,
|
||||
QIOChannelShutdown how,
|
||||
Error **errp);
|
||||
@@ -528,6 +539,78 @@ void qio_channel_set_follow_coroutine_ctx(QIOChannel *ioc, bool enabled);
|
||||
int qio_channel_close(QIOChannel *ioc,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_pwritev
|
||||
* @ioc: the channel object
|
||||
* @iov: the array of memory regions to write data from
|
||||
* @niov: the length of the @iov array
|
||||
* @offset: offset in the channel where writes should begin
|
||||
* @errp: pointer to a NULL-initialized error object
|
||||
*
|
||||
* Not all implementations will support this facility, so may report
|
||||
* an error. To avoid errors, the caller may check for the feature
|
||||
* flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
|
||||
*
|
||||
* Behaves as qio_channel_writev_full, apart from not supporting
|
||||
* sending of file handles as well as beginning the write at the
|
||||
* passed @offset
|
||||
*
|
||||
*/
|
||||
ssize_t qio_channel_pwritev(QIOChannel *ioc, const struct iovec *iov,
|
||||
size_t niov, off_t offset, Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_pwrite
|
||||
* @ioc: the channel object
|
||||
* @buf: the memory region to write data into
|
||||
* @buflen: the number of bytes to @buf
|
||||
* @offset: offset in the channel where writes should begin
|
||||
* @errp: pointer to a NULL-initialized error object
|
||||
*
|
||||
* Not all implementations will support this facility, so may report
|
||||
* an error. To avoid errors, the caller may check for the feature
|
||||
* flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
|
||||
*
|
||||
*/
|
||||
ssize_t qio_channel_pwrite(QIOChannel *ioc, char *buf, size_t buflen,
|
||||
off_t offset, Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_preadv
|
||||
* @ioc: the channel object
|
||||
* @iov: the array of memory regions to read data into
|
||||
* @niov: the length of the @iov array
|
||||
* @offset: offset in the channel where writes should begin
|
||||
* @errp: pointer to a NULL-initialized error object
|
||||
*
|
||||
* Not all implementations will support this facility, so may report
|
||||
* an error. To avoid errors, the caller may check for the feature
|
||||
* flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
|
||||
*
|
||||
* Behaves as qio_channel_readv_full, apart from not supporting
|
||||
* receiving of file handles as well as beginning the read at the
|
||||
* passed @offset
|
||||
*
|
||||
*/
|
||||
ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
|
||||
size_t niov, off_t offset, Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_pread
|
||||
* @ioc: the channel object
|
||||
* @buf: the memory region to write data into
|
||||
* @buflen: the number of bytes to @buf
|
||||
* @offset: offset in the channel where writes should begin
|
||||
* @errp: pointer to a NULL-initialized error object
|
||||
*
|
||||
* Not all implementations will support this facility, so may report
|
||||
* an error. To avoid errors, the caller may check for the feature
|
||||
* flag QIO_CHANNEL_FEATURE_SEEKABLE prior to calling this method.
|
||||
*
|
||||
*/
|
||||
ssize_t qio_channel_pread(QIOChannel *ioc, char *buf, size_t buflen,
|
||||
off_t offset, Error **errp);
|
||||
|
||||
/**
|
||||
* qio_channel_shutdown:
|
||||
* @ioc: the channel object
|
||||
|
||||
@@ -50,6 +50,8 @@ unsigned int qemu_get_be16(QEMUFile *f);
|
||||
unsigned int qemu_get_be32(QEMUFile *f);
|
||||
uint64_t qemu_get_be64(QEMUFile *f);
|
||||
|
||||
bool qemu_file_is_seekable(QEMUFile *f);
|
||||
|
||||
static inline void qemu_put_be64s(QEMUFile *f, const uint64_t *pv)
|
||||
{
|
||||
qemu_put_be64(f, *pv);
|
||||
|
||||
@@ -67,6 +67,19 @@ static inline void clear_bit(long nr, unsigned long *addr)
|
||||
*p &= ~mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear_bit_atomic - Clears a bit in memory atomically
|
||||
* @nr: Bit to clear
|
||||
* @addr: Address to start counting from
|
||||
*/
|
||||
static inline void clear_bit_atomic(long nr, unsigned long *addr)
|
||||
{
|
||||
unsigned long mask = BIT_MASK(nr);
|
||||
unsigned long *p = addr + BIT_WORD(nr);
|
||||
|
||||
return qatomic_and(p, ~mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* change_bit - Toggle a bit in memory
|
||||
* @nr: Bit to change
|
||||
|
||||
@@ -36,6 +36,10 @@ qio_channel_file_new_fd(int fd)
|
||||
|
||||
ioc->fd = fd;
|
||||
|
||||
if (lseek(fd, 0, SEEK_CUR) != (off_t)-1) {
|
||||
qio_channel_set_feature(QIO_CHANNEL(ioc), QIO_CHANNEL_FEATURE_SEEKABLE);
|
||||
}
|
||||
|
||||
trace_qio_channel_file_new_fd(ioc, fd);
|
||||
|
||||
return ioc;
|
||||
@@ -60,6 +64,10 @@ qio_channel_file_new_path(const char *path,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (lseek(ioc->fd, 0, SEEK_CUR) != (off_t)-1) {
|
||||
qio_channel_set_feature(QIO_CHANNEL(ioc), QIO_CHANNEL_FEATURE_SEEKABLE);
|
||||
}
|
||||
|
||||
trace_qio_channel_file_new_path(ioc, path, flags, mode, ioc->fd);
|
||||
|
||||
return ioc;
|
||||
@@ -138,6 +146,58 @@ static ssize_t qio_channel_file_writev(QIOChannel *ioc,
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PREADV
|
||||
static ssize_t qio_channel_file_preadv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
off_t offset,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
|
||||
ssize_t ret;
|
||||
|
||||
retry:
|
||||
ret = preadv(fioc->fd, iov, niov, offset);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
|
||||
error_setg_errno(errp, errno, "Unable to read from file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t qio_channel_file_pwritev(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
size_t niov,
|
||||
off_t offset,
|
||||
Error **errp)
|
||||
{
|
||||
QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
|
||||
ssize_t ret;
|
||||
|
||||
retry:
|
||||
ret = pwritev(fioc->fd, iov, niov, offset);
|
||||
if (ret <= 0) {
|
||||
if (errno == EAGAIN) {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
if (errno == EINTR) {
|
||||
goto retry;
|
||||
}
|
||||
error_setg_errno(errp, errno, "Unable to write to file");
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_PREADV */
|
||||
|
||||
static int qio_channel_file_set_blocking(QIOChannel *ioc,
|
||||
bool enabled,
|
||||
Error **errp)
|
||||
@@ -182,6 +242,11 @@ static int qio_channel_file_close(QIOChannel *ioc,
|
||||
{
|
||||
QIOChannelFile *fioc = QIO_CHANNEL_FILE(ioc);
|
||||
|
||||
if (qemu_fdatasync(fioc->fd) < 0) {
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to synchronize file data with storage device");
|
||||
return -1;
|
||||
}
|
||||
if (qemu_close(fioc->fd) < 0) {
|
||||
error_setg_errno(errp, errno,
|
||||
"Unable to close file");
|
||||
@@ -223,6 +288,10 @@ static void qio_channel_file_class_init(ObjectClass *klass,
|
||||
ioc_klass->io_writev = qio_channel_file_writev;
|
||||
ioc_klass->io_readv = qio_channel_file_readv;
|
||||
ioc_klass->io_set_blocking = qio_channel_file_set_blocking;
|
||||
#ifdef CONFIG_PREADV
|
||||
ioc_klass->io_pwritev = qio_channel_file_pwritev;
|
||||
ioc_klass->io_preadv = qio_channel_file_preadv;
|
||||
#endif
|
||||
ioc_klass->io_seek = qio_channel_file_seek;
|
||||
ioc_klass->io_close = qio_channel_file_close;
|
||||
ioc_klass->io_create_watch = qio_channel_file_create_watch;
|
||||
|
||||
@@ -454,6 +454,64 @@ GSource *qio_channel_add_watch_source(QIOChannel *ioc,
|
||||
}
|
||||
|
||||
|
||||
ssize_t qio_channel_pwritev(QIOChannel *ioc, const struct iovec *iov,
|
||||
size_t niov, off_t offset, Error **errp)
|
||||
{
|
||||
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
||||
|
||||
if (!klass->io_pwritev) {
|
||||
error_setg(errp, "Channel does not support pwritev");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_SEEKABLE)) {
|
||||
error_setg_errno(errp, EINVAL, "Requested channel is not seekable");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return klass->io_pwritev(ioc, iov, niov, offset, errp);
|
||||
}
|
||||
|
||||
ssize_t qio_channel_pwrite(QIOChannel *ioc, char *buf, size_t buflen,
|
||||
off_t offset, Error **errp)
|
||||
{
|
||||
struct iovec iov = {
|
||||
.iov_base = buf,
|
||||
.iov_len = buflen
|
||||
};
|
||||
|
||||
return qio_channel_pwritev(ioc, &iov, 1, offset, errp);
|
||||
}
|
||||
|
||||
ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
|
||||
size_t niov, off_t offset, Error **errp)
|
||||
{
|
||||
QIOChannelClass *klass = QIO_CHANNEL_GET_CLASS(ioc);
|
||||
|
||||
if (!klass->io_preadv) {
|
||||
error_setg(errp, "Channel does not support preadv");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_SEEKABLE)) {
|
||||
error_setg_errno(errp, EINVAL, "Requested channel is not seekable");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return klass->io_preadv(ioc, iov, niov, offset, errp);
|
||||
}
|
||||
|
||||
ssize_t qio_channel_pread(QIOChannel *ioc, char *buf, size_t buflen,
|
||||
off_t offset, Error **errp)
|
||||
{
|
||||
struct iovec iov = {
|
||||
.iov_base = buf,
|
||||
.iov_len = buflen
|
||||
};
|
||||
|
||||
return qio_channel_preadv(ioc, &iov, 1, offset, errp);
|
||||
}
|
||||
|
||||
int qio_channel_shutdown(QIOChannel *ioc,
|
||||
QIOChannelShutdown how,
|
||||
Error **errp)
|
||||
|
||||
@@ -15,18 +15,41 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "channel.h"
|
||||
#include "fd.h"
|
||||
#include "migration.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "io/channel-file.h"
|
||||
#include "io/channel-util.h"
|
||||
#include "options.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
||||
static struct FdOutgoingArgs {
|
||||
int fd;
|
||||
} outgoing_args;
|
||||
|
||||
int fd_args_get_fd(void)
|
||||
{
|
||||
return outgoing_args.fd;
|
||||
}
|
||||
|
||||
void fd_cleanup_outgoing_migration(void)
|
||||
{
|
||||
if (outgoing_args.fd > 0) {
|
||||
close(outgoing_args.fd);
|
||||
outgoing_args.fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void fd_start_outgoing_migration(MigrationState *s, const char *fdname, Error **errp)
|
||||
{
|
||||
QIOChannel *ioc;
|
||||
int fd = monitor_get_fd(monitor_cur(), fdname, errp);
|
||||
|
||||
outgoing_args.fd = -1;
|
||||
|
||||
if (fd == -1) {
|
||||
return;
|
||||
}
|
||||
@@ -38,6 +61,8 @@ void fd_start_outgoing_migration(MigrationState *s, const char *fdname, Error **
|
||||
return;
|
||||
}
|
||||
|
||||
outgoing_args.fd = fd;
|
||||
|
||||
qio_channel_set_name(ioc, "migration-fd-outgoing");
|
||||
migration_channel_connect(s, ioc, NULL, NULL);
|
||||
object_unref(OBJECT(ioc));
|
||||
@@ -73,4 +98,23 @@ void fd_start_incoming_migration(const char *fdname, Error **errp)
|
||||
fd_accept_incoming_migration,
|
||||
NULL, NULL,
|
||||
g_main_context_get_thread_default());
|
||||
|
||||
if (migrate_multifd()) {
|
||||
int channels = migrate_multifd_channels();
|
||||
|
||||
while (channels--) {
|
||||
ioc = QIO_CHANNEL(qio_channel_file_new_fd(dup(fd)));
|
||||
|
||||
if (QIO_CHANNEL_FILE(ioc)->fd == -1) {
|
||||
error_setg(errp, "Failed to duplicate fd %d", fd);
|
||||
return;
|
||||
}
|
||||
|
||||
qio_channel_set_name(ioc, "migration-fd-incoming");
|
||||
qio_channel_add_watch_full(ioc, G_IO_IN,
|
||||
fd_accept_incoming_migration,
|
||||
NULL, NULL,
|
||||
g_main_context_get_thread_default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,6 @@ void fd_start_incoming_migration(const char *fdname, Error **errp);
|
||||
|
||||
void fd_start_outgoing_migration(MigrationState *s, const char *fdname,
|
||||
Error **errp);
|
||||
void fd_cleanup_outgoing_migration(void);
|
||||
int fd_args_get_fd(void);
|
||||
#endif
|
||||
|
||||
+141
-8
@@ -6,17 +6,25 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "exec/ramblock.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "channel.h"
|
||||
#include "fd.h"
|
||||
#include "file.h"
|
||||
#include "migration.h"
|
||||
#include "io/channel-file.h"
|
||||
#include "io/channel-util.h"
|
||||
#include "options.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define OFFSET_OPTION ",offset="
|
||||
|
||||
static struct FileOutgoingArgs {
|
||||
char *fname;
|
||||
} outgoing_args;
|
||||
|
||||
/* Remove the offset option from @filespec and return it in @offsetp. */
|
||||
|
||||
int file_parse_offset(char *filespec, uint64_t *offsetp, Error **errp)
|
||||
@@ -36,6 +44,41 @@ int file_parse_offset(char *filespec, uint64_t *offsetp, Error **errp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void file_cleanup_outgoing_migration(void)
|
||||
{
|
||||
g_free(outgoing_args.fname);
|
||||
outgoing_args.fname = NULL;
|
||||
}
|
||||
|
||||
bool file_send_channel_create(gpointer opaque, Error **errp)
|
||||
{
|
||||
QIOChannelFile *ioc;
|
||||
int flags = O_WRONLY;
|
||||
bool ret = false;
|
||||
int fd = fd_args_get_fd();
|
||||
|
||||
if (fd && fd != -1) {
|
||||
ioc = qio_channel_file_new_fd(dup(fd));
|
||||
} else {
|
||||
ioc = qio_channel_file_new_path(outgoing_args.fname, flags, 0, errp);
|
||||
if (!ioc) {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
multifd_channel_connect(opaque, QIO_CHANNEL(ioc));
|
||||
ret = true;
|
||||
|
||||
out:
|
||||
/*
|
||||
* File channel creation is synchronous. However posting this
|
||||
* semaphore here is simpler than adding a special case.
|
||||
*/
|
||||
multifd_send_channel_created();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void file_start_outgoing_migration(MigrationState *s,
|
||||
FileMigrationArgs *file_args, Error **errp)
|
||||
{
|
||||
@@ -52,6 +95,8 @@ void file_start_outgoing_migration(MigrationState *s,
|
||||
return;
|
||||
}
|
||||
|
||||
outgoing_args.fname = g_strdup(filename);
|
||||
|
||||
ioc = QIO_CHANNEL(fioc);
|
||||
if (offset && qio_channel_io_seek(ioc, offset, SEEK_SET, errp) < 0) {
|
||||
return;
|
||||
@@ -74,7 +119,8 @@ void file_start_incoming_migration(FileMigrationArgs *file_args, Error **errp)
|
||||
g_autofree char *filename = g_strdup(file_args->filename);
|
||||
QIOChannelFile *fioc = NULL;
|
||||
uint64_t offset = file_args->offset;
|
||||
QIOChannel *ioc;
|
||||
int channels = 1;
|
||||
int i = 0;
|
||||
|
||||
trace_migration_file_incoming(filename);
|
||||
|
||||
@@ -83,13 +129,100 @@ void file_start_incoming_migration(FileMigrationArgs *file_args, Error **errp)
|
||||
return;
|
||||
}
|
||||
|
||||
ioc = QIO_CHANNEL(fioc);
|
||||
if (offset && qio_channel_io_seek(ioc, offset, SEEK_SET, errp) < 0) {
|
||||
if (offset &&
|
||||
qio_channel_io_seek(QIO_CHANNEL(fioc), offset, SEEK_SET, errp) < 0) {
|
||||
return;
|
||||
}
|
||||
qio_channel_set_name(QIO_CHANNEL(ioc), "migration-file-incoming");
|
||||
qio_channel_add_watch_full(ioc, G_IO_IN,
|
||||
file_accept_incoming_migration,
|
||||
NULL, NULL,
|
||||
g_main_context_get_thread_default());
|
||||
|
||||
if (migrate_multifd()) {
|
||||
channels += migrate_multifd_channels();
|
||||
}
|
||||
|
||||
do {
|
||||
QIOChannel *ioc = QIO_CHANNEL(fioc);
|
||||
|
||||
qio_channel_set_name(ioc, "migration-file-incoming");
|
||||
qio_channel_add_watch_full(ioc, G_IO_IN,
|
||||
file_accept_incoming_migration,
|
||||
NULL, NULL,
|
||||
g_main_context_get_thread_default());
|
||||
|
||||
fioc = qio_channel_file_new_fd(dup(fioc->fd));
|
||||
|
||||
if (!fioc || fioc->fd == -1) {
|
||||
error_setg(errp, "Error creating migration incoming channel");
|
||||
break;
|
||||
}
|
||||
} while (++i < channels);
|
||||
}
|
||||
|
||||
int file_write_ramblock_iov(QIOChannel *ioc, const struct iovec *iov,
|
||||
int niov, RAMBlock *block, Error **errp)
|
||||
{
|
||||
ssize_t ret = -1;
|
||||
int i, slice_idx, slice_num;
|
||||
uintptr_t base, next, offset;
|
||||
size_t len;
|
||||
|
||||
slice_idx = 0;
|
||||
slice_num = 1;
|
||||
|
||||
/*
|
||||
* If the iov array doesn't have contiguous elements, we need to
|
||||
* split it in slices because we only have one file offset for the
|
||||
* whole iov. Do this here so callers don't need to break the iov
|
||||
* array themselves.
|
||||
*/
|
||||
for (i = 0; i < niov; i++, slice_num++) {
|
||||
base = (uintptr_t) iov[i].iov_base;
|
||||
|
||||
if (i != niov - 1) {
|
||||
len = iov[i].iov_len;
|
||||
next = (uintptr_t) iov[i + 1].iov_base;
|
||||
|
||||
if (base + len == next) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Use the offset of the first element of the segment that
|
||||
* we're sending.
|
||||
*/
|
||||
offset = (uintptr_t) iov[slice_idx].iov_base - (uintptr_t) block->host;
|
||||
if (offset >= block->used_length) {
|
||||
error_setg(errp, "offset " RAM_ADDR_FMT
|
||||
"outside of ramblock %s range", offset, block->idstr);
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = qio_channel_pwritev(ioc, &iov[slice_idx], slice_num,
|
||||
block->pages_offset + offset, errp);
|
||||
if (ret < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
slice_idx += slice_num;
|
||||
slice_num = 0;
|
||||
}
|
||||
|
||||
return (ret < 0) ? ret : 0;
|
||||
}
|
||||
|
||||
int multifd_file_recv_data(MultiFDRecvParams *p, Error **errp)
|
||||
{
|
||||
MultiFDRecvData *data = p->data;
|
||||
size_t ret;
|
||||
|
||||
ret = qio_channel_pread(p->c, (char *) data->opaque,
|
||||
data->size, data->file_offset, errp);
|
||||
if (ret != data->size) {
|
||||
error_prepend(errp,
|
||||
"multifd recv (%u): read 0x%zx, expected 0x%zx",
|
||||
p->id, ret, data->size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,18 @@
|
||||
#define QEMU_MIGRATION_FILE_H
|
||||
|
||||
#include "qapi/qapi-types-migration.h"
|
||||
#include "io/task.h"
|
||||
#include "channel.h"
|
||||
#include "multifd.h"
|
||||
|
||||
void file_start_incoming_migration(FileMigrationArgs *file_args, Error **errp);
|
||||
|
||||
void file_start_outgoing_migration(MigrationState *s,
|
||||
FileMigrationArgs *file_args, Error **errp);
|
||||
int file_parse_offset(char *filespec, uint64_t *offsetp, Error **errp);
|
||||
void file_cleanup_outgoing_migration(void);
|
||||
bool file_send_channel_create(gpointer opaque, Error **errp);
|
||||
int file_write_ramblock_iov(QIOChannel *ioc, const struct iovec *iov,
|
||||
int niov, RAMBlock *block, Error **errp);
|
||||
int multifd_file_recv_data(MultiFDRecvParams *p, Error **errp);
|
||||
#endif
|
||||
|
||||
+52
-4
@@ -140,9 +140,38 @@ static bool transport_supports_multi_channels(MigrationAddress *addr)
|
||||
if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) {
|
||||
SocketAddress *saddr = &addr->u.socket;
|
||||
|
||||
return saddr->type == SOCKET_ADDRESS_TYPE_INET ||
|
||||
saddr->type == SOCKET_ADDRESS_TYPE_UNIX ||
|
||||
saddr->type == SOCKET_ADDRESS_TYPE_VSOCK;
|
||||
if (saddr->type == SOCKET_ADDRESS_TYPE_FD) {
|
||||
return migrate_mapped_ram();
|
||||
}
|
||||
|
||||
return (saddr->type == SOCKET_ADDRESS_TYPE_INET ||
|
||||
saddr->type == SOCKET_ADDRESS_TYPE_UNIX ||
|
||||
saddr->type == SOCKET_ADDRESS_TYPE_VSOCK);
|
||||
} else if (addr->transport == MIGRATION_ADDRESS_TYPE_FILE) {
|
||||
return migrate_mapped_ram();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool migration_needs_seekable_channel(void)
|
||||
{
|
||||
return migrate_mapped_ram();
|
||||
}
|
||||
|
||||
static bool transport_supports_seeking(MigrationAddress *addr)
|
||||
{
|
||||
if (addr->transport == MIGRATION_ADDRESS_TYPE_FILE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point, the user might not yet have passed the file
|
||||
* descriptor to QEMU, so we cannot know for sure whether it
|
||||
* refers to a plain file or a socket. Let it through anyway.
|
||||
*/
|
||||
if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) {
|
||||
return addr->u.socket.type == SOCKET_ADDRESS_TYPE_FD;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -152,6 +181,12 @@ static bool
|
||||
migration_channels_and_transport_compatible(MigrationAddress *addr,
|
||||
Error **errp)
|
||||
{
|
||||
if (migration_needs_seekable_channel() &&
|
||||
!transport_supports_seeking(addr)) {
|
||||
error_setg(errp, "Migration requires seekable transport (e.g. file)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (migration_needs_multiple_sockets() &&
|
||||
!transport_supports_multi_channels(addr)) {
|
||||
error_setg(errp, "Migration requires multi-channel URIs (e.g. tcp)");
|
||||
@@ -881,7 +916,8 @@ void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp)
|
||||
uint32_t channel_magic = 0;
|
||||
int ret = 0;
|
||||
|
||||
if (migrate_multifd() && !migrate_postcopy_ram() &&
|
||||
if (migrate_multifd() && !migrate_mapped_ram() &&
|
||||
!migrate_postcopy_ram() &&
|
||||
qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_READ_MSG_PEEK)) {
|
||||
/*
|
||||
* With multiple channels, it is possible that we receive channels
|
||||
@@ -1950,6 +1986,18 @@ static bool migrate_prepare(MigrationState *s, bool blk, bool blk_inc,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (migrate_mapped_ram()) {
|
||||
if (migrate_tls()) {
|
||||
error_setg(errp, "Cannot use TLS with mapped-ram");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (migrate_multifd_compression()) {
|
||||
error_setg(errp, "Cannot use compression with mapped-ram");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (migrate_mode_is_cpr(s)) {
|
||||
const char *conflict = NULL;
|
||||
|
||||
|
||||
+13
-13
@@ -69,7 +69,7 @@ static int zlib_send_setup(MultiFDSendParams *p, Error **errp)
|
||||
err_msg = "out of memory for buf";
|
||||
goto err_free_zbuff;
|
||||
}
|
||||
p->data = z;
|
||||
p->compress_data = z;
|
||||
return 0;
|
||||
|
||||
err_free_zbuff:
|
||||
@@ -92,15 +92,15 @@ err_free_z:
|
||||
*/
|
||||
static void zlib_send_cleanup(MultiFDSendParams *p, Error **errp)
|
||||
{
|
||||
struct zlib_data *z = p->data;
|
||||
struct zlib_data *z = p->compress_data;
|
||||
|
||||
deflateEnd(&z->zs);
|
||||
g_free(z->zbuff);
|
||||
z->zbuff = NULL;
|
||||
g_free(z->buf);
|
||||
z->buf = NULL;
|
||||
g_free(p->data);
|
||||
p->data = NULL;
|
||||
g_free(p->compress_data);
|
||||
p->compress_data = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +117,7 @@ static void zlib_send_cleanup(MultiFDSendParams *p, Error **errp)
|
||||
static int zlib_send_prepare(MultiFDSendParams *p, Error **errp)
|
||||
{
|
||||
MultiFDPages_t *pages = p->pages;
|
||||
struct zlib_data *z = p->data;
|
||||
struct zlib_data *z = p->compress_data;
|
||||
z_stream *zs = &z->zs;
|
||||
uint32_t out_size = 0;
|
||||
int ret;
|
||||
@@ -194,7 +194,7 @@ static int zlib_recv_setup(MultiFDRecvParams *p, Error **errp)
|
||||
struct zlib_data *z = g_new0(struct zlib_data, 1);
|
||||
z_stream *zs = &z->zs;
|
||||
|
||||
p->data = z;
|
||||
p->compress_data = z;
|
||||
zs->zalloc = Z_NULL;
|
||||
zs->zfree = Z_NULL;
|
||||
zs->opaque = Z_NULL;
|
||||
@@ -224,17 +224,17 @@ static int zlib_recv_setup(MultiFDRecvParams *p, Error **errp)
|
||||
*/
|
||||
static void zlib_recv_cleanup(MultiFDRecvParams *p)
|
||||
{
|
||||
struct zlib_data *z = p->data;
|
||||
struct zlib_data *z = p->compress_data;
|
||||
|
||||
inflateEnd(&z->zs);
|
||||
g_free(z->zbuff);
|
||||
z->zbuff = NULL;
|
||||
g_free(p->data);
|
||||
p->data = NULL;
|
||||
g_free(p->compress_data);
|
||||
p->compress_data = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* zlib_recv_pages: read the data from the channel into actual pages
|
||||
* zlib_recv: read the data from the channel into actual pages
|
||||
*
|
||||
* Read the compressed buffer, and uncompress it into the actual
|
||||
* pages.
|
||||
@@ -244,9 +244,9 @@ static void zlib_recv_cleanup(MultiFDRecvParams *p)
|
||||
* @p: Params for the channel that we are using
|
||||
* @errp: pointer to an error
|
||||
*/
|
||||
static int zlib_recv_pages(MultiFDRecvParams *p, Error **errp)
|
||||
static int zlib_recv(MultiFDRecvParams *p, Error **errp)
|
||||
{
|
||||
struct zlib_data *z = p->data;
|
||||
struct zlib_data *z = p->compress_data;
|
||||
z_stream *zs = &z->zs;
|
||||
uint32_t in_size = p->next_packet_size;
|
||||
/* we measure the change of total_out */
|
||||
@@ -319,7 +319,7 @@ static MultiFDMethods multifd_zlib_ops = {
|
||||
.send_prepare = zlib_send_prepare,
|
||||
.recv_setup = zlib_recv_setup,
|
||||
.recv_cleanup = zlib_recv_cleanup,
|
||||
.recv_pages = zlib_recv_pages
|
||||
.recv = zlib_recv
|
||||
};
|
||||
|
||||
static void multifd_zlib_register(void)
|
||||
|
||||
+13
-13
@@ -52,7 +52,7 @@ static int zstd_send_setup(MultiFDSendParams *p, Error **errp)
|
||||
struct zstd_data *z = g_new0(struct zstd_data, 1);
|
||||
int res;
|
||||
|
||||
p->data = z;
|
||||
p->compress_data = z;
|
||||
z->zcs = ZSTD_createCStream();
|
||||
if (!z->zcs) {
|
||||
g_free(z);
|
||||
@@ -90,14 +90,14 @@ static int zstd_send_setup(MultiFDSendParams *p, Error **errp)
|
||||
*/
|
||||
static void zstd_send_cleanup(MultiFDSendParams *p, Error **errp)
|
||||
{
|
||||
struct zstd_data *z = p->data;
|
||||
struct zstd_data *z = p->compress_data;
|
||||
|
||||
ZSTD_freeCStream(z->zcs);
|
||||
z->zcs = NULL;
|
||||
g_free(z->zbuff);
|
||||
z->zbuff = NULL;
|
||||
g_free(p->data);
|
||||
p->data = NULL;
|
||||
g_free(p->compress_data);
|
||||
p->compress_data = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +114,7 @@ static void zstd_send_cleanup(MultiFDSendParams *p, Error **errp)
|
||||
static int zstd_send_prepare(MultiFDSendParams *p, Error **errp)
|
||||
{
|
||||
MultiFDPages_t *pages = p->pages;
|
||||
struct zstd_data *z = p->data;
|
||||
struct zstd_data *z = p->compress_data;
|
||||
int ret;
|
||||
uint32_t i;
|
||||
|
||||
@@ -183,7 +183,7 @@ static int zstd_recv_setup(MultiFDRecvParams *p, Error **errp)
|
||||
struct zstd_data *z = g_new0(struct zstd_data, 1);
|
||||
int ret;
|
||||
|
||||
p->data = z;
|
||||
p->compress_data = z;
|
||||
z->zds = ZSTD_createDStream();
|
||||
if (!z->zds) {
|
||||
g_free(z);
|
||||
@@ -221,18 +221,18 @@ static int zstd_recv_setup(MultiFDRecvParams *p, Error **errp)
|
||||
*/
|
||||
static void zstd_recv_cleanup(MultiFDRecvParams *p)
|
||||
{
|
||||
struct zstd_data *z = p->data;
|
||||
struct zstd_data *z = p->compress_data;
|
||||
|
||||
ZSTD_freeDStream(z->zds);
|
||||
z->zds = NULL;
|
||||
g_free(z->zbuff);
|
||||
z->zbuff = NULL;
|
||||
g_free(p->data);
|
||||
p->data = NULL;
|
||||
g_free(p->compress_data);
|
||||
p->compress_data = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* zstd_recv_pages: read the data from the channel into actual pages
|
||||
* zstd_recv: read the data from the channel into actual pages
|
||||
*
|
||||
* Read the compressed buffer, and uncompress it into the actual
|
||||
* pages.
|
||||
@@ -242,13 +242,13 @@ static void zstd_recv_cleanup(MultiFDRecvParams *p)
|
||||
* @p: Params for the channel that we are using
|
||||
* @errp: pointer to an error
|
||||
*/
|
||||
static int zstd_recv_pages(MultiFDRecvParams *p, Error **errp)
|
||||
static int zstd_recv(MultiFDRecvParams *p, Error **errp)
|
||||
{
|
||||
uint32_t in_size = p->next_packet_size;
|
||||
uint32_t out_size = 0;
|
||||
uint32_t expected_size = p->normal_num * p->page_size;
|
||||
uint32_t flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK;
|
||||
struct zstd_data *z = p->data;
|
||||
struct zstd_data *z = p->compress_data;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
@@ -310,7 +310,7 @@ static MultiFDMethods multifd_zstd_ops = {
|
||||
.send_prepare = zstd_send_prepare,
|
||||
.recv_setup = zstd_recv_setup,
|
||||
.recv_cleanup = zstd_recv_cleanup,
|
||||
.recv_pages = zstd_recv_pages
|
||||
.recv = zstd_recv
|
||||
};
|
||||
|
||||
static void multifd_zstd_register(void)
|
||||
|
||||
+336
-81
File diff suppressed because it is too large
Load Diff
+23
-4
@@ -13,8 +13,13 @@
|
||||
#ifndef QEMU_MIGRATION_MULTIFD_H
|
||||
#define QEMU_MIGRATION_MULTIFD_H
|
||||
|
||||
#include "ram.h"
|
||||
|
||||
typedef struct MultiFDRecvData MultiFDRecvData;
|
||||
|
||||
bool multifd_send_setup(void);
|
||||
void multifd_send_shutdown(void);
|
||||
void multifd_send_channel_created(void);
|
||||
int multifd_recv_setup(Error **errp);
|
||||
void multifd_recv_cleanup(void);
|
||||
void multifd_recv_shutdown(void);
|
||||
@@ -23,6 +28,8 @@ void multifd_recv_new_channel(QIOChannel *ioc, Error **errp);
|
||||
void multifd_recv_sync_main(void);
|
||||
int multifd_send_sync_main(void);
|
||||
bool multifd_queue_page(RAMBlock *block, ram_addr_t offset);
|
||||
bool multifd_recv(void);
|
||||
MultiFDRecvData *multifd_get_recv_data(void);
|
||||
|
||||
/* Multifd Compression flags */
|
||||
#define MULTIFD_FLAG_SYNC (1 << 0)
|
||||
@@ -63,6 +70,13 @@ typedef struct {
|
||||
RAMBlock *block;
|
||||
} MultiFDPages_t;
|
||||
|
||||
struct MultiFDRecvData {
|
||||
void *opaque;
|
||||
size_t size;
|
||||
/* for preadv */
|
||||
off_t file_offset;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
/* Fields are only written at creating/deletion time */
|
||||
/* No lock required for them, they are read only */
|
||||
@@ -127,7 +141,7 @@ typedef struct {
|
||||
/* number of iovs used */
|
||||
uint32_t iovs_num;
|
||||
/* used for compression methods */
|
||||
void *data;
|
||||
void *compress_data;
|
||||
} MultiFDSendParams;
|
||||
|
||||
typedef struct {
|
||||
@@ -152,6 +166,8 @@ typedef struct {
|
||||
|
||||
/* syncs main thread and channels */
|
||||
QemuSemaphore sem_sync;
|
||||
/* sem where to wait for more work */
|
||||
QemuSemaphore sem;
|
||||
|
||||
/* this mutex protects the following parameters */
|
||||
QemuMutex mutex;
|
||||
@@ -161,6 +177,8 @@ typedef struct {
|
||||
uint32_t flags;
|
||||
/* global number of generated multifd packets */
|
||||
uint64_t packet_num;
|
||||
int pending_job;
|
||||
MultiFDRecvData *data;
|
||||
|
||||
/* thread local variables. No locking required */
|
||||
|
||||
@@ -183,7 +201,7 @@ typedef struct {
|
||||
/* num of non zero pages */
|
||||
uint32_t normal_num;
|
||||
/* used for de-compression methods */
|
||||
void *data;
|
||||
void *compress_data;
|
||||
} MultiFDRecvParams;
|
||||
|
||||
typedef struct {
|
||||
@@ -197,8 +215,8 @@ typedef struct {
|
||||
int (*recv_setup)(MultiFDRecvParams *p, Error **errp);
|
||||
/* Cleanup for receiving side */
|
||||
void (*recv_cleanup)(MultiFDRecvParams *p);
|
||||
/* Read all pages */
|
||||
int (*recv_pages)(MultiFDRecvParams *p, Error **errp);
|
||||
/* Read all data */
|
||||
int (*recv)(MultiFDRecvParams *p, Error **errp);
|
||||
} MultiFDMethods;
|
||||
|
||||
void multifd_register_ops(int method, MultiFDMethods *ops);
|
||||
@@ -211,5 +229,6 @@ static inline void multifd_send_prepare_header(MultiFDSendParams *p)
|
||||
p->iovs_num++;
|
||||
}
|
||||
|
||||
void multifd_channel_connect(MultiFDSendParams *p, QIOChannel *ioc);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -204,6 +204,7 @@ Property migration_properties[] = {
|
||||
DEFINE_PROP_MIG_CAP("x-switchover-ack",
|
||||
MIGRATION_CAPABILITY_SWITCHOVER_ACK),
|
||||
DEFINE_PROP_MIG_CAP("x-dirty-limit", MIGRATION_CAPABILITY_DIRTY_LIMIT),
|
||||
DEFINE_PROP_MIG_CAP("mapped-ram", MIGRATION_CAPABILITY_MAPPED_RAM),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
@@ -263,6 +264,13 @@ bool migrate_events(void)
|
||||
return s->capabilities[MIGRATION_CAPABILITY_EVENTS];
|
||||
}
|
||||
|
||||
bool migrate_mapped_ram(void)
|
||||
{
|
||||
MigrationState *s = migrate_get_current();
|
||||
|
||||
return s->capabilities[MIGRATION_CAPABILITY_MAPPED_RAM];
|
||||
}
|
||||
|
||||
bool migrate_ignore_shared(void)
|
||||
{
|
||||
MigrationState *s = migrate_get_current();
|
||||
@@ -645,6 +653,26 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
if (new_caps[MIGRATION_CAPABILITY_MAPPED_RAM]) {
|
||||
if (new_caps[MIGRATION_CAPABILITY_XBZRLE]) {
|
||||
error_setg(errp,
|
||||
"Mapped-ram migration is incompatible with xbzrle");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_caps[MIGRATION_CAPABILITY_COMPRESS]) {
|
||||
error_setg(errp,
|
||||
"Mapped-ram migration is incompatible with compression");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (new_caps[MIGRATION_CAPABILITY_POSTCOPY_RAM]) {
|
||||
error_setg(errp,
|
||||
"Mapped-ram migration is incompatible with postcopy");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1218,6 +1246,13 @@ bool migrate_params_check(MigrationParameters *params, Error **errp)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (migrate_mapped_ram() &&
|
||||
(migrate_multifd_compression() || migrate_tls())) {
|
||||
error_setg(errp,
|
||||
"Mapped-ram only available for non-compressed non-TLS multifd migration");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (params->has_x_vcpu_dirty_limit_period &&
|
||||
(params->x_vcpu_dirty_limit_period < 1 ||
|
||||
params->x_vcpu_dirty_limit_period > 1000)) {
|
||||
@@ -1312,6 +1347,12 @@ static void migrate_params_test_apply(MigrateSetParameters *params,
|
||||
if (params->has_multifd_compression) {
|
||||
dest->multifd_compression = params->multifd_compression;
|
||||
}
|
||||
if (params->has_multifd_zlib_level) {
|
||||
dest->multifd_zlib_level = params->multifd_zlib_level;
|
||||
}
|
||||
if (params->has_multifd_zstd_level) {
|
||||
dest->multifd_zstd_level = params->multifd_zstd_level;
|
||||
}
|
||||
if (params->has_xbzrle_cache_size) {
|
||||
dest->xbzrle_cache_size = params->xbzrle_cache_size;
|
||||
}
|
||||
@@ -1447,6 +1488,12 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
|
||||
if (params->has_multifd_compression) {
|
||||
s->parameters.multifd_compression = params->multifd_compression;
|
||||
}
|
||||
if (params->has_multifd_zlib_level) {
|
||||
s->parameters.multifd_zlib_level = params->multifd_zlib_level;
|
||||
}
|
||||
if (params->has_multifd_zstd_level) {
|
||||
s->parameters.multifd_zstd_level = params->multifd_zstd_level;
|
||||
}
|
||||
if (params->has_xbzrle_cache_size) {
|
||||
s->parameters.xbzrle_cache_size = params->xbzrle_cache_size;
|
||||
xbzrle_cache_resize(params->xbzrle_cache_size, errp);
|
||||
|
||||
@@ -31,6 +31,7 @@ bool migrate_compress(void);
|
||||
bool migrate_dirty_bitmaps(void);
|
||||
bool migrate_dirty_limit(void);
|
||||
bool migrate_events(void);
|
||||
bool migrate_mapped_ram(void);
|
||||
bool migrate_ignore_shared(void);
|
||||
bool migrate_late_block_activate(void);
|
||||
bool migrate_multifd(void);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "options.h"
|
||||
#include "qapi/error.h"
|
||||
#include "rdma.h"
|
||||
#include "io/channel-file.h"
|
||||
|
||||
#define IO_BUF_SIZE 32768
|
||||
#define MAX_IOV_SIZE MIN_CONST(IOV_MAX, 64)
|
||||
@@ -255,6 +256,10 @@ static void qemu_iovec_release_ram(QEMUFile *f)
|
||||
memset(f->may_free, 0, sizeof(f->may_free));
|
||||
}
|
||||
|
||||
bool qemu_file_is_seekable(QEMUFile *f)
|
||||
{
|
||||
return qio_channel_has_feature(f->ioc, QIO_CHANNEL_FEATURE_SEEKABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes QEMUFile buffer
|
||||
@@ -447,6 +452,107 @@ void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, size_t size)
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_put_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
|
||||
off_t pos)
|
||||
{
|
||||
Error *err = NULL;
|
||||
size_t ret;
|
||||
|
||||
if (f->last_error) {
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_fflush(f);
|
||||
ret = qio_channel_pwrite(f->ioc, (char *)buf, buflen, pos, &err);
|
||||
|
||||
if (err) {
|
||||
qemu_file_set_error_obj(f, -EIO, err);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ssize_t)ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
qemu_file_set_error_obj(f, -EAGAIN, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret != buflen) {
|
||||
error_setg(&err, "Partial write of size %zu, expected %zu", ret,
|
||||
buflen);
|
||||
qemu_file_set_error_obj(f, -EIO, err);
|
||||
return;
|
||||
}
|
||||
|
||||
stat64_add(&mig_stats.qemu_file_transferred, buflen);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
size_t qemu_get_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
|
||||
off_t pos)
|
||||
{
|
||||
Error *err = NULL;
|
||||
size_t ret;
|
||||
|
||||
if (f->last_error) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = qio_channel_pread(f->ioc, (char *)buf, buflen, pos, &err);
|
||||
|
||||
if ((ssize_t)ret == -1 || err) {
|
||||
qemu_file_set_error_obj(f, -EIO, err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((ssize_t)ret == QIO_CHANNEL_ERR_BLOCK) {
|
||||
qemu_file_set_error_obj(f, -EAGAIN, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ret != buflen) {
|
||||
error_setg(&err, "Partial read of size %zu, expected %zu", ret, buflen);
|
||||
qemu_file_set_error_obj(f, -EIO, err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void qemu_set_offset(QEMUFile *f, off_t off, int whence)
|
||||
{
|
||||
Error *err = NULL;
|
||||
off_t ret;
|
||||
|
||||
if (qemu_file_is_writable(f)) {
|
||||
qemu_fflush(f);
|
||||
} else {
|
||||
/* Drop all cached buffers if existed; will trigger a re-fill later */
|
||||
f->buf_index = 0;
|
||||
f->buf_size = 0;
|
||||
}
|
||||
|
||||
ret = qio_channel_io_seek(f->ioc, off, whence, &err);
|
||||
if (ret == (off_t)-1) {
|
||||
qemu_file_set_error_obj(f, -EIO, err);
|
||||
}
|
||||
}
|
||||
|
||||
off_t qemu_get_offset(QEMUFile *f)
|
||||
{
|
||||
Error *err = NULL;
|
||||
off_t ret;
|
||||
|
||||
qemu_fflush(f);
|
||||
|
||||
ret = qio_channel_io_seek(f->ioc, 0, SEEK_CUR, &err);
|
||||
if (ret == (off_t)-1) {
|
||||
qemu_file_set_error_obj(f, -EIO, err);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void qemu_put_byte(QEMUFile *f, int v)
|
||||
{
|
||||
if (f->last_error) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user