:p
atchew
Login
Reduce some number of parts passed to writev. Avoid possible allocation sending data with writev. Reduce number of allocations sending memory state. Edwin Török (1): tools/libs/guest: allocate various migration arrays just once Frediano Ziglio (3): xenguest: Reduce number of parts in write_split_record xenguest: Reduce number of I/O vectors in write_batch xenguest: Allows writev_exact to change iov array tools/libs/ctrl/xc_private.c | 26 ++------- tools/libs/ctrl/xc_private.h | 2 +- tools/libs/guest/xg_sr_common.c | 6 +- tools/libs/guest/xg_sr_common.h | 13 +++++ tools/libs/guest/xg_sr_save.c | 99 ++++++++++++++------------------- 5 files changed, 63 insertions(+), 83 deletions(-) -- 2.43.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Small optimization. There's no much sense to split the header in 2 pieces, it will just take more time and space to reassemble them in the final buffer. This also avoids truncating combined_length to 32 bit in case of 64 bit machines potentially avoiding following record_length check. The function become more coherent with following read_record function. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> --- tools/libs/guest/xg_sr_common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/libs/guest/xg_sr_common.c b/tools/libs/guest/xg_sr_common.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_common.c +++ b/tools/libs/guest/xg_sr_common.c @@ -XXX,XX +XXX,XX @@ int write_split_record(struct xc_sr_context *ctx, struct xc_sr_record *rec, static const char zeroes[(1u << REC_ALIGN_ORDER) - 1] = { 0 }; xc_interface *xch = ctx->xch; - typeof(rec->length) combined_length = rec->length + sz; + size_t combined_length = rec->length + sz; size_t record_length = ROUNDUP(combined_length, REC_ALIGN_ORDER); + struct xc_sr_rhdr rhdr = { rec->type, combined_length }; struct iovec parts[] = { - { &rec->type, sizeof(rec->type) }, - { &combined_length, sizeof(combined_length) }, + { &rhdr, sizeof(rhdr) }, { rec->data, rec->length }, { buf, sz }, { (void *)zeroes, record_length - combined_length }, -- 2.43.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Small optimization. Reduce number of pieces passed to writev. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> --- tools/libs/guest/xg_sr_save.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_save.c +++ b/tools/libs/guest/xg_sr_save.c @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) void *page, *orig_page; uint64_t *rec_pfns = NULL; struct iovec *iov = NULL; int iovcnt = 0; - struct xc_sr_rec_page_data_header hdr = { 0 }; - struct xc_sr_record rec = { - .type = REC_TYPE_PAGE_DATA, + struct { + struct xc_sr_rhdr rec; + struct xc_sr_rec_page_data_header page_data; + } hdrs = { + { .type = REC_TYPE_PAGE_DATA }, + { 0 }, }; assert(nr_pfns != 0); @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) /* Pointers to locally allocated pages. Need freeing. */ local_pages = calloc(nr_pfns, sizeof(*local_pages)); /* iovec[] for writev(). */ - iov = malloc((nr_pfns + 4) * sizeof(*iov)); + iov = malloc((nr_pfns + 2) * sizeof(*iov)); if ( !mfns || !types || !errors || !guest_data || !local_pages || !iov ) { @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) goto err; } - hdr.count = nr_pfns; + hdrs.rec.length = sizeof(hdrs.page_data); + hdrs.rec.length += nr_pfns * sizeof(*rec_pfns); + hdrs.rec.length += nr_pages * PAGE_SIZE; - rec.length = sizeof(hdr); - rec.length += nr_pfns * sizeof(*rec_pfns); - rec.length += nr_pages * PAGE_SIZE; + hdrs.page_data.count = nr_pfns; for ( i = 0; i < nr_pfns; ++i ) rec_pfns[i] = ((uint64_t)(types[i]) << 32) | ctx->save.batch_pfns[i]; - iov[0].iov_base = &rec.type; - iov[0].iov_len = sizeof(rec.type); + iov[0].iov_base = &hdrs; + iov[0].iov_len = sizeof(hdrs); - iov[1].iov_base = &rec.length; - iov[1].iov_len = sizeof(rec.length); + iov[1].iov_base = rec_pfns; + iov[1].iov_len = nr_pfns * sizeof(*rec_pfns); - iov[2].iov_base = &hdr; - iov[2].iov_len = sizeof(hdr); - - iov[3].iov_base = rec_pfns; - iov[3].iov_len = nr_pfns * sizeof(*rec_pfns); - - iovcnt = 4; + iovcnt = 2; if ( nr_pages ) { -- 2.43.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Avoid having to allocate and copy the array if a partial write happens. The implementation in tools/libs/store/xs.c already use this signature and method. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> --- tools/libs/ctrl/xc_private.c | 26 +++++--------------------- tools/libs/ctrl/xc_private.h | 2 +- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/tools/libs/ctrl/xc_private.c b/tools/libs/ctrl/xc_private.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/ctrl/xc_private.c +++ b/tools/libs/ctrl/xc_private.c @@ -XXX,XX +XXX,XX @@ int write_exact(int fd, const void *data, size_t size) /* * MiniOS's libc doesn't know about writev(). Implement it as multiple write()s. */ -int writev_exact(int fd, const struct iovec *iov, int iovcnt) +int writev_exact(int fd, struct iovec *iov, int iovcnt) { int rc, i; @@ -XXX,XX +XXX,XX @@ int writev_exact(int fd, const struct iovec *iov, int iovcnt) return 0; } #else -int writev_exact(int fd, const struct iovec *iov, int iovcnt) +int writev_exact(int fd, struct iovec *iov, int iovcnt) { - struct iovec *local_iov = NULL; int rc = 0, iov_idx = 0, saved_errno = 0; ssize_t len; @@ -XXX,XX +XXX,XX @@ int writev_exact(int fd, const struct iovec *iov, int iovcnt) len -= iov[iov_idx++].iov_len; else { - /* Partial write of iov[iov_idx]. Copy iov so we can adjust - * element iov_idx and resubmit the rest. */ - if ( !local_iov ) - { - local_iov = malloc(iovcnt * sizeof(*iov)); - if ( !local_iov ) - { - saved_errno = ENOMEM; - rc = -1; - goto out; - } - - iov = memcpy(local_iov, iov, iovcnt * sizeof(*iov)); - } - - local_iov[iov_idx].iov_base += len; - local_iov[iov_idx].iov_len -= len; + /* Partial write of iov[iov_idx]. */ + iov[iov_idx].iov_base += len; + iov[iov_idx].iov_len -= len; break; } } @@ -XXX,XX +XXX,XX @@ int writev_exact(int fd, const struct iovec *iov, int iovcnt) saved_errno = 0; out: - free(local_iov); errno = saved_errno; return rc; } diff --git a/tools/libs/ctrl/xc_private.h b/tools/libs/ctrl/xc_private.h index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/ctrl/xc_private.h +++ b/tools/libs/ctrl/xc_private.h @@ -XXX,XX +XXX,XX @@ int xc_flush_mmu_updates(xc_interface *xch, struct xc_mmu *mmu); /* Return 0 on success; -1 on error setting errno. */ int read_exact(int fd, void *data, size_t size); /* EOF => -1, errno=0 */ int write_exact(int fd, const void *data, size_t size); -int writev_exact(int fd, const struct iovec *iov, int iovcnt); +int writev_exact(int fd, struct iovec *iov, int iovcnt); int xc_ffs8(uint8_t x); int xc_ffs16(uint16_t x); -- 2.43.0
From: Edwin Török <edwin.torok@citrix.com> Allocate these array just once at the start of migration, using the maximum batch size, and free them at the end. Signed-off-by: Edwin Török <edwin.torok@citrix.com> Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> --- tools/libs/guest/xg_sr_common.h | 13 +++++++ tools/libs/guest/xg_sr_save.c | 66 +++++++++++++-------------------- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_common.h +++ b/tools/libs/guest/xg_sr_common.h @@ -XXX,XX +XXX,XX @@ static inline int update_blob(struct xc_sr_blob *blob, return 0; } +struct xc_sr_context_save_buffers +{ + xen_pfn_t batch_pfns[MAX_BATCH_SIZE]; + xen_pfn_t mfns[MAX_BATCH_SIZE]; + xen_pfn_t types[MAX_BATCH_SIZE]; + int errors[MAX_BATCH_SIZE]; + void *guest_data[MAX_BATCH_SIZE]; + void *local_pages[MAX_BATCH_SIZE]; + struct iovec iov[MAX_BATCH_SIZE + 2]; /* headers + data */ + uint64_t rec_pfns[MAX_BATCH_SIZE]; +}; + struct xc_sr_context { xc_interface *xch; @@ -XXX,XX +XXX,XX @@ struct xc_sr_context unsigned long *deferred_pages; unsigned long nr_deferred_pages; xc_hypercall_buffer_t dirty_bitmap_hbuf; + struct xc_sr_context_save_buffers *buffers; } save; struct /* Restore data. */ diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_save.c +++ b/tools/libs/guest/xg_sr_save.c @@ -XXX,XX +XXX,XX @@ static int write_checkpoint_record(struct xc_sr_context *ctx) static int write_batch(struct xc_sr_context *ctx) { xc_interface *xch = ctx->xch; - xen_pfn_t *mfns = NULL, *types = NULL; + xen_pfn_t *mfns, *types; void *guest_mapping = NULL; - void **guest_data = NULL; - void **local_pages = NULL; - int *errors = NULL, rc = -1; + void **guest_data; + void **local_pages; + int *errors, rc = -1; unsigned int i, p, nr_pages = 0, nr_pages_mapped = 0; unsigned int nr_pfns = ctx->save.nr_batch_pfns; void *page, *orig_page; - uint64_t *rec_pfns = NULL; - struct iovec *iov = NULL; int iovcnt = 0; + uint64_t *rec_pfns; + struct iovec *iov; int iovcnt = 0; struct { struct xc_sr_rhdr rec; struct xc_sr_rec_page_data_header page_data; @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) }; assert(nr_pfns != 0); + assert(nr_pfns <= MAX_BATCH_SIZE); + assert(ctx->save.buffers); /* Mfns of the batch pfns. */ - mfns = malloc(nr_pfns * sizeof(*mfns)); + mfns = ctx->save.buffers->mfns; /* Types of the batch pfns. */ - types = malloc(nr_pfns * sizeof(*types)); + types = ctx->save.buffers->types; /* Errors from attempting to map the gfns. */ - errors = malloc(nr_pfns * sizeof(*errors)); + errors = ctx->save.buffers->errors; /* Pointers to page data to send. Mapped gfns or local allocations. */ - guest_data = calloc(nr_pfns, sizeof(*guest_data)); + guest_data = ctx->save.buffers->guest_data; + memset(guest_data, 0, sizeof(*guest_data) * nr_pfns); /* Pointers to locally allocated pages. Need freeing. */ - local_pages = calloc(nr_pfns, sizeof(*local_pages)); + local_pages = ctx->save.buffers->local_pages; + memset(local_pages, 0, sizeof(*local_pages) * nr_pfns); /* iovec[] for writev(). */ - iov = malloc((nr_pfns + 2) * sizeof(*iov)); - - if ( !mfns || !types || !errors || !guest_data || !local_pages || !iov ) - { - ERROR("Unable to allocate arrays for a batch of %u pages", - nr_pfns); - goto err; - } + iov = ctx->save.buffers->iov; + rec_pfns = ctx->save.buffers->rec_pfns; for ( i = 0; i < nr_pfns; ++i ) { @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) } } - rec_pfns = malloc(nr_pfns * sizeof(*rec_pfns)); - if ( !rec_pfns ) - { - ERROR("Unable to allocate %zu bytes of memory for page data pfn list", - nr_pfns * sizeof(*rec_pfns)); - goto err; - } - hdrs.rec.length = sizeof(hdrs.page_data); hdrs.rec.length += nr_pfns * sizeof(*rec_pfns); hdrs.rec.length += nr_pages * PAGE_SIZE; @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) rc = ctx->save.nr_batch_pfns = 0; err: - free(rec_pfns); if ( guest_mapping ) xenforeignmemory_unmap(xch->fmem, guest_mapping, nr_pages_mapped); for ( i = 0; local_pages && i < nr_pfns; ++i ) + { free(local_pages[i]); - free(iov); - free(local_pages); - free(guest_data); - free(errors); - free(types); - free(mfns); + local_pages[i] = NULL; + } return rc; } @@ -XXX,XX +XXX,XX @@ static int setup(struct xc_sr_context *ctx) dirty_bitmap = xc_hypercall_buffer_alloc_pages( xch, dirty_bitmap, NRPAGES(bitmap_size(ctx->save.p2m_size))); - ctx->save.batch_pfns = malloc(MAX_BATCH_SIZE * - sizeof(*ctx->save.batch_pfns)); ctx->save.deferred_pages = bitmap_alloc(ctx->save.p2m_size); + ctx->save.buffers = calloc(1, sizeof(*ctx->save.buffers)); - if ( !ctx->save.batch_pfns || !dirty_bitmap || !ctx->save.deferred_pages ) + if ( !dirty_bitmap || !ctx->save.deferred_pages || !ctx->save.buffers) { - ERROR("Unable to allocate memory for dirty bitmaps, batch pfns and" - " deferred pages"); + ERROR("Unable to allocate memory for dirty bitmaps, deferred pages" + " and various batch buffers"); rc = -1; errno = ENOMEM; goto err; } + ctx->save.batch_pfns = ctx->save.buffers->batch_pfns; rc = 0; @@ -XXX,XX +XXX,XX @@ static void cleanup(struct xc_sr_context *ctx) xc_hypercall_buffer_free_pages(xch, dirty_bitmap, NRPAGES(bitmap_size(ctx->save.p2m_size))); free(ctx->save.deferred_pages); - free(ctx->save.batch_pfns); + free(ctx->save.buffers); } /* -- 2.43.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Reduce some number of parts passed to writev. Avoid possible allocation sending data with writev. Reduce number of allocations sending memory state. Changes since v1: - add commit to cache up to 4 pages in hypercall; - add other 2 commits reducing chunks passed to write/writev. Changes since v2: - update patches commit prefixes; - add other 2 optisations. Edwin Török (2): libs/guest: allocate various migration arrays just once libs/call: cache up to 4 pages in hypercall bounce buffers Frediano Ziglio (7): libs/guest: Reduce number of parts in write_split_record libs/guest: Reduce number of I/O vectors in write_batch libs/guest: Reduce number of I/O vectors in write_batch libs/guest: Use a single write_exact in write_headers libs/guest: avoids using 2 indexes libs/guest: fill directly iov structure libs/ctrl: Allows writev_exact to change iov array tools/libs/call/buffer.c | 28 +++-- tools/libs/call/core.c | 3 +- tools/libs/call/private.h | 8 +- tools/libs/ctrl/xc_private.c | 26 +---- tools/libs/ctrl/xc_private.h | 2 +- tools/libs/guest/xg_sr_common.c | 6 +- tools/libs/guest/xg_sr_common.h | 12 +++ tools/libs/guest/xg_sr_restore.c | 26 ++--- tools/libs/guest/xg_sr_save.c | 169 +++++++++++++------------------ 9 files changed, 128 insertions(+), 152 deletions(-) -- 2.54.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Small optimization. There's no much sense to split the header in 2 pieces, it will just take more time and space to reassemble them in the final buffer. This also avoids truncating combined_length to 32 bit in case of 64 bit machines potentially avoiding following record_length check. The function become more coherent with following read_record function. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> -- Changes since v2: - change prefix in subject. --- tools/libs/guest/xg_sr_common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/libs/guest/xg_sr_common.c b/tools/libs/guest/xg_sr_common.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_common.c +++ b/tools/libs/guest/xg_sr_common.c @@ -XXX,XX +XXX,XX @@ int write_split_record(struct xc_sr_context *ctx, struct xc_sr_record *rec, static const char zeroes[(1u << REC_ALIGN_ORDER) - 1] = { 0 }; xc_interface *xch = ctx->xch; - typeof(rec->length) combined_length = rec->length + sz; + size_t combined_length = rec->length + sz; size_t record_length = ROUNDUP(combined_length, REC_ALIGN_ORDER); + struct xc_sr_rhdr rhdr = { rec->type, combined_length }; struct iovec parts[] = { - { &rec->type, sizeof(rec->type) }, - { &combined_length, sizeof(combined_length) }, + { &rhdr, sizeof(rhdr) }, { rec->data, rec->length }, { buf, sz }, { (void *)zeroes, record_length - combined_length }, -- 2.54.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Small optimization. Reduce number of pieces passed to writev. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> -- Changes since v2: - change prefix in subject. --- tools/libs/guest/xg_sr_save.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_save.c +++ b/tools/libs/guest/xg_sr_save.c @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) void *page, *orig_page; uint64_t *rec_pfns = NULL; struct iovec *iov = NULL; int iovcnt = 0; - struct xc_sr_rec_page_data_header hdr = { 0 }; - struct xc_sr_record rec = { - .type = REC_TYPE_PAGE_DATA, + struct { + struct xc_sr_rhdr rec; + struct xc_sr_rec_page_data_header page_data; + } hdrs = { + { .type = REC_TYPE_PAGE_DATA }, + { 0 }, }; assert(nr_pfns != 0); @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) /* Pointers to locally allocated pages. Need freeing. */ local_pages = calloc(nr_pfns, sizeof(*local_pages)); /* iovec[] for writev(). */ - iov = malloc((nr_pfns + 4) * sizeof(*iov)); + iov = malloc((nr_pfns + 2) * sizeof(*iov)); if ( !mfns || !types || !errors || !guest_data || !local_pages || !iov ) { @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) goto err; } - hdr.count = nr_pfns; + hdrs.rec.length = sizeof(hdrs.page_data); + hdrs.rec.length += nr_pfns * sizeof(*rec_pfns); + hdrs.rec.length += nr_pages * PAGE_SIZE; - rec.length = sizeof(hdr); - rec.length += nr_pfns * sizeof(*rec_pfns); - rec.length += nr_pages * PAGE_SIZE; + hdrs.page_data.count = nr_pfns; for ( i = 0; i < nr_pfns; ++i ) rec_pfns[i] = ((uint64_t)(types[i]) << 32) | ctx->save.batch_pfns[i]; - iov[0].iov_base = &rec.type; - iov[0].iov_len = sizeof(rec.type); + iov[0].iov_base = &hdrs; + iov[0].iov_len = sizeof(hdrs); - iov[1].iov_base = &rec.length; - iov[1].iov_len = sizeof(rec.length); + iov[1].iov_base = rec_pfns; + iov[1].iov_len = nr_pfns * sizeof(*rec_pfns); - iov[2].iov_base = &hdr; - iov[2].iov_len = sizeof(hdr); - - iov[3].iov_base = rec_pfns; - iov[3].iov_len = nr_pfns * sizeof(*rec_pfns); - - iovcnt = 4; + iovcnt = 2; if ( nr_pages ) { -- 2.54.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Each page was sent using a different iovec item. This potentially exceed Linux maximum (1024). Also some implementation (MiniOS) emulate writev with multiple write calls. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> -- Changes since v2: - change prefix in subject. --- tools/libs/guest/xg_sr_save.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_save.c +++ b/tools/libs/guest/xg_sr_save.c @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) { for ( i = 0; i < nr_pfns; ++i ) { - if ( guest_data[i] ) + if ( !guest_data[i] ) + continue; + + if ( iov[iovcnt-1].iov_base + iov[iovcnt-1].iov_len != guest_data[i] ) { iov[iovcnt].iov_base = guest_data[i]; iov[iovcnt].iov_len = PAGE_SIZE; iovcnt++; - --nr_pages; } + else + { + iov[iovcnt-1].iov_len += PAGE_SIZE; + } + --nr_pages; } } -- 2.54.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Reduce number of syscalls. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> -- Changes since v2: - change prefix in subject. --- tools/libs/guest/xg_sr_save.c | 37 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_save.c +++ b/tools/libs/guest/xg_sr_save.c @@ -XXX,XX +XXX,XX @@ static int write_headers(struct xc_sr_context *ctx, uint16_t guest_type) { xc_interface *xch = ctx->xch; int32_t xen_version = xc_version(xch, XENVER_version, NULL); - struct xc_sr_ihdr ihdr = { - .marker = IHDR_MARKER, - .id = htonl(IHDR_ID), - .version = htonl(3), - .options = htons(IHDR_OPT_LITTLE_ENDIAN), - }; - struct xc_sr_dhdr dhdr = { - .type = guest_type, - .page_shift = XC_PAGE_SHIFT, - .xen_major = (xen_version >> 16) & 0xffff, - .xen_minor = (xen_version) & 0xffff, + struct { + struct xc_sr_ihdr ihdr; + struct xc_sr_dhdr dhdr; + } hdrs = { + { + .marker = IHDR_MARKER, + .id = htonl(IHDR_ID), + .version = htonl(3), + .options = htons(IHDR_OPT_LITTLE_ENDIAN), + }, + { + .type = guest_type, + .page_shift = XC_PAGE_SHIFT, + .xen_major = (xen_version >> 16) & 0xffff, + .xen_minor = (xen_version) & 0xffff, + }, }; if ( xen_version < 0 ) @@ -XXX,XX +XXX,XX @@ static int write_headers(struct xc_sr_context *ctx, uint16_t guest_type) return -1; } - if ( write_exact(ctx->fd, &ihdr, sizeof(ihdr)) ) - { - PERROR("Unable to write Image Header to stream"); - return -1; - } - - if ( write_exact(ctx->fd, &dhdr, sizeof(dhdr)) ) + if ( write_exact(ctx->fd, &hdrs, sizeof(hdrs)) ) { - PERROR("Unable to write Domain Header to stream"); + PERROR("Unable to write Headers to stream"); return -1; } -- 2.54.0
From: Edwin Török <edwin.torok@citrix.com> Allocate these array just once at the start of migration, using the maximum batch size, and free them at the end. Signed-off-by: Edwin Török <edwin.torok@citrix.com> Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> -- Changes since v2: - change prefix in subject. --- tools/libs/guest/xg_sr_common.h | 13 +++++++ tools/libs/guest/xg_sr_save.c | 66 +++++++++++++-------------------- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_common.h +++ b/tools/libs/guest/xg_sr_common.h @@ -XXX,XX +XXX,XX @@ static inline int update_blob(struct xc_sr_blob *blob, return 0; } +struct xc_sr_context_save_buffers +{ + xen_pfn_t batch_pfns[MAX_BATCH_SIZE]; + xen_pfn_t mfns[MAX_BATCH_SIZE]; + xen_pfn_t types[MAX_BATCH_SIZE]; + int errors[MAX_BATCH_SIZE]; + void *guest_data[MAX_BATCH_SIZE]; + void *local_pages[MAX_BATCH_SIZE]; + struct iovec iov[MAX_BATCH_SIZE + 2]; /* headers + data */ + uint64_t rec_pfns[MAX_BATCH_SIZE]; +}; + struct xc_sr_context { xc_interface *xch; @@ -XXX,XX +XXX,XX @@ struct xc_sr_context unsigned long *deferred_pages; unsigned long nr_deferred_pages; xc_hypercall_buffer_t dirty_bitmap_hbuf; + struct xc_sr_context_save_buffers *buffers; } save; struct /* Restore data. */ diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_save.c +++ b/tools/libs/guest/xg_sr_save.c @@ -XXX,XX +XXX,XX @@ static int write_checkpoint_record(struct xc_sr_context *ctx) static int write_batch(struct xc_sr_context *ctx) { xc_interface *xch = ctx->xch; - xen_pfn_t *mfns = NULL, *types = NULL; + xen_pfn_t *mfns, *types; void *guest_mapping = NULL; - void **guest_data = NULL; - void **local_pages = NULL; - int *errors = NULL, rc = -1; + void **guest_data; + void **local_pages; + int *errors, rc = -1; unsigned int i, p, nr_pages = 0, nr_pages_mapped = 0; unsigned int nr_pfns = ctx->save.nr_batch_pfns; void *page, *orig_page; - uint64_t *rec_pfns = NULL; - struct iovec *iov = NULL; int iovcnt = 0; + uint64_t *rec_pfns; + struct iovec *iov; int iovcnt = 0; struct { struct xc_sr_rhdr rec; struct xc_sr_rec_page_data_header page_data; @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) }; assert(nr_pfns != 0); + assert(nr_pfns <= MAX_BATCH_SIZE); + assert(ctx->save.buffers); /* Mfns of the batch pfns. */ - mfns = malloc(nr_pfns * sizeof(*mfns)); + mfns = ctx->save.buffers->mfns; /* Types of the batch pfns. */ - types = malloc(nr_pfns * sizeof(*types)); + types = ctx->save.buffers->types; /* Errors from attempting to map the gfns. */ - errors = malloc(nr_pfns * sizeof(*errors)); + errors = ctx->save.buffers->errors; /* Pointers to page data to send. Mapped gfns or local allocations. */ - guest_data = calloc(nr_pfns, sizeof(*guest_data)); + guest_data = ctx->save.buffers->guest_data; + memset(guest_data, 0, sizeof(*guest_data) * nr_pfns); /* Pointers to locally allocated pages. Need freeing. */ - local_pages = calloc(nr_pfns, sizeof(*local_pages)); + local_pages = ctx->save.buffers->local_pages; + memset(local_pages, 0, sizeof(*local_pages) * nr_pfns); /* iovec[] for writev(). */ - iov = malloc((nr_pfns + 2) * sizeof(*iov)); - - if ( !mfns || !types || !errors || !guest_data || !local_pages || !iov ) - { - ERROR("Unable to allocate arrays for a batch of %u pages", - nr_pfns); - goto err; - } + iov = ctx->save.buffers->iov; + rec_pfns = ctx->save.buffers->rec_pfns; for ( i = 0; i < nr_pfns; ++i ) { @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) } } - rec_pfns = malloc(nr_pfns * sizeof(*rec_pfns)); - if ( !rec_pfns ) - { - ERROR("Unable to allocate %zu bytes of memory for page data pfn list", - nr_pfns * sizeof(*rec_pfns)); - goto err; - } - hdrs.rec.length = sizeof(hdrs.page_data); hdrs.rec.length += nr_pfns * sizeof(*rec_pfns); hdrs.rec.length += nr_pages * PAGE_SIZE; @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) rc = ctx->save.nr_batch_pfns = 0; err: - free(rec_pfns); if ( guest_mapping ) xenforeignmemory_unmap(xch->fmem, guest_mapping, nr_pages_mapped); for ( i = 0; local_pages && i < nr_pfns; ++i ) + { free(local_pages[i]); - free(iov); - free(local_pages); - free(guest_data); - free(errors); - free(types); - free(mfns); + local_pages[i] = NULL; + } return rc; } @@ -XXX,XX +XXX,XX @@ static int setup(struct xc_sr_context *ctx) dirty_bitmap = xc_hypercall_buffer_alloc_pages( xch, dirty_bitmap, NRPAGES(bitmap_size(ctx->save.p2m_size))); - ctx->save.batch_pfns = malloc(MAX_BATCH_SIZE * - sizeof(*ctx->save.batch_pfns)); ctx->save.deferred_pages = bitmap_alloc(ctx->save.p2m_size); + ctx->save.buffers = calloc(1, sizeof(*ctx->save.buffers)); - if ( !ctx->save.batch_pfns || !dirty_bitmap || !ctx->save.deferred_pages ) + if ( !dirty_bitmap || !ctx->save.deferred_pages || !ctx->save.buffers) { - ERROR("Unable to allocate memory for dirty bitmaps, batch pfns and" - " deferred pages"); + ERROR("Unable to allocate memory for dirty bitmaps, deferred pages" + " and various batch buffers"); rc = -1; errno = ENOMEM; goto err; } + ctx->save.batch_pfns = ctx->save.buffers->batch_pfns; rc = 0; @@ -XXX,XX +XXX,XX @@ static void cleanup(struct xc_sr_context *ctx) xc_hypercall_buffer_free_pages(xch, dirty_bitmap, NRPAGES(bitmap_size(ctx->save.p2m_size))); free(ctx->save.deferred_pages); - free(ctx->save.batch_pfns); + free(ctx->save.buffers); } /* -- 2.54.0
From: Edwin Török <edwin.torok@citrix.com> During migration there are a lot of mmap/munmap calls, because `xc_get_pfn_type_batch` exceeds the default hypercall bounce buffer cache size, and needs to allocate every time it is called. `munmap` is slow, especially in a PV Dom0 (takes an emulation fault), so is best avoided. Eventually it'd be good if the memory pool from xmalloc_tlsf.c was reused here, but for now make it handle the commonly encountered sizes (so far up to 4 pages). Signed-off-by: Edwin Török <edwin.torok@citrix.com> Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> -- Changes since v2: - change prefix in subject. --- tools/libs/call/buffer.c | 28 +++++++++++++++++----------- tools/libs/call/core.c | 3 ++- tools/libs/call/private.h | 8 +++++--- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/tools/libs/call/buffer.c b/tools/libs/call/buffer.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/call/buffer.c +++ b/tools/libs/call/buffer.c @@ -XXX,XX +XXX,XX @@ static void *cache_alloc(xencall_handle *xcall, size_t nr_pages) if ( xcall->buffer_current_allocations > xcall->buffer_maximum_allocations ) xcall->buffer_maximum_allocations = xcall->buffer_current_allocations; - if ( nr_pages > 1 ) + if ( nr_pages > ARRAY_SIZE(xcall->buffer_cache) ) { xcall->buffer_cache_toobig++; } - else if ( xcall->buffer_cache_nr > 0 ) + else if ( xcall->buffer_cache_nr[nr_pages-1] > 0 ) { - p = xcall->buffer_cache[--xcall->buffer_cache_nr]; + p = xcall->buffer_cache[nr_pages-1][--xcall->buffer_cache_nr[nr_pages-1]]; xcall->buffer_cache_hits++; } else @@ -XXX,XX +XXX,XX @@ static int cache_free(xencall_handle *xcall, void *p, size_t nr_pages) xcall->buffer_total_releases++; xcall->buffer_current_allocations--; - if ( nr_pages == 1 && - xcall->buffer_cache_nr < BUFFER_CACHE_SIZE ) + if ( nr_pages && nr_pages < ARRAY_SIZE(xcall->buffer_cache) && + xcall->buffer_cache_nr[nr_pages-1] < BUFFER_CACHE_SIZE ) { - xcall->buffer_cache[xcall->buffer_cache_nr++] = p; + xcall->buffer_cache[nr_pages-1][xcall->buffer_cache_nr[nr_pages-1]++] = p; rc = 1; } @@ -XXX,XX +XXX,XX @@ void buffer_release_cache(xencall_handle *xcall) DBGPRINTF("current allocations:%d maximum allocations:%d", xcall->buffer_current_allocations, xcall->buffer_maximum_allocations); - DBGPRINTF("cache current size:%d", - xcall->buffer_cache_nr); + for ( unsigned i = 0; i < ARRAY_SIZE(xcall->buffer_cache_nr); ++i ) + { + DBGPRINTF("cache current size[%u pages]:%d", i+1, + xcall->buffer_cache_nr[i]); + } DBGPRINTF("cache hits:%d misses:%d toobig:%d", xcall->buffer_cache_hits, xcall->buffer_cache_misses, xcall->buffer_cache_toobig); - while ( xcall->buffer_cache_nr > 0 ) + for ( unsigned i = 0; i < ARRAY_SIZE(xcall->buffer_cache_nr); ++i ) { - p = xcall->buffer_cache[--xcall->buffer_cache_nr]; - osdep_free_pages(xcall, p, 1); + while ( xcall->buffer_cache_nr[i] > 0 ) + { + p = xcall->buffer_cache[i][--xcall->buffer_cache_nr[i]]; + osdep_free_pages(xcall, p, i + 1); + } } cache_unlock(xcall); diff --git a/tools/libs/call/core.c b/tools/libs/call/core.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/call/core.c +++ b/tools/libs/call/core.c @@ -XXX,XX +XXX,XX @@ */ #include <stdlib.h> +#include <string.h> #include "private.h" @@ -XXX,XX +XXX,XX @@ xencall_handle *xencall_open(xentoollog_logger *logger, unsigned open_flags) xentoolcore__register_active_handle(&xcall->tc_ah); xcall->flags = open_flags; - xcall->buffer_cache_nr = 0; + memset(xcall->buffer_cache_nr, 0, sizeof(xcall->buffer_cache_nr)); xcall->buffer_total_allocations = 0; xcall->buffer_total_releases = 0; diff --git a/tools/libs/call/private.h b/tools/libs/call/private.h index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/call/private.h +++ b/tools/libs/call/private.h @@ -XXX,XX +XXX,XX @@ struct xencall_handle { Xentoolcore__Active_Handle tc_ah; /* - * A simple cache of unused, single page, hypercall buffers + * A simple cache of unused, small, hypercall buffers + * buffer_cache[i]'s size is (i+1) pages * * Protected by a global lock. */ #define BUFFER_CACHE_SIZE 4 - int buffer_cache_nr; - void *buffer_cache[BUFFER_CACHE_SIZE]; +#define BUFFER_CACHE_NRPAGES 4 + int buffer_cache_nr[BUFFER_CACHE_NRPAGES]; + void *buffer_cache[BUFFER_CACHE_NRPAGES][BUFFER_CACHE_SIZE]; /* * Hypercall buffer statistics. All protected by the global -- 2.54.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Simplify code, after the first scan of the various arrays we don't need to keep original types and PFNs but only the ones having data. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> --- tools/libs/guest/xg_sr_restore.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tools/libs/guest/xg_sr_restore.c b/tools/libs/guest/xg_sr_restore.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_restore.c +++ b/tools/libs/guest/xg_sr_restore.c @@ -XXX,XX +XXX,XX @@ static int process_page_data(struct xc_sr_context *ctx, unsigned int count, int *map_errs = malloc(count * sizeof(*map_errs)); int rc; void *mapping = NULL, *guest_page = NULL; - unsigned int i, /* i indexes the pfns from the record. */ - j, /* j indexes the subset of pfns we decide to map. */ - nr_pages = 0; + unsigned nr_pages; if ( !mfns || !map_errs ) { @@ -XXX,XX +XXX,XX @@ static int process_page_data(struct xc_sr_context *ctx, unsigned int count, goto err; } - for ( i = 0; i < count; ++i ) + nr_pages = 0; + for ( unsigned i = 0; i < count; ++i ) { ctx->restore.ops.set_page_type(ctx, pfns[i], types[i]); - if ( page_type_has_stream_data(types[i]) ) - mfns[nr_pages++] = ctx->restore.ops.pfn_to_gfn(ctx, pfns[i]); + if ( !page_type_has_stream_data(types[i]) ) + continue; + + mfns[nr_pages] = ctx->restore.ops.pfn_to_gfn(ctx, pfns[i]); + pfns[nr_pages] = pfns[i]; + types[nr_pages] = types[i]; + nr_pages++; } /* Nothing to do? */ @@ -XXX,XX +XXX,XX @@ static int process_page_data(struct xc_sr_context *ctx, unsigned int count, goto err; } - for ( i = 0, j = 0; i < count; ++i ) + for ( unsigned i = 0; i < nr_pages; ++i ) { - if ( !page_type_has_stream_data(types[i]) ) - continue; - - if ( map_errs[j] ) + if ( map_errs[i] ) { rc = -1; ERROR("Mapping pfn %#"PRIpfn" (mfn %#"PRIpfn", type %#"PRIx32") failed with %d", - pfns[i], mfns[j], types[i], map_errs[j]); + pfns[i], mfns[i], types[i], map_errs[i]); goto err; } @@ -XXX,XX +XXX,XX @@ static int process_page_data(struct xc_sr_context *ctx, unsigned int count, memcpy(guest_page, page_data, PAGE_SIZE); } - ++j; guest_page += PAGE_SIZE; page_data += PAGE_SIZE; } -- 2.54.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Instead of storing page pointers into an array and lately adding to iov vector add the pages directly to iov to avoid "guest_data" array. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> --- tools/libs/guest/xg_sr_common.h | 1 - tools/libs/guest/xg_sr_save.c | 62 ++++++++++++--------------------- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_common.h +++ b/tools/libs/guest/xg_sr_common.h @@ -XXX,XX +XXX,XX @@ struct xc_sr_context_save_buffers xen_pfn_t mfns[MAX_BATCH_SIZE]; xen_pfn_t types[MAX_BATCH_SIZE]; int errors[MAX_BATCH_SIZE]; - void *guest_data[MAX_BATCH_SIZE]; void *local_pages[MAX_BATCH_SIZE]; struct iovec iov[MAX_BATCH_SIZE + 2]; /* headers + data */ uint64_t rec_pfns[MAX_BATCH_SIZE]; diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/guest/xg_sr_save.c +++ b/tools/libs/guest/xg_sr_save.c @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) xc_interface *xch = ctx->xch; xen_pfn_t *mfns, *types; void *guest_mapping = NULL; - void **guest_data; void **local_pages; int *errors, rc = -1; unsigned int i, p, nr_pages = 0, nr_pages_mapped = 0; @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) types = ctx->save.buffers->types; /* Errors from attempting to map the gfns. */ errors = ctx->save.buffers->errors; - /* Pointers to page data to send. Mapped gfns or local allocations. */ - guest_data = ctx->save.buffers->guest_data; - memset(guest_data, 0, sizeof(*guest_data) * nr_pfns); /* Pointers to locally allocated pages. Need freeing. */ local_pages = ctx->save.buffers->local_pages; memset(local_pages, 0, sizeof(*local_pages) * nr_pfns); @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) mfns[nr_pages++] = mfns[i]; } + hdrs.rec.length = sizeof(hdrs.page_data); + hdrs.rec.length += nr_pfns * sizeof(*rec_pfns); + + hdrs.page_data.count = nr_pfns; + + iov[0].iov_base = &hdrs; + iov[0].iov_len = sizeof(hdrs); + + iov[1].iov_base = rec_pfns; + iov[1].iov_len = nr_pfns * sizeof(*rec_pfns); + + iovcnt = 2; + if ( nr_pages > 0 ) { guest_mapping = xenforeignmemory_map( @@ -XXX,XX +XXX,XX @@ static int write_batch(struct xc_sr_context *ctx) else goto err; } + else if ( iov[iovcnt-1].iov_base + iov[iovcnt-1].iov_len != page ) + { + iov[iovcnt].iov_base = page; + iov[iovcnt].iov_len = PAGE_SIZE; + iovcnt++; + } else - guest_data[i] = page; + { + iov[iovcnt-1].iov_len += PAGE_SIZE; + } rc = -1; ++p; } } - hdrs.rec.length = sizeof(hdrs.page_data); - hdrs.rec.length += nr_pfns * sizeof(*rec_pfns); hdrs.rec.length += nr_pages * PAGE_SIZE; - hdrs.page_data.count = nr_pfns; - for ( i = 0; i < nr_pfns; ++i ) rec_pfns[i] = ((uint64_t)(types[i]) << 32) | ctx->save.batch_pfns[i]; - iov[0].iov_base = &hdrs; - iov[0].iov_len = sizeof(hdrs); - - iov[1].iov_base = rec_pfns; - iov[1].iov_len = nr_pfns * sizeof(*rec_pfns); - - iovcnt = 2; - - if ( nr_pages ) - { - for ( i = 0; i < nr_pfns; ++i ) - { - if ( !guest_data[i] ) - continue; - - if ( iov[iovcnt-1].iov_base + iov[iovcnt-1].iov_len != guest_data[i] ) - { - iov[iovcnt].iov_base = guest_data[i]; - iov[iovcnt].iov_len = PAGE_SIZE; - iovcnt++; - } - else - { - iov[iovcnt-1].iov_len += PAGE_SIZE; - } - --nr_pages; - } - } - if ( writev_exact(ctx->fd, iov, iovcnt) ) { PERROR("Failed to write page data to stream"); goto err; } - /* Sanity check we have sent all the pages we expected to. */ - assert(nr_pages == 0); rc = ctx->save.nr_batch_pfns = 0; err: -- 2.54.0
From: Frediano Ziglio <frediano.ziglio@citrix.com> Avoid having to allocate and copy the array if a partial write happens. The implementation in tools/libs/store/xs.c already use this signature and method. Signed-off-by: Frediano Ziglio <frediano.ziglio@citrix.com> -- Changes since v2: - change prefix in subject. --- tools/libs/ctrl/xc_private.c | 26 +++++--------------------- tools/libs/ctrl/xc_private.h | 2 +- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/tools/libs/ctrl/xc_private.c b/tools/libs/ctrl/xc_private.c index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/ctrl/xc_private.c +++ b/tools/libs/ctrl/xc_private.c @@ -XXX,XX +XXX,XX @@ int write_exact(int fd, const void *data, size_t size) /* * MiniOS's libc doesn't know about writev(). Implement it as multiple write()s. */ -int writev_exact(int fd, const struct iovec *iov, int iovcnt) +int writev_exact(int fd, struct iovec *iov, int iovcnt) { int rc, i; @@ -XXX,XX +XXX,XX @@ int writev_exact(int fd, const struct iovec *iov, int iovcnt) return 0; } #else -int writev_exact(int fd, const struct iovec *iov, int iovcnt) +int writev_exact(int fd, struct iovec *iov, int iovcnt) { - struct iovec *local_iov = NULL; int rc = 0, iov_idx = 0, saved_errno = 0; ssize_t len; @@ -XXX,XX +XXX,XX @@ int writev_exact(int fd, const struct iovec *iov, int iovcnt) len -= iov[iov_idx++].iov_len; else { - /* Partial write of iov[iov_idx]. Copy iov so we can adjust - * element iov_idx and resubmit the rest. */ - if ( !local_iov ) - { - local_iov = malloc(iovcnt * sizeof(*iov)); - if ( !local_iov ) - { - saved_errno = ENOMEM; - rc = -1; - goto out; - } - - iov = memcpy(local_iov, iov, iovcnt * sizeof(*iov)); - } - - local_iov[iov_idx].iov_base += len; - local_iov[iov_idx].iov_len -= len; + /* Partial write of iov[iov_idx]. */ + iov[iov_idx].iov_base += len; + iov[iov_idx].iov_len -= len; break; } } @@ -XXX,XX +XXX,XX @@ int writev_exact(int fd, const struct iovec *iov, int iovcnt) saved_errno = 0; out: - free(local_iov); errno = saved_errno; return rc; } diff --git a/tools/libs/ctrl/xc_private.h b/tools/libs/ctrl/xc_private.h index XXXXXXX..XXXXXXX 100644 --- a/tools/libs/ctrl/xc_private.h +++ b/tools/libs/ctrl/xc_private.h @@ -XXX,XX +XXX,XX @@ int xc_flush_mmu_updates(xc_interface *xch, struct xc_mmu *mmu); /* Return 0 on success; -1 on error setting errno. */ int read_exact(int fd, void *data, size_t size); /* EOF => -1, errno=0 */ int write_exact(int fd, const void *data, size_t size); -int writev_exact(int fd, const struct iovec *iov, int iovcnt); +int writev_exact(int fd, struct iovec *iov, int iovcnt); int xc_ffs8(uint8_t x); int xc_ffs16(uint16_t x); -- 2.54.0