lguest: block device speedup
Subsystems (2)
PATCH 1/9 lguest: block device speedup
Jens Axboe pointed out that end_request() does not end the entire request. Go figure. On the upside, he wrote the replacement for me! Now we do far less block traffic, and our performance sucks less.
drivers/block/lguest_blk.c +12 -2
@@ -45,6 +45,16 @@ struct blockdevstruct request *req; }; +/* Jens gave me this nice helper to end all chunks of a request. */ +static void end_entire_request (struct request *req,int uptodate) +{ +if (end_that_request_first (req, uptodate, req->hard_nr_sectors)) +BUG (); +add_disk_randomness (req->rq_disk); +blkdev_dequeue_request (req); +end_that_request_last (req, uptodate); +} +static irqreturn_t lgb_irq (int irq,void *_bd) {struct blockdev *bd = _bd;
@@ -61,7 +71,7 @@ static irqreturn_t lgb_irq(int irq, void }spin_lock_irqsave (&bd->lock, flags); -end_request (bd->req, bd->lb_page->result ==1 ); +end_entire_request (bd->req, bd->lb_page->result ==1 ); bd->req =NULL ; bd->dma.used_len =0 ;blk_start_queue (bd->disk->queue);
@@ -149,7 +159,7 @@ again:pr_debug ("Got non-command 0x%08x\n" , req->cmd_type); error: req->errors++; -end_request (req,0 ); +end_entire_request (req,0 ); goto again; }else {if (rq_data_dir (req) ==WRITE )
PATCH 2/9 lguest: bridging support in example code
Expand the --tunnet option to take a bridge name as an argument, so that the tap interface is added to the specified bridge. This makes it convenient to use bridging for connecting the guest to external networks.
Documentation/lguest/lguest.c +45 -12
@@ -23,7 +23,8 @@#include <sys/time.h> #include <time.h> #include <netinet/in.h> -#include <linux/if.h> +#include <net/if.h> +#include <linux/sockios.h> #include <linux/if_tun.h> #include <sys/uio.h> #include <termios.h>
@@ -36,6 +37,7 @@ typedef uint8_t u8;#define PAGE_PRESENT 0x7/* Present, RW, Execute */ #define NET_PEERNUM 1 +#define BRIDGE_PFX "bridge:"static bool verbose; #define verbose(args...) \
@@ -582,20 +584,16 @@ static u32 handle_block_output(int fd, c ((u8)(ip >>8 )), \ ((u8)(ip)) -static void configure_device (const char *devname,u32 ipaddr, +static void configure_device (int fd,const char *devname,u32 ipaddr,unsigned char hwaddr[6 ]) {struct ifreq ifr; -int fd;struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;memset (&ifr,0 ,sizeof (ifr));strcpy (ifr.ifr_name, devname); sin->sin_family =AF_INET ; sin->sin_addr.s_addr =htonl (ipaddr); - fd =socket (PF_INET ,SOCK_DGRAM ,IPPROTO_IP ); -if (fd <0 ) -err (1 ,"opening IP socket" );if (ioctl (fd,SIOCSIFADDR , &ifr) !=0 )err (1 ,"Setting %s interface address" , devname); ifr.ifr_flags =IFF_UP ;
@@ -724,13 +722,34 @@ static u32 str2ip(const char *ipaddr)return (byte[0 ] <<24 ) | (byte[1 ] <<16 ) | (byte[2 ] <<8 ) | byte[3 ]; } -static void setup_tun_net (const char *ipaddr, +/* adapted from libbridge */ +static void add_to_bridge (int fd,const char *if_name,const char *br_name) +{ +int ifidx; +struct ifreq ifr; + +if (!*br_name) +errx (1 ,"must specify bridge name" ); + + ifidx =if_nametoindex (if_name); +if (!ifidx) +errx (1 ,"interface %s does not exist!" , if_name); + +strncpy (ifr.ifr_name, br_name,IFNAMSIZ ); + ifr.ifr_ifindex = ifidx; +if (ioctl (fd,SIOCBRADDIF , &ifr) <0 ) +err (1 ,"can't add %s to bridge %s" , if_name, br_name); +} + +static void setup_tun_net (const char *arg,struct lguest_device_desc *descs,struct devices *devices) {struct device *dev;struct ifreq ifr; -int netfd; +int netfd, ipfd; +u32 ipaddr; +const char *br_name =NULL ; netfd =open ("/dev/net/tun" ,O_RDWR );if (netfd <0 )
@@ -748,15 +767,29 @@ static void setup_tun_net(const char *ip dev->priv =malloc (sizeof (bool )); *(bool *)dev->priv = false; + ipfd =socket (PF_INET ,SOCK_DGRAM ,IPPROTO_IP ); +if (ipfd <0 ) +err (1 ,"opening IP socket" ); + +if (!strncmp (BRIDGE_PFX , arg,strlen (BRIDGE_PFX ))) { + ipaddr =INADDR_ANY ; + br_name = arg +strlen (BRIDGE_PFX ); +add_to_bridge (ipfd, ifr.ifr_name, br_name); + }else + ipaddr =str2ip (arg); +/* We are peer 0, rest is all NO_GUEST */ memset (dev->mem,0xFF ,getpagesize ()); -configure_device (ifr.ifr_name,str2ip (ipaddr), dev->mem); +configure_device (ipfd, ifr.ifr_name, ipaddr, dev->mem); +close (ipfd);/* You will be peer 1: we should create enough jitter to randomize */ dev->desc->features =NET_PEERNUM |LGUEST_DEVICE_F_RANDOMNESS ;verbose ("device %p@%p: tun net %u.%u.%u.%u\n" , dev->desc, (void *)(dev->desc->pfn *getpagesize ()), -HIPQUAD (str2ip (ipaddr))); +HIPQUAD (ipaddr)); +if (br_name) +verbose ("attched to bridge: %s\n" , br_name); } static void setup_block_file(const char *filename,
@@ -887,8 +920,8 @@ int main(int argc, char *argv[])if (argc <4 )errx (1 ,"Usage: lguest [--verbose] <mem> vmlinux " -"[--sharenet=<filename>|--tunnet=<ipaddr>|--block=<filename>" -"|--initrd=<filename>]... [args...]" ); +"[--sharenet=<filename>|--tunnet=(<ipaddr>|bridge:<bridgename>)" +"|--block=<filename>|--initrd=<filename>]... [args...]" ); zero_fd =open ("/dev/zero" ,O_RDONLY ,0 );if (zero_fd <0 )
Documentation/lguest/lguest.txt +17 -1
@@ -77,10 +77,26 @@ Running Lguest: /proc/sys/net/ipv4/ip_forward". In this example, I would configure eth0 inside the guest at 192.168.19.2. + Another method is to bridge the tap device to an external interface + using --tunnet=bridge:<bridgename>, and perhaps run dhcp on the guest + to obtain an IP address. The bridge needs to be configured first: + this option simply adds the tap interface to it. + + A simple example on my system: + + ifconfig eth0 0.0.0.0 + brctl addbr lg0 + ifconfig lg0 up + dhclient lg0 + + Then use --tunnet=bridge:lg0 when launching the guest. + + See http://linux-net.osdl.org/index.php/Bridge for general information + on how to get bridging working. + - You can also create an inter-guest network using "--sharenet=<filename>": any two guests using the same file are on the same network. This file is created if it does not exist. - Lguest I/O model:
PATCH 3/9 lguest: cleanup: allocate separate pages for switcher code
We don't need physically-contiguous pages for the hypervisor, since we use map_vm_area anyway.
Two other related cleanups: pass the number of pages to init_pagetables() so we can remove the constant from the header, and call populate_hypervisor_pte_page() on each page as we allocate it, rather than as a separate loop.
arch/i386/lguest/core.c +29 -18
@@ -24,17 +24,21 @@ static char __initdata hypervisor_blob[]#include "hypervisor-blob.c" }; -#define MAX_LGUEST_GUESTS \ - (((PAGE_SIZE << HYPERVISOR_PAGE_ORDER) - sizeof(hypervisor_blob)) \ +/* 64k ought to be enough for anybody! */ +#define HYPERVISOR_PAGES (65536 / PAGE_SIZE) + +#define MAX_LGUEST_GUESTS \ + (((HYPERVISOR_PAGES * PAGE_SIZE) - sizeof(hypervisor_blob)) \ / sizeof(struct lguest_state))static struct vm_struct *hypervisor_vma; +/* Pages for hypervisor itself */ +static struct page *hype_page[HYPERVISOR_PAGES ];static int cpu_had_pge;static struct {unsigned long offset;unsigned short segment; } lguest_entry __attribute_used__; -struct page *hype_pages;/* Contiguous pages. */ struct lguest lguests[MAX_LGUEST_GUESTS ];DEFINE_MUTEX (lguest_lock);
@@ -58,15 +62,19 @@ struct lguest_state *__lguest_states(voistatic __init int map_hypervisor (void ) { -unsigned int i; -int err; -struct page *pages[HYPERVISOR_PAGES ], **pagep = pages; - - hype_pages =alloc_pages (GFP_KERNEL |__GFP_ZERO,HYPERVISOR_PAGE_ORDER ); -if (!hype_pages) -return -ENOMEM ; - - hypervisor_vma =__get_vm_area (PAGE_SIZE <<HYPERVISOR_PAGE_ORDER , +int i, err; +struct page **pagep = hype_page; + +for (i =0 ; i <ARRAY_SIZE (hype_page); i++) { +unsigned long addr =get_zeroed_page (GFP_KERNEL ); +if (!addr) { + err = -ENOMEM ; + goto free_some_pages; + } + hype_page[i] =virt_to_page (addr); + } + + hypervisor_vma =__get_vm_area (ARRAY_SIZE (hype_page) *PAGE_SIZE ,VM_ALLOC ,HYPE_ADDR ,VMALLOC_END );if (!hypervisor_vma) { err = -ENOMEM ;
@@ -74,9 +82,6 @@ static __init int map_hypervisor(void) goto free_pages; } -for (i =0 ; i <HYPERVISOR_PAGES ; i++) - pages[i] = hype_pages + i; - err =map_vm_area (hypervisor_vma,PAGE_KERNEL , &pagep);if (err) {printk ("lguest: map_vm_area failed: %i\n" , err);
@@ -100,14 +105,20 @@ free_vma: free_vma:vunmap (hypervisor_vma->addr); free_pages: -__free_pages (hype_pages,HYPERVISOR_PAGE_ORDER ); + i =ARRAY_SIZE (hype_page); +free_some_pages: +for (--i; i >=0 ; i--) +__free_pages (hype_page[i],0 );return err; } static __exit void unmap_hypervisor(void) { +unsigned int i; +vunmap (hypervisor_vma->addr); -__free_pages (hype_pages,HYPERVISOR_PAGE_ORDER ); +for (i =0 ; i <ARRAY_SIZE (hype_page); i++) +__free_pages (hype_page[i],0 ); }/* IN/OUT insns: enough to get us past boot-time probing. */
@@ -390,7 +401,7 @@ static int __init init(void)if (err)return err; - err =init_pagetables (hype_pages); + err =init_pagetables (hype_page,HYPERVISOR_PAGES );if (err) {unmap_hypervisor ();return err;
arch/i386/lguest/lg.h +1 -5
@@ -2,9 +2,6 @@#define _LGUEST_H#include <asm/desc.h> -/* 64k ought to be enough for anybody! */ -#define HYPERVISOR_PAGE_ORDER (16 - PAGE_SHIFT) -#define HYPERVISOR_PAGES (1 << HYPERVISOR_PAGE_ORDER)#define GDT_ENTRY_LGUEST_CS 10#define GDT_ENTRY_LGUEST_DS 11
@@ -43,7 +40,7 @@ struct lguest_regs }; __exit void free_pagetables(void); -__init int init_pagetables (struct page *hype_pages); +__init int init_pagetables (struct page **hype_page,int pages);/* Full 4G segment descriptors, suitable for CS and DS. */ #define FULL_EXEC_SEGMENT ((struct desc_struct){0x0000ffff, 0x00cf9b00})
@@ -122,7 +119,6 @@ struct lgueststruct host_trap interrupt[LGUEST_IRQS ]; }; -extern struct page *hype_pages;/* Contiguous pages. */ extern struct lguest lguests[];extern struct mutex lguest_lock;
arch/i386/lguest/page_tables.c +16 -27
61 lines not on this page · raw message
PATCH 4/9 lguest: cleanup: clean up regs save/restore
We previously put "cr3" in the guest regs restored and saved: the guest cannot change cr3, so saving it it silly. Hand it across to the host<->guest switcher in ebx.
While we're there, only save the host registers we need to; tell GCC we clobber everything we can.
Finally, and trap 2 (NMI) doesn't supply a error code (we don't handle NMI yet, but the test is wrong, so fix it before I get confused).
arch/i386/lguest/core.c +4 -4
@@ -260,11 +260,11 @@ static void run_guest_once(struct lguest {unsigned int clobber; -/* Put eflags on stack, lcall does rest. */ +/* Put eflags on stack, lcall does rest: suitable for iret return. */ asmvolatile ("pushf; lcall *lguest_entry" - :"=a" (clobber),"=d" (clobber) - :"0" (lg->state),"1" (get_idt_table ()) - :"memory" ); + :"=a" (clobber),"=d" (clobber),"=b" (clobber) + :"0" (lg->state),"1" (get_idt_table ()),"2" (lg->cr3) + :"memory" ,"%ecx" ,"%edi" ,"%esi" ); }int run_guest (struct lguest *lg,char *__user user)
arch/i386/lguest/hypervisor.S +30 -38
111 lines not on this page · raw message
arch/i386/lguest/lg.h +1 -1
@@ -24,7 +24,6 @@ struct lguest_regsstruct lguest_regs {/* Manually saved part. */ -u32 cr3;u32 ebx, ecx, edx;u32 esi, edi, ebp;u32 gs;
@@ -81,6 +80,7 @@ struct lguestu32 pfn_limit;u32 page_offset;u32 cr2; +u32 cr3;int timer_on;int halted;int ts;
arch/i386/lguest/lguest_user.c +2 -3
@@ -4,7 +4,7 @@#include <linux/fs.h> #include "lg.h" -static struct lguest_state *setup_guest_state (unsigned int num,void *pgdir, +static struct lguest_state *setup_guest_state (unsigned int num,unsigned long start) {struct lguest_state *guest = &__lguest_states ()[num];
@@ -38,7 +38,6 @@ static struct lguest_state *setup_guest_/* Write out stack in format lguest expects, so we can switch to it. */ regs = &guest->regs; - regs->cr3 =__pa (pgdir); regs->eax = regs->ebx = regs->ecx = regs->edx = regs->esp =0 ; regs->edi =LGUEST_MAGIC_EDI ; regs->ebp =LGUEST_MAGIC_EBP ;
@@ -149,7 +148,7 @@ static int initialize(struct file *file,if (err) goto free_trap_page; - lg->state =setup_guest_state (i, lg->pgdirs[lg->pgdidx].pgdir,args[2 ]); + lg->state =setup_guest_state (i, args[2 ]);if (!lg->state) { err = -ENOEXEC ; goto release_pgtable;
arch/i386/lguest/page_tables.c +2 -1
@@ -223,7 +223,7 @@ void guest_new_pagetable(struct lguest *if (newpgdir ==ARRAY_SIZE (lg->pgdirs)) newpgdir =new_pgdir (lg, pgtable); lg->pgdidx = newpgdir; - lg->state->regs.cr3 =__pa (lg->pgdirs[lg->pgdidx].pgdir); + lg->cr3 =__pa (lg->pgdirs[lg->pgdidx].pgdir);pin_stack_pages (lg); }
@@ -296,6 +296,7 @@ int init_guest_pagetable(struct lguest * lg->pgdirs[lg->pgdidx].pgdir = (u32 *)get_zeroed_page (GFP_KERNEL );if (!lg->pgdirs[lg->pgdidx].pgdir)return -ENOMEM ; + lg->cr3 =__pa (lg->pgdirs[lg->pgdidx].pgdir);return 0 ; }
PATCH 5/9 lguest: documentation fixes
1: It helps if you connect the bridge to a link.
2: You can theoretically run lguest with no boot parameters.
Documentation/lguest/lguest.c +1 -1
@@ -938,7 +938,7 @@ int main(int argc, char *argv[]) argc--; } -if (argc <4 ) +if (argc <3 )errx (1 ,"Usage: lguest [--verbose] <mem> vmlinux " "[--sharenet=<filename>|--tunnet=(<ipaddr>|bridge:<bridgename>)" "|--block=<filename>|--initrd=<filename>]... [args...]" );
Documentation/lguest/lguest.txt +1 -0
@@ -90,6 +90,7 @@ Running Lguest: ifconfig eth0 0.0.0.0 brctl addbr lg0 ifconfig lg0 up + brctl addif lg0 eth0 dhclient lg0 Then use --tunnet=bridge:lg0 when launching the guest.
PATCH 6/9 lguest: pin stack page optimization
We make sure that the stack is always mapped in pin_stack_pages by simply calling demand_page, but that calls get_user_pages() to find the pfn, which is way overkill since the page is almost certainly already mapped. So don't call pin_stack_pages every context switch (unless genuinely a completely clean context, all the kernel mappings are kept in sync), and when we do call it, have it check if it needs to call demand_page().
This speeds guest context switch by 25%:
Before:
Time for one context switch via pipe: 10606 nsec
After:
Time for one context switch via pipe: 7805 nsec
Native:
Time for one context switch via pipe: 4701 nsec
arch/i386/lguest/page_tables.c +25 -6
@@ -155,14 +158,29 @@ int demand_page(struct lguest *lg, u32 vreturn page_in (lg, vaddr, (write ? _PAGE_DIRTY :0 )|_PAGE_ACCESSED); } +/* This is much faster than the full demand_page logic. */ +static int page_writable (struct lguest *lg,unsigned long vaddr) +{ +u32 *top, *pte; + + top =toplev (lg, lg->pgdidx, vaddr); +if (!(*top & _PAGE_PRESENT)) +return 0 ; + + pte =pteof (lg, *top, vaddr); +return (*pte & (_PAGE_PRESENT|_PAGE_RW)) == (_PAGE_PRESENT|_PAGE_RW); +} +void pin_stack_pages (struct lguest *lg) {unsigned int i;u32 stack = lg->state->tss.esp1; -for (i =0 ; i < lg->stack_pages; i++) -if (!demand_page (lg, stack - i*PAGE_SIZE ,1 )) +for (i =0 ; i < lg->stack_pages; i++) { +if (!page_writable (lg, stack - i *PAGE_SIZE ) + && !demand_page (lg, stack - i *PAGE_SIZE ,1 ))kill_guest (lg,"bad stack page %i@%#x" , i, stack); + } }static unsigned int find_pgdir (struct lguest *lg,u32 pgtable)
@@ -198,7 +216,7 @@ void guest_pagetable_flush_user(struct lflush_user_mappings (lg, lg->pgdidx); } -static unsigned int new_pgdir (struct lguest *lg,u32 cr3) +static unsigned int new_pgdir (struct lguest *lg,u32 cr3,int *blank_pgdir) {unsigned int next;
@@ -207,6 +225,9 @@ static unsigned int new_pgdir(struct lgu lg->pgdirs[next].pgdir = (u32 *)get_zeroed_page (GFP_KERNEL );if (!lg->pgdirs[next].pgdir) next = lg->pgdidx; +else +/* There are no mappings: you'll need to re-pin */ + *blank_pgdir =1 ; } lg->pgdirs[next].cr3 = cr3;/* Release all the non-kernel mappings. */
@@ -217,14 +238,15 @@ static unsigned int new_pgdir(struct lguvoid guest_new_pagetable (struct lguest *lg,u32 pgtable) { -int newpgdir; +int newpgdir, repin =0 ; newpgdir =find_pgdir (lg, pgtable);if (newpgdir ==ARRAY_SIZE (lg->pgdirs)) - newpgdir =new_pgdir (lg, pgtable); + newpgdir =new_pgdir (lg, pgtable, &repin); lg->pgdidx = newpgdir; lg->cr3 =__pa (lg->pgdirs[lg->pgdidx].pgdir); -pin_stack_pages (lg); +if (repin) +pin_stack_pages (lg); }static void release_all_pagetables (struct lguest *lg)
PATCH 7/9 lguest: use read-only pages rather than segments to protect high-mapped switcher
The current lguest uses segment limits to ensure that the guest cannot reach the switcher code at the top of virtual memory. This is bad for two reasons:
1) It introduces complexity when the guest wants to use 4G segments (ie. glibc's __thread support).
2) It doesn't work on x86-64 boxes.
The alternative is used here: in the host we map the actual switcher code, two per-cpu pages. The switcher code and one per-cpu page are
read-only: the read-only page contains the saved host state and the GDT, IDT and TSS the guest is using. The other per-cpu page is the stack page for the hypervisor, which is writable by the guest. This is where we save the guest registers: it's safe because while we're doing this we know the (UP) guest isn't running.
Switching into the guest involves copying in the registers, GDT and IDT to this cpu's pages, the copying the registers out on the way back. This is optimized in another patch.
arch/i386/kernel/asm-offsets.c +10 -10
@@ -122,15 +122,15 @@ void foo(void)#ifdef CONFIG_LGUEST_GUEST BLANK ();OFFSET (LGUEST_DATA_irq_enabled, lguest_data, irq_enabled); -OFFSET (LGUEST_STATE_host_stackptr, lguest_state, host.stackptr); -OFFSET (LGUEST_STATE_host_pgdir, lguest_state, host.pgdir); -OFFSET (LGUEST_STATE_host_gdt, lguest_state, host.gdt); -OFFSET (LGUEST_STATE_host_idt, lguest_state, host.idt); -OFFSET (LGUEST_STATE_regs, lguest_state, regs); -OFFSET (LGUEST_STATE_gdt, lguest_state, gdt); -OFFSET (LGUEST_STATE_idt, lguest_state, idt); -OFFSET (LGUEST_STATE_gdt_table, lguest_state, gdt_table); -OFFSET (LGUEST_STATE_trapnum, lguest_state, regs.trapnum); -OFFSET (LGUEST_STATE_errcode, lguest_state, regs.errcode); +OFFSET (LGUEST_PAGES_host_gdt_desc, lguest_pages, state.host_gdt_desc); +OFFSET (LGUEST_PAGES_host_idt_desc, lguest_pages, state.host_idt_desc); +OFFSET (LGUEST_PAGES_host_cr3, lguest_pages, state.host_cr3); +OFFSET (LGUEST_PAGES_host_sp, lguest_pages, state.host_sp); +OFFSET (LGUEST_PAGES_guest_gdt_desc, lguest_pages,state.guest_gdt_desc); +OFFSET (LGUEST_PAGES_guest_idt_desc, lguest_pages,state.guest_idt_desc); +OFFSET (LGUEST_PAGES_guest_gdt, lguest_pages, state.guest_gdt); +OFFSET (LGUEST_PAGES_regs_trapnum, lguest_pages, regs.trapnum); +OFFSET (LGUEST_PAGES_regs_errcode, lguest_pages, regs.errcode); +OFFSET (LGUEST_PAGES_regs, lguest_pages, regs);#endif }
arch/i386/lguest/Makefile +2 -2
@@ -6,8 +6,8 @@ lg-objs := core.o hypercalls.o page_tabl lg-objs := core.o hypercalls.o page_tables.o interrupts_and_traps.o \ segments.o io.o lguest_user.o -# We use top 4MB for guest traps page, then hypervisor. */ -HYPE_ADDR := (0xFFC00000+4096) +# We use top 4MB for hypervisor. */ +HYPE_ADDR := 0xFFC00000 # The data is only 1k (256 interrupt handler pointers) HYPE_DATA_SIZE := 1024 CFLAGS += -DHYPE_ADDR="$(HYPE_ADDR)" -DHYPE_DATA_SIZE="$(HYPE_DATA_SIZE)"
arch/i386/lguest/core.c +86 -89
296 lines not on this page · raw message
arch/i386/lguest/hypercalls.c +2 -2
@@ -32,8 +32,8 @@ static void guest_set_stack(struct lgueskill_guest (lg,"bad stack segment %i" , seg);if (pages >2 )kill_guest (lg,"bad stack pages %u" , pages); - lg->state->tss.ss1 = seg; - lg->state->tss.esp1 = esp; + lg->ss1 = seg; + lg->esp1 = esp; lg->stack_pages = pages;pin_stack_pages (lg); }
arch/i386/lguest/hypervisor.S +46 -44
141 lines not on this page · raw message
arch/i386/lguest/interrupts_and_traps.c +163 -124
334 lines not on this page · raw message
arch/i386/lguest/lg.h +86 -85
245 lines not on this page · raw message
arch/i386/lguest/lguest_user.c +5 -58
101 lines not on this page · raw message
arch/i386/lguest/page_tables.c +19 -19
@@ -168,16 +168,10 @@ static int page_writable(struct lguest *return (*pte & (_PAGE_PRESENT|_PAGE_RW)) == (_PAGE_PRESENT|_PAGE_RW); } -void pin_stack_pages (struct lguest *lg) -{ -unsigned int i; -u32 stack = lg->state->tss.esp1; - -for (i =0 ; i < lg->stack_pages; i++) { -if (!page_writable (lg, stack - i *PAGE_SIZE ) - && !demand_page (lg, stack - i *PAGE_SIZE ,1 )) -kill_guest (lg,"bad stack page %i@%#x" , i, stack); - } +void pin_page (struct lguest *lg,u32 addr) +{ +if (!page_writable (lg, addr) && !demand_page (lg, addr,0 )) +kill_guest (lg,"bad trap page %#x" , addr); }static unsigned int find_pgdir (struct lguest *lg,u32 pgtable)
@@ -243,7 +237,7 @@ void guest_new_pagetable(struct lguest * lg->pgdidx = newpgdir; lg->cr3 =__pa (lg->pgdirs[lg->pgdidx].pgdir);if (repin) -pin_stack_pages (lg); +pin_trap_pages (lg); }static void release_all_pagetables (struct lguest *lg)
@@ -259,7 +253,7 @@ void guest_pagetable_clear_all(struct lgvoid guest_pagetable_clear_all (struct lguest *lg) {release_all_pagetables (lg); -pin_stack_pages (lg); +pin_trap_pages (lg); } static void do_set_pte(struct lguest *lg, int idx,
@@ -329,11 +323,9 @@ void free_guest_pagetable(struct lguest }/* Caller must be preempt-safe */ -void map_trap_page (struct lguest *lg) +void map_hypervisor_in_guest (struct lguest *lg) {int cpu =smp_processor_id (); - -hypervisor_pte_page (cpu)[0 ] = (__pa (lg->trap_page)|_PAGE_PRESENT);/* Since hypervisor less that 4MB, we simply mug top pte page. */ lg->pgdirs[lg->pgdidx].pgdir[HYPERVISOR_PGD_ENTRY] =
@@ -356,10 +348,18 @@ static __init void populate_hypervisor_pu32 *pte =hypervisor_pte_page (cpu);for (i =0 ; i < pages; i++) { -/* First entry set dynamically in map_trap_page */ - pte[i+1 ] = ((page_to_pfn (hype_page[i]) <<PAGE_SHIFT ) - | _PAGE_KERNEL_EXEC); - } + pte[i] = ((page_to_pfn (hype_page[i]) <<PAGE_SHIFT ) + | _PAGE_PRESENT|_PAGE_ACCESSED); + } + +/* We only map this CPU's pages, so guest can't see others. */ + i = pages + cpu*2 ; + +/* First page (regs) is rw, second (state) is ro. */ + pte[i] = ((page_to_pfn (hype_page[i]) <<PAGE_SHIFT ) + | _PAGE_PRESENT|_PAGE_ACCESSED|_PAGE_RW); + pte[i+1 ] = ((page_to_pfn (hype_page[i+1 ]) <<PAGE_SHIFT ) + | _PAGE_PRESENT|_PAGE_ACCESSED); }__init int init_pagetables (struct page **hype_page,int pages)
arch/i386/lguest/segments.c +78 -135
249 lines not on this page · raw message
include/asm-i386/lguest.h +0 -3
@@ -59,9 +59,6 @@ struct lguest_data/* Blocked interrupts. */ DECLARE_BITMAP (interrupts,LGUEST_IRQS ); -/* Last (userspace) address we got a GPF & reloaded gs. */ -unsigned int gs_gpf_eip; -/* Virtual address of page fault. */ unsigned long cr2;
PATCH 8/9 lguest: Optimize away copy in and out of per-cpu guest pages
Rather than copy in IDT, GDT and TSS every time, we only need do it when something has changed (ie. guest IDT/GDT/TSS has changed, or guest has changed CPU, or CPU has just run another guest).
For the registers, we simply allocate them an entire page and map that over the stack page in the guest.
This restores context switch speed to be comparable to the old segment-using lguest.
arch/i386/lguest/core.c +26 -28
@@ -37,6 +37,7 @@ static struct {unsigned short segment; }lguest_entry __attribute_used__;DEFINE_MUTEX (lguest_lock); +static DEFINE_PER_CPU(struct lguest *, last_guest);/* FIXME: Make dynamic. */ #define MAX_LGUEST_GUESTS 16
@@ -144,10 +145,10 @@ static int emulate_insn(struct lguest *l {u8 insn;unsigned int insnlen =0 , in =0 , shift =0 ; -unsigned long physaddr =guest_pa (lg, lg->regs.eip); +unsigned long physaddr =guest_pa (lg, lg->regs->eip);/* This only works for addresses in linear mapping... */ -if (lg->regs.eip < lg->page_offset) +if (lg->regs->eip < lg->page_offset)return 0 ;lhread (lg, &insn, physaddr,1 );
@@ -180,11 +181,11 @@ static int emulate_insn(struct lguest *lif (in) {/* Lower bit tells is whether it's a 16 or 32 bit access */ if (insn &0x1 ) - lg->regs.eax =0xFFFFFFFF ; + lg->regs->eax =0xFFFFFFFF ;else - lg->regs.eax |= (0xFFFF << shift); - } - lg->regs.eip += insnlen; + lg->regs->eax |= (0xFFFF << shift); + } + lg->regs->eip += insnlen;return 1 ; }
@@ -260,36 +261,35 @@ static void run_guest_once(struct lguest : "memory", "%edx", "%ecx", "%edi", "%esi"); } -static void copy_in_guest_info (struct lguest_pages *pages, -struct lguest *lg) -{ -/* Copy in regs. */ - pages->regs = lg->regs; - -/* TSS entries for direct traps. */ +static void copy_in_guest_info (struct lguest_pages *pages,struct lguest *lg) +{ +if (__get_cpu_var (last_guest) != lg || lg->last_pages != pages) { +__get_cpu_var (last_guest) = lg; + lg->last_pages = pages; + lg->changed =CHANGED_ALL ; + } + +/* These are pretty cheap, so we do them unconditionally. */ + pages->state.host_cr3 =__pa (current->mm->pgd); +map_hypervisor_in_guest (lg, pages); pages->state.guest_tss.esp1 = lg->esp1; pages->state.guest_tss.ss1 = lg->ss1; -/* CR3 */ - pages->state.host_cr3 =__pa (current->mm->pgd); -/* Copy direct trap entries. */ -copy_traps (lg, pages->state.guest_idt,lguest_default_idt_entries ()); +if (lg->changed &CHANGED_IDT ) +copy_traps (lg, pages->state.guest_idt, +lguest_default_idt_entries ());/* Copy all GDT entries but the TSS. */ -copy_gdt (lg, pages->state.guest_gdt); -} - -static void copy_out_guest_info (struct lguest *lg, -const struct lguest_pages *pages) -{ -/* We just want the regs back. */ - lg->regs = pages->regs; +if (lg->changed &CHANGED_GDT ) +copy_gdt (lg, pages->state.guest_gdt); + + lg->changed =0 ; }int run_guest (struct lguest *lg,char *__user user) { -struct lguest_regs *regs = &lg->regs; +struct lguest_regs *regs = lg->regs;while (!lg->dead) {unsigned int cr2 =0 ;/* Damn gcc */
@@ -327,10 +327,8 @@ int run_guest(struct lguest *lg, char *_set_ts (lg->ts); pages =lguest_pages (raw_smp_processor_id ()); -map_hypervisor_in_guest (lg);copy_in_guest_info (pages, lg);run_guest_once (lg, pages); -copy_out_guest_info (lg, pages);/* Save cr2 now if we page-faulted. */ if (regs->trapnum ==14 )
arch/i386/lguest/hypervisor.S +5 -10
@@ -76,6 +76,8 @@ switch_to_guest: /* Figure out where we are, based on stack (at top of regs). */ \ movl %esp, %eax; \ subl $LGUEST_PAGES_regs, %eax; \ + /* Put trap number in %ebx before we switch cr3 and lose it. */ \ + movl LGUEST_PAGES_regs_trapnum(%eax), %ebx; \ /* Switch to host page tables (host GDT, IDT and stack are in host \ mem, so need this first) */ \ movl LGUEST_PAGES_host_cr3(%eax), %edx; \
@@ -104,23 +106,15 @@ return_to_host: deliver_to_host: SWITCH_TO_HOST -decode_idt_and_jmp: /* Decode IDT and jump to hosts' irq handler. When that does iret, it * will return to run_guest_once. This is a feature. */ movl (LGUEST_PAGES_host_idt_desc+2)(%eax), %edx - movl LGUEST_PAGES_regs_trapnum(%eax), %eax - leal (%edx,%eax,8), %eax + leal (%edx,%ebx,8), %eax movzwl (%eax),%edx movl 4(%eax), %eax xorw %ax, %ax orl %eax, %edx jmp *%edx - -/* FIXME: NMI needs something completely different. Don't SWITCH_TO_HOST. */ -deliver_to_host_with_errcode: - SWITCH_TO_HOST - pushl LGUEST_PAGES_regs_errcode(%eax) - jmp decode_idt_and_jmp /* Real hardware interrupts are delivered straight to the host. Others cause us to return to run_guest_once so it can decide what to do. Note
@@ -154,7 +148,8 @@ default_idt_entries: default_idt_entries: .text IRQ_STUBS 0 1 return_to_host /* First two traps */ - IRQ_STUB 2 deliver_to_host_with_errcode /* NMI */ +/* FIXME: NMI needs something completely different. Don't SWITCH_TO_HOST. */ + IRQ_STUB 2 deliver_to_host /* NMI */ IRQ_STUBS 3 31 return_to_host /* Rest of traps */ IRQ_STUBS 32 127 deliver_to_host /* Real interrupts */ IRQ_STUB 128 return_to_host /* System call (overridden) */
arch/i386/lguest/interrupts_and_traps.c +4 -3
@@ -25,7 +25,7 @@ static void reflect_trap(struct lguest * {u32 __user *gstack;u32 eflags, ss, irq_enable; -struct lguest_regs *regs = &lg->regs; +struct lguest_regs *regs = lg->regs;/* If they want a ring change, we use new stack and push old ss/esp */ if ((regs->ss&0x3 ) !=GUEST_DPL ) {
@@ -121,11 +121,11 @@ void check_bug_kill(struct lguest *lg)void check_bug_kill (struct lguest *lg) {#ifdef CONFIG_BUG -u32 eip = lg->regs.eip -PAGE_OFFSET ; +u32 eip = lg->regs->eip -PAGE_OFFSET ;u16 insn;/* This only works for addresses in linear mapping... */ -if (lg->regs.eip <PAGE_OFFSET ) +if (lg->regs->eip <PAGE_OFFSET )return ;lhread (lg, &insn, eip,sizeof (insn));if (insn ==0x0b0f ) {
@@ -219,6 +219,7 @@ void load_guest_idt_entry(struct lguestif (num ==2 || num ==8 || num ==15 || num ==LGUEST_TRAP_ENTRY )return ; + lg->changed |=CHANGED_IDT ;if (num <ARRAY_SIZE (lg->idt))set_trap (lg, &lg->idt[num], num, lo, hi);else if (num ==SYSCALL_VECTOR )
arch/i386/lguest/lg.h +12 -2
@@ -118,10 +118,16 @@ struct lguest_pagesstruct lguest_ro_state state; } __attribute__((aligned(PAGE_SIZE))); +#define CHANGED_IDT 1 +#define CHANGED_GDT 2 +#define CHANGED_ALL 3 +/* The private info the thread maintains about the guest. */ struct lguest { -struct lguest_regs regs; +/* At end of a page shared mapped over lguest_pages in guest. */ +unsigned long regs_page; +struct lguest_regs *regs;struct lguest_data __user *lguest_data;struct task_struct *tsk;struct mm_struct *mm;/* == tsk->mm, but that becomes NULL on exit */
@@ -138,6 +144,10 @@ struct lguestu32 esp1;u8 ss1; +/* Bitmap of what has changed: see CHANGED_* above. */ +int changed; +struct lguest_pages *last_pages; +/* We keep a small number of these. */ u32 pgdidx;struct pgdir pgdirs[4 ];
@@ -210,7 +220,7 @@ void guest_pagetable_flush_user(struct lvoid guest_pagetable_flush_user (struct lguest *lg);void guest_set_pte (struct lguest *lg,unsigned long cr3,unsigned long vaddr,u32 val); -void map_hypervisor_in_guest (struct lguest *lg); +void map_hypervisor_in_guest (struct lguest *lg,struct lguest_pages *pages);int demand_page (struct lguest *info,u32 cr2,int write);void pin_page (struct lguest *lg,u32 addr);
arch/i386/lguest/lguest_user.c +12 -2
@@ -100,19 +100,28 @@ static int initialize(struct file *file, lg->guestid = i; lg->pfn_limit = args[0 ]; lg->page_offset = args[3 ]; + lg->regs_page =get_zeroed_page (GFP_KERNEL ); +if (!lg->regs_page) { + err = -ENOMEM ; + goto release_guest; + } + lg->regs = (void *)lg->regs_page +PAGE_SIZE -sizeof (*lg->regs); err =init_guest_pagetable (lg, args[1 ]);if (err) - goto release_guest; + goto free_regs; -setup_regs (&lg->regs, args[2 ]); +setup_regs (lg->regs, args[2 ]); lg->tsk = current; lg->mm =get_task_mm (current); + lg->last_pages =NULL ;mutex_unlock (&lguest_lock); file->private_data = lg;return sizeof (args); +free_regs: +free_page (lg->regs_page); release_guest:memset (lg,0 ,sizeof (*lg)); unlock:
@@ -160,6 +169,7 @@ static int close(struct inode *inode, stmmput (lg->mm);if (lg->dead != (void *)1 )kfree (lg->dead); +free_page (lg->regs_page);memset (lg,0 ,sizeof (*lg));mutex_unlock (&lguest_lock);return 0 ;
arch/i386/lguest/page_tables.c +9 -5
@@ -99,7 +99,7 @@ static u32 get_pte(struct lguest *lg, u3swapped. It'd be nice to have a callback when Linux wants to swap out. */ /* We fault pages in, which allows us to update accessed/dirty bits. -* Return NULL or the pte page. */ +* Return true if we got page. */ static int page_in (struct lguest *lg,u32 vaddr,unsigned flags) {u32 gtop, gpte;
@@ -323,13 +323,17 @@ void free_guest_pagetable(struct lguest }/* Caller must be preempt-safe */ -void map_hypervisor_in_guest (struct lguest *lg) -{ -int cpu =smp_processor_id (); +void map_hypervisor_in_guest (struct lguest *lg,struct lguest_pages *pages) +{ +u32 *hype_pte_page =__get_cpu_var (hypervisor_pte_pages);/* Since hypervisor less that 4MB, we simply mug top pte page. */ lg->pgdirs[lg->pgdidx].pgdir[HYPERVISOR_PGD_ENTRY ] = - (__pa (hypervisor_pte_page (cpu))| _PAGE_KERNEL); + (__pa (hype_pte_page) | _PAGE_KERNEL); + +/* Map our regs page over stack page. */ + hype_pte_page[(unsigned long )pages /PAGE_SIZE %PTES_PER_PAGE ] + = (__pa (lg->regs_page) | _PAGE_KERNEL); }static void free_hypervisor_pte_pages (void )
arch/i386/lguest/segments.c +11 -9
@@ -24,15 +24,15 @@ static int ignored_gdt(unsigned int num)/* We don't allow removal of CS, DS or SS; it doesn't make sense. */ static void check_segment_use (struct lguest *lg,unsigned int desc) { -if (lg->regs.gs /8 == desc) - lg->regs.gs =0 ; -if (lg->regs.fs /8 == desc) - lg->regs.fs =0 ; -if (lg->regs.es /8 == desc) - lg->regs.es =0 ; -if (lg->regs.ds /8 == desc - || lg->regs.cs /8 == desc - || lg->regs.ss /8 == desc) +if (lg->regs->gs /8 == desc) + lg->regs->gs =0 ; +if (lg->regs->fs /8 == desc) + lg->regs->fs =0 ; +if (lg->regs->es /8 == desc) + lg->regs->es =0 ; +if (lg->regs->ds /8 == desc + || lg->regs->cs /8 == desc + || lg->regs->ss /8 == desc)kill_guest (lg,"Removed live GDT entry %u" , desc); }
@@ -103,6 +103,7 @@ void load_guest_gdt(struct lguest *lg, ulhread (lg, lg->gdt, table, num *sizeof (lg->gdt[0 ]));fixup_gdt_table (lg); + lg->changed |=CHANGED_GDT ; }void guest_load_tls (struct lguest *lg,const struct desc_struct __user *gtls)
@@ -111,4 +112,5 @@ void guest_load_tls(struct lguest *lg, clhread (lg, tls, (u32 )gtls,sizeof (*tls)*GDT_ENTRY_TLS_ENTRIES );fixup_gdt_table (lg); + lg->changed |=CHANGED_GDT ; }
PATCH 9/9 lguest: don't crash host on NMI
"handle" NMI by ignoring it. Can't have been important, right? As the lguest64 hackers explained, handling NMI is a PITA. Now oprofile does not crash machine.
arch/i386/lguest/hypervisor.S +6 -2
@@ -116,6 +116,11 @@ deliver_to_host: orl %eax, %edx jmp *%edx +/* We ignore NMI and return. */ +handle_nmi: + addl $8, %esp + iret + /* Real hardware interrupts are delivered straight to the host. Others cause us to return to run_guest_once so it can decide what to do. Note that some of these are overridden by the guest to deliver directly, and
@@ -148,8 +153,7 @@ default_idt_entries: default_idt_entries: .text IRQ_STUBS 0 1 return_to_host /* First two traps */ -/* FIXME: NMI needs something completely different. Don't SWITCH_TO_HOST. */ - IRQ_STUB 2 deliver_to_host /* NMI */ + IRQ_STUB 2 handle_nmi /* NMI */ IRQ_STUBS 3 31 return_to_host /* Rest of traps */ IRQ_STUBS 32 127 deliver_to_host /* Real interrupts */ IRQ_STUB 128 return_to_host /* System call (overridden) */
Quoted 6 lines · Rusty Russell
diff -r
fdc8cbc1fd61drivers/block/lguest_blk.cdrivers/block/lguest_blk.c +0
-0@@ -45,6 +45,16 @@ struct blockdevstruct request *req; };
drivers/block/lguest_blk.c struct blockdev · line 56
Quoted 10 lines
drivers/block/lguest_blk.c +9
-0+/* Jens gave me this nice helper to end all chunks of a request. */ +static void end_entire_request (struct request *req,int uptodate) +{ +if (end_that_request_first (req, uptodate, req->hard_nr_sectors)) +BUG (); +add_disk_randomness (req->rq_disk); +blkdev_dequeue_request (req); +end_that_request_last (req, uptodate); +}
I think we really want this in common code, ll_rw_blk.c should have:
static int __end_request(struct request *req, int uptodate,
unsigned int sectors)
{
if (!end_that_request_first(req, uptodate, sectors)) {
add_disk_randomness(req->rq_disk);
blkdev_dequeue_request(req);
end_that_request_last(req, uptodate);
return 1;
}
return 0;
}/* TODO: add kerneldoc comment */
/* XXX: should be called end_partial_request */ void end_request(struct request *req, int uptodate)
{
__end_request(req, uptodate, req->hard_cur_sectors);
}/* TODO: add kerneldoc comment */
void end_entired_request(struct request *req, int uptodate)
{
if (!__end_request(req, uptodate, req->hard_nr_sectors))
BUG();
}the latter two maybe as inlines
Quoted 8 lines · Christoph Hellwig
Quoted 7 lines · Rusty Russell
diff -r
fdc8cbc1fd61drivers/block/lguest_blk.cdrivers/block/lguest_blk.c +0
-0@@ -45,6 +45,16 @@ struct blockdevstruct request *req; };
Quoted 11 lines
drivers/block/lguest_blk.c struct blockdev · line 56
Quoted 9 lines
drivers/block/lguest_blk.c +9
-0+/* Jens gave me this nice helper to end all chunks of a request. */ +static void end_entire_request (struct request *req,int uptodate) +{ +if (end_that_request_first (req, uptodate, req->hard_nr_sectors)) +BUG (); +add_disk_randomness (req->rq_disk); +blkdev_dequeue_request (req); +end_that_request_last (req, uptodate); +}I think we really want this in common code,
ll_rw_blk.cshould have:
Yeah I know, this is also what I wrote to Rusty when I reviewed this code and made the suggestion. The kernel also needs a full sweep to get rid of end_request() entirely, it's really a relic from ancient times.
Signature · 1 line
Jens Axboe