|
超级调用是特权操作,有xen来实现.采用的是软中断的方式.它是整个xen的基础.
具体的实现是首先我们有一些超级调用的处理函数,和他们对应的超级调用号,有一个索引文件来记录他们的对应关系.
超级调用发生在guest的内核层,某一个函数如果需要使用超级调用,则会先跳转到超级调用页的相应地方,通过int82h 内陷进入xen中,搜索索引来跳转到超级调用的处理程序.
如果是guest的用户态,必须先进入内核态,通过调用内核态的函数来调用超级调用.
通过xen4.4的代码来分析一下这个过程:
在Hypercall-x86_64.h中记录了guest中内核超级调用函数
static inline int
HYPERVISOR_set_trap_table(
trap_info_t *table)
{
return _hypercall1(int, set_trap_table, table);
}
这个超级调用调用了_hypercall1 (和上面文件的地方相同)
#define _hypercall1(type, name, a1)\
({\
long __res, __ign1;\
asm volatile (\
"call hypercall_page + ("STR(__HYPERVISOR_##name)" * 32)"\
: "=a" (__res), "=D" (__ign1)\
: "1" ((long)(a1))\
: "memory" );\
(type)__res;\
})
_hypercallN其实都是跳转到超级调用对应的
超级调用页的初始化(xen/arch/x86/x86_64/traps.c)void hypercall_page_initialise(struct domain *d, void *hypercall_page)
{
memset(hypercall_page, 0xCC, PAGE_SIZE);
if ( has_hvm_container_domain(d) )
hvm_hypercall_page_initialise(d, hypercall_page);
else if ( !is_pv_32bit_domain(d) )
hypercall_page_initialise_ring3_kernel(hypercall_page);
else
hypercall_page_initialise_ring1_kernel(hypercall_page);
}超级调用页中写的一些什么呢,我们看其中一个
static void hypercall_page_initialise_ring3_kernel(void *hypercall_page)
{
char *p;
int i;
/* Fill in all the transfer points with template machine code. */
for ( i = 0; i < (PAGE_SIZE / 32); i++ )
{
if ( i == __HYPERVISOR_iret )
continue;
p = (char *)(hypercall_page + (i * 32));
*(u8 *)(p+ 0) = 0x51; /* push %rcx */
*(u16 *)(p+ 1) = 0x5341; /* push %r11 */
*(u8 *)(p+ 3) = 0xb8; /* mov $<i>,%eax */
*(u32 *)(p+ 4) = i;
*(u16 *)(p+ 8) = 0x050f; /* syscall */
*(u16 *)(p+10) = 0x5b41; /* pop %r11 */
*(u8 *)(p+12) = 0x59; /* pop %rcx */
*(u8 *)(p+13) = 0xc3; /* ret */
}
/*
* HYPERVISOR_iret is special because it doesn't return and expects a
* special stack frame. Guests jump at this transfer point instead of
* calling it.
*/
p = (char *)(hypercall_page + (__HYPERVISOR_iret * 32));
*(u8 *)(p+ 0) = 0x51; /* push %rcx */
*(u16 *)(p+ 1) = 0x5341; /* push %r11 */
*(u8 *)(p+ 3) = 0x50; /* push %rax */
*(u8 *)(p+ 4) = 0xb8; /* mov $__HYPERVISOR_iret,%eax */
*(u32 *)(p+ 5) = __HYPERVISOR_iret;
*(u16 *)(p+ 9) = 0x050f; /* syscall */
}
直接用机器码写的指令,然后其中会用syscall(和int82h可以认为是一样)来进入xen中.这样才可以执行这些特权指令.这是规范,出于安全的考虑.
xen先保存内核的状态,然后根据索引来跳转到的不同的超级调用的处理函数.
索引文件 xen/arch/x86/x86_64/entry.SENTRY(hypercall_table)
.quad do_set_trap_table /* 0 */
.quad do_mmu_update
.quad do_set_gdt
.quad do_stack_switch
.quad do_set_callbacks
.quad do_fpu_taskswitch /* 5 */
.quad do_sched_op_compat
.quad do_platform_op
.quad do_set_debugreg
.quad do_get_debugreg
.quad do_update_descriptor /* 10 */
.quad do_ni_hypercall
.quad do_memory_op
.quad do_multicall
.quad do_update_va_mapping
.quad do_set_timer_op /* 15 */
.quad do_event_channel_op_compat
具体的处理函数在xen/arch/x86/traps.c中
long do_set_trap_table(XEN_GUEST_HANDLE_PARAM(const_trap_info_t) traps)
{
struct trap_info cur;
struct vcpu *curr = current;
struct trap_info *dst = curr->arch.pv_vcpu.trap_ctxt;
long rc = 0;
/* If no table is presented then clear the entire virtual IDT. */
if ( guest_handle_is_null(traps) )
{
memset(dst, 0, NR_VECTORS * sizeof(*dst));
init_int80_direct_trap(curr);
return 0;
}
for ( ; ; )
{
if ( hypercall_preempt_check() )
{
rc = hypercall_create_continuation(
__HYPERVISOR_set_trap_table, "h", traps);
break;
}
if ( copy_from_guest(&cur, traps, 1) )
{
rc = -EFAULT;
break;
}
if ( cur.address == 0 )
break;
if ( !is_canonical_address(cur.address) )
return -EINVAL;
fixup_guest_code_selector(curr->domain, cur.cs);
memcpy(&dst[cur.vector], &cur, sizeof(cur));
if ( cur.vector == 0x80 )
init_int80_direct_trap(curr);
guest_handle_add_offset(traps, 1);
}
return rc;
}
这是超级调用的整个过程,整个过程中唯一一点不清楚的是syscall进入xen中的汇编语言xen/arch/x86/x86_64/entry.S(姑且认为就是保存了内核状态,并跳转)
这些知识只是在更改超级调用的时候才会用到,超级调用已经成为了xen的一种机制,提供特权操作的机制.我们可以再别的地方任意使用,不考虑其中的细节.
版权声明:本文为博主原创文章,未经博主允许不得转载。 |
|
|