mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 18:50:05 +01:00
Fix context switching race which could cause vcpu_pause()
to not actually do its job properly. This requires us to move clearing of VCPUF_running to be protected by the scheduler lock. Signed-off-by: Keir Fraser <keir@xensource.com>
This commit is contained in:
1 parent
a5479372a0
commit
02bf186308
6 files changed
+140
-92
No files matched your search
+12
-19
@@ -280,7 +280,6 @@ void cs01foo(void) {}
|
||||
|
||||
unsigned long context_switch_count = 0;
|
||||
|
||||
// context_switch
|
||||
void context_switch(struct vcpu *prev, struct vcpu *next)
|
||||
{
|
||||
//printk("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
|
||||
@@ -290,22 +289,14 @@ void context_switch(struct vcpu *prev, struct vcpu *next)
|
||||
//if (prev->domain->domain_id == 0 && next->domain->domain_id == 1) cs01foo();
|
||||
//printk("@@sw %d->%d\n",prev->domain->domain_id,next->domain->domain_id);
|
||||
#ifdef CONFIG_VTI
|
||||
unsigned long psr;
|
||||
/* Interrupt is enabled after next task is chosen.
|
||||
* So we have to disable it for stack switch.
|
||||
*/
|
||||
local_irq_save(psr);
|
||||
vtm_domain_out(prev);
|
||||
/* Housekeeping for prev domain */
|
||||
#endif // CONFIG_VTI
|
||||
|
||||
#endif
|
||||
context_switch_count++;
|
||||
switch_to(prev,next,prev);
|
||||
#ifdef CONFIG_VTI
|
||||
/* Post-setup for new domain */
|
||||
vtm_domain_in(current);
|
||||
local_irq_restore(psr);
|
||||
#endif // CONFIG_VTI
|
||||
#endif
|
||||
|
||||
// leave this debug for now: it acts as a heartbeat when more than
|
||||
// one domain is active
|
||||
{
|
||||
@@ -315,25 +306,27 @@ int id = ((struct vcpu *)current)->domain->domain_id & 0xf;
|
||||
if (!cnt[id]--) { printk("%x",id); cnt[id] = 500000; }
|
||||
if (!i--) { printk("+",id); i = 1000000; }
|
||||
}
|
||||
clear_bit(_VCPUF_running, &prev->vcpu_flags);
|
||||
//if (!is_idle_task(next->domain) )
|
||||
//send_guest_virq(next, VIRQ_TIMER);
|
||||
|
||||
#ifdef CONFIG_VTI
|
||||
if (VMX_DOMAIN(current))
|
||||
vmx_load_all_rr(current);
|
||||
return;
|
||||
#else // CONFIG_VTI
|
||||
#else
|
||||
if (!is_idle_task(current->domain)) {
|
||||
load_region_regs(current);
|
||||
if (vcpu_timer_expired(current)) vcpu_pend_timer(current);
|
||||
}
|
||||
if (vcpu_timer_expired(current)) vcpu_pend_timer(current);
|
||||
#endif // CONFIG_VTI
|
||||
#endif
|
||||
}
|
||||
|
||||
void context_switch_finalise(struct vcpu *next)
|
||||
{
|
||||
/* nothing to do */
|
||||
}
|
||||
|
||||
void continue_running(struct vcpu *same)
|
||||
{
|
||||
/* nothing to do */
|
||||
/* nothing to do */
|
||||
}
|
||||
|
||||
void panic_domain(struct pt_regs *regs, const char *fmt, ...)
|
||||
|
||||
+93
-63
@@ -48,6 +48,8 @@ boolean_param("noreboot", opt_noreboot);
|
||||
|
||||
struct percpu_ctxt {
|
||||
struct vcpu *curr_vcpu;
|
||||
unsigned int context_not_finalised;
|
||||
unsigned int dirty_segment_mask;
|
||||
} __cacheline_aligned;
|
||||
static struct percpu_ctxt percpu_ctxt[NR_CPUS];
|
||||
|
||||
@@ -541,51 +543,59 @@ void toggle_guest_mode(struct vcpu *v)
|
||||
__r; })
|
||||
|
||||
#if CONFIG_VMX
|
||||
#define load_msrs(_p, _n) if (vmx_switch_on) vmx_load_msrs((_p), (_n))
|
||||
#define load_msrs(n) if (vmx_switch_on) vmx_load_msrs(n)
|
||||
#else
|
||||
#define load_msrs(_p, _n) ((void)0)
|
||||
#define load_msrs(n) ((void)0)
|
||||
#endif
|
||||
|
||||
static void load_segments(struct vcpu *p, struct vcpu *n)
|
||||
/*
|
||||
* save_segments() writes a mask of segments which are dirty (non-zero),
|
||||
* allowing load_segments() to avoid some expensive segment loads and
|
||||
* MSR writes.
|
||||
*/
|
||||
#define DIRTY_DS 0x01
|
||||
#define DIRTY_ES 0x02
|
||||
#define DIRTY_FS 0x04
|
||||
#define DIRTY_GS 0x08
|
||||
#define DIRTY_FS_BASE 0x10
|
||||
#define DIRTY_GS_BASE_USER 0x20
|
||||
|
||||
static void load_segments(struct vcpu *n)
|
||||
{
|
||||
struct vcpu_guest_context *pctxt = &p->arch.guest_context;
|
||||
struct vcpu_guest_context *nctxt = &n->arch.guest_context;
|
||||
int all_segs_okay = 1;
|
||||
unsigned int dirty_segment_mask, cpu = smp_processor_id();
|
||||
|
||||
/* Load and clear the dirty segment mask. */
|
||||
dirty_segment_mask = percpu_ctxt[cpu].dirty_segment_mask;
|
||||
percpu_ctxt[cpu].dirty_segment_mask = 0;
|
||||
|
||||
/* Either selector != 0 ==> reload. */
|
||||
if ( unlikely(pctxt->user_regs.ds | nctxt->user_regs.ds) )
|
||||
if ( unlikely((dirty_segment_mask & DIRTY_DS) | nctxt->user_regs.ds) )
|
||||
all_segs_okay &= loadsegment(ds, nctxt->user_regs.ds);
|
||||
|
||||
/* Either selector != 0 ==> reload. */
|
||||
if ( unlikely(pctxt->user_regs.es | nctxt->user_regs.es) )
|
||||
if ( unlikely((dirty_segment_mask & DIRTY_ES) | nctxt->user_regs.es) )
|
||||
all_segs_okay &= loadsegment(es, nctxt->user_regs.es);
|
||||
|
||||
/*
|
||||
* Either selector != 0 ==> reload.
|
||||
* Also reload to reset FS_BASE if it was non-zero.
|
||||
*/
|
||||
if ( unlikely(pctxt->user_regs.fs |
|
||||
pctxt->fs_base |
|
||||
if ( unlikely((dirty_segment_mask & (DIRTY_FS | DIRTY_FS_BASE)) |
|
||||
nctxt->user_regs.fs) )
|
||||
{
|
||||
all_segs_okay &= loadsegment(fs, nctxt->user_regs.fs);
|
||||
if ( pctxt->user_regs.fs ) /* != 0 selector kills fs_base */
|
||||
pctxt->fs_base = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Either selector != 0 ==> reload.
|
||||
* Also reload to reset GS_BASE if it was non-zero.
|
||||
*/
|
||||
if ( unlikely(pctxt->user_regs.gs |
|
||||
pctxt->gs_base_user |
|
||||
if ( unlikely((dirty_segment_mask & (DIRTY_GS | DIRTY_GS_BASE_USER)) |
|
||||
nctxt->user_regs.gs) )
|
||||
{
|
||||
/* Reset GS_BASE with user %gs? */
|
||||
if ( pctxt->user_regs.gs || !nctxt->gs_base_user )
|
||||
if ( (dirty_segment_mask & DIRTY_GS) || !nctxt->gs_base_user )
|
||||
all_segs_okay &= loadsegment(gs, nctxt->user_regs.gs);
|
||||
if ( pctxt->user_regs.gs ) /* != 0 selector kills gs_base_user */
|
||||
pctxt->gs_base_user = 0;
|
||||
}
|
||||
|
||||
/* This can only be non-zero if selector is NULL. */
|
||||
@@ -650,7 +660,9 @@ static void load_segments(struct vcpu *p, struct vcpu *n)
|
||||
|
||||
static void save_segments(struct vcpu *v)
|
||||
{
|
||||
struct cpu_user_regs *regs = &v->arch.guest_context.user_regs;
|
||||
struct vcpu_guest_context *ctxt = &v->arch.guest_context;
|
||||
struct cpu_user_regs *regs = &ctxt->user_regs;
|
||||
unsigned int dirty_segment_mask = 0;
|
||||
|
||||
if ( VMX_DOMAIN(v) )
|
||||
rdmsrl(MSR_SHADOW_GS_BASE, v->arch.arch_vmx.msr_content.shadow_gs);
|
||||
@@ -659,18 +671,34 @@ static void save_segments(struct vcpu *v)
|
||||
__asm__ __volatile__ ( "movl %%es,%0" : "=m" (regs->es) );
|
||||
__asm__ __volatile__ ( "movl %%fs,%0" : "=m" (regs->fs) );
|
||||
__asm__ __volatile__ ( "movl %%gs,%0" : "=m" (regs->gs) );
|
||||
}
|
||||
|
||||
static void clear_segments(void)
|
||||
{
|
||||
__asm__ __volatile__ (
|
||||
" movl %0,%%ds; "
|
||||
" movl %0,%%es; "
|
||||
" movl %0,%%fs; "
|
||||
" movl %0,%%gs; "
|
||||
""safe_swapgs" "
|
||||
" movl %0,%%gs"
|
||||
: : "r" (0) );
|
||||
if ( regs->ds )
|
||||
dirty_segment_mask |= DIRTY_DS;
|
||||
|
||||
if ( regs->es )
|
||||
dirty_segment_mask |= DIRTY_ES;
|
||||
|
||||
if ( regs->fs )
|
||||
{
|
||||
dirty_segment_mask |= DIRTY_FS;
|
||||
ctxt->fs_base = 0; /* != 0 selector kills fs_base */
|
||||
}
|
||||
else if ( ctxt->fs_base )
|
||||
{
|
||||
dirty_segment_mask |= DIRTY_FS_BASE;
|
||||
}
|
||||
|
||||
if ( regs->gs )
|
||||
{
|
||||
dirty_segment_mask |= DIRTY_GS;
|
||||
ctxt->gs_base_user = 0; /* != 0 selector kills gs_base_user */
|
||||
}
|
||||
else if ( ctxt->gs_base_user )
|
||||
{
|
||||
dirty_segment_mask |= DIRTY_GS_BASE_USER;
|
||||
}
|
||||
|
||||
percpu_ctxt[smp_processor_id()].dirty_segment_mask = dirty_segment_mask;
|
||||
}
|
||||
|
||||
long do_switch_to_user(void)
|
||||
@@ -706,10 +734,9 @@ long do_switch_to_user(void)
|
||||
|
||||
#elif defined(__i386__)
|
||||
|
||||
#define load_segments(_p, _n) ((void)0)
|
||||
#define load_msrs(_p, _n) ((void)0)
|
||||
#define save_segments(_p) ((void)0)
|
||||
#define clear_segments() ((void)0)
|
||||
#define load_segments(n) ((void)0)
|
||||
#define load_msrs(n) ((void)0)
|
||||
#define save_segments(p) ((void)0)
|
||||
|
||||
static inline void switch_kernel_stack(struct vcpu *n, unsigned int cpu)
|
||||
{
|
||||
@@ -726,9 +753,9 @@ static inline void switch_kernel_stack(struct vcpu *n, unsigned int cpu)
|
||||
static void __context_switch(void)
|
||||
{
|
||||
struct cpu_user_regs *stack_regs = guest_cpu_user_regs();
|
||||
unsigned int cpu = smp_processor_id();
|
||||
struct vcpu *p = percpu_ctxt[cpu].curr_vcpu;
|
||||
struct vcpu *n = current;
|
||||
unsigned int cpu = smp_processor_id();
|
||||
struct vcpu *p = percpu_ctxt[cpu].curr_vcpu;
|
||||
struct vcpu *n = current;
|
||||
|
||||
if ( !is_idle_task(p->domain) )
|
||||
{
|
||||
@@ -786,23 +813,27 @@ static void __context_switch(void)
|
||||
|
||||
void context_switch(struct vcpu *prev, struct vcpu *next)
|
||||
{
|
||||
struct vcpu *realprev;
|
||||
|
||||
local_irq_disable();
|
||||
unsigned int cpu = smp_processor_id();
|
||||
|
||||
set_current(next);
|
||||
|
||||
if ( ((realprev = percpu_ctxt[smp_processor_id()].curr_vcpu) == next) ||
|
||||
is_idle_task(next->domain) )
|
||||
{
|
||||
local_irq_enable();
|
||||
}
|
||||
else
|
||||
if ( (percpu_ctxt[cpu].curr_vcpu != next) && !is_idle_task(next->domain) )
|
||||
{
|
||||
__context_switch();
|
||||
percpu_ctxt[cpu].context_not_finalised = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void context_switch_finalise(struct vcpu *next)
|
||||
{
|
||||
unsigned int cpu = smp_processor_id();
|
||||
|
||||
if ( percpu_ctxt[cpu].context_not_finalised )
|
||||
{
|
||||
percpu_ctxt[cpu].context_not_finalised = 0;
|
||||
|
||||
BUG_ON(percpu_ctxt[cpu].curr_vcpu != next);
|
||||
|
||||
local_irq_enable();
|
||||
|
||||
if ( VMX_DOMAIN(next) )
|
||||
{
|
||||
vmx_restore_msrs(next);
|
||||
@@ -810,19 +841,11 @@ void context_switch(struct vcpu *prev, struct vcpu *next)
|
||||
else
|
||||
{
|
||||
load_LDT(next);
|
||||
load_segments(realprev, next);
|
||||
load_msrs(realprev, next);
|
||||
load_segments(next);
|
||||
load_msrs(next);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We do this late on because it doesn't need to be protected by the
|
||||
* schedule_lock, and because we want this to be the very last use of
|
||||
* 'prev' (after this point, a dying domain's info structure may be freed
|
||||
* without warning).
|
||||
*/
|
||||
clear_bit(_VCPUF_running, &prev->vcpu_flags);
|
||||
|
||||
schedule_tail(next);
|
||||
BUG();
|
||||
}
|
||||
@@ -835,12 +858,19 @@ void continue_running(struct vcpu *same)
|
||||
|
||||
int __sync_lazy_execstate(void)
|
||||
{
|
||||
if ( percpu_ctxt[smp_processor_id()].curr_vcpu == current )
|
||||
return 0;
|
||||
__context_switch();
|
||||
load_LDT(current);
|
||||
clear_segments();
|
||||
return 1;
|
||||
unsigned long flags;
|
||||
int switch_required;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
switch_required = (percpu_ctxt[smp_processor_id()].curr_vcpu != current);
|
||||
|
||||
if ( switch_required )
|
||||
__context_switch();
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
return switch_required;
|
||||
}
|
||||
|
||||
void sync_lazy_execstate_cpu(unsigned int cpu)
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ static u32 msr_data_index[VMX_MSR_COUNT] =
|
||||
* are not modified once set for generic domains, we don't save them,
|
||||
* but simply reset them to the values set at percpu_traps_init().
|
||||
*/
|
||||
void vmx_load_msrs(struct vcpu *p, struct vcpu *n)
|
||||
void vmx_load_msrs(struct vcpu *n)
|
||||
{
|
||||
struct msr_state *host_state;
|
||||
host_state = &percpu_msr[smp_processor_id()];
|
||||
|
||||
+10
-5
@@ -474,13 +474,14 @@ static void __enter_scheduler(void)
|
||||
|
||||
set_ac_timer(&schedule_data[cpu].s_timer, now + r_time);
|
||||
|
||||
/* Must be protected by the schedule_lock! */
|
||||
set_bit(_VCPUF_running, &next->vcpu_flags);
|
||||
|
||||
spin_unlock_irq(&schedule_data[cpu].schedule_lock);
|
||||
|
||||
if ( unlikely(prev == next) )
|
||||
{
|
||||
spin_unlock_irq(&schedule_data[cpu].schedule_lock);
|
||||
return continue_running(prev);
|
||||
}
|
||||
|
||||
clear_bit(_VCPUF_running, &prev->vcpu_flags);
|
||||
set_bit(_VCPUF_running, &next->vcpu_flags);
|
||||
|
||||
perfc_incrc(sched_ctx);
|
||||
|
||||
@@ -517,6 +518,10 @@ static void __enter_scheduler(void)
|
||||
next->domain->domain_id, next->vcpu_id);
|
||||
|
||||
context_switch(prev, next);
|
||||
|
||||
spin_unlock_irq(&schedule_data[cpu].schedule_lock);
|
||||
|
||||
context_switch_finalise(next);
|
||||
}
|
||||
|
||||
/* No locking needed -- pointer comparison is safe :-) */
|
||||
|
||||
@@ -28,10 +28,10 @@ extern int start_vmx(void);
|
||||
extern void stop_vmx(void);
|
||||
|
||||
#if defined (__x86_64__)
|
||||
extern void vmx_load_msrs(struct vcpu *p, struct vcpu *n);
|
||||
extern void vmx_load_msrs(struct vcpu *n);
|
||||
void vmx_restore_msrs(struct vcpu *d);
|
||||
#else
|
||||
#define vmx_load_msrs(_p, _n) ((void)0)
|
||||
#define vmx_load_msrs(_n) ((void)0)
|
||||
#define vmx_restore_msrs(_v) ((void)0)
|
||||
#endif
|
||||
|
||||
|
||||
+22
-2
@@ -258,12 +258,32 @@ extern void sync_lazy_execstate_mask(cpumask_t mask);
|
||||
extern void sync_lazy_execstate_all(void);
|
||||
extern int __sync_lazy_execstate(void);
|
||||
|
||||
/* Called by the scheduler to switch to another vcpu. */
|
||||
/*
|
||||
* Called by the scheduler to switch to another VCPU. On entry, although
|
||||
* VCPUF_running is no longer asserted for @prev, its context is still running
|
||||
* on the local CPU and is not committed to memory. The local scheduler lock
|
||||
* is therefore still held, and interrupts are disabled, because the local CPU
|
||||
* is in an inconsistent state.
|
||||
*
|
||||
* The callee must ensure that the local CPU is no longer running in @prev's
|
||||
* context, and that the context is saved to memory, before returning.
|
||||
* Alternatively, if implementing lazy context switching, it suffices to ensure
|
||||
* that invoking __sync_lazy_execstate() will switch and commit @prev's state.
|
||||
*/
|
||||
extern void context_switch(
|
||||
struct vcpu *prev,
|
||||
struct vcpu *next);
|
||||
|
||||
/* Called by the scheduler to continue running the current vcpu. */
|
||||
/*
|
||||
* On some architectures (notably x86) it is not possible to entirely load
|
||||
* @next's context with interrupts disabled. These may implement a function to
|
||||
* finalise loading the new context after interrupts are re-enabled. This
|
||||
* function is not given @prev and is not permitted to access it.
|
||||
*/
|
||||
extern void context_switch_finalise(
|
||||
struct vcpu *next);
|
||||
|
||||
/* Called by the scheduler to continue running the current VCPU. */
|
||||
extern void continue_running(
|
||||
struct vcpu *same);
|
||||
|
||||
|
||||