Compare commits
	
		
			34 Commits
		
	
	
		
			qdev-array
			...
			multifd-fi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 1426f4034c | ||
|  | 4bc24a1768 | ||
|  | c8a4fbac41 | ||
|  | cb362dd6a2 | ||
|  | eeefd3103f | ||
|  | b4e9a2b235 | ||
|  | e98d9aaec1 | ||
|  | cba2c89c13 | ||
|  | 60645076f3 | ||
|  | 01c82ca093 | ||
|  | ea602b85ed | ||
|  | 65ceb3509b | ||
|  | f33324162e | ||
|  | 3f46b2e302 | ||
|  | 7d9bc61bbe | ||
|  | 28c14413ee | ||
|  | 60eca4db23 | ||
|  | 66687953e3 | ||
|  | 92ef7c2da4 | ||
|  | 0f985fa252 | ||
|  | 5ea00e7789 | ||
|  | ae9ef03607 | ||
|  | 83b60e1f77 | ||
|  | 607b519673 | ||
|  | 75a0a94d57 | ||
|  | f8c7a93b21 | ||
|  | e1bf06c06e | ||
|  | 2001f4bd65 | ||
|  | 7dbc58d1b8 | ||
|  | 257ff17dce | ||
|  | e392ce4809 | ||
|  | 677780fd36 | ||
|  | f6834bad8d | ||
|  | 0e1bc6f8f7 | 
| @@ -572,6 +572,27 @@ Others (especially either older devices or system devices which for | ||||
| some reason don't have a bus concept) make use of the ``instance id`` | ||||
| for otherwise identically named devices. | ||||
|  | ||||
| Fixed-ram format | ||||
| ---------------- | ||||
|  | ||||
| When the ``fixed-ram`` capability is enabled, a slightly different | ||||
| stream format is used for the RAM section. Instead of having a | ||||
| sequential stream of pages that follow the RAMBlock headers, the dirty | ||||
| pages for a RAMBlock follow its header. This ensures that each RAM | ||||
| page has a fixed offset in the resulting migration file. | ||||
|  | ||||
| The ``fixed-ram`` capability must be enabled in both source and | ||||
| destination with: | ||||
|  | ||||
|     ``migrate_set_capability fixed-ram on`` | ||||
|  | ||||
| Since pages are written to their relatives 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. In more practical terms, this | ||||
| migration format requires the ``file:`` URI when migrating. | ||||
|  | ||||
| Return path | ||||
| ----------- | ||||
|  | ||||
|   | ||||
| @@ -44,6 +44,14 @@ struct RAMBlock { | ||||
|     size_t page_size; | ||||
|     /* dirty bitmap used during migration */ | ||||
|     unsigned long *bmap; | ||||
|     /* shadow dirty bitmap used when migrating to a file */ | ||||
|     unsigned long *shadow_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; | ||||
|  | ||||
|   | ||||
| @@ -33,8 +33,10 @@ OBJECT_DECLARE_TYPE(QIOChannel, QIOChannelClass, | ||||
| #define QIO_CHANNEL_ERR_BLOCK -2 | ||||
|  | ||||
| #define QIO_CHANNEL_WRITE_FLAG_ZERO_COPY 0x1 | ||||
| #define QIO_CHANNEL_WRITE_FLAG_WITH_OFFSET 0x2 | ||||
|  | ||||
| #define QIO_CHANNEL_READ_FLAG_MSG_PEEK 0x1 | ||||
| #define QIO_CHANNEL_READ_FLAG_WITH_OFFSET 0x2 | ||||
|  | ||||
| typedef enum QIOChannelFeature QIOChannelFeature; | ||||
|  | ||||
| @@ -44,6 +46,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 +133,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 +541,126 @@ 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_write_full_all: | ||||
|  * @ioc: the channel object | ||||
|  * @iov: the array of memory regions to write data from | ||||
|  * @niov: the length of the @iov array | ||||
|  * @offset: the iovec offset in the file where to write the data | ||||
|  * @fds: an array of file handles to send | ||||
|  * @nfds: number of file handles in @fds | ||||
|  * @flags: write flags (QIO_CHANNEL_WRITE_FLAG_*) | ||||
|  * @errp: pointer to a NULL-initialized error object | ||||
|  * | ||||
|  * | ||||
|  * Selects between a writev or pwritev channel writer function. | ||||
|  * | ||||
|  * If QIO_CHANNEL_WRITE_FLAG_OFFSET is passed in flags, pwritev is | ||||
|  * used and @offset is expected to be a meaningful value, @fds and | ||||
|  * @nfds are ignored; otherwise uses writev and @offset is ignored. | ||||
|  * | ||||
|  * Returns: 0 if all bytes were written, or -1 on error | ||||
|  */ | ||||
| int qio_channel_write_full_all(QIOChannel *ioc, const struct iovec *iov, | ||||
|                                size_t niov, off_t offset, int *fds, size_t nfds, | ||||
|                                int flags, 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_read_full_all: | ||||
|  * @ioc: the channel object | ||||
|  * @iov: the array of memory regions to read data to | ||||
|  * @niov: the length of the @iov array | ||||
|  * @offset: the iovec offset in the file from where to read the data | ||||
|  * @fds: an array of file handles to send | ||||
|  * @nfds: number of file handles in @fds | ||||
|  * @flags: read flags (QIO_CHANNEL_READ_FLAG_*) | ||||
|  * @errp: pointer to a NULL-initialized error object | ||||
|  * | ||||
|  * | ||||
|  * Selects between a readv or preadv channel reader function. | ||||
|  * | ||||
|  * If QIO_CHANNEL_READ_FLAG_OFFSET is passed in flags, preadv is | ||||
|  * used and @offset is expected to be a meaningful value, @fds and | ||||
|  * @nfds are ignored; otherwise uses readv and @offset is ignored. | ||||
|  * | ||||
|  * Returns: 0 if all bytes were read, or -1 on error | ||||
|  */ | ||||
| int qio_channel_read_full_all(QIOChannel *ioc, const struct iovec *iov, | ||||
|                               size_t niov, off_t offset, | ||||
|                               int flags, 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 | ||||
|   | ||||
| @@ -597,6 +597,8 @@ int qemu_lock_fd_test(int fd, int64_t start, int64_t len, bool exclusive); | ||||
| bool qemu_has_ofd_lock(void); | ||||
| #endif | ||||
|  | ||||
| bool qemu_has_direct_io(void); | ||||
|  | ||||
| #if defined(__HAIKU__) && defined(__i386__) | ||||
| #define FMT_pid "%ld" | ||||
| #elif defined(WIN64) | ||||
|   | ||||
| @@ -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,56 @@ static ssize_t qio_channel_file_writev(QIOChannel *ioc, | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| 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; | ||||
| } | ||||
|  | ||||
| static int qio_channel_file_set_blocking(QIOChannel *ioc, | ||||
|                                          bool enabled, | ||||
|                                          Error **errp) | ||||
| @@ -223,6 +281,8 @@ 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; | ||||
|     ioc_klass->io_pwritev = qio_channel_file_pwritev; | ||||
|     ioc_klass->io_preadv = qio_channel_file_preadv; | ||||
|     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; | ||||
|   | ||||
							
								
								
									
										140
									
								
								io/channel.c
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								io/channel.c
									
									
									
									
									
								
							| @@ -454,6 +454,146 @@ 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); | ||||
| } | ||||
|  | ||||
| static int qio_channel_preadv_pwritev_contiguous(QIOChannel *ioc, | ||||
|                                                  const struct iovec *iov, | ||||
|                                                  size_t niov, off_t offset, | ||||
|                                                  bool is_write, Error **errp) | ||||
| { | ||||
|     ssize_t ret; | ||||
|     int i, slice_idx, slice_num; | ||||
|     uint64_t base, next, file_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 = (uint64_t) iov[i].iov_base; | ||||
|  | ||||
|         if (i != niov - 1) { | ||||
|             len = iov[i].iov_len; | ||||
|             next = (uint64_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. | ||||
|          */ | ||||
|         file_offset = offset + (uint64_t) iov[slice_idx].iov_base; | ||||
|  | ||||
|         if (is_write) { | ||||
|             ret = qio_channel_pwritev(ioc, &iov[slice_idx], slice_num, | ||||
|                                       file_offset, errp); | ||||
|         } else { | ||||
|             ret = qio_channel_preadv(ioc, &iov[slice_idx], slice_num, | ||||
|                                      file_offset, errp); | ||||
|         } | ||||
|  | ||||
|         if (ret < 0) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         slice_idx += slice_num; | ||||
|         slice_num = 0; | ||||
|     } | ||||
|  | ||||
|     return (ret < 0) ? -1 : 0; | ||||
| } | ||||
|  | ||||
| int qio_channel_write_full_all(QIOChannel *ioc, | ||||
|                                 const struct iovec *iov, | ||||
|                                 size_t niov, off_t offset, | ||||
|                                 int *fds, size_t nfds, | ||||
|                                 int flags, Error **errp) | ||||
| { | ||||
|     if (flags & QIO_CHANNEL_WRITE_FLAG_WITH_OFFSET) { | ||||
|         return qio_channel_preadv_pwritev_contiguous(ioc, iov, niov, | ||||
|                                                      offset, true, errp); | ||||
|     } | ||||
|  | ||||
|     return qio_channel_writev_full_all(ioc, iov, niov, NULL, 0, flags, 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); | ||||
| } | ||||
|  | ||||
| int qio_channel_read_full_all(QIOChannel *ioc, const struct iovec *iov, | ||||
|                               size_t niov, off_t offset, | ||||
|                               int flags, Error **errp) | ||||
| { | ||||
|     if (flags & QIO_CHANNEL_READ_FLAG_WITH_OFFSET) { | ||||
|         return qio_channel_preadv_pwritev_contiguous(ioc, iov, niov, | ||||
|                                                      offset, false, errp); | ||||
|     } | ||||
|  | ||||
|     return qio_channel_readv_full_all(ioc, iov, niov, NULL, NULL, 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) | ||||
|   | ||||
							
								
								
									
										192
									
								
								migration/file.c
									
									
									
									
									
								
							
							
						
						
									
										192
									
								
								migration/file.c
									
									
									
									
									
								
							| @@ -6,17 +6,28 @@ | ||||
|  */ | ||||
|  | ||||
| #include "qemu/osdep.h" | ||||
| #include "qemu/cutils.h" | ||||
| #include "qapi/error.h" | ||||
| #include "qapi/qapi-commands-misc.h" | ||||
| #include "qemu/cutils.h" | ||||
| #include "qemu/error-report.h" | ||||
| #include "channel.h" | ||||
| #include "file.h" | ||||
| #include "migration.h" | ||||
| #include "io/channel-file.h" | ||||
| #include "io/channel-util.h" | ||||
| #include "monitor/monitor.h" | ||||
| #include "options.h" | ||||
| #include "trace.h" | ||||
|  | ||||
| #define OFFSET_OPTION ",offset=" | ||||
|  | ||||
| static struct FileOutgoingArgs { | ||||
|     char *fname; | ||||
|     int flags; | ||||
|     int mode; | ||||
|     int64_t fdset_id; | ||||
| } 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 +47,139 @@ int file_parse_offset(char *filespec, uint64_t *offsetp, Error **errp) | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * If the open flags and file status flags from the file descriptors | ||||
|  * in the fdset don't match what QEMU expects, errno gets set to | ||||
|  * EACCES. Let's provide a more user-friendly message. | ||||
|  */ | ||||
| static void file_fdset_error(int flags, Error **errp) | ||||
| { | ||||
|     ERRP_GUARD(); | ||||
|  | ||||
|     if (errno == EACCES) { | ||||
|         /* ditch the previous error */ | ||||
|         error_free(*errp); | ||||
|         *errp = NULL; | ||||
|  | ||||
|         error_setg(errp, "Fdset is missing a file descriptor with flags: %#x\n", | ||||
|                    flags); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void file_remove_fdset(void) | ||||
| { | ||||
|     if (outgoing_args.fdset_id != -1) { | ||||
|         qmp_remove_fd(outgoing_args.fdset_id, false, -1, NULL); | ||||
|         outgoing_args.fdset_id = -1; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Due to the behavior of the dup() system call, we need the fdset to | ||||
|  * have two non-duplicate fds so we can enable direct IO in the | ||||
|  * secondary channels without affecting the main channel. | ||||
|  */ | ||||
| static bool file_parse_fdset(const char *filename, int64_t *fdset_id, | ||||
|                              Error **errp) | ||||
| { | ||||
|     FdsetInfoList *fds_info; | ||||
|     FdsetFdInfoList *fd_info; | ||||
|     const char *fdset_id_str; | ||||
|     int nfds = 0; | ||||
|  | ||||
|     *fdset_id = -1; | ||||
|  | ||||
|     if (!strstart(filename, "/dev/fdset/", &fdset_id_str)) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     if (!migrate_multifd()) { | ||||
|         error_setg(errp, "fdset is only supported with multifd"); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     *fdset_id = qemu_parse_fd(fdset_id_str); | ||||
|  | ||||
|     for (fds_info = qmp_query_fdsets(NULL); fds_info; | ||||
|          fds_info = fds_info->next) { | ||||
|  | ||||
|         if (*fdset_id != fds_info->value->fdset_id) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         for (fd_info = fds_info->value->fds; fd_info; fd_info = fd_info->next) { | ||||
|             if (nfds++ > 2) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (nfds != 2) { | ||||
|         error_setg(errp, "Outgoing migration needs two fds in the fdset, " | ||||
|                    "got %d", nfds); | ||||
|         qmp_remove_fd(*fdset_id, false, -1, NULL); | ||||
|         *fdset_id = -1; | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| static void qio_channel_file_connect_worker(QIOTask *task, gpointer opaque) | ||||
| { | ||||
|     /* noop */ | ||||
| } | ||||
|  | ||||
| int file_send_channel_destroy(QIOChannel *ioc) | ||||
| { | ||||
|     if (ioc) { | ||||
|         qio_channel_close(ioc, NULL); | ||||
|         object_unref(OBJECT(ioc)); | ||||
|     } | ||||
|     g_free(outgoing_args.fname); | ||||
|     outgoing_args.fname = NULL; | ||||
|  | ||||
|     file_remove_fdset(); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| void file_send_channel_create(QIOTaskFunc f, void *data) | ||||
| { | ||||
|     QIOChannelFile *ioc; | ||||
|     QIOTask *task; | ||||
|     Error *err = NULL; | ||||
|     int flags = outgoing_args.flags; | ||||
|  | ||||
|     if (migrate_direct_io()) { | ||||
| #ifdef O_DIRECT | ||||
|         /* | ||||
|          * Enable O_DIRECT for the secondary channels. These are used | ||||
|          * for sending ram pages and writes should be guaranteed to be | ||||
|          * aligned to at least page size. | ||||
|          */ | ||||
|         flags |= O_DIRECT; | ||||
| #else | ||||
|         error_setg(&err, "System does not support O_DIRECT"); | ||||
|         error_append_hint(&err, | ||||
|                           "Try disabling direct-io migration capability\n"); | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     if (!err) { | ||||
|         ioc = qio_channel_file_new_path(outgoing_args.fname, flags, | ||||
|                                         outgoing_args.mode, &err); | ||||
|     } | ||||
|  | ||||
|     task = qio_task_new(OBJECT(ioc), f, (gpointer)data, NULL); | ||||
|     if (!ioc) { | ||||
|         file_fdset_error(flags, &err); | ||||
|         qio_task_set_error(task, err); | ||||
|     } | ||||
|  | ||||
|     qio_task_run_in_thread(task, qio_channel_file_connect_worker, | ||||
|                            (gpointer)data, NULL, NULL); | ||||
| } | ||||
|  | ||||
| void file_start_outgoing_migration(MigrationState *s, | ||||
|                                    FileMigrationArgs *file_args, Error **errp) | ||||
| { | ||||
| @@ -43,12 +187,22 @@ void file_start_outgoing_migration(MigrationState *s, | ||||
|     g_autofree char *filename = g_strdup(file_args->filename); | ||||
|     uint64_t offset = file_args->offset; | ||||
|     QIOChannel *ioc; | ||||
|     int flags = O_CREAT | O_TRUNC | O_WRONLY; | ||||
|     mode_t mode = 0660; | ||||
|  | ||||
|     trace_migration_file_outgoing(filename); | ||||
|  | ||||
|     fioc = qio_channel_file_new_path(filename, O_CREAT | O_WRONLY | O_TRUNC, | ||||
|                                      0600, errp); | ||||
|     if (!file_parse_fdset(filename, &outgoing_args.fdset_id, errp)) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     outgoing_args.fname = g_strdup(filename); | ||||
|     outgoing_args.flags = flags; | ||||
|     outgoing_args.mode = mode; | ||||
|  | ||||
|     fioc = qio_channel_file_new_path(filename, flags, mode, errp); | ||||
|     if (!fioc) { | ||||
|         file_fdset_error(flags, errp); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
| @@ -74,22 +228,34 @@ 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, fd, flags = O_RDONLY; | ||||
|  | ||||
|     trace_migration_file_incoming(filename); | ||||
|  | ||||
|     fioc = qio_channel_file_new_path(filename, O_RDONLY, 0, errp); | ||||
|     fioc = qio_channel_file_new_path(filename, flags, 0, errp); | ||||
|     if (!fioc) { | ||||
|         file_fdset_error(flags, errp); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     ioc = QIO_CHANNEL(fioc); | ||||
|     if (offset && qio_channel_io_seek(ioc, offset, SEEK_SET, errp) < 0) { | ||||
|         return; | ||||
|     if (migrate_multifd()) { | ||||
|         channels += migrate_multifd_channels(); | ||||
|     } | ||||
|     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()); | ||||
|  | ||||
|     fd = fioc->fd; | ||||
|  | ||||
|     do { | ||||
|         QIOChannel *ioc = QIO_CHANNEL(fioc); | ||||
|  | ||||
|         if (offset && qio_channel_io_seek(ioc, offset, SEEK_SET, errp) < 0) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         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()); | ||||
|     } while (++i < channels && (fioc = qio_channel_file_new_fd(fd))); | ||||
| } | ||||
|   | ||||
| @@ -9,10 +9,15 @@ | ||||
| #define QEMU_MIGRATION_FILE_H | ||||
|  | ||||
| #include "qapi/qapi-types-migration.h" | ||||
| #include "io/task.h" | ||||
| #include "channel.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_send_channel_create(QIOTaskFunc f, void *data); | ||||
| int file_send_channel_destroy(QIOChannel *ioc); | ||||
| #endif | ||||
|   | ||||
| @@ -392,6 +392,12 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict) | ||||
|         monitor_printf(mon, "%s: %s\n", | ||||
|             MigrationParameter_str(MIGRATION_PARAMETER_MODE), | ||||
|             qapi_enum_lookup(&MigMode_lookup, params->mode)); | ||||
|  | ||||
|         if (params->has_direct_io) { | ||||
|             monitor_printf(mon, "%s: %s\n", | ||||
|                            MigrationParameter_str(MIGRATION_PARAMETER_DIRECT_IO), | ||||
|                            params->direct_io ? "on" : "off"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     qapi_free_MigrationParameters(params); | ||||
| @@ -679,6 +685,10 @@ void hmp_migrate_set_parameter(Monitor *mon, const QDict *qdict) | ||||
|         p->has_mode = true; | ||||
|         visit_type_MigMode(v, param, &p->mode, &err); | ||||
|         break; | ||||
|     case MIGRATION_PARAMETER_DIRECT_IO: | ||||
|         p->has_direct_io = true; | ||||
|         visit_type_bool(v, param, &p->direct_io, &err); | ||||
|         break; | ||||
|     default: | ||||
|         assert(0); | ||||
|     } | ||||
|   | ||||
| @@ -128,20 +128,43 @@ static bool migration_needs_multiple_sockets(void) | ||||
|     return migrate_multifd() || migrate_postcopy_preempt(); | ||||
| } | ||||
|  | ||||
| static bool transport_supports_multi_channels(SocketAddress *saddr) | ||||
| static bool transport_supports_multi_channels(MigrationAddress *addr) | ||||
| { | ||||
|     return saddr->type == SOCKET_ADDRESS_TYPE_INET || | ||||
|            saddr->type == SOCKET_ADDRESS_TYPE_UNIX || | ||||
|            saddr->type == SOCKET_ADDRESS_TYPE_VSOCK; | ||||
|     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); | ||||
|     } else if (addr->transport == MIGRATION_ADDRESS_TYPE_FILE) { | ||||
|         return migrate_fixed_ram(); | ||||
|     } else { | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static bool migration_needs_seekable_channel(void) | ||||
| { | ||||
|     return migrate_fixed_ram(); | ||||
| } | ||||
|  | ||||
| static bool transport_supports_seeking(MigrationAddress *addr) | ||||
| { | ||||
|     return addr->transport == MIGRATION_ADDRESS_TYPE_FILE; | ||||
| } | ||||
|  | ||||
| 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() && | ||||
|         (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) && | ||||
|         !transport_supports_multi_channels(&addr->u.socket)) { | ||||
|         !transport_supports_multi_channels(addr)) { | ||||
|         error_setg(errp, "Migration requires multi-channel URIs (e.g. tcp)"); | ||||
|         return false; | ||||
|     } | ||||
| @@ -843,6 +866,8 @@ void migration_ioc_process_incoming(QIOChannel *ioc, Error **errp) | ||||
|         } | ||||
|  | ||||
|         default_channel = (channel_magic == cpu_to_be32(QEMU_VM_FILE_MAGIC)); | ||||
|     } else if (migrate_multifd() && migrate_fixed_ram()) { | ||||
|         default_channel = multifd_recv_first_channel(); | ||||
|     } else { | ||||
|         default_channel = !mis->from_src_file; | ||||
|     } | ||||
|   | ||||
| @@ -17,6 +17,7 @@ | ||||
| #include "exec/ramblock.h" | ||||
| #include "qemu/error-report.h" | ||||
| #include "qapi/error.h" | ||||
| #include "file.h" | ||||
| #include "ram.h" | ||||
| #include "migration.h" | ||||
| #include "migration-stats.h" | ||||
| @@ -28,6 +29,7 @@ | ||||
| #include "threadinfo.h" | ||||
| #include "options.h" | ||||
| #include "qemu/yank.h" | ||||
| #include "io/channel-file.h" | ||||
| #include "io/channel-socket.h" | ||||
| #include "yank_functions.h" | ||||
|  | ||||
| @@ -140,6 +142,7 @@ static void nocomp_recv_cleanup(MultiFDRecvParams *p) | ||||
| static int nocomp_recv_pages(MultiFDRecvParams *p, Error **errp) | ||||
| { | ||||
|     uint32_t flags = p->flags & MULTIFD_FLAG_COMPRESSION_MASK; | ||||
|     uint64_t read_base = 0; | ||||
|  | ||||
|     if (flags != MULTIFD_FLAG_NOCOMP) { | ||||
|         error_setg(errp, "multifd %u: flags received %x flags expected %x", | ||||
| @@ -150,7 +153,13 @@ static int nocomp_recv_pages(MultiFDRecvParams *p, Error **errp) | ||||
|         p->iov[i].iov_base = p->host + p->normal[i]; | ||||
|         p->iov[i].iov_len = p->page_size; | ||||
|     } | ||||
|     return qio_channel_readv_all(p->c, p->iov, p->normal_num, errp); | ||||
|  | ||||
|     if (migrate_fixed_ram()) { | ||||
|         read_base = p->pages->block->pages_offset - (uint64_t) p->host; | ||||
|     } | ||||
|  | ||||
|     return qio_channel_read_full_all(p->c, p->iov, p->normal_num, read_base, | ||||
|                                      p->read_flags, errp); | ||||
| } | ||||
|  | ||||
| static MultiFDMethods multifd_nocomp_ops = { | ||||
| @@ -236,12 +245,12 @@ static int multifd_recv_initial_packet(QIOChannel *c, Error **errp) | ||||
|     return msg.id; | ||||
| } | ||||
|  | ||||
| static MultiFDPages_t *multifd_pages_init(size_t size) | ||||
| static MultiFDPages_t *multifd_pages_init(uint32_t n) | ||||
| { | ||||
|     MultiFDPages_t *pages = g_new0(MultiFDPages_t, 1); | ||||
|  | ||||
|     pages->allocated = size; | ||||
|     pages->offset = g_new0(ram_addr_t, size); | ||||
|     pages->allocated = n; | ||||
|     pages->offset = g_new0(ram_addr_t, n); | ||||
|  | ||||
|     return pages; | ||||
| } | ||||
| @@ -250,13 +259,25 @@ static void multifd_pages_clear(MultiFDPages_t *pages) | ||||
| { | ||||
|     pages->num = 0; | ||||
|     pages->allocated = 0; | ||||
|     pages->packet_num = 0; | ||||
|     pages->block = NULL; | ||||
|     g_free(pages->offset); | ||||
|     pages->offset = NULL; | ||||
|     g_free(pages); | ||||
| } | ||||
|  | ||||
| static void multifd_set_file_bitmap(MultiFDSendParams *p) | ||||
| { | ||||
|     MultiFDPages_t *pages = p->pages; | ||||
|  | ||||
|     if (!pages->block) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     for (int i = 0; i < p->normal_num; i++) { | ||||
|         ramblock_set_shadow_bmap_atomic(pages->block, pages->offset[i]); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void multifd_send_fill_packet(MultiFDSendParams *p) | ||||
| { | ||||
|     MultiFDPacket_t *packet = p->packet; | ||||
| @@ -391,7 +412,7 @@ struct { | ||||
|  * false. | ||||
|  */ | ||||
|  | ||||
| static int multifd_send_pages(QEMUFile *f) | ||||
| static int multifd_send_pages(void) | ||||
| { | ||||
|     int i; | ||||
|     static int next_channel; | ||||
| @@ -403,6 +424,7 @@ static int multifd_send_pages(QEMUFile *f) | ||||
|     } | ||||
|  | ||||
|     qemu_sem_wait(&multifd_send_state->channels_ready); | ||||
|  | ||||
|     /* | ||||
|      * next_channel can remain from a previous migration that was | ||||
|      * using more channels, so ensure it doesn't overflow if the | ||||
| @@ -437,7 +459,7 @@ static int multifd_send_pages(QEMUFile *f) | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| int multifd_queue_page(QEMUFile *f, RAMBlock *block, ram_addr_t offset) | ||||
| int multifd_queue_page(RAMBlock *block, ram_addr_t offset) | ||||
| { | ||||
|     MultiFDPages_t *pages = multifd_send_state->pages; | ||||
|     bool changed = false; | ||||
| @@ -457,12 +479,12 @@ int multifd_queue_page(QEMUFile *f, RAMBlock *block, ram_addr_t offset) | ||||
|         changed = true; | ||||
|     } | ||||
|  | ||||
|     if (multifd_send_pages(f) < 0) { | ||||
|     if (multifd_send_pages() < 0) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     if (changed) { | ||||
|         return multifd_queue_page(f, block, offset); | ||||
|         return multifd_queue_page(block, offset); | ||||
|     } | ||||
|  | ||||
|     return 1; | ||||
| @@ -511,7 +533,11 @@ static void multifd_send_terminate_threads(Error *err) | ||||
|  | ||||
| static int multifd_send_channel_destroy(QIOChannel *send) | ||||
| { | ||||
|     return socket_send_channel_destroy(send); | ||||
|     if (migrate_to_file()) { | ||||
|         return file_send_channel_destroy(send); | ||||
|     } else { | ||||
|         return socket_send_channel_destroy(send); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void multifd_save_cleanup(void) | ||||
| @@ -584,7 +610,7 @@ static int multifd_zero_copy_flush(QIOChannel *c) | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| int multifd_send_sync_main(QEMUFile *f) | ||||
| int multifd_send_sync_main(void) | ||||
| { | ||||
|     int i; | ||||
|     bool flush_zero_copy; | ||||
| @@ -593,12 +619,40 @@ int multifd_send_sync_main(QEMUFile *f) | ||||
|         return 0; | ||||
|     } | ||||
|     if (multifd_send_state->pages->num) { | ||||
|         if (multifd_send_pages(f) < 0) { | ||||
|         if (multifd_send_pages() < 0) { | ||||
|             error_report("%s: multifd_send_pages fail", __func__); | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (!migrate_multifd_packets()) { | ||||
|         /* | ||||
|          * There's no sync packet to send. Just make sure the sending | ||||
|          * above has finished. | ||||
|          */ | ||||
|         for (i = 0; i < migrate_multifd_channels(); i++) { | ||||
|             qemu_sem_wait(&multifd_send_state->channels_ready); | ||||
|         } | ||||
|  | ||||
|         /* sanity check and release the channels */ | ||||
|         for (i = 0; i < migrate_multifd_channels(); i++) { | ||||
|             MultiFDSendParams *p = &multifd_send_state->params[i]; | ||||
|  | ||||
|             qemu_mutex_lock(&p->mutex); | ||||
|             assert(!p->pending_job || p->quit); | ||||
|             if (p->quit) { | ||||
|                 error_report("%s: channel %d has already quit!", __func__, i); | ||||
|                 qemu_mutex_unlock(&p->mutex); | ||||
|                 return -1; | ||||
|             } | ||||
|             qemu_mutex_unlock(&p->mutex); | ||||
|  | ||||
|             qemu_sem_post(&p->sem); | ||||
|         } | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * When using zero-copy, it's necessary to flush the pages before any of | ||||
|      * the pages can be sent again, so we'll make sure the new version of the | ||||
| @@ -654,18 +708,22 @@ static void *multifd_send_thread(void *opaque) | ||||
|     Error *local_err = NULL; | ||||
|     int ret = 0; | ||||
|     bool use_zero_copy_send = migrate_zero_copy_send(); | ||||
|     bool use_packets = migrate_multifd_packets(); | ||||
|  | ||||
|     thread = migration_threads_add(p->name, qemu_get_thread_id()); | ||||
|  | ||||
|     trace_multifd_send_thread_start(p->id); | ||||
|     rcu_register_thread(); | ||||
|  | ||||
|     if (multifd_send_initial_packet(p, &local_err) < 0) { | ||||
|         ret = -1; | ||||
|         goto out; | ||||
|     if (use_packets) { | ||||
|         if (multifd_send_initial_packet(p, &local_err) < 0) { | ||||
|             ret = -1; | ||||
|             goto out; | ||||
|         } | ||||
|  | ||||
|         /* initial packet */ | ||||
|         p->num_packets = 1; | ||||
|     } | ||||
|     /* initial packet */ | ||||
|     p->num_packets = 1; | ||||
|  | ||||
|     while (true) { | ||||
|         qemu_sem_post(&multifd_send_state->channels_ready); | ||||
| @@ -677,11 +735,12 @@ static void *multifd_send_thread(void *opaque) | ||||
|         qemu_mutex_lock(&p->mutex); | ||||
|  | ||||
|         if (p->pending_job) { | ||||
|             uint64_t packet_num = p->packet_num; | ||||
|             uint32_t flags; | ||||
|             uint64_t write_base; | ||||
|  | ||||
|             p->normal_num = 0; | ||||
|  | ||||
|             if (use_zero_copy_send) { | ||||
|             if (!use_packets || use_zero_copy_send) { | ||||
|                 p->iovs_num = 0; | ||||
|             } else { | ||||
|                 p->iovs_num = 1; | ||||
| @@ -699,16 +758,30 @@ static void *multifd_send_thread(void *opaque) | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             multifd_send_fill_packet(p); | ||||
|  | ||||
|             if (use_packets) { | ||||
|                 multifd_send_fill_packet(p); | ||||
|                 p->num_packets++; | ||||
|                 write_base = 0; | ||||
|             } else { | ||||
|                 multifd_set_file_bitmap(p); | ||||
|  | ||||
|                 /* | ||||
|                  * If we subtract the host page now, we don't need to | ||||
|                  * pass it into qio_channel_write_full_all() below. | ||||
|                  */ | ||||
|                 write_base = p->pages->block->pages_offset - | ||||
|                     (uint64_t)p->pages->block->host; | ||||
|             } | ||||
|  | ||||
|             flags = p->flags; | ||||
|             p->flags = 0; | ||||
|             p->num_packets++; | ||||
|             p->total_normal_pages += p->normal_num; | ||||
|             p->pages->num = 0; | ||||
|             p->pages->block = NULL; | ||||
|             qemu_mutex_unlock(&p->mutex); | ||||
|  | ||||
|             trace_multifd_send(p->id, packet_num, p->normal_num, flags, | ||||
|             trace_multifd_send(p->id, p->packet_num, p->normal_num, flags, | ||||
|                                p->next_packet_size); | ||||
|  | ||||
|             if (use_zero_copy_send) { | ||||
| @@ -718,14 +791,15 @@ static void *multifd_send_thread(void *opaque) | ||||
|                 if (ret != 0) { | ||||
|                     break; | ||||
|                 } | ||||
|             } else { | ||||
|             } else if (use_packets) { | ||||
|                 /* Send header using the same writev call */ | ||||
|                 p->iov[0].iov_len = p->packet_len; | ||||
|                 p->iov[0].iov_base = p->packet; | ||||
|             } | ||||
|  | ||||
|             ret = qio_channel_writev_full_all(p->c, p->iov, p->iovs_num, NULL, | ||||
|                                               0, p->write_flags, &local_err); | ||||
|             ret = qio_channel_write_full_all(p->c, p->iov, p->iovs_num, | ||||
|                                              write_base, NULL, 0, | ||||
|                                              p->write_flags, &local_err); | ||||
|             if (ret != 0) { | ||||
|                 break; | ||||
|             } | ||||
| @@ -852,14 +926,14 @@ static bool multifd_channel_connect(MultiFDSendParams *p, | ||||
|         migration_ioc_register_yank(ioc); | ||||
|         p->registered_yank = true; | ||||
|         p->c = ioc; | ||||
|         p->running = true; | ||||
|         qemu_thread_create(&p->thread, p->name, multifd_send_thread, p, | ||||
|                            QEMU_THREAD_JOINABLE); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| static void multifd_new_send_channel_cleanup(MultiFDSendParams *p, | ||||
|                                              QIOChannel *ioc, Error *err) | ||||
| static void multifd_new_send_channel_cleanup(MultiFDSendParams *p, Error *err) | ||||
| { | ||||
|      migrate_set_error(migrate_get_current(), err); | ||||
|      /* Error happen, we need to tell who pay attention to me */ | ||||
| @@ -871,39 +945,44 @@ static void multifd_new_send_channel_cleanup(MultiFDSendParams *p, | ||||
|       * its status. | ||||
|       */ | ||||
|      p->quit = true; | ||||
|      object_unref(OBJECT(ioc)); | ||||
|      error_free(err); | ||||
| } | ||||
|  | ||||
| static void multifd_new_send_channel_async(QIOTask *task, gpointer opaque) | ||||
| { | ||||
|     MultiFDSendParams *p = opaque; | ||||
|     QIOChannel *ioc = QIO_CHANNEL(qio_task_get_source(task)); | ||||
|     Object *obj = qio_task_get_source(task); | ||||
|     Error *local_err = NULL; | ||||
|  | ||||
|     trace_multifd_new_send_channel_async(p->id); | ||||
|     if (!qio_task_propagate_error(task, &local_err)) { | ||||
|         p->c = ioc; | ||||
|         qio_channel_set_delay(p->c, false); | ||||
|         p->running = true; | ||||
|         QIOChannel *ioc = QIO_CHANNEL(obj); | ||||
|  | ||||
|         qio_channel_set_delay(ioc, false); | ||||
|         if (multifd_channel_connect(p, ioc, &local_err)) { | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     trace_multifd_new_send_channel_async_error(p->id, local_err); | ||||
|     multifd_new_send_channel_cleanup(p, ioc, local_err); | ||||
|     multifd_new_send_channel_cleanup(p, local_err); | ||||
|     object_unref(obj); | ||||
| } | ||||
|  | ||||
| static void multifd_new_send_channel_create(gpointer opaque) | ||||
| { | ||||
|     socket_send_channel_create(multifd_new_send_channel_async, opaque); | ||||
|     if (migrate_to_file()) { | ||||
|         file_send_channel_create(multifd_new_send_channel_async, opaque); | ||||
|     } else { | ||||
|         socket_send_channel_create(multifd_new_send_channel_async, opaque); | ||||
|     } | ||||
| } | ||||
|  | ||||
| int multifd_save_setup(Error **errp) | ||||
| { | ||||
|     int thread_count; | ||||
|     uint32_t page_count = MULTIFD_PACKET_SIZE / qemu_target_page_size(); | ||||
|     bool use_packets = migrate_multifd_packets(); | ||||
|     uint8_t i; | ||||
|  | ||||
|     if (!migrate_multifd()) { | ||||
| @@ -928,20 +1007,28 @@ int multifd_save_setup(Error **errp) | ||||
|         p->pending_job = 0; | ||||
|         p->id = i; | ||||
|         p->pages = multifd_pages_init(page_count); | ||||
|         p->packet_len = sizeof(MultiFDPacket_t) | ||||
|                       + sizeof(uint64_t) * page_count; | ||||
|         p->packet = g_malloc0(p->packet_len); | ||||
|         p->packet->magic = cpu_to_be32(MULTIFD_MAGIC); | ||||
|         p->packet->version = cpu_to_be32(MULTIFD_VERSION); | ||||
|  | ||||
|         if (use_packets) { | ||||
|             p->packet_len = sizeof(MultiFDPacket_t) | ||||
|                           + sizeof(uint64_t) * page_count; | ||||
|             p->packet = g_malloc0(p->packet_len); | ||||
|             p->packet->magic = cpu_to_be32(MULTIFD_MAGIC); | ||||
|             p->packet->version = cpu_to_be32(MULTIFD_VERSION); | ||||
|  | ||||
|             /* We need one extra place for the packet header */ | ||||
|             p->iov = g_new0(struct iovec, page_count + 1); | ||||
|         } else { | ||||
|             p->iov = g_new0(struct iovec, page_count); | ||||
|         } | ||||
|         p->name = g_strdup_printf("multifdsend_%d", i); | ||||
|         /* We need one extra place for the packet header */ | ||||
|         p->iov = g_new0(struct iovec, page_count + 1); | ||||
|         p->normal = g_new0(ram_addr_t, page_count); | ||||
|         p->page_size = qemu_target_page_size(); | ||||
|         p->page_count = page_count; | ||||
|  | ||||
|         if (migrate_zero_copy_send()) { | ||||
|             p->write_flags = QIO_CHANNEL_WRITE_FLAG_ZERO_COPY; | ||||
|         } else if (!use_packets) { | ||||
|             p->write_flags |= QIO_CHANNEL_WRITE_FLAG_WITH_OFFSET; | ||||
|         } else { | ||||
|             p->write_flags = 0; | ||||
|         } | ||||
| @@ -965,6 +1052,8 @@ int multifd_save_setup(Error **errp) | ||||
|  | ||||
| struct { | ||||
|     MultiFDRecvParams *params; | ||||
|     /* array of pages to receive */ | ||||
|     MultiFDPages_t *pages; | ||||
|     /* number of created threads */ | ||||
|     int count; | ||||
|     /* syncs main thread and channels */ | ||||
| @@ -975,6 +1064,75 @@ struct { | ||||
|     MultiFDMethods *ops; | ||||
| } *multifd_recv_state; | ||||
|  | ||||
| static int multifd_recv_pages(QEMUFile *f) | ||||
| { | ||||
|     int i; | ||||
|     static int next_recv_channel; | ||||
|     MultiFDRecvParams *p = NULL; | ||||
|     MultiFDPages_t *pages = multifd_recv_state->pages; | ||||
|  | ||||
|     /* | ||||
|      * next_channel can remain from a previous migration that was | ||||
|      * using more channels, so ensure it doesn't overflow if the | ||||
|      * limit is lower now. | ||||
|      */ | ||||
|     next_recv_channel %= migrate_multifd_channels(); | ||||
|     for (i = next_recv_channel;; i = (i + 1) % migrate_multifd_channels()) { | ||||
|         p = &multifd_recv_state->params[i]; | ||||
|  | ||||
|         qemu_mutex_lock(&p->mutex); | ||||
|         if (p->quit) { | ||||
|             error_report("%s: channel %d has already quit!", __func__, i); | ||||
|             qemu_mutex_unlock(&p->mutex); | ||||
|             return -1; | ||||
|         } | ||||
|         if (!p->pending_job) { | ||||
|             p->pending_job++; | ||||
|             next_recv_channel = (i + 1) % migrate_multifd_channels(); | ||||
|             break; | ||||
|         } | ||||
|         qemu_mutex_unlock(&p->mutex); | ||||
|     } | ||||
|  | ||||
|     multifd_recv_state->pages = p->pages; | ||||
|     p->pages = pages; | ||||
|     qemu_mutex_unlock(&p->mutex); | ||||
|     qemu_sem_post(&p->sem); | ||||
|  | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| int multifd_recv_queue_page(QEMUFile *f, RAMBlock *block, ram_addr_t offset) | ||||
| { | ||||
|     MultiFDPages_t *pages = multifd_recv_state->pages; | ||||
|     bool changed = false; | ||||
|  | ||||
|     if (!pages->block) { | ||||
|         pages->block = block; | ||||
|     } | ||||
|  | ||||
|     if (pages->block == block) { | ||||
|         pages->offset[pages->num] = offset; | ||||
|         pages->num++; | ||||
|  | ||||
|         if (pages->num < pages->allocated) { | ||||
|             return 1; | ||||
|         } | ||||
|     } else { | ||||
|         changed = true; | ||||
|     } | ||||
|  | ||||
|     if (multifd_recv_pages(f) < 0) { | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     if (changed) { | ||||
|         return multifd_recv_queue_page(f, block, offset); | ||||
|     } | ||||
|  | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| static void multifd_recv_terminate_threads(Error *err) | ||||
| { | ||||
|     int i; | ||||
| @@ -996,6 +1154,7 @@ static void multifd_recv_terminate_threads(Error *err) | ||||
|  | ||||
|         qemu_mutex_lock(&p->mutex); | ||||
|         p->quit = true; | ||||
|         qemu_sem_post(&p->sem); | ||||
|         /* | ||||
|          * We could arrive here for two reasons: | ||||
|          *  - normal quit, i.e. everything went fine, just finished | ||||
| @@ -1045,8 +1204,11 @@ void multifd_load_cleanup(void) | ||||
|         p->c = NULL; | ||||
|         qemu_mutex_destroy(&p->mutex); | ||||
|         qemu_sem_destroy(&p->sem_sync); | ||||
|         qemu_sem_destroy(&p->sem); | ||||
|         g_free(p->name); | ||||
|         p->name = NULL; | ||||
|         multifd_pages_clear(p->pages); | ||||
|         p->pages = NULL; | ||||
|         p->packet_len = 0; | ||||
|         g_free(p->packet); | ||||
|         p->packet = NULL; | ||||
| @@ -1059,6 +1221,8 @@ void multifd_load_cleanup(void) | ||||
|     qemu_sem_destroy(&multifd_recv_state->sem_sync); | ||||
|     g_free(multifd_recv_state->params); | ||||
|     multifd_recv_state->params = NULL; | ||||
|     multifd_pages_clear(multifd_recv_state->pages); | ||||
|     multifd_recv_state->pages = NULL; | ||||
|     g_free(multifd_recv_state); | ||||
|     multifd_recv_state = NULL; | ||||
| } | ||||
| @@ -1067,9 +1231,10 @@ void multifd_recv_sync_main(void) | ||||
| { | ||||
|     int i; | ||||
|  | ||||
|     if (!migrate_multifd()) { | ||||
|     if (!migrate_multifd() || !migrate_multifd_packets()) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     for (i = 0; i < migrate_multifd_channels(); i++) { | ||||
|         MultiFDRecvParams *p = &multifd_recv_state->params[i]; | ||||
|  | ||||
| @@ -1094,6 +1259,7 @@ static void *multifd_recv_thread(void *opaque) | ||||
| { | ||||
|     MultiFDRecvParams *p = opaque; | ||||
|     Error *local_err = NULL; | ||||
|     bool use_packets = migrate_multifd_packets(); | ||||
|     int ret; | ||||
|  | ||||
|     trace_multifd_recv_thread_start(p->id); | ||||
| @@ -1101,22 +1267,45 @@ static void *multifd_recv_thread(void *opaque) | ||||
|  | ||||
|     while (true) { | ||||
|         uint32_t flags; | ||||
|         p->normal_num = 0; | ||||
|  | ||||
|         if (p->quit) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         ret = qio_channel_read_all_eof(p->c, (void *)p->packet, | ||||
|                                        p->packet_len, &local_err); | ||||
|         if (ret == 0 || ret == -1) {   /* 0: EOF  -1: Error */ | ||||
|             break; | ||||
|         } | ||||
|         if (use_packets) { | ||||
|             ret = qio_channel_read_all_eof(p->c, (void *)p->packet, | ||||
|                                            p->packet_len, &local_err); | ||||
|             if (ret == 0 || ret == -1) {   /* 0: EOF  -1: Error */ | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|         qemu_mutex_lock(&p->mutex); | ||||
|         ret = multifd_recv_unfill_packet(p, &local_err); | ||||
|         if (ret) { | ||||
|             qemu_mutex_unlock(&p->mutex); | ||||
|             break; | ||||
|             qemu_mutex_lock(&p->mutex); | ||||
|             ret = multifd_recv_unfill_packet(p, &local_err); | ||||
|             if (ret) { | ||||
|                 qemu_mutex_unlock(&p->mutex); | ||||
|                 break; | ||||
|             } | ||||
|             p->num_packets++; | ||||
|         } else { | ||||
|             /* | ||||
|              * No packets, so we need to wait for the vmstate code to | ||||
|              * queue pages. | ||||
|              */ | ||||
|             qemu_sem_wait(&p->sem); | ||||
|             qemu_mutex_lock(&p->mutex); | ||||
|             if (!p->pending_job) { | ||||
|                 qemu_mutex_unlock(&p->mutex); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             for (int i = 0; i < p->pages->num; i++) { | ||||
|                 p->normal[p->normal_num] = p->pages->offset[i]; | ||||
|                 p->normal_num++; | ||||
|             } | ||||
|  | ||||
|             p->pages->num = 0; | ||||
|             p->host = p->pages->block->host; | ||||
|         } | ||||
|  | ||||
|         flags = p->flags; | ||||
| @@ -1124,7 +1313,7 @@ static void *multifd_recv_thread(void *opaque) | ||||
|         p->flags &= ~MULTIFD_FLAG_SYNC; | ||||
|         trace_multifd_recv(p->id, p->packet_num, p->normal_num, flags, | ||||
|                            p->next_packet_size); | ||||
|         p->num_packets++; | ||||
|  | ||||
|         p->total_normal_pages += p->normal_num; | ||||
|         qemu_mutex_unlock(&p->mutex); | ||||
|  | ||||
| @@ -1139,6 +1328,13 @@ static void *multifd_recv_thread(void *opaque) | ||||
|             qemu_sem_post(&multifd_recv_state->sem_sync); | ||||
|             qemu_sem_wait(&p->sem_sync); | ||||
|         } | ||||
|  | ||||
|         if (!use_packets) { | ||||
|             qemu_mutex_lock(&p->mutex); | ||||
|             p->pending_job--; | ||||
|             p->pages->block = NULL; | ||||
|             qemu_mutex_unlock(&p->mutex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (local_err) { | ||||
| @@ -1159,6 +1355,7 @@ int multifd_load_setup(Error **errp) | ||||
| { | ||||
|     int thread_count; | ||||
|     uint32_t page_count = MULTIFD_PACKET_SIZE / qemu_target_page_size(); | ||||
|     bool use_packets = migrate_multifd_packets(); | ||||
|     uint8_t i; | ||||
|  | ||||
|     /* | ||||
| @@ -1172,6 +1369,7 @@ int multifd_load_setup(Error **errp) | ||||
|     thread_count = migrate_multifd_channels(); | ||||
|     multifd_recv_state = g_malloc0(sizeof(*multifd_recv_state)); | ||||
|     multifd_recv_state->params = g_new0(MultiFDRecvParams, thread_count); | ||||
|     multifd_recv_state->pages = multifd_pages_init(page_count); | ||||
|     qatomic_set(&multifd_recv_state->count, 0); | ||||
|     qemu_sem_init(&multifd_recv_state->sem_sync, 0); | ||||
|     multifd_recv_state->ops = multifd_ops[migrate_multifd_compression()]; | ||||
| @@ -1181,11 +1379,19 @@ int multifd_load_setup(Error **errp) | ||||
|  | ||||
|         qemu_mutex_init(&p->mutex); | ||||
|         qemu_sem_init(&p->sem_sync, 0); | ||||
|         qemu_sem_init(&p->sem, 0); | ||||
|         p->quit = false; | ||||
|         p->pending_job = 0; | ||||
|         p->id = i; | ||||
|         p->packet_len = sizeof(MultiFDPacket_t) | ||||
|                       + sizeof(uint64_t) * page_count; | ||||
|         p->packet = g_malloc0(p->packet_len); | ||||
|         p->pages = multifd_pages_init(page_count); | ||||
|  | ||||
|         if (use_packets) { | ||||
|             p->packet_len = sizeof(MultiFDPacket_t) | ||||
|                 + sizeof(uint64_t) * page_count; | ||||
|             p->packet = g_malloc0(p->packet_len); | ||||
|         } else { | ||||
|             p->read_flags |= QIO_CHANNEL_READ_FLAG_WITH_OFFSET; | ||||
|         } | ||||
|         p->name = g_strdup_printf("multifdrecv_%d", i); | ||||
|         p->iov = g_new0(struct iovec, page_count); | ||||
|         p->normal = g_new0(ram_addr_t, page_count); | ||||
| @@ -1207,6 +1413,11 @@ int multifd_load_setup(Error **errp) | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| bool multifd_recv_first_channel(void) | ||||
| { | ||||
|     return !multifd_recv_state; | ||||
| } | ||||
|  | ||||
| bool multifd_recv_all_channels_created(void) | ||||
| { | ||||
|     int thread_count = migrate_multifd_channels(); | ||||
| @@ -1231,18 +1442,26 @@ void multifd_recv_new_channel(QIOChannel *ioc, Error **errp) | ||||
| { | ||||
|     MultiFDRecvParams *p; | ||||
|     Error *local_err = NULL; | ||||
|     int id; | ||||
|     bool use_packets = migrate_multifd_packets(); | ||||
|     int id, num_packets = 0; | ||||
|  | ||||
|     id = multifd_recv_initial_packet(ioc, &local_err); | ||||
|     if (id < 0) { | ||||
|         multifd_recv_terminate_threads(local_err); | ||||
|         error_propagate_prepend(errp, local_err, | ||||
|                                 "failed to receive packet" | ||||
|                                 " via multifd channel %d: ", | ||||
|                                 qatomic_read(&multifd_recv_state->count)); | ||||
|         return; | ||||
|     if (use_packets) { | ||||
|         id = multifd_recv_initial_packet(ioc, &local_err); | ||||
|         if (id < 0) { | ||||
|             multifd_recv_terminate_threads(local_err); | ||||
|             error_propagate_prepend(errp, local_err, | ||||
|                                     "failed to receive packet" | ||||
|                                     " via multifd channel %d: ", | ||||
|                                     qatomic_read(&multifd_recv_state->count)); | ||||
|             return; | ||||
|         } | ||||
|         trace_multifd_recv_new_channel(id); | ||||
|  | ||||
|         /* initial packet */ | ||||
|         num_packets = 1; | ||||
|     } else { | ||||
|         id = qatomic_read(&multifd_recv_state->count); | ||||
|     } | ||||
|     trace_multifd_recv_new_channel(id); | ||||
|  | ||||
|     p = &multifd_recv_state->params[id]; | ||||
|     if (p->c != NULL) { | ||||
| @@ -1253,9 +1472,8 @@ void multifd_recv_new_channel(QIOChannel *ioc, Error **errp) | ||||
|         return; | ||||
|     } | ||||
|     p->c = ioc; | ||||
|     p->num_packets = num_packets; | ||||
|     object_ref(OBJECT(ioc)); | ||||
|     /* initial packet */ | ||||
|     p->num_packets = 1; | ||||
|  | ||||
|     p->running = true; | ||||
|     qemu_thread_create(&p->thread, p->name, multifd_recv_thread, p, | ||||
|   | ||||
| @@ -18,11 +18,13 @@ void multifd_save_cleanup(void); | ||||
| int multifd_load_setup(Error **errp); | ||||
| void multifd_load_cleanup(void); | ||||
| void multifd_load_shutdown(void); | ||||
| bool multifd_recv_first_channel(void); | ||||
| bool multifd_recv_all_channels_created(void); | ||||
| void multifd_recv_new_channel(QIOChannel *ioc, Error **errp); | ||||
| void multifd_recv_sync_main(void); | ||||
| int multifd_send_sync_main(QEMUFile *f); | ||||
| int multifd_queue_page(QEMUFile *f, RAMBlock *block, ram_addr_t offset); | ||||
| int multifd_send_sync_main(void); | ||||
| int multifd_queue_page(RAMBlock *block, ram_addr_t offset); | ||||
| int multifd_recv_queue_page(QEMUFile *f, RAMBlock *block, ram_addr_t offset); | ||||
|  | ||||
| /* Multifd Compression flags */ | ||||
| #define MULTIFD_FLAG_SYNC (1 << 0) | ||||
| @@ -58,8 +60,6 @@ typedef struct { | ||||
|     uint32_t num; | ||||
|     /* number of allocated pages */ | ||||
|     uint32_t allocated; | ||||
|     /* global number of generated multifd packets */ | ||||
|     uint64_t packet_num; | ||||
|     /* offset of each page */ | ||||
|     ram_addr_t *offset; | ||||
|     RAMBlock *block; | ||||
| @@ -152,9 +152,13 @@ typedef struct { | ||||
|     uint32_t page_size; | ||||
|     /* number of pages in a full packet */ | ||||
|     uint32_t page_count; | ||||
|     /* multifd flags for receiving ram */ | ||||
|     int read_flags; | ||||
|  | ||||
|     /* 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; | ||||
| @@ -166,6 +170,13 @@ typedef struct { | ||||
|     uint32_t flags; | ||||
|     /* global number of generated multifd packets */ | ||||
|     uint64_t packet_num; | ||||
|     int pending_job; | ||||
|     /* array of pages to sent. | ||||
|      * The owner of 'pages' depends of 'pending_job' value: | ||||
|      * pending_job == 0 -> migration_thread can use it. | ||||
|      * pending_job != 0 -> multifd_channel can use it. | ||||
|      */ | ||||
|     MultiFDPages_t *pages; | ||||
|  | ||||
|     /* thread local variables. No locking required */ | ||||
|  | ||||
| @@ -209,4 +220,3 @@ typedef struct { | ||||
| void multifd_register_ops(int method, MultiFDMethods *ops); | ||||
|  | ||||
| #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("x-fixed-ram", MIGRATION_CAPABILITY_FIXED_RAM), | ||||
|     DEFINE_PROP_END_OF_LIST(), | ||||
| }; | ||||
|  | ||||
| @@ -263,6 +264,13 @@ bool migrate_events(void) | ||||
|     return s->capabilities[MIGRATION_CAPABILITY_EVENTS]; | ||||
| } | ||||
|  | ||||
| bool migrate_fixed_ram(void) | ||||
| { | ||||
|     MigrationState *s = migrate_get_current(); | ||||
|  | ||||
|     return s->capabilities[MIGRATION_CAPABILITY_FIXED_RAM]; | ||||
| } | ||||
|  | ||||
| bool migrate_ignore_shared(void) | ||||
| { | ||||
|     MigrationState *s = migrate_get_current(); | ||||
| @@ -377,6 +385,11 @@ bool migrate_multifd_flush_after_each_section(void) | ||||
|     return s->multifd_flush_after_each_section; | ||||
| } | ||||
|  | ||||
| bool migrate_multifd_packets(void) | ||||
| { | ||||
|     return !migrate_fixed_ram(); | ||||
| } | ||||
|  | ||||
| bool migrate_postcopy(void) | ||||
| { | ||||
|     return migrate_postcopy_ram() || migrate_dirty_bitmaps(); | ||||
| @@ -396,6 +409,13 @@ bool migrate_tls(void) | ||||
|     return s->parameters.tls_creds && *s->parameters.tls_creds; | ||||
| } | ||||
|  | ||||
| bool migrate_to_file(void) | ||||
| { | ||||
|     MigrationState *s = migrate_get_current(); | ||||
|  | ||||
|     return qemu_file_is_seekable(s->to_dst_file); | ||||
| } | ||||
|  | ||||
| typedef enum WriteTrackingSupport { | ||||
|     WT_SUPPORT_UNKNOWN = 0, | ||||
|     WT_SUPPORT_ABSENT, | ||||
| @@ -645,6 +665,26 @@ bool migrate_caps_check(bool *old_caps, bool *new_caps, Error **errp) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (new_caps[MIGRATION_CAPABILITY_FIXED_RAM]) { | ||||
|         if (new_caps[MIGRATION_CAPABILITY_XBZRLE]) { | ||||
|             error_setg(errp, | ||||
|                        "Fixed-ram migration is incompatible with xbzrle"); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         if (new_caps[MIGRATION_CAPABILITY_COMPRESS]) { | ||||
|             error_setg(errp, | ||||
|                        "Fixed-ram migration is incompatible with compression"); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         if (new_caps[MIGRATION_CAPABILITY_POSTCOPY_RAM]) { | ||||
|             error_setg(errp, | ||||
|                        "Fixed-ram migration is incompatible with postcopy ram"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| @@ -795,6 +835,22 @@ int migrate_decompress_threads(void) | ||||
|     return s->parameters.decompress_threads; | ||||
| } | ||||
|  | ||||
| bool migrate_direct_io(void) | ||||
| { | ||||
|     MigrationState *s = migrate_get_current(); | ||||
|  | ||||
|     /* For now O_DIRECT is only supported with fixed-ram */ | ||||
|     if (!s->capabilities[MIGRATION_CAPABILITY_FIXED_RAM]) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     if (s->parameters.has_direct_io) { | ||||
|         return s->parameters.direct_io; | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| uint64_t migrate_downtime_limit(void) | ||||
| { | ||||
|     MigrationState *s = migrate_get_current(); | ||||
| @@ -1012,6 +1068,11 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp) | ||||
|     params->has_mode = true; | ||||
|     params->mode = s->parameters.mode; | ||||
|  | ||||
|     if (s->parameters.has_direct_io) { | ||||
|         params->has_direct_io = true; | ||||
|         params->direct_io = s->parameters.direct_io; | ||||
|     } | ||||
|  | ||||
|     return params; | ||||
| } | ||||
|  | ||||
| @@ -1047,6 +1108,7 @@ void migrate_params_init(MigrationParameters *params) | ||||
|     params->has_x_vcpu_dirty_limit_period = true; | ||||
|     params->has_vcpu_dirty_limit = true; | ||||
|     params->has_mode = true; | ||||
|     params->has_direct_io = qemu_has_direct_io(); | ||||
| } | ||||
|  | ||||
| /* | ||||
| @@ -1348,6 +1410,10 @@ static void migrate_params_test_apply(MigrateSetParameters *params, | ||||
|     if (params->has_mode) { | ||||
|         dest->mode = params->mode; | ||||
|     } | ||||
|  | ||||
|     if (params->has_direct_io) { | ||||
|         dest->direct_io = params->direct_io; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void migrate_params_apply(MigrateSetParameters *params, Error **errp) | ||||
| @@ -1492,6 +1558,10 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp) | ||||
|     if (params->has_mode) { | ||||
|         s->parameters.mode = params->mode; | ||||
|     } | ||||
|  | ||||
|     if (params->has_direct_io) { | ||||
|         s->parameters.direct_io = params->direct_io; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **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_fixed_ram(void); | ||||
| bool migrate_ignore_shared(void); | ||||
| bool migrate_late_block_activate(void); | ||||
| bool migrate_multifd(void); | ||||
| @@ -55,9 +56,11 @@ bool migrate_zero_copy_send(void); | ||||
|  */ | ||||
|  | ||||
| bool migrate_multifd_flush_after_each_section(void); | ||||
| bool migrate_multifd_packets(void); | ||||
| bool migrate_postcopy(void); | ||||
| bool migrate_rdma(void); | ||||
| bool migrate_tls(void); | ||||
| bool migrate_to_file(void); | ||||
|  | ||||
| /* capabilities helpers */ | ||||
|  | ||||
| @@ -78,6 +81,7 @@ uint8_t migrate_cpu_throttle_increment(void); | ||||
| uint8_t migrate_cpu_throttle_initial(void); | ||||
| bool migrate_cpu_throttle_tailslow(void); | ||||
| int migrate_decompress_threads(void); | ||||
| bool migrate_direct_io(void); | ||||
| uint64_t migrate_downtime_limit(void); | ||||
| uint8_t migrate_max_cpu_throttle(void); | ||||
| uint64_t migrate_max_bandwidth(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,81 @@ 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; | ||||
|  | ||||
|     if (f->last_error) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     qemu_fflush(f); | ||||
|     qio_channel_pwrite(f->ioc, (char *)buf, buflen, pos, &err); | ||||
|  | ||||
|     if (err) { | ||||
|         qemu_file_set_error_obj(f, -EIO, err); | ||||
|     } else { | ||||
|         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; | ||||
|     ssize_t ret; | ||||
|  | ||||
|     if (f->last_error) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     ret = qio_channel_pread(f->ioc, (char *)buf, buflen, pos, &err); | ||||
|     if (ret == -1 || err) { | ||||
|         goto error; | ||||
|     } | ||||
|  | ||||
|     return (size_t)ret; | ||||
|  | ||||
|  error: | ||||
|     qemu_file_set_error_obj(f, -EIO, err); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| void qemu_set_offset(QEMUFile *f, off_t off, int whence) | ||||
| { | ||||
|     Error *err = NULL; | ||||
|     off_t ret; | ||||
|  | ||||
|     qemu_fflush(f); | ||||
|  | ||||
|     if (!qemu_file_is_writable(f)) { | ||||
|         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) { | ||||
|   | ||||
| @@ -75,6 +75,10 @@ QEMUFile *qemu_file_get_return_path(QEMUFile *f); | ||||
| int qemu_fflush(QEMUFile *f); | ||||
| void qemu_file_set_blocking(QEMUFile *f, bool block); | ||||
| int qemu_file_get_to_fd(QEMUFile *f, int fd, size_t size); | ||||
| void qemu_set_offset(QEMUFile *f, off_t off, int whence); | ||||
| off_t qemu_get_offset(QEMUFile *f); | ||||
| void qemu_put_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen, off_t pos); | ||||
| size_t qemu_get_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen, off_t pos); | ||||
|  | ||||
| QIOChannel *qemu_file_get_ioc(QEMUFile *file); | ||||
|  | ||||
|   | ||||
							
								
								
									
										222
									
								
								migration/ram.c
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								migration/ram.c
									
									
									
									
									
								
							| @@ -1127,12 +1127,18 @@ static int save_zero_page(RAMState *rs, PageSearchStatus *pss, | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     stat64_add(&mig_stats.zero_pages, 1); | ||||
|  | ||||
|     if (migrate_fixed_ram()) { | ||||
|         /* zero pages are not transferred with fixed-ram */ | ||||
|         clear_bit_atomic(offset >> TARGET_PAGE_BITS, pss->block->shadow_bmap); | ||||
|         return 1; | ||||
|     } | ||||
|  | ||||
|     len += save_page_header(pss, file, pss->block, offset | RAM_SAVE_FLAG_ZERO); | ||||
|     qemu_put_byte(file, 0); | ||||
|     len += 1; | ||||
|     ram_release_page(pss->block->idstr, offset); | ||||
|  | ||||
|     stat64_add(&mig_stats.zero_pages, 1); | ||||
|     ram_transferred_add(len); | ||||
|  | ||||
|     /* | ||||
| @@ -1190,14 +1196,20 @@ static int save_normal_page(PageSearchStatus *pss, RAMBlock *block, | ||||
| { | ||||
|     QEMUFile *file = pss->pss_channel; | ||||
|  | ||||
|     ram_transferred_add(save_page_header(pss, pss->pss_channel, block, | ||||
|                                          offset | RAM_SAVE_FLAG_PAGE)); | ||||
|     if (async) { | ||||
|         qemu_put_buffer_async(file, buf, TARGET_PAGE_SIZE, | ||||
|                               migrate_release_ram() && | ||||
|                               migration_in_postcopy()); | ||||
|     if (migrate_fixed_ram()) { | ||||
|         qemu_put_buffer_at(file, buf, TARGET_PAGE_SIZE, | ||||
|                            block->pages_offset + offset); | ||||
|         set_bit(offset >> TARGET_PAGE_BITS, block->shadow_bmap); | ||||
|     } else { | ||||
|         qemu_put_buffer(file, buf, TARGET_PAGE_SIZE); | ||||
|         ram_transferred_add(save_page_header(pss, pss->pss_channel, block, | ||||
|                                              offset | RAM_SAVE_FLAG_PAGE)); | ||||
|         if (async) { | ||||
|             qemu_put_buffer_async(file, buf, TARGET_PAGE_SIZE, | ||||
|                                   migrate_release_ram() && | ||||
|                                   migration_in_postcopy()); | ||||
|         } else { | ||||
|             qemu_put_buffer(file, buf, TARGET_PAGE_SIZE); | ||||
|         } | ||||
|     } | ||||
|     ram_transferred_add(TARGET_PAGE_SIZE); | ||||
|     stat64_add(&mig_stats.normal_pages, 1); | ||||
| @@ -1250,10 +1262,9 @@ static int ram_save_page(RAMState *rs, PageSearchStatus *pss) | ||||
|     return pages; | ||||
| } | ||||
|  | ||||
| static int ram_save_multifd_page(QEMUFile *file, RAMBlock *block, | ||||
|                                  ram_addr_t offset) | ||||
| static int ram_save_multifd_page(RAMBlock *block, ram_addr_t offset) | ||||
| { | ||||
|     if (multifd_queue_page(file, block, offset) < 0) { | ||||
|     if (multifd_queue_page(block, offset) < 0) { | ||||
|         return -1; | ||||
|     } | ||||
|     stat64_add(&mig_stats.normal_pages, 1); | ||||
| @@ -1333,10 +1344,10 @@ static int find_dirty_block(RAMState *rs, PageSearchStatus *pss) | ||||
|         pss->page = 0; | ||||
|         pss->block = QLIST_NEXT_RCU(pss->block, next); | ||||
|         if (!pss->block) { | ||||
|             if (migrate_multifd() && | ||||
|             if (!migrate_fixed_ram() && migrate_multifd() && | ||||
|                 !migrate_multifd_flush_after_each_section()) { | ||||
|                 QEMUFile *f = rs->pss[RAM_CHANNEL_PRECOPY].pss_channel; | ||||
|                 int ret = multifd_send_sync_main(f); | ||||
|                 int ret = multifd_send_sync_main(); | ||||
|                 if (ret < 0) { | ||||
|                     return ret; | ||||
|                 } | ||||
| @@ -2067,7 +2078,7 @@ static int ram_save_target_page_legacy(RAMState *rs, PageSearchStatus *pss) | ||||
|      * still see partially copied pages which is data corruption. | ||||
|      */ | ||||
|     if (migrate_multifd() && !migration_in_postcopy()) { | ||||
|         return ram_save_multifd_page(pss->pss_channel, block, offset); | ||||
|         return ram_save_multifd_page(block, offset); | ||||
|     } | ||||
|  | ||||
|     return ram_save_page(rs, pss); | ||||
| @@ -2413,6 +2424,8 @@ static void ram_save_cleanup(void *opaque) | ||||
|         block->clear_bmap = NULL; | ||||
|         g_free(block->bmap); | ||||
|         block->bmap = NULL; | ||||
|         g_free(block->shadow_bmap); | ||||
|         block->shadow_bmap = NULL; | ||||
|     } | ||||
|  | ||||
|     xbzrle_cleanup(); | ||||
| @@ -2780,6 +2793,7 @@ static void ram_list_init_bitmaps(void) | ||||
|              */ | ||||
|             block->bmap = bitmap_new(pages); | ||||
|             bitmap_set(block->bmap, 0, pages); | ||||
|             block->shadow_bmap = bitmap_new(block->used_length >> TARGET_PAGE_BITS); | ||||
|             block->clear_bmap_shift = shift; | ||||
|             block->clear_bmap = bitmap_new(clear_bmap_size(pages, shift)); | ||||
|         } | ||||
| @@ -2917,6 +2931,73 @@ void qemu_guest_free_page_hint(void *addr, size_t len) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #define FIXED_RAM_HDR_VERSION 1 | ||||
| struct FixedRamHeader { | ||||
|     uint32_t version; | ||||
|     uint64_t page_size; | ||||
|     uint64_t bitmap_offset; | ||||
|     uint64_t pages_offset; | ||||
|     /* end of v1 */ | ||||
| } QEMU_PACKED; | ||||
|  | ||||
| static void fixed_ram_insert_header(QEMUFile *file, RAMBlock *block) | ||||
| { | ||||
|     g_autofree struct FixedRamHeader *header; | ||||
|     size_t header_size, bitmap_size; | ||||
|     long num_pages; | ||||
|  | ||||
|     header = g_new0(struct FixedRamHeader, 1); | ||||
|     header_size = sizeof(struct FixedRamHeader); | ||||
|  | ||||
|     num_pages = block->used_length >> TARGET_PAGE_BITS; | ||||
|     bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long); | ||||
|  | ||||
|     /* | ||||
|      * Save the file offsets of where the bitmap and the pages should | ||||
|      * go as they are written at the end of migration and during the | ||||
|      * iterative phase, respectively. | ||||
|      */ | ||||
|     block->bitmap_offset = qemu_get_offset(file) + header_size; | ||||
|     block->pages_offset = ROUND_UP(block->bitmap_offset + | ||||
|                                    bitmap_size, 0x100000); | ||||
|  | ||||
|     header->version = cpu_to_be32(FIXED_RAM_HDR_VERSION); | ||||
|     header->page_size = cpu_to_be64(TARGET_PAGE_SIZE); | ||||
|     header->bitmap_offset = cpu_to_be64(block->bitmap_offset); | ||||
|     header->pages_offset = cpu_to_be64(block->pages_offset); | ||||
|  | ||||
|     qemu_put_buffer(file, (uint8_t *) header, header_size); | ||||
| } | ||||
|  | ||||
| static bool fixed_ram_read_header(QEMUFile *file, struct FixedRamHeader *header, | ||||
|                                   Error **errp) | ||||
| { | ||||
|     size_t ret, header_size = sizeof(struct FixedRamHeader); | ||||
|  | ||||
|     ret = qemu_get_buffer(file, (uint8_t *)header, header_size); | ||||
|     if (ret != header_size) { | ||||
|         error_setg(errp, "Could not read whole fixed-ram migration header " | ||||
|                    "(expected %ld, got %ld)", header_size, ret); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /* migration stream is big-endian */ | ||||
|     be32_to_cpus(&header->version); | ||||
|  | ||||
|     if (header->version > FIXED_RAM_HDR_VERSION) { | ||||
|         error_setg(errp, "Migration fixed-ram capability version mismatch " | ||||
|                    "(expected %d, got %d)", FIXED_RAM_HDR_VERSION, | ||||
|                    header->version); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     be64_to_cpus(&header->page_size); | ||||
|     be64_to_cpus(&header->bitmap_offset); | ||||
|     be64_to_cpus(&header->pages_offset); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Each of ram_save_setup, ram_save_iterate and ram_save_complete has | ||||
|  * long-running RCU critical section.  When rcu-reclaims in the code | ||||
| @@ -2966,6 +3047,12 @@ static int ram_save_setup(QEMUFile *f, void *opaque) | ||||
|             if (migrate_ignore_shared()) { | ||||
|                 qemu_put_be64(f, block->mr->addr); | ||||
|             } | ||||
|  | ||||
|             if (migrate_fixed_ram()) { | ||||
|                 fixed_ram_insert_header(f, block); | ||||
|                 /* prepare offset for next ramblock */ | ||||
|                 qemu_set_offset(f, block->pages_offset + block->used_length, SEEK_SET); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -2985,7 +3072,7 @@ static int ram_save_setup(QEMUFile *f, void *opaque) | ||||
|     migration_ops->ram_save_target_page = ram_save_target_page_legacy; | ||||
|  | ||||
|     qemu_mutex_unlock_iothread(); | ||||
|     ret = multifd_send_sync_main(f); | ||||
|     ret = multifd_send_sync_main(); | ||||
|     qemu_mutex_lock_iothread(); | ||||
|     if (ret < 0) { | ||||
|         return ret; | ||||
| @@ -2999,6 +3086,25 @@ static int ram_save_setup(QEMUFile *f, void *opaque) | ||||
|     return qemu_fflush(f); | ||||
| } | ||||
|  | ||||
| static void ram_save_shadow_bmap(QEMUFile *f) | ||||
| { | ||||
|     RAMBlock *block; | ||||
|  | ||||
|     RAMBLOCK_FOREACH_MIGRATABLE(block) { | ||||
|         long num_pages = block->used_length >> TARGET_PAGE_BITS; | ||||
|         long bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long); | ||||
|         qemu_put_buffer_at(f, (uint8_t *)block->shadow_bmap, bitmap_size, | ||||
|                            block->bitmap_offset); | ||||
|         /* to catch any thread late sending pages */ | ||||
|         block->shadow_bmap = NULL; | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ramblock_set_shadow_bmap_atomic(RAMBlock *block, ram_addr_t offset) | ||||
| { | ||||
|     set_bit_atomic(offset >> TARGET_PAGE_BITS, block->shadow_bmap); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * ram_save_iterate: iterative stage for migration | ||||
|  * | ||||
| @@ -3109,7 +3215,7 @@ out: | ||||
|     if (ret >= 0 | ||||
|         && migration_is_setup_or_active(migrate_get_current()->state)) { | ||||
|         if (migrate_multifd() && migrate_multifd_flush_after_each_section()) { | ||||
|             ret = multifd_send_sync_main(rs->pss[RAM_CHANNEL_PRECOPY].pss_channel); | ||||
|             ret = multifd_send_sync_main(); | ||||
|             if (ret < 0) { | ||||
|                 return ret; | ||||
|             } | ||||
| @@ -3183,7 +3289,7 @@ static int ram_save_complete(QEMUFile *f, void *opaque) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     ret = multifd_send_sync_main(rs->pss[RAM_CHANNEL_PRECOPY].pss_channel); | ||||
|     ret = multifd_send_sync_main(); | ||||
|     if (ret < 0) { | ||||
|         return ret; | ||||
|     } | ||||
| @@ -3191,6 +3297,11 @@ static int ram_save_complete(QEMUFile *f, void *opaque) | ||||
|     if (migrate_multifd() && !migrate_multifd_flush_after_each_section()) { | ||||
|         qemu_put_be64(f, RAM_SAVE_FLAG_MULTIFD_FLUSH); | ||||
|     } | ||||
|  | ||||
|     if (migrate_fixed_ram()) { | ||||
|         ram_save_shadow_bmap(f); | ||||
|     } | ||||
|  | ||||
|     qemu_put_be64(f, RAM_SAVE_FLAG_EOS); | ||||
|     return qemu_fflush(f); | ||||
| } | ||||
| @@ -3789,6 +3900,71 @@ void colo_flush_ram_cache(void) | ||||
|     trace_colo_flush_ram_cache_end(); | ||||
| } | ||||
|  | ||||
| static void read_ramblock_fixed_ram(QEMUFile *f, RAMBlock *block, | ||||
|                                     long num_pages, unsigned long *bitmap) | ||||
| { | ||||
|     unsigned long set_bit_idx, clear_bit_idx; | ||||
|     unsigned long len; | ||||
|     ram_addr_t offset; | ||||
|     void *host; | ||||
|     size_t read, completed, read_len; | ||||
|  | ||||
|     for (set_bit_idx = find_first_bit(bitmap, num_pages); | ||||
|          set_bit_idx < num_pages; | ||||
|          set_bit_idx = find_next_bit(bitmap, num_pages, clear_bit_idx + 1)) { | ||||
|  | ||||
|         clear_bit_idx = find_next_zero_bit(bitmap, num_pages, set_bit_idx + 1); | ||||
|  | ||||
|         len = TARGET_PAGE_SIZE * (clear_bit_idx - set_bit_idx); | ||||
|         offset = set_bit_idx << TARGET_PAGE_BITS; | ||||
|  | ||||
|         for (read = 0, completed = 0; completed < len; offset += read) { | ||||
|             host = host_from_ram_block_offset(block, offset); | ||||
|             read_len = MIN(len, TARGET_PAGE_SIZE); | ||||
|  | ||||
|             if (migrate_multifd()) { | ||||
|                 multifd_recv_queue_page(f, block, offset); | ||||
|                 read = read_len; | ||||
|             } else { | ||||
|                 read = qemu_get_buffer_at(f, host, read_len, | ||||
|                                           block->pages_offset + offset); | ||||
|             } | ||||
|             completed += read; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| static int parse_ramblock_fixed_ram(QEMUFile *f, RAMBlock *block, | ||||
|                                     ram_addr_t length, Error **errp) | ||||
| { | ||||
|     g_autofree unsigned long *bitmap = NULL; | ||||
|     struct FixedRamHeader header; | ||||
|     size_t bitmap_size; | ||||
|     long num_pages; | ||||
|  | ||||
|     if (!fixed_ram_read_header(f, &header, errp)) { | ||||
|         return -EINVAL; | ||||
|     } | ||||
|  | ||||
|     block->pages_offset = header.pages_offset; | ||||
|     num_pages = length / header.page_size; | ||||
|     bitmap_size = BITS_TO_LONGS(num_pages) * sizeof(unsigned long); | ||||
|  | ||||
|     bitmap = g_malloc0(bitmap_size); | ||||
|     if (qemu_get_buffer_at(f, (uint8_t *)bitmap, bitmap_size, | ||||
|                            header.bitmap_offset) != bitmap_size) { | ||||
|         error_setg(errp, "Error parsing dirty bitmap"); | ||||
|         return -EINVAL; | ||||
|     } | ||||
|  | ||||
|     read_ramblock_fixed_ram(f, block, num_pages, bitmap); | ||||
|  | ||||
|     /* Skip pages array */ | ||||
|     qemu_set_offset(f, block->pages_offset + length, SEEK_SET); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int parse_ramblock(QEMUFile *f, RAMBlock *block, ram_addr_t length) | ||||
| { | ||||
|     int ret = 0; | ||||
| @@ -3797,6 +3973,16 @@ static int parse_ramblock(QEMUFile *f, RAMBlock *block, ram_addr_t length) | ||||
|  | ||||
|     assert(block); | ||||
|  | ||||
|     if (migrate_fixed_ram()) { | ||||
|         Error *local_err = NULL; | ||||
|  | ||||
|         ret = parse_ramblock_fixed_ram(f, block, length, &local_err); | ||||
|         if (local_err) { | ||||
|             error_report_err(local_err); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     if (!qemu_ram_is_migratable(block)) { | ||||
|         error_report("block %s should not be migrated !", block->idstr); | ||||
|         return -EINVAL; | ||||
|   | ||||
| @@ -75,6 +75,7 @@ bool ram_dirty_bitmap_reload(MigrationState *s, RAMBlock *rb, Error **errp); | ||||
| bool ramblock_page_is_discarded(RAMBlock *rb, ram_addr_t start); | ||||
| void postcopy_preempt_shutdown_file(MigrationState *s); | ||||
| void *postcopy_preempt_thread(void *opaque); | ||||
| void ramblock_set_shadow_bmap_atomic(RAMBlock *block, ram_addr_t offset); | ||||
|  | ||||
| /* ram cache */ | ||||
| int colo_init_ram_cache(void); | ||||
|   | ||||
| @@ -245,6 +245,7 @@ static bool should_validate_capability(int capability) | ||||
|     /* Validate only new capabilities to keep compatibility. */ | ||||
|     switch (capability) { | ||||
|     case MIGRATION_CAPABILITY_X_IGNORE_SHARED: | ||||
|     case MIGRATION_CAPABILITY_FIXED_RAM: | ||||
|         return true; | ||||
|     default: | ||||
|         return false; | ||||
|   | ||||
| @@ -173,9 +173,9 @@ static void monitor_fdset_cleanup(MonFdset *mon_fdset) | ||||
|     MonFdsetFd *mon_fdset_fd_next; | ||||
|  | ||||
|     QLIST_FOREACH_SAFE(mon_fdset_fd, &mon_fdset->fds, next, mon_fdset_fd_next) { | ||||
|         if ((mon_fdset_fd->removed || | ||||
|                 (QLIST_EMPTY(&mon_fdset->dup_fds) && mon_refcount == 0)) && | ||||
|                 runstate_is_running()) { | ||||
|         if (mon_fdset_fd->removed || | ||||
|             (QLIST_EMPTY(&mon_fdset->dup_fds) && mon_refcount == 0 && | ||||
|              runstate_is_running())) { | ||||
|             close(mon_fdset_fd->fd); | ||||
|             g_free(mon_fdset_fd->opaque); | ||||
|             QLIST_REMOVE(mon_fdset_fd, next); | ||||
| @@ -406,6 +406,23 @@ AddfdInfo *monitor_fdset_add_fd(int fd, bool has_fdset_id, int64_t fdset_id, | ||||
|     return fdinfo; | ||||
| } | ||||
|  | ||||
| static bool monitor_fdset_flags_match(int flags, int fd_flags) | ||||
| { | ||||
|     bool match = false; | ||||
|  | ||||
|     if ((flags & O_ACCMODE) == (fd_flags & O_ACCMODE)) { | ||||
|         match = true; | ||||
|  | ||||
| #ifdef O_DIRECT | ||||
|         if ((flags & O_DIRECT) != (fd_flags & O_DIRECT)) { | ||||
|             match = false; | ||||
|         } | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     return match; | ||||
| } | ||||
|  | ||||
| int monitor_fdset_dup_fd_add(int64_t fdset_id, int flags) | ||||
| { | ||||
| #ifdef _WIN32 | ||||
| @@ -431,7 +448,7 @@ int monitor_fdset_dup_fd_add(int64_t fdset_id, int flags) | ||||
|                 return -1; | ||||
|             } | ||||
|  | ||||
|             if ((flags & O_ACCMODE) == (mon_fd_flags & O_ACCMODE)) { | ||||
|             if (monitor_fdset_flags_match(flags, mon_fd_flags)) { | ||||
|                 fd = mon_fdset_fd->fd; | ||||
|                 break; | ||||
|             } | ||||
|   | ||||
| @@ -531,6 +531,10 @@ | ||||
| #     and can result in more stable read performance.  Requires KVM | ||||
| #     with accelerator property "dirty-ring-size" set.  (Since 8.1) | ||||
| # | ||||
| # @fixed-ram: Migrate using fixed offsets for each RAM page.  Requires | ||||
| #     a migration URI that supports seeking, such as a file.  (since | ||||
| #     8.2) | ||||
| # | ||||
| # Features: | ||||
| # | ||||
| # @deprecated: Member @block is deprecated.  Use blockdev-mirror with | ||||
| @@ -555,7 +559,7 @@ | ||||
|            { 'name': 'x-ignore-shared', 'features': [ 'unstable' ] }, | ||||
|            'validate-uuid', 'background-snapshot', | ||||
|            'zero-copy-send', 'postcopy-preempt', 'switchover-ack', | ||||
|            'dirty-limit'] } | ||||
|            'dirty-limit', 'fixed-ram'] } | ||||
|  | ||||
| ## | ||||
| # @MigrationCapabilityStatus: | ||||
| @@ -874,6 +878,9 @@ | ||||
| # @mode: Migration mode. See description in @MigMode. Default is 'normal'. | ||||
| #        (Since 8.2) | ||||
| # | ||||
| # @direct-io: Open migration files with O_DIRECT when possible. Not | ||||
| #             all migration transports support this. (since 8.1) | ||||
| # | ||||
| # Features: | ||||
| # | ||||
| # @deprecated: Member @block-incremental is deprecated.  Use | ||||
| @@ -907,7 +914,8 @@ | ||||
|            'block-bitmap-mapping', | ||||
|            { 'name': 'x-vcpu-dirty-limit-period', 'features': ['unstable'] }, | ||||
|            'vcpu-dirty-limit', | ||||
|            'mode'] } | ||||
|            'mode', | ||||
|            'direct-io'] } | ||||
|  | ||||
| ## | ||||
| # @MigrateSetParameters: | ||||
| @@ -1062,6 +1070,9 @@ | ||||
| # @mode: Migration mode. See description in @MigMode. Default is 'normal'. | ||||
| #        (Since 8.2) | ||||
| # | ||||
| # @direct-io: Open migration files with O_DIRECT when possible. Not | ||||
| #             all migration transports support this. (since 8.1) | ||||
| # | ||||
| # Features: | ||||
| # | ||||
| # @deprecated: Member @block-incremental is deprecated.  Use | ||||
| @@ -1115,7 +1126,8 @@ | ||||
|             '*x-vcpu-dirty-limit-period': { 'type': 'uint64', | ||||
|                                             'features': [ 'unstable' ] }, | ||||
|             '*vcpu-dirty-limit': 'uint64', | ||||
|             '*mode': 'MigMode'} } | ||||
|             '*mode': 'MigMode', | ||||
|             '*direct-io': 'bool' } } | ||||
|  | ||||
| ## | ||||
| # @migrate-set-parameters: | ||||
| @@ -1290,6 +1302,9 @@ | ||||
| # @mode: Migration mode. See description in @MigMode. Default is 'normal'. | ||||
| #        (Since 8.2) | ||||
| # | ||||
| # @direct-io: Open migration files with O_DIRECT when possible. Not | ||||
| #             all migration transports support this. (since 8.1) | ||||
| # | ||||
| # Features: | ||||
| # | ||||
| # @deprecated: Member @block-incremental is deprecated.  Use | ||||
| @@ -1340,7 +1355,8 @@ | ||||
|             '*x-vcpu-dirty-limit-period': { 'type': 'uint64', | ||||
|                                             'features': [ 'unstable' ] }, | ||||
|             '*vcpu-dirty-limit': 'uint64', | ||||
|             '*mode': 'MigMode'} } | ||||
|             '*mode': 'MigMode', | ||||
|             '*direct-io': 'bool' } } | ||||
|  | ||||
| ## | ||||
| # @query-migrate-parameters: | ||||
|   | ||||
| @@ -118,6 +118,12 @@ void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...) | ||||
|  | ||||
|     rsp = qtest_qmp(to, "{ 'execute': 'migrate-incoming', 'arguments': %p}", | ||||
|                     args); | ||||
|  | ||||
|     if (!qdict_haskey(rsp, "return")) { | ||||
|         g_autoptr(GString) s = qobject_to_json_pretty(QOBJECT(rsp), true); | ||||
|         g_test_message("%s", s->str); | ||||
|     } | ||||
|  | ||||
|     g_assert(qdict_haskey(rsp, "return")); | ||||
|     qobject_unref(rsp); | ||||
|  | ||||
|   | ||||
| @@ -2135,6 +2135,14 @@ static void *test_mode_reboot_start(QTestState *from, QTestState *to) | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| static void *migrate_fixed_ram_start(QTestState *from, QTestState *to) | ||||
| { | ||||
|     migrate_set_capability(from, "fixed-ram", true); | ||||
|     migrate_set_capability(to, "fixed-ram", true); | ||||
|  | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| static void test_mode_reboot(void) | ||||
| { | ||||
|     g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs, | ||||
| @@ -2149,6 +2157,171 @@ static void test_mode_reboot(void) | ||||
|     test_file_common(&args, true); | ||||
| } | ||||
|  | ||||
| static void test_precopy_file_fixed_ram_live(void) | ||||
| { | ||||
|     g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs, | ||||
|                                            FILE_TEST_FILENAME); | ||||
|     MigrateCommon args = { | ||||
|         .connect_uri = uri, | ||||
|         .listen_uri = "defer", | ||||
|         .start_hook = migrate_fixed_ram_start, | ||||
|     }; | ||||
|  | ||||
|     test_file_common(&args, false); | ||||
| } | ||||
|  | ||||
| static void test_precopy_file_fixed_ram(void) | ||||
| { | ||||
|     g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs, | ||||
|                                            FILE_TEST_FILENAME); | ||||
|     MigrateCommon args = { | ||||
|         .connect_uri = uri, | ||||
|         .listen_uri = "defer", | ||||
|         .start_hook = migrate_fixed_ram_start, | ||||
|     }; | ||||
|  | ||||
|     test_file_common(&args, true); | ||||
| } | ||||
|  | ||||
| static void *migrate_multifd_fixed_ram_start(QTestState *from, QTestState *to) | ||||
| { | ||||
|     migrate_fixed_ram_start(from, to); | ||||
|  | ||||
|     migrate_set_parameter_int(from, "multifd-channels", 4); | ||||
|     migrate_set_parameter_int(to, "multifd-channels", 4); | ||||
|  | ||||
|     migrate_set_capability(from, "multifd", true); | ||||
|     migrate_set_capability(to, "multifd", true); | ||||
|  | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| static void test_multifd_file_fixed_ram_live(void) | ||||
| { | ||||
|     g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs, | ||||
|                                            FILE_TEST_FILENAME); | ||||
|     MigrateCommon args = { | ||||
|         .connect_uri = uri, | ||||
|         .listen_uri = "defer", | ||||
|         .start_hook = migrate_multifd_fixed_ram_start, | ||||
|     }; | ||||
|  | ||||
|     test_file_common(&args, false); | ||||
| } | ||||
|  | ||||
| static void test_multifd_file_fixed_ram(void) | ||||
| { | ||||
|     g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs, | ||||
|                                            FILE_TEST_FILENAME); | ||||
|     MigrateCommon args = { | ||||
|         .connect_uri = uri, | ||||
|         .listen_uri = "defer", | ||||
|         .start_hook = migrate_multifd_fixed_ram_start, | ||||
|     }; | ||||
|  | ||||
|     test_file_common(&args, true); | ||||
| } | ||||
|  | ||||
| #ifdef O_DIRECT | ||||
| static void *migrate_multifd_fixed_ram_dio_start(QTestState *from, QTestState *to) | ||||
| { | ||||
|     migrate_multifd_fixed_ram_start(from, to); | ||||
|  | ||||
|     migrate_set_parameter_bool(from, "direct-io", true); | ||||
|     migrate_set_parameter_bool(to, "direct-io", true); | ||||
|  | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| static void test_multifd_file_fixed_ram_dio(void) | ||||
| { | ||||
|     g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs, | ||||
|                                            FILE_TEST_FILENAME); | ||||
|     MigrateCommon args = { | ||||
|         .connect_uri = uri, | ||||
|         .listen_uri = "defer", | ||||
|         .start_hook = migrate_multifd_fixed_ram_dio_start, | ||||
|     }; | ||||
|  | ||||
|     test_file_common(&args, true); | ||||
| } | ||||
|  | ||||
| static void migrate_multifd_fixed_ram_fdset_dio_end(QTestState *from, | ||||
|                                                     QTestState *to, | ||||
|                                                     void *opaque) | ||||
| { | ||||
|     QDict *resp; | ||||
|     QList *fdsets; | ||||
|  | ||||
|     /* | ||||
|      * Check that we removed the fdsets after migration, otherwise a | ||||
|      * second migration would fail due to too many fdsets. | ||||
|      */ | ||||
|  | ||||
|     resp = qtest_qmp(from, "{'execute': 'query-fdsets', " | ||||
|                      "'arguments': {}}"); | ||||
|     g_assert(qdict_haskey(resp, "return")); | ||||
|     fdsets = qdict_get_qlist(resp, "return"); | ||||
|     g_assert(fdsets && qlist_empty(fdsets)); | ||||
| } | ||||
| #endif /* O_DIRECT */ | ||||
|  | ||||
| static void *migrate_multifd_fixed_ram_fdset(QTestState *from, QTestState *to) | ||||
| { | ||||
|     g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME); | ||||
|     int fds[3]; | ||||
|     int src_flags = O_CREAT|O_WRONLY; | ||||
|     int dst_flags = O_CREAT|O_RDONLY; | ||||
|  | ||||
|     /* main outgoing channel: no O_DIRECT */ | ||||
|     fds[0] = open(file, src_flags, 0660); | ||||
|     assert(fds[0] != -1); | ||||
|  | ||||
| #ifdef O_DIRECT | ||||
|     src_flags |= O_DIRECT; | ||||
| #endif | ||||
|  | ||||
|     /* secondary outgoing channels */ | ||||
|     fds[1] = open(file, src_flags, 0660); | ||||
|     assert(fds[1] != -1); | ||||
|  | ||||
|     qtest_qmp_fds_assert_success(from, &fds[0], 1, "{'execute': 'add-fd', " | ||||
|                                  "'arguments': {'fdset-id': 1}}"); | ||||
|  | ||||
|     qtest_qmp_fds_assert_success(from, &fds[1], 1, "{'execute': 'add-fd', " | ||||
|                                  "'arguments': {'fdset-id': 1}}"); | ||||
|  | ||||
|     /* incoming channel */ | ||||
|     fds[2] = open(file, dst_flags, 0660); | ||||
|     assert(fds[2] != -1); | ||||
|  | ||||
|     qtest_qmp_fds_assert_success(to, &fds[2], 1, "{'execute': 'add-fd', " | ||||
|                                  "'arguments': {'fdset-id': 1}}"); | ||||
|  | ||||
| #ifdef O_DIRECT | ||||
|         migrate_multifd_fixed_ram_dio_start(from, to); | ||||
| #else | ||||
|         migrate_multifd_fixed_ram_start(from, to); | ||||
| #endif | ||||
|  | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| static void test_multifd_file_fixed_ram_fdset(void) | ||||
| { | ||||
|     g_autofree char *uri = g_strdup_printf("file:/dev/fdset/1,offset=0x100"); | ||||
|     MigrateCommon args = { | ||||
|         .connect_uri = uri, | ||||
|         .listen_uri = "defer", | ||||
|         .start_hook = migrate_multifd_fixed_ram_fdset, | ||||
| #ifdef O_DIRECT | ||||
|         .finish_hook = migrate_multifd_fixed_ram_fdset_dio_end, | ||||
| #endif | ||||
|     }; | ||||
|  | ||||
|     test_file_common(&args, true); | ||||
| } | ||||
|  | ||||
| static void test_precopy_tcp_plain(void) | ||||
| { | ||||
|     MigrateCommon args = { | ||||
| @@ -3232,6 +3405,23 @@ int main(int argc, char **argv) | ||||
|         qtest_add_func("/migration/mode/reboot", test_mode_reboot); | ||||
|     } | ||||
|  | ||||
|     qtest_add_func("/migration/precopy/file/fixed-ram", | ||||
|                    test_precopy_file_fixed_ram); | ||||
|     qtest_add_func("/migration/precopy/file/fixed-ram/live", | ||||
|                    test_precopy_file_fixed_ram_live); | ||||
|  | ||||
|     qtest_add_func("/migration/multifd/file/fixed-ram", | ||||
|                    test_multifd_file_fixed_ram); | ||||
|     qtest_add_func("/migration/multifd/file/fixed-ram/live", | ||||
|                    test_multifd_file_fixed_ram_live); | ||||
| #ifdef O_DIRECT | ||||
|     qtest_add_func("/migration/multifd/file/fixed-ram/dio", | ||||
|                    test_multifd_file_fixed_ram_dio); | ||||
| #endif | ||||
|  | ||||
|     qtest_add_func("/migration/multifd/file/fixed-ram/fdset", | ||||
|                    test_multifd_file_fixed_ram_fdset); | ||||
|  | ||||
| #ifdef CONFIG_GNUTLS | ||||
|     qtest_add_func("/migration/precopy/unix/tls/psk", | ||||
|                    test_precopy_unix_tls_psk); | ||||
| @@ -3304,14 +3494,8 @@ int main(int argc, char **argv) | ||||
|     } | ||||
|     qtest_add_func("/migration/multifd/tcp/plain/none", | ||||
|                    test_multifd_tcp_none); | ||||
|     /* | ||||
|      * This test is flaky and sometimes fails in CI and otherwise: | ||||
|      * don't run unless user opts in via environment variable. | ||||
|      */ | ||||
|     if (getenv("QEMU_TEST_FLAKY_TESTS")) { | ||||
|         qtest_add_func("/migration/multifd/tcp/plain/cancel", | ||||
|                        test_multifd_tcp_cancel); | ||||
|     } | ||||
|     qtest_add_func("/migration/multifd/tcp/plain/cancel", | ||||
|                    test_multifd_tcp_cancel); | ||||
|     qtest_add_func("/migration/multifd/tcp/plain/zlib", | ||||
|                    test_multifd_tcp_zlib); | ||||
| #ifdef CONFIG_ZSTD | ||||
|   | ||||
| @@ -277,6 +277,15 @@ int qemu_lock_fd_test(int fd, int64_t start, int64_t len, bool exclusive) | ||||
| } | ||||
| #endif | ||||
|  | ||||
| bool qemu_has_direct_io(void) | ||||
| { | ||||
| #ifdef O_DIRECT | ||||
|     return true; | ||||
| #else | ||||
|     return false; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| static int qemu_open_cloexec(const char *name, int flags, mode_t mode) | ||||
| { | ||||
|     int ret; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user