Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


61bf38f4e077a0ca11320115cb79b6aae194c8c1
[palacios.git] / nautilus / palacios-stubs.c
1 #include <nautilus/nautilus.h>
2 #include <nautilus/printk.h>
3 #include <nautilus/thread.h>
4 #include <nautilus/cpu.h>
5 #include <nautilus/mm.h>
6 #include <dev/timer.h>
7 #include <dev/apic.h>
8
9 #include <palacios/vmm.h>
10
11 #include "palacios.h"
12 #include "palacios-nautilus-mm.h"
13 #include "console.h"
14
15
16 /*
17   This is a simple proof-of-concept implementation of the Palacios
18   host interface on top of Nautilus.   It is sufficient to allow
19   us to boot a guest OS running Linux.   A few things to note:
20
21   - Nautilus currently has a grand-unified allocator designed to help
22     support parallel run-time integration.   All of alloc/valloc/page 
23     allocation are built on top of that. See palacios-nautilus-mm.c
24     for how this works for page allocation.
25   - For page allocation, constraints, NUMA, and filter expressions are
26     ignored.
27   - To make this work, you also need updates on the Nautilus side.
28     (these will eventually get into the Nautilus repo)
29   - thread migration is not supported currently
30   - hooking of host interrupts is not supported currently.
31   - Palacios can sleep, yield, wakeup, etc, but be aware
32     that Nautilus threads operate differently than those of
33     a traditional kernel.
34
35   Usage:
36   - Do Nautilus regular startup to bring all cores to idle
37   - From a kernel thread, ideally the init thread on core 0, 
38     do palacios_vmm_init(memory_size_bytes,options)
39   - Create, launch, etc, VMs using the Palacios v3_* functions
40     (note that these are NOT wrapped here)
41   - Console assumes void *the_vm is defined in the host, and it
42     is whatever a v3_create_vm() returned.    This is the VM that
43     has console access (keyboard and screen).
44   - After you are done, do a palacios_vmm_deinit();
45
46 */
47
48 // The following can be used to track memory bugs
49 // zero memory after allocation (now applies to valloc and page alloc as well)
50 #define ALLOC_ZERO_MEM 1
51 // pad allocations by this many bytes on both ends of block (heap only)
52 #define ALLOC_PAD       0
53 #define MAX_THREAD_NAME 32
54
55
56 int run_nk_thread = 0;
57
58 static struct v3_vm_info * irq_to_guest_map[256];
59
60 static unsigned int cpu_khz=-1;
61
62 static char *print_buffer[NR_CPUS];
63
64 static void deinit_print_buffers(void)
65 {
66     int i;
67
68     for (i=0;i<NR_CPUS;i++) {
69         if (print_buffer[i]) { 
70             palacios_free(print_buffer[i]);
71             print_buffer[i]=0;
72         }
73     }
74 }
75
76 static int init_print_buffers(void)
77 {
78     int i;
79     
80     memset(print_buffer,0,sizeof(char*)*NR_CPUS);
81
82
83     for (i=0;i<NR_CPUS;i++) { 
84         print_buffer[i] = palacios_alloc(V3_PRINTK_BUF_SIZE);
85         if (!print_buffer[i]) { 
86             ERROR("Cannot allocate print buffer for cpu %d\n",i);
87             deinit_print_buffers();
88             return -1;
89         }
90         memset(print_buffer[i],0,V3_PRINTK_BUF_SIZE);
91     }
92
93     
94     return 0;
95
96 }
97
98
99  
100 /**
101  * Prints a message to the console.
102  */
103 void palacios_print_scoped(void * vm, int vcore, const char *fmt, ...) 
104 {
105
106   va_list ap;
107   unsigned int cpu = palacios_get_cpu();
108   char *buf = cpu < NR_CPUS ? print_buffer[cpu] : 0;
109
110
111   if (!buf) { 
112       printk("palacios (pcore %u): output skipped - no allocated buffer\n",cpu);
113       return;
114   } 
115
116
117   va_start(ap, fmt);
118   vsnprintf(buf,V3_PRINTK_BUF_SIZE, fmt, ap);
119   va_end(ap);
120
121   if (vm) {
122     if (vcore>=0) { 
123       printk(KERN_INFO "palacios (pcore %u vm %s vcore %u): %s",
124              cpu,
125              "some_guest",
126              vcore,
127              buf);
128     } else {
129        printk(KERN_INFO "palacios (pcore %u vm %s): %s",
130              cpu,
131              "some_guest",
132              buf);
133     }
134   } else {
135     printk(KERN_INFO "palacios (pcore %u): %s",
136            cpu,
137            buf);
138   }
139     
140   return;
141 }
142
143
144
145 /*
146  * Allocates a contiguous region of pages of the requested size.
147  * Returns the physical address of the first page in the region.
148  */
149 void *palacios_allocate_pages(int num_pages, unsigned int alignment, int node_id, int (*filter_func)(void *paddr, void *filter_state), void *filter_state) 
150 {
151     void * pg_addr = NULL;
152
153     if (num_pages<=0) { 
154         ERROR("ALERT ALERT Attempt to allocate zero or fewer pages (%d pages, alignment %d, node %d, filter_func %p, filter_state %p)\n",num_pages, alignment, node_id, filter_func, filter_state);
155         return NULL;
156     }
157
158     pg_addr = (void *)alloc_palacios_pgs(num_pages, alignment, node_id, filter_func, filter_state);
159
160     if (!pg_addr) { 
161         ERROR("ALERT ALERT  Page allocation has FAILED Warning (%d pages, alignment %d, node %d, filter_func %p, filter_state %p)\n",num_pages, alignment, node_id, filter_func, filter_state);
162         return NULL;
163     }
164
165 #if ALLOC_ZERO_MEM
166     memset(pg_addr,0,num_pages*4096);
167 #endif
168
169     //    INFO("allocpages: %p (%llu pages) alignment=%u\n", pg_addr, num_pages, alignment);
170
171     return pg_addr;
172 }
173
174
175 /**
176  * Frees a page previously allocated via palacios_allocate_page().
177  * Note that palacios_allocate_page() can allocate multiple pages with
178  * a single call while palacios_free_page() only frees a single page.
179  */
180
181 void palacios_free_pages(void * page_paddr, int num_pages) {
182     if (!page_paddr) { 
183         ERROR("Ignoring free pages: 0x%p (0x%lx)for %d pages\n", page_paddr, (uintptr_t)page_paddr, num_pages);
184         return;
185     }
186     free_palacios_pgs((uintptr_t)page_paddr, num_pages);
187
188     //    INFO("freepages: %p (%llu pages) alignment=%u\n", page_paddr, num_pages);
189 }
190
191
192 void *
193 palacios_alloc_extended(unsigned int size, unsigned int flags, int node) {
194     void * addr = NULL;
195
196     if (size==0) { 
197       ERROR("ALERT ALERT attempt to kmalloc zero bytes rejected\n");
198       return NULL;
199     }
200
201     if (node==-1) { 
202         addr = malloc(size+2*ALLOC_PAD);
203     } else {
204         // currently no numa-zone specific kmalloc
205         addr = malloc(size+2*ALLOC_PAD);
206     }
207
208     if (!addr) { 
209        ERROR("ALERT ALERT  kmalloc has FAILED FAILED FAILED\n");
210        return NULL;
211     }   
212
213 #if ALLOC_ZERO_MEM
214     memset(addr,0,size+2*ALLOC_PAD);
215 #endif
216
217     //INFO("malloc: 0x%p (%llu bytes)\n",addr+ALLOC_PAD,size);
218
219     return addr+ALLOC_PAD;
220 }
221
222 void *
223 palacios_valloc(unsigned int size)
224 {
225     void * addr = NULL;
226
227     if (size==0) { 
228       ERROR("ALERT ALERT attempt to vmalloc zero bytes rejected\n");
229       return NULL;
230     }
231
232     // currently no vmalloc
233     addr = malloc(size);
234
235     if (!addr) {
236        ERROR("ALERT ALERT  vmalloc has FAILED FAILED FAILED\n");
237        return NULL;
238     }   
239
240 #if ALLOC_ZERO_MEM
241     memset(addr,0,size);
242 #endif
243
244     //INFO("valloc: 0x%p (%llu bytes)\n",addr,size);
245
246     return addr;
247 }
248
249 void palacios_vfree(void *p)
250 {
251   if (!p) { 
252       ERROR("Ignoring vfree: 0x%p\n",p);
253       return;
254   }
255   // no vfree currently
256   free(p);
257
258   //INFO("vfree: 0x%p\n",p);
259 }
260
261 /**
262  * Allocates 'size' bytes of kernel memory.
263  * Returns the kernel virtual address of the memory allocated.
264  */
265 void *
266 palacios_alloc(unsigned int size) 
267 {
268     return palacios_alloc_extended(size,0,-1);
269 }
270
271 /**
272  * Frees memory that was previously allocated by palacios_alloc().
273  */
274 void
275 palacios_free(void *addr)
276 {
277     return;
278     if (!addr) {
279         ERROR("Ignoring free : 0x%p\n", addr);
280         return;
281     }
282     // no kfree
283     free(addr-ALLOC_PAD);
284     //INFO("free: %p\n",addr-ALLOC_PAD);
285 }
286
287 /**
288  * Converts a kernel virtual address to the corresponding physical address.
289  */
290 void *
291 palacios_vaddr_to_paddr(
292         void *                  vaddr
293 )
294 {
295     return vaddr; // our memory mapping is identity
296
297 }
298
299 /**
300  * Converts a physical address to the corresponding kernel virtual address.
301  */
302 void *
303 palacios_paddr_to_vaddr(
304         void *                  paddr
305 )
306 {
307     return paddr; // our memory mapping is identity
308 }
309
310 /**
311  * Runs a function on the specified CPU.
312  */
313 void 
314 palacios_xcall(
315         int                     cpu_id, 
316         void                    (*fn)(void *arg),
317         void *                  arg
318 )
319 {
320
321     smp_xcall(cpu_id,fn,arg,1);
322
323     return;
324 }
325
326
327
328 struct nautilus_thread_arg {
329     int (*fn)(void * arg);
330     void *arg; 
331     char name[MAX_THREAD_NAME];
332 };
333
334 static void nautilus_thread_target(void * in, void ** out) 
335 {
336     struct nautilus_thread_arg * thread_info = (struct nautilus_thread_arg *)in;
337     int ret;
338
339     ret = thread_info->fn(thread_info->arg);
340
341     INFO("Palacios Thread (%s) EXITING with return code %d\n", thread_info->name, ret);
342
343     palacios_free(thread_info); 
344 }
345
346 /**
347  * Creates a kernel thread.
348  */
349 void *
350 palacios_create_and_start_kernel_thread(
351         int (*fn)               (void * arg),
352         void *                  arg,
353         char *                  thread_name,
354         v3_resource_control_t   *rctl) 
355 {
356     
357     struct nautilus_thread_arg * thread_info = palacios_alloc(sizeof(struct nautilus_thread_arg));
358     nk_thread_id_t tid = 0;
359     
360     if (!thread_info) { 
361         ERROR("ALERT ALERT Unable to allocate thread\n");
362         return NULL;
363     }
364     
365     thread_info->fn = fn;
366     thread_info->arg = arg;
367     strncpy(thread_info->name,thread_name,MAX_THREAD_NAME);
368     thread_info->name[MAX_THREAD_NAME-1] =0;
369     
370     nk_thread_start(nautilus_thread_target, thread_info, 0, 0, 0, &tid, CPU_ANY);
371
372     return tid;
373 }
374
375
376 /**
377  * Starts a kernel thread on the specified CPU.
378  */
379 void * 
380 palacios_create_thread_on_cpu(int cpu_id,
381                               int (*fn)(void * arg), 
382                               void * arg, 
383                               char * thread_name,
384                               v3_resource_control_t *rctl) 
385 {
386     nk_thread_id_t newtid;
387     nk_thread_t * newthread = NULL;
388     struct nautilus_thread_arg * thread_info = palacios_alloc(sizeof(struct nautilus_thread_arg));
389     
390     thread_info->fn = fn;
391     thread_info->arg = arg;
392     strncpy(thread_info->name, thread_name, MAX_THREAD_NAME);
393     thread_info->name[MAX_THREAD_NAME-1] = 0;
394     
395     //INFO("CREATING A THREAD ON CPU ID: %d\n", cpu_id);
396     
397     if (nk_thread_create(nautilus_thread_target, thread_info, 0, 0, 0, &newtid, cpu_id) < 0) {
398         ERROR("COULD NOT CREATE THREAD\n");
399         return NULL;
400     }
401     //INFO("newtid: %lu\n", newtid);
402     
403     return newtid;
404 }
405
406 void
407 palacios_start_thread(void * th)
408 {
409     nk_thread_run(th);
410 }
411
412 /*
413   Convenience wrapper
414 */
415 void * 
416 palacios_create_and_start_thread_on_cpu(int cpu_id,
417                                         int (*fn)(void * arg), 
418                                         void * arg, 
419                                         char * thread_name,
420                                         v3_resource_control_t *rctl ) 
421 {
422
423     nk_thread_id_t tid;
424
425     struct nautilus_thread_arg * thread_info = palacios_alloc(sizeof(struct nautilus_thread_arg));
426
427     if (!thread_info) { 
428         ERROR("ALERT ALERT Unable to allocate thread to start on cpu\n");
429         return NULL;
430     }
431
432     thread_info->fn = fn;
433     thread_info->arg = arg;
434     strncpy(thread_info->name,thread_name,MAX_THREAD_NAME);
435     thread_info->name[MAX_THREAD_NAME-1] =0;
436
437     nk_thread_start(nautilus_thread_target, thread_info, 0, 0, 0,&tid,cpu_id); //
438
439     return tid;
440 }
441
442
443
444 /**
445  * Rebind a kernel thread to the specified CPU
446  * The thread will be running on target CPU on return
447  * non-zero return means failure
448  */
449 int
450 palacios_move_thread_to_cpu(int new_cpu_id, 
451                             void * thread_ptr) 
452 {
453
454     INFO("Moving thread (%p) to cpu %d\n", thread_ptr, new_cpu_id);
455     ERROR("NOT CURRENTLY SUPPORTED\n");
456     return -1;
457 }
458
459
460 /**
461  * Returns the CPU ID that the caller is running on.
462  */
463 unsigned int 
464 palacios_get_cpu(void) 
465 {
466     return  my_cpu_id(); 
467 }
468
469 static void
470 palacios_interrupt_cpu( struct v3_vm_info *     vm, 
471                         int                     cpu_id, 
472                         int                     vector)
473 {
474   apic_ipi(per_cpu_get(apic),cpu_id,vector); // find out apic_dev * and cpu to apic id mapping 
475 }
476
477 struct pt_regs;
478
479
480 /**
481  * Dispatches an interrupt to Palacios for handling.
482  */
483 static void
484 palacios_dispatch_interrupt( int vector, void * dev, struct pt_regs * regs ) {
485     struct v3_interrupt intr = {
486         .irq            = vector,
487         .error          = 0, //regs->orig_ax, /* TODO fix this */
488         .should_ack     = 1,
489     };
490     
491     if (irq_to_guest_map[vector]) {
492         v3_deliver_irq(irq_to_guest_map[vector], &intr);
493     }
494     
495 }
496
497 /**
498  * Instructs the kernel to forward the specified IRQ to Palacios.
499  */
500 static int
501 palacios_hook_interrupt(struct v3_vm_info *     vm,
502                         unsigned int            vector ) 
503 {
504     ERROR("UNSUPPORTED: PALACIOS_HOOK_INTERRUPT\n");
505     return -1;
506 }
507
508
509 /**
510  * Acknowledges an interrupt.
511  */
512 static int
513 palacios_ack_interrupt(
514         int                     vector
515
516 {
517     ERROR("UNSUPPORTED: PALACIOS_ACK_INTERRUPT\n");
518     return -1;
519 }
520   
521 /**
522  * Returns the CPU frequency in kilohertz.
523  */
524 unsigned int
525 palacios_get_cpu_khz(void) 
526 {
527     if (cpu_khz==-1) { 
528         uint32_t cpu = (uint32_t)my_cpu_id();
529         
530         cpu_khz = nk_detect_cpu_freq(cpu);
531         if (cpu_khz==-1) {
532             INFO("CANNOT GET THE CPU FREQUENCY. FAKING TO 1000000\n");
533             cpu_khz=1000000;
534         }
535     }
536     INFO("Nautilus frequency at %u KHz\n",cpu_khz);
537     return cpu_khz;
538 }
539
540 /**
541  * Yield the CPU so other host OS tasks can run.
542  * This will return immediately if there is no other thread that is runnable
543  * And there is no real bound on how long it will yield
544  */
545 void
546 palacios_yield_cpu(void)
547 {
548     nk_yield();
549     return;
550 }
551
552 /**
553  * Yield the CPU so other host OS tasks can run.
554  * Given now immediately if there is no other thread that is runnable
555  * And there is no real bound on how long it will yield
556  */
557 void palacios_sleep_cpu(unsigned int us)
558 {
559     // sleep not supported on Nautilus
560     // just yield
561     nk_yield();
562     udelay(us);
563 }
564
565 void palacios_wakeup_cpu(void *thread)
566 {
567     // threads never go to sleep, so shouldn't happen
568     ERROR("ERROR ERROR: WAKEUP_CPU CALLED. THREADS ARE NEVER ASLEEP");
569     return;
570 }
571
572 /**
573  * Allocates a mutex.
574  * Returns NULL on failure.
575  */
576 void *
577 palacios_mutex_alloc(void)
578 {
579     spinlock_t *lock = palacios_alloc(sizeof(spinlock_t));
580     
581     if (lock) {
582         spinlock_init(lock);
583     } else {
584         ERROR("ALERT ALERT Unable to allocate lock\n");
585         return NULL;
586     }
587     
588     return lock;
589 }
590
591 void palacios_mutex_init(void *mutex)
592 {
593     spinlock_t *lock = (spinlock_t*)mutex;
594     
595     if (lock) {
596         spinlock_init(lock);
597         LOCKCHECK_ALLOC(lock);
598     }
599     
600 }
601
602 void palacios_mutex_deinit(void *mutex)
603 {
604     spinlock_t *lock = (spinlock_t*)mutex;
605   
606     if (lock) {
607         spinlock_deinit(lock);
608         LOCKCHECK_FREE(lock);
609     }
610 }
611
612
613 /**
614  * Frees a mutex.
615  */
616 void
617 palacios_mutex_free(void * mutex) {
618     palacios_free(mutex);
619     LOCKCHECK_FREE(mutex);
620 }
621
622 /**
623  * Locks a mutex.
624  */
625 void 
626 palacios_mutex_lock(void * mutex, int must_spin) {
627     LOCKCHECK_LOCK_PRE(mutex);
628     spin_lock((spinlock_t *)mutex);
629     LOCKCHECK_LOCK_POST(mutex);
630 }
631
632
633 /**
634  * Locks a mutex, disabling interrupts on this core
635  */
636 void *
637 palacios_mutex_lock_irqsave(void * mutex, int must_spin) {
638     
639     unsigned long flags; 
640     
641     LOCKCHECK_LOCK_IRQSAVE_PRE(mutex,flags);
642     flags = spin_lock_irq_save((spinlock_t *)mutex);
643     LOCKCHECK_LOCK_IRQSAVE_POST(mutex,flags);
644
645     //INFO("lock irqsave flags=%lu\n",flags);
646     return (void *)flags;
647 }
648
649
650 /**
651  * Unlocks a mutex.
652  */
653 void 
654 palacios_mutex_unlock(
655         void *                  mutex
656
657 {
658     LOCKCHECK_UNLOCK_PRE(mutex);
659     spin_unlock((spinlock_t *)mutex);
660     LOCKCHECK_UNLOCK_POST(mutex);
661 }
662
663
664 /**
665  * Unlocks a mutex and restores previous interrupt state on this core
666  */
667 void 
668 palacios_mutex_unlock_irqrestore(void *mutex, void *flags)
669 {
670     //INFO("unlock irqrestore flags=%lu\n",(unsigned long)flags);
671     LOCKCHECK_UNLOCK_IRQRESTORE_PRE(mutex,(unsigned long)flags);
672     // This is correct, flags is opaque
673     spin_unlock_irq_restore((spinlock_t *)mutex,(uint8_t) (unsigned long)flags);
674     LOCKCHECK_UNLOCK_IRQRESTORE_POST(mutex,(unsigned long)flags);
675 }
676
677
678 /**
679  * Structure used by the Palacios hypervisor to interface with the host kernel.
680  */
681 static struct v3_os_hooks palacios_os_hooks = {
682         .print                      = palacios_print_scoped, 
683         .allocate_pages             = palacios_allocate_pages,  
684         .free_pages                 = palacios_free_pages, 
685         .vmalloc                    = palacios_valloc, 
686         .vfree                      = palacios_vfree, 
687         .malloc                     = palacios_alloc, 
688         .free                       = palacios_free, 
689         .vaddr_to_paddr             = palacios_vaddr_to_paddr,  
690         .paddr_to_vaddr             = palacios_paddr_to_vaddr,  
691         .hook_interrupt             = palacios_hook_interrupt,  
692         .ack_irq                    = palacios_ack_interrupt,  
693         .get_cpu_khz                = palacios_get_cpu_khz, 
694         .start_kernel_thread        = palacios_create_and_start_kernel_thread, 
695         .yield_cpu                  = palacios_yield_cpu, 
696         .sleep_cpu                  = palacios_sleep_cpu, 
697         .wakeup_cpu                 = palacios_wakeup_cpu, 
698         .mutex_alloc                = palacios_mutex_alloc, 
699         .mutex_free                 = palacios_mutex_free, 
700         .mutex_lock                 = palacios_mutex_lock, 
701         .mutex_unlock               = palacios_mutex_unlock, 
702         .mutex_lock_irqsave         = palacios_mutex_lock_irqsave,  
703         .mutex_unlock_irqrestore    = palacios_mutex_unlock_irqrestore, 
704         .get_cpu                    = palacios_get_cpu, 
705         .interrupt_cpu              = palacios_interrupt_cpu, 
706         .call_on_cpu                = palacios_xcall, 
707         .create_thread_on_cpu       = palacios_create_thread_on_cpu, 
708         .start_thread               = palacios_start_thread, 
709         .move_thread_to_cpu         = palacios_move_thread_to_cpu, // unsupported
710 };
711
712
713
714
715 int palacios_vmm_init(uint64_t memsize, char * options)
716 {
717     int num_cpus = nautilus_info.sys.num_cpus;
718     char * cpu_mask = NULL;
719
720     if (num_cpus > 0) {
721         int major = 0;
722         int minor = 0;
723         int i = 0;
724
725         cpu_mask = palacios_alloc((num_cpus / 8) + 1);
726         
727         if (!cpu_mask) { 
728             ERROR("Cannot allocate cpu mask\n");
729             return -1;
730         }
731
732         memset(cpu_mask, 0, (num_cpus / 8) + 1);
733         
734         for (i = 0; i < num_cpus; i++) {
735
736             major = i / 8;
737             minor = i % 8;
738     
739             *(cpu_mask + major) |= (0x1 << minor);
740         }
741     }
742
743     INFO("calling palacios-mm init\n");
744     if (init_palacios_nautilus_mm(memsize)) { 
745         ERROR("Failted to initialize memory management\n");
746         return -1;
747     }
748     INFO("palacios-mm init done\n");
749     
750     memset(irq_to_guest_map, 0, sizeof(struct v3_vm_info *) * 256);
751
752     if (init_print_buffers()) {
753         INFO("Cannot initialize print buffers\n");
754         palacios_free(cpu_mask);
755         return -1;
756     }
757     
758     INFO("printbuffer init done\n");
759
760     //palacios_print_scoped(0, 0, "Hi%llu\n", 134217728);
761
762     INFO("NR_CPU: %d\n", NR_CPUS);
763
764     INFO("palacios_init starting - calling init_v3\n");
765
766     INFO("calling init_v3 = %p\n", Init_V3);
767
768     INFO("num_cpus: %d\ncpu_mask: %x\noptions: %s\n", num_cpus, *cpu_mask, options);
769
770     Init_V3(&palacios_os_hooks, cpu_mask, num_cpus, options);
771
772     INFO("init_v3 done\n");
773
774 #ifdef V3_CONFIG_CONSOLE
775     INFO("Initializing console\n");
776     nautilus_console_init();
777 #endif
778
779
780     return 0;
781
782 }
783
784
785 int palacios_vmm_exit( void ) 
786 {
787
788 #ifdef V3_CONFIG_CONSOLE
789     nautilus_console_deinit();
790 #endif
791
792     Shutdown_V3();
793
794     INFO("palacios shutdown complete\n");
795
796     deinit_print_buffers();
797
798     deinit_palacios_nautilus_mm(); // free memory from the allocator
799
800     return 0;
801 }