]> Zhao Yanbai Git Server - minix.git/commitdiff
RS/VM: proper preparation for multi-VM live update 47/3147/1
authorDavid van Moolenbroek <david@minix3.org>
Tue, 14 Jul 2015 05:42:48 +0000 (07:42 +0200)
committerDavid van Moolenbroek <david@minix3.org>
Thu, 17 Sep 2015 14:01:06 +0000 (14:01 +0000)
Due to changed VM internals, more elaborate preparation is required
before a live update with multiple components including VM can take
place.  This patch adds the essential preparation infrastructure to
VM and adapts RS to make use of it.  As a side effect, it is no
longer necessary to supply RS as the last component (if at all)
during the set-up of a multicomponent live update operation.

Change-Id: If069fd3f93f96f9d5433998e4615f861465ef448

13 files changed:
etc/system.conf
minix/commands/service/parse.c
minix/include/minix/com.h
minix/include/minix/vm.h
minix/lib/libsys/Makefile
minix/lib/libsys/vm_prepare.c [new file with mode: 0644]
minix/servers/rs/const.h
minix/servers/rs/request.c
minix/servers/rs/update.c
minix/servers/vm/main.c
minix/servers/vm/proto.h
minix/servers/vm/rs.c
minix/servers/vm/utility.c

index b920a69934cf0f9648ae776b8769ad365eb872c7..c1e67bd3dd22c10f98be336b2a60078b47043634 100644 (file)
@@ -11,7 +11,8 @@ service rs
                RS_SET_PRIV     # 37
                RS_UPDATE       # 41
                RS_MEMCTL       # 42
-               PROCCTL
+               PROCCTL         # 45
+               RS_PREPARE      # 48
                ;
        io      NONE;           # No I/O range allowed
        irq     NONE;           # No IRQ allowed
index 26a91a1241a48c6f9a05fbcb961dda328b9acc29..2cf52599f2bd7db6cf87a29b85b0d04a8a5e1866 100644 (file)
@@ -754,6 +754,7 @@ struct
        { "CLEARCACHE",         VM_CLEARCACHE },
        { "VFS_MMAP",           VM_VFS_MMAP },
        { "VFS_REPLY",          VM_VFS_REPLY },
+       { "RS_PREPARE",         VM_RS_PREPARE },
        { NULL,                 0 },
 };
 
index 26415097f4288141746495be46d58bb1df2bb0dd..df3a7100674710d2716912d523299c1dec1118fa 100644 (file)
 
 #define VM_GETRUSAGE           (VM_RQ_BASE+47)
 
+#define VM_RS_PREPARE          (VM_RQ_BASE+48)
+
 /* Total. */
-#define NR_VM_CALLS                            48
+#define NR_VM_CALLS                            49
 #define VM_CALL_MASK_SIZE                      BITMAP_CHUNKS(NR_VM_CALLS)
 
 /* not handled as a normal VM call, thus at the end of the reserved rage */
index f1b75259a47d08d82af59ffa76915aeee0ee2925..6047e0ed6be4fbee2bc0964e575215da80dc5411 100644 (file)
@@ -19,6 +19,7 @@ int vm_notify_sig(endpoint_t ep, endpoint_t ipc_ep);
 int vm_set_priv(endpoint_t ep, void *buf, int sys_proc);
 int vm_update(endpoint_t src_e, endpoint_t dst_e, int flags);
 int vm_memctl(endpoint_t ep, int req, void** addr, size_t *len);
+int vm_prepare(endpoint_t src_e, endpoint_t dst_e, int flags);
 int vm_query_exit(endpoint_t *endpt);
 int vm_watch_exit(endpoint_t ep);
 int minix_vfs_mmap(endpoint_t who, off_t offset, size_t len,
index cb6874f5b3a351e434a8bdd54d33e04dc38f2bbd..1e5d7fcd0a8fce3a36d7f76ffe1cecbd1eba53f9 100644 (file)
@@ -100,6 +100,7 @@ SRCS+=  \
        vm_map_phys.c \
        vm_memctl.c \
        vm_notify_sig.c \
+       vm_prepare.c \
        vm_procctl.c \
        vm_query_exit.c \
        vm_set_priv.c \
diff --git a/minix/lib/libsys/vm_prepare.c b/minix/lib/libsys/vm_prepare.c
new file mode 100644 (file)
index 0000000..9cf3d04
--- /dev/null
@@ -0,0 +1,17 @@
+#include "syslib.h"
+
+#include <unistd.h>
+#include <string.h>
+
+int
+vm_prepare(endpoint_t src_e, endpoint_t dst_e, int flags)
+{
+       message m;
+
+       memset(&m, 0, sizeof(m));
+       m.m_lsys_vm_update.src = src_e;
+       m.m_lsys_vm_update.dst = dst_e;
+       m.m_lsys_vm_update.flags = flags;
+
+       return _taskcall(VM_PROC_NR, VM_RS_PREPARE, &m);
+}
index 050c3007d552ddd344b882d3ffb995c60d56b359..d5a3bc895983070daff96332cfdcbf32d1a7a77b 100644 (file)
 #define RUPDATE_INIT() memset(&rupdate, 0, sizeof(rupdate))
 #define RUPDATE_CLEAR() RUPDATE_INIT()
 
+/* Note that we have 'B' last in order to allow 'continue' statements */
 #define RUPDATE_ITER(HEAD, RPUPD_PREV, RPUPD, B) do { \
-        RPUPD = HEAD; \
-        RPUPD_PREV = NULL; \
-        while(RPUPD) { \
+        for(RPUPD = HEAD, RPUPD_PREV = NULL; RPUPD != NULL; \
+          RPUPD_PREV = RPUPD, RPUPD = RPUPD->next_rpupd) { \
             B \
-            RPUPD_PREV = RPUPD; \
-            RPUPD = RPUPD->next_rpupd; \
         } \
      } while(0)
 #define RUPDATE_REV_ITER(TAIL, RPUPD_PREV, RPUPD, B) do { \
         RPUPD = TAIL; \
-        while(RPUPD) { \
+        for(RPUPD = TAIL; RPUPD != NULL; RPUPD = RPUPD->prev_rpupd) { \
             RPUPD_PREV = RPUPD->prev_rpupd; \
             B \
-            RPUPD = RPUPD->prev_rpupd; \
         } \
      } while(0)
 
index 88436d6c72c268b87060736ee6367ae000f12fbe..5babfeb0c6a8962b84f07880623ff3f52d0641f7 100644 (file)
@@ -667,10 +667,6 @@ int do_update(message *m_ptr)
           printf("RS: the specified process is already part of the currently scheduled update\n");
           return EINVAL;
       }
-      if(rupdate.last_rpupd->rp->r_pub->endpoint == RS_PROC_NR) {
-          printf("RS: RS should always be the last service to update in a multi-component update\n");
-          return EINVAL;
-      }
   }
 
   /* Prepare-only update for VM, PM, and VFS is only supported with an unreachable state. */
index f6c6f200e842df4fcd7e139ed64e0230a7a26d3e..8dedfa5b66c9ad464a2b12d39160228bc6461f33 100644 (file)
@@ -23,35 +23,62 @@ void rupdate_clear_upds()
 void rupdate_add_upd(struct rprocupd* rpupd)
 {
   /* Add an update descriptor to the update chain. */
-  struct rprocupd* prev_rpupd;
+  struct rprocupd *prev_rpupd, *walk_rpupd;
+  endpoint_t ep;
   int lu_flags;
 
-  rpupd->prev_rpupd = rupdate.last_rpupd;
-  if(rupdate.num_rpupds == 0) {
-      rupdate.first_rpupd = rpupd;
-      rupdate.curr_rpupd = rpupd;
-  }
-  else {
-      rupdate.last_rpupd->next_rpupd = rpupd;
+  /* In order to allow multicomponent-with-VM live updates to be processed
+   * correctly, we perform partial sorting on the chain: RS is to be last (if
+   * present), VM is to be right before it (if present), and all the other
+   * processes are to be at the start of the chain.
+   */
+
+  ep = rpupd->rp->r_pub->endpoint;
+
+  assert(rpupd->next_rpupd == NULL);
+  assert(rpupd->prev_rpupd == NULL);
+
+  /* Determine what element to insert after, if not at the head. */
+  prev_rpupd = rupdate.last_rpupd;
+  if (prev_rpupd != NULL && ep != RS_PROC_NR &&
+    prev_rpupd->rp->r_pub->endpoint == RS_PROC_NR)
+      prev_rpupd = prev_rpupd->prev_rpupd;
+  if (prev_rpupd != NULL && ep != RS_PROC_NR && ep != VM_PROC_NR &&
+    prev_rpupd->rp->r_pub->endpoint == VM_PROC_NR)
+      prev_rpupd = prev_rpupd->prev_rpupd;
+
+  /* Perform the insertion. */
+  if (prev_rpupd == NULL) {
+      rpupd->next_rpupd = rupdate.first_rpupd;
+      rupdate.first_rpupd = rupdate.curr_rpupd = rpupd;
+  } else {
+      rpupd->next_rpupd = prev_rpupd->next_rpupd;
+      rpupd->prev_rpupd = prev_rpupd;
+      prev_rpupd->next_rpupd = rpupd;
   }
-  rupdate.last_rpupd = rpupd;
+
+  if (rpupd->next_rpupd != NULL)
+      rpupd->next_rpupd->prev_rpupd = rpupd;
+  else
+      rupdate.last_rpupd = rpupd;
+
   rupdate.num_rpupds++;
 
   /* Propagate relevant flags from the new descriptor. */
   lu_flags = rpupd->lu_flags & (SEF_LU_INCLUDES_VM|SEF_LU_INCLUDES_RS|SEF_LU_UNSAFE|SEF_LU_MULTI);
   if(lu_flags) {
-      RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, rpupd,
-          rpupd->lu_flags |= lu_flags;
-          rpupd->init_flags |= lu_flags;
+      RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, walk_rpupd,
+          walk_rpupd->lu_flags |= lu_flags;
+          walk_rpupd->init_flags |= lu_flags;
       );
   }
 
   /* Set VM/RS update descriptor pointers. */
   if(!rupdate.vm_rpupd && (lu_flags & SEF_LU_INCLUDES_VM)) {
-      rupdate.vm_rpupd = rupdate.last_rpupd;
+      rupdate.vm_rpupd = rpupd;
   }
   else if(!rupdate.rs_rpupd && (lu_flags & SEF_LU_INCLUDES_RS)) {
-      rupdate.rs_rpupd = rupdate.last_rpupd;
+      rupdate.rs_rpupd = rpupd;
   }
 }
 
@@ -419,14 +446,6 @@ int start_update_prepare(int allow_retries)
                   if(rpupd->lu_flags & SEF_LU_NOMMAP) {
                       rp->r_pub->sys_flags |= SF_VM_NOMMAP;
                   }
-                  if(!(rpupd->lu_flags & SEF_LU_UNSAFE)) {
-                      if(rs_verbose)
-                          printf("RS: %s pinning memory\n", srv_to_string(rp));
-                      vm_memctl(rp->r_pub->new_endpoint, VM_RS_MEM_PIN, 0, 0);
-                      if(rs_verbose)
-                          printf("RS: %s pinning memory\n", srv_to_string(new_rp));
-                      vm_memctl(new_rp->r_pub->endpoint, VM_RS_MEM_PIN, 0, 0);
-                  }
               }
           }
       );
@@ -448,7 +467,9 @@ int start_update_prepare(int allow_retries)
 struct rprocupd* start_update_prepare_next()
 {
   /* Request the next service in the update chain to prepare for the update. */
-  struct rprocupd *rpupd = NULL;
+  struct rprocupd *rpupd, *prev_rpupd, *walk_rpupd;
+  struct rproc *rp, *new_rp;
+
   if(!RUPDATE_IS_UPDATING()) {
       rpupd = rupdate.first_rpupd;
   }
@@ -458,6 +479,34 @@ struct rprocupd* start_update_prepare_next()
   if(!rpupd) {
       return NULL;
   }
+
+  if (RUPDATE_IS_UPD_VM_MULTI() && rpupd == rupdate.vm_rpupd) {
+      /* We are doing a multicomponent live update that includes VM, and all
+       * services are now ready (and thereby stopped) except VM and possibly
+       * RS. This is the last point in time, and therefore also the best, that
+       * we can ask the (old) VM instance to do stuff for us, before we ask it
+       * to get ready as well: preallocate and pin memory, and copy over
+       * memory-mapped regions. Do this now, for all services except VM
+       * itself. In particular, also do it for RS, as we know that RS (yes,
+       * this service) is not going to create problems from here on.
+       */
+      RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, walk_rpupd,
+          if (UPD_IS_PREPARING_ONLY(walk_rpupd))
+              continue; /* skip prepare-only processes */
+          if (walk_rpupd == rupdate.vm_rpupd)
+              continue; /* skip VM */
+          rp = walk_rpupd->rp;
+          new_rp = rp->r_new_rp;
+          assert(rp && new_rp);
+          if (rs_verbose)
+              printf("RS: preparing VM for %s -> %s\n", srv_to_string(rp),
+                srv_to_string(new_rp));
+          /* Ask VM to prepare the new instance based on the old instance. */
+          vm_prepare(rp->r_pub->new_endpoint, new_rp->r_pub->endpoint,
+            rp->r_pub->sys_flags);
+      );
+  }
+
   rupdate.flags |= RS_UPDATING;
 
   while(1) {
index 6f37e676c1d7e22ad853e3d5dc8f40547499dab3..7488cdc2fceb37511d71c0488e5671b5674d49cb 100644 (file)
@@ -554,6 +554,7 @@ void init_vm(void)
 
        /* Calls from RS */
        CALLMAP(VM_RS_SET_PRIV, do_rs_set_priv);
+       CALLMAP(VM_RS_PREPARE, do_rs_prepare);
        CALLMAP(VM_RS_UPDATE, do_rs_update);
        CALLMAP(VM_RS_MEMCTL, do_rs_memctl);
 
index 546fe1734f7234ab70a66aea8c3f839eaacdd30b..c46eb5d0670b78321e86b077c21050ac34ac6ff0 100644 (file)
@@ -49,6 +49,7 @@ int do_info(message *);
 int swap_proc_slot(struct vmproc *src_vmp, struct vmproc *dst_vmp);
 int swap_proc_dyn_data(struct vmproc *src_vmp, struct vmproc *dst_vmp,
     int sys_upd_flags);
+int map_proc_dyn_data(struct vmproc *src_vmp, struct vmproc *dst_vmp);
 void adjust_proc_refs(void);
 int do_getrusage(message *m);
 
@@ -192,6 +193,7 @@ void map_sanitycheck(const char *file, int line);
 
 /* rs.c */
 int do_rs_set_priv(message *m);
+int do_rs_prepare(message *m);
 int do_rs_update(message *m);
 int do_rs_memctl(message *m);
 
index d6c2ac8be8c0273fba9a7a002387608a584ba68d..9f7298cb58e147e1ffa2308c3347ad15e8be3954 100644 (file)
@@ -66,6 +66,85 @@ int do_rs_set_priv(message *m)
        return OK;
 }
 
+/*===========================================================================*
+ *                             do_rs_prepare                                *
+ *===========================================================================*/
+int do_rs_prepare(message *m_ptr)
+{
+       /* Prepare a new instance of a service for an upcoming live-update
+        * switch, based on the old instance of this service.  This call is
+        * used only by RS and only for a multicomponent live update which
+        * includes VM.  In this case, all processes need to be prepared such
+        * that they don't require the new VM instance to perform actions
+        * during live update that cannot be undone in the case of a rollback.
+        */
+       endpoint_t src_e, dst_e;
+       int src_p, dst_p;
+       struct vmproc *src_vmp, *dst_vmp;
+       struct vir_region *src_data_vr, *dst_data_vr;
+       vir_bytes src_addr, dst_addr;
+       int sys_upd_flags;
+
+       src_e = m_ptr->m_lsys_vm_update.src;
+       dst_e = m_ptr->m_lsys_vm_update.dst;
+        sys_upd_flags = m_ptr->m_lsys_vm_update.flags;
+
+       /* Lookup slots for source and destination process. */
+       if(vm_isokendpt(src_e, &src_p) != OK) {
+               printf("VM: do_rs_prepare: bad src endpoint %d\n", src_e);
+               return EINVAL;
+       }
+       src_vmp = &vmproc[src_p];
+       if(vm_isokendpt(dst_e, &dst_p) != OK) {
+               printf("VM: do_rs_prepare: bad dst endpoint %d\n", dst_e);
+               return EINVAL;
+       }
+       dst_vmp = &vmproc[dst_p];
+
+       /* Pin memory for the source process. */
+       map_pin_memory(src_vmp);
+
+       /* See if the source process has a larger heap than the destination
+        * process.  If so, extend the heap of the destination process to
+        * match the source's.  While this may end up wasting quite some
+        * memory, it is absolutely essential that the destination process
+        * does not run out of heap memory during the live update window,
+        * and since most processes will be doing an identity transfer, they
+        * are likely to require as much heap as their previous instances.
+        * Better safe than sorry.  TODO: prevent wasting memory somehow;
+        * this seems particularly relevant for RS.
+        */
+       src_data_vr = region_search(&src_vmp->vm_regions_avl, VM_MMAPBASE,
+           AVL_LESS);
+       assert(src_data_vr);
+       dst_data_vr = region_search(&dst_vmp->vm_regions_avl, VM_MMAPBASE,
+           AVL_LESS);
+       assert(dst_data_vr);
+
+       src_addr = src_data_vr->vaddr + src_data_vr->length;
+       dst_addr = dst_data_vr->vaddr + dst_data_vr->length;
+       if (src_addr > dst_addr)
+               real_brk(dst_vmp, src_addr);
+
+       /* Now also pin memory for the destination process. */
+       map_pin_memory(dst_vmp);
+
+       /* Finally, map the source process's memory-mapped regions into the
+        * destination process.  This needs to happen now, because VM may not
+        * allocate any objects during the live update window, since this
+        * would prevent successful rollback of VM afterwards.  The
+        * destination may not actually touch these regions during the live
+        * update window either, because they are mapped copy-on-write and a
+        * pagefault would also cause object allocation.  Objects are pages,
+        * slab objects, anything in the new VM instance to which changes are
+        * visible in the old VM basically.
+        */
+       if (!(sys_upd_flags & SF_VM_NOMMAP))
+               map_proc_dyn_data(src_vmp, dst_vmp);
+
+       return OK;
+}
+
 /*===========================================================================*
  *                             do_rs_update                                 *
  *===========================================================================*/
index 8583b032c6a40c1d0b39b2d269a5c1b7a4d6215e..08bc28849f05578229456eb1532e2165cad21c10 100644 (file)
@@ -223,12 +223,13 @@ int swap_proc_slot(struct vmproc *src_vmp, struct vmproc *dst_vmp)
  * Transfer memory mapped regions, using CoW sharing, from 'src_vmp' to
  * 'dst_vmp', for the source process's address range of 'start_addr'
  * (inclusive) to 'end_addr' (exclusive).  Return OK or an error code.
+ * If the regions seem to have been transferred already, do nothing.
  */
 static int
-transfer_mmap_regions(struct vmproc *dst_vmp, struct vmproc *src_vmp,
+transfer_mmap_regions(struct vmproc *src_vmp, struct vmproc *dst_vmp,
        vir_bytes start_addr, vir_bytes end_addr)
 {
-       struct vir_region *start_vr, *end_vr;
+       struct vir_region *start_vr, *check_vr, *end_vr;
 
        start_vr = region_search(&src_vmp->vm_regions_avl, start_addr,
            AVL_GREATER_EQUAL);
@@ -236,6 +237,31 @@ transfer_mmap_regions(struct vmproc *dst_vmp, struct vmproc *src_vmp,
        if (start_vr == NULL || start_vr->vaddr >= end_addr)
                return OK; /* nothing to do */
 
+       /* In the case of multicomponent live update that includes VM, this
+        * function may be called for the same process more than once, for the
+        * sake of keeping code paths as little divergent as possible while at
+        * the same time ensuring that the regions are copied early enough.
+        *
+        * To compensate for these multiple calls, we perform a very simple
+        * check here to see if the region to transfer is already present in
+        * the target process.  If so, we can safely skip copying the regions
+        * again, because there is no other possible explanation for the
+        * region being present already.  Things would go horribly wrong if we
+        * tried copying anyway, but this check is not good enough to detect
+        * all such problems, since we do a check on the base address only.
+        */
+       check_vr = region_search(&dst_vmp->vm_regions_avl, start_vr->vaddr,
+           AVL_EQUAL);
+       if (check_vr != NULL) {
+#if LU_DEBUG
+               printf("VM: transfer_mmap_regions: skipping transfer from "
+                   "%d to %d (0x%lx already present)\n",
+                   src_vmp->vm_endpoint, dst_vmp->vm_endpoint,
+                   start_vr->vaddr);
+#endif
+               return OK;
+       }
+
        end_vr = region_search(&src_vmp->vm_regions_avl, end_addr, AVL_LESS);
        assert(end_vr != NULL);
        assert(start_vr->vaddr <= end_vr->vaddr);
@@ -249,6 +275,38 @@ transfer_mmap_regions(struct vmproc *dst_vmp, struct vmproc *src_vmp,
        return map_proc_copy_range(dst_vmp, src_vmp, start_vr, end_vr);
 }
 
+/*
+ * Create copy-on-write mappings in process 'dst_vmp' for all memory-mapped
+ * regions present in 'src_vmp'.  Return OK on success, or an error otherwise.
+ * In the case of failure, successfully created mappings are not undone.
+ */
+int
+map_proc_dyn_data(struct vmproc *src_vmp, struct vmproc *dst_vmp)
+{
+       int r;
+
+#if LU_DEBUG
+       printf("VM: mapping dynamic data from %d to %d\n",
+           src_vmp->vm_endpoint, dst_vmp->vm_endpoint);
+#endif
+
+       /* Transfer memory mapped regions now. To sandbox the new instance and
+        * prevent state corruption on rollback, we share all the regions
+        * between the two instances as COW.
+        */
+       r = transfer_mmap_regions(src_vmp, dst_vmp, VM_MMAPBASE, VM_MMAPTOP);
+
+       /* If the stack is not mapped at the VM_DATATOP, there might be some
+        * more regions hiding above the stack.  We also have to transfer
+        * those.
+        */
+       if (r == OK && VM_STACKTOP < VM_DATATOP)
+               r = transfer_mmap_regions(src_vmp, dst_vmp, VM_STACKTOP,
+                   VM_DATATOP);
+
+       return r;
+}
+
 /*===========================================================================*
  *                           swap_proc_dyn_data                             *
  *===========================================================================*/
@@ -297,22 +355,8 @@ int swap_proc_dyn_data(struct vmproc *src_vmp, struct vmproc *dst_vmp,
        /* Make sure regions are consistent. */
        assert(region_search_root(&src_vmp->vm_regions_avl) && region_search_root(&dst_vmp->vm_regions_avl));
 
-       /* Transfer memory mapped regions now. To sandbox the new instance and
-        * prevent state corruption on rollback, we share all the regions
-        * between the two instances as COW. Source and destination are
-        * intentionally swapped in these calls!
-        */
-       r = transfer_mmap_regions(src_vmp, dst_vmp, VM_MMAPBASE, VM_MMAPTOP);
-
-       /* If the stack is not mapped at the VM_DATATOP, there might be some
-        * more regions hiding above the stack.  We also have to transfer
-        * those.
-        */
-       if (r == OK && VM_STACKTOP < VM_DATATOP)
-               r = transfer_mmap_regions(src_vmp, dst_vmp, VM_STACKTOP,
-                   VM_DATATOP);
-
-       return r;
+       /* Source and destination are intentionally swapped here! */
+       return map_proc_dyn_data(dst_vmp, src_vmp);
 }
 
 void *mmap(void *addr, size_t len, int f, int f2, int f3, off_t o)