超级调用总结
超级调用总结
阅读了几篇优质文章之后,本人对超级调用又有了进一步的理解,文章主要从这几个方面来写:
-
当前疑惑
-
理解超级调用
-
PV超级调用与HVM超级调用
-
xen4.4中实现超级调用
-
xen4.11中实现超级调用
优质文章链接为:
Xen Log 3-Add New Hypercall to Xen
0.当前疑惑
1.到底需不需要在/hvm/hvm.c中增加HYPERVISOR_
?
有的加了;有的没有加也实现了;
2.在domain-U中修改头文件时,修改/hvm/hvm.c还是/usr/include/xen/xen.h?两个好像都可以。但是本人在实验过程中,发现xen4.11中并没有/hvm/hvm.c,因此只能在Xen.h中修改?结果是可行的。/usr/include/xen/xen.h 和 hvm/hvm.c之间的区别/xen/arch/x86/hvm/hvm.c,还有/usr/include/xen/sys/privcmd.h
- 可能需要实验的是,如果不该Xen.h是否会成功
- 在Xen4.4中如果不修改hvm.c而是修改Xen.h是否会成功
- hvm.c是不是只与全虚拟化有关系?而xen.h是对于所有的VM都适用?
3.超级调用实现原理文中hvm超级调用失败的原因是什么?
1.理解超级调用
本部分结构如下:
- 1.1系统调用
- 1.2超级调用与系统调用的共同点
- 1.3超级调用与系统调用的区别
- 1.4Xen中超级调用须知
1.1系统调用
1.1.1 内核态和用户态
计算机是由软件和硬件组成的,除了硬件,可以将软件分为操作系统OS和用户程序。为了保证系统和用户程序之间的隔离性,避免用户程序的误操作对系统产生影响,区分不同程序拥有的权限,系统定义了内核态和用户态。当用户执行一般操作的时候,只需要在用户态运行;当用户需要执行一些敏感操作时,需要向系统调用服务,进入内核态。
1.1.2 特权级
一般地,有Ring0,Ring1,Ring2,Ring3,执行权限依次降低。在没有虚拟化技术的系统中,操作系统运行在Ring0,用户程序运行在Ring3。
虚拟化技术需要将主机系统的资源虚拟化,分配给不同的虚拟机使用,因而在操作系统和用户程序之间增加了一个Hypervisor层,用于协调访问服务器上的所有物理设备和虚拟机。
Xen作为一种Hypervisor,如图1,位于Hardware和操作系统之间,为其上运行的虚拟机内核提供虚拟化环境,因而Xen具有最高的特权级。
Xen半虚拟化 & x86 特权模式
图2左;没有半虚拟化的x86-32:操作系统位于Ring0;应用程序Ring3
图2右;半虚拟化的x86-32:Xen位于Ring0;操作系统Ring1;应用程序Ring3
图3左;没有半虚拟化的x86-64:操作系统位于Ring0;应用程序Ring3
图3右;半虚拟化的x86-64:Xen和操作系统位于Ring0;应用程序Ring3
Xen全虚拟化 & x86 特权模式
HVM中运行的是没有修改的OS,操作系统位于Ring0;应用程序Ring3。但是Xen具有最高的特权级,此时Xen运行在根(Root)模式,可以认为运行在-1环。
1.1.3系统调用的实现方式
系统调用是用户空间应用程序和内核提供的服务之间的接口。服务在内核中,因此需要一个进程跨越内核和用户空间。在i386中有两种系统调用实现方式:
中断
快速系统调用(sysenter和sysexit)
1.1.4系统调用中的名词
系统调用:实质是函数调用
系统调用号:每一个系统调用定义了一个唯一的编号
系统调用表:将系统调用号与相应的服务例程关联起来
系统调用处理程序:用户空间程序和内核服务之间的接口;一个函数;
封装例程:例如一些函数库中的库函数
服务例程:系统调用对应的内核函数
1.1.5系统调用的过程
其流程可以描述为:
应用程序----封装例程----系统调用的系统调用号---软中断0x80----内核空间的软中断系统调用处理程序---超级调用表---系统调用服务例程---软中断返回封装例程----应用程序
1.1.6系统调用的两种方式
直接调用syscall(),参数为系统调用号
调用库函数中封装例程
1.1.7系统调用中的宏定义
_syscall0 _syscall1 ... _syscall5
#0-5是除了系统调用号的参数个数
eax:系统调用号
ebx ecx edx esi edi:其余参数
1.2超级调用与系统调用的关系
1.2.1名词对应
超级调用号-----系统调用号
超级调用表-----系统调用表
hypercall-----system_call
0x82-----0x80
1.2.2宏定义
_hypercall0 _hypercall1 ... _hypercall5
#0-5是除了系统调用号的参数个数
1.2.3超级调用的过程
1.2.4超级调用的两种方式
由于hypercall只能在内核态进行调用,所以可以放到systemcall中或者编译为kernel module在进行加载时调用;xen本身也提供了使用privcmd 的工具能够来调用hypercall。
privcmd
kernel module
1.3超级调用与系统调用的区别
1.3.1超级调用页
系统调用:封装例程直接调用软中断0x80
超级调用:通过超级调用页间接调用软中断指令;封装例程----超级调用页----可能含有0x82(和虚拟化类型有关);
超级调用页的初始化
源代码:/xen/arch/x86/x86_32/traps.c
xen在domain创建时将超级调用页映射到domain的内核空间
根据domain的安装类型初始化超级调用页
为每个超级调用添加处理代码(代码会根据安装类型有区别)
初始化超级调用页的过程感觉就是把一堆一样的代码写进到hypercall_page中;但是并没有具体函数的信息;具体的函数或者超级调用是在超级调用表中进行映射的;也能看出超级调用页区分了各种不同安装类型的超级调用页的初始化;结果是可以指向同一个超级调用表
1.4Xen中超级调用须知
1.4.1内存与超级调用
每个超级调用在超级调用页中对应的代码长度:最大是32B
2.PV超级调用与HVM超级调用
2.1全虚拟化中的超级调用
在全虚拟化中,由于Guest OS 的代码没有被修改,因此Guest OS 的特权操作, 比如更新页表等主要通过VT 技术的VMX 操作来实现。敏感操作也不像半虚拟化那样通过超级调用来实现,也是通过VMX 的指令来实现。因此全虚拟化下的Guest OS 即HVM 很少使用超级调用。
需要硬件辅助技术:Intel VT(vmx)和AMD SVM技术
客户机domain:客户机模式;无关键指令时;
Xen:根模式;root模式;涉及到关键指令时进入;
只要不涉及到关键指令,客户机就运行在客户机模式下;当涉及到关键指令时,客户机就会退出客户机模式(VM exit),进入root模式,在root模式中执行完相应的关键指令后又会进入到客户机模式(VM entry)。
Intel VT技术:VMCALL调用VM monitor;Xen的hypervisor
AMD:VMMCALL使客户机和VMM通信
2.2半虚拟化中超级调用
半虚拟化中超级调用和系统调用的位置
3.Xen4.4中实现超级调用
下边是在Xen4.4中实现超级调用的过程,其中3.0部分与虚拟化模式无关;3.1和3.2分别是半虚拟和全虚拟化虚拟机中测试超级调用的操作过程。
3.0不区分虚拟化模式的修改部分
-
超级调用号
xen/include/public/xen.h
#define __HYPERVISOR_tmem_op 38 #define __HYPERVISOR_xc_reserved_op 39 /* reserved for XenClient */ +#define __HYPERVISOR_leeham_hycall 39 /* add this temporarily; 39 is not used before */
-
超级调用表
xen/arch/x86/x86_64/entry.S
.byte 1 /* do_tmem_op */ + .byte 3 /* do_leeham_hycall */
-
函数头文件
xen/include/asm-x86/hypercall.h
extern int do_leeham_hycall(char* message1,char *message2);
-
自定义超级调用函数
xen/arch/x86/mm.c
int do_leeham_hycall(char* message1,char *message2)
{
if(message1&&message2)
{
printk("The message1 is : \n%s\n",message1);
printk("The message2 is : \n%s\n",message2);
}
else printk("no message!\n");
return 1;
}
3.1半虚拟化
-
首先修改xen.h
/usr/include/xen/xen.h中 添加的__HYPERVISOR_的宏定义
-
编写测试程序
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<sys/ioctl.h> #include<string.h> #include<sys/types.h> #include<fcntl.h> #include<xenctrl.h> #include<xen/sys/privcmd.h> int main(int argc,char* argv[]) { int fd,ret; char *message1,*message2; if(argc!=3){ printf("please put 2 parameter!"); return -1; } message1=(char*)malloc(sizeof(char)*(strlen(argv[1])+1)); message2=(char*)malloc(sizeof(char)*(strlen(argv[2])+1)); strcpy(message1,argv[1]); strcpy(message2,argv[2]); privcmd_hypercall_t hcall={__HYPERVISOR_leeham_hycall,{message1,message2,0,0,0}}; fd=open("/proc/xen/privcmd",O_RDWR); if(fd<0){ perror("open"); exit(1); } else{ printf("fd=%d\n",fd); } ret=ioctl(fd,IOCTL_PRIVCMD_HYPERCALL,&hcall); printf("ret=%d\n",ret); }
-
make install-xen;重启
3.2全虚拟化
这部分我没有实验;请见原文;其中hypercall的名称是create_sim
-
首先在xen/arch/x86/hvm/hvm.c 中添加该hypercall
static hvm_hypercall_t *hvm_hypercall32_table[NR_hypercalls] = { ...... HYPERCALL(tmem_op), + HYPERCALL(create_sim) };
-
在HVM domU 中写一个模块sim.c ,来调用该hypercall, 如下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/vmalloc.h> #include <linux/mm.h> #include <asm/pgtable.h> #define __HYPERVISOR_create_sim 39 char *hypercall_stubs; /* 下面两个define 可以参考文件asm/hypercall.h */ #define _hypercall2(type, name, a1, a2) / ({ / type __res; / long __ign1, __ign2; / asm volatile ( / HYPERCALL_STR(name) / : "=a" (__res), "=D" (__ign1), "=S" (__ign2) / : "1" ((long)(a1)), "2" ((long)(a2)) / : "memory" ); / __res; / }) #define HYPERCALL_STR(name) / "mov $("__stringify(__HYPERVISOR_##name)" * 32),%%eax; "/ "add hypercall_stubs,%%eax; " / "call *%%eax" static int init_hypercall(void) { uint32_t eax, ebx, ecx, edx, pages, msr, i; char signature[13]; /* 通过cpuid 指令获取当前操作系统的状态 */ cpuid(0x40000000, &eax, &ebx, &ecx, &edx); *(uint32_t*)(signature + 0) = ebx; *(uint32_t*)(signature + 4) = ecx; *(uint32_t*)(signature + 8) = edx; signature[12] = 0; /* 判断是不是运行在Xen VMM 上 */ if (strcmp("XenVMMXenVMM", signature) || (eax < 0x40000002)) { printk(KERN_WARNING "Detected Xen platform device but not Xen VMM?" " (sig %s, eax %x)/n",signature, eax); return -EINVAL; } /* 获取xen 的版本号 */ cpuid(0x40000001, &eax, &ebx, &ecx, &edx); printk(KERN_INFO "Xen version %d.%d./n", eax >> 16, eax & 0xffff); /* 获取超级调用页的数目pages,( 一般情况下只有一页) 以及相应的寄存器值msr */ cpuid(0x40000002, &pages, &msr, &ecx, &edx); printk(KERN_INFO"there are %u pages/n", pages); /* 分配超级调用页 */ hypercall_stubs = __vmalloc(pages * PAGE_SIZE,GFP_KERNEL, __pgprot(__PAGE_KERNEL & ~_PAGE_NX)); if (hypercall_stubs == NULL) return -ENOMEM; /* 将超级调用页的物理页框号写到MSR 中 */ for (i = 0; i < pages; i++) { unsigned long pfn; pfn = vmalloc_to_pfn((char *)hypercall_stubs + i*PAGE_SIZE); wrmsrl(msr, ((u64)pfn << PAGE_SHIFT) + i); } printk(KERN_INFO "Hypercall area is %u pages./n", pages); _hypercall2(unsigned long, create_sim, 1,3); return 0; } module_init(init_hypercall);
-
编写makefile文件
Make 文件如下: obj-m := sim.o all: make -C /lib/modules/`uname -r`/build M=`pwd` modules clean: make -C /lib/modules/`uname -r`/build M=`pwd` clean
-
编译,加载模块
make
insmod sim.ko
切换到dom 0 中,利用xm dm 查看日志
(XEN) creat the sim space using shadow page
4.Xen4.11中实现超级调用
类比Xen4.4,我们得到下述内容。
4.0大致运行流程
1.当用户态生成超级调用后,首先进入entry.S;执行下边代码之后会进入do_entry_int82函数;
ENTRY(entry_int82)
...
call do_entry_int82
2.xen-4.11.0.source\xen\arch\x86\pv\traps.c中定义了:do_entry_int82();
void do_entry_int82(struct cpu_user_regs *regs)
{
if ( unlikely(untrusted_msi) )
check_for_unexpected_msi((uint8_t)regs->entry_vector);
pv_hypercall(regs);
}
3.xen-4.11.0.source\xen\arch\x86\pv\hypercall.c中定义了:pv_hypercall()
void pv_hypercall(struct cpu_user_regs *regs)
{
...
}
4.其中使用到pv_hypercall_table系统调用表\xen\arch\x86\pv\hypercall.c;还用到\xen\arch\x86\hypercall.c中的hypercall_args_table
pv_hypercall_table:
......
#ifdef CONFIG_TMEM
HYPERCALL(tmem_op),
#endif
......
hypercall_args_table:
...
ARGS(xenpmu_op, 2),
ARGS(dm_op, 3),
...
4.1思路说明
4.1.1在Xen源码中修改
- 超级调用号
xen/include/public/xen.h 修改调用号
...
#define __HYPERVISOR_xc_reserved_op 39 /* reserved for XenClient */
...
- 超级调用表
xen/arch/x86/hypercall.c 增加自定义超级调用的参数个数;
const hypercall_args_t hypercall_args_table[NR_hypercalls] =
{
......
ARGS(xenpmu_op, 2),
......
}
xen\arch\x86\pv\hypercall.c修改超级调用表:
......
#ifdef CONFIG_TMEM
HYPERCALL(tmem_op),
#endif
......
- 修改函数头文件
/asm-x86/hypercall.h
......
extern long
do_set_trap_table(
XEN_GUEST_HANDLE_PARAM(const_trap_info_t) traps);
......
- 添加hypercall定义
目前的理解是对定义文件的内容没有限制。只要在/xen项目中就可以了。
4.1.2在DomainU中测试
-
首先修改xen.h
/usr/include/xen/xen.h中 添加的__HYPERVISOR_的宏定义
-
编写测试程序
同上
4.2实际操作
4.1.1在Xen中修改代码
- 超级调用号
xen/include/public/xen.h 修改调用号
...
#define __HYPERVISOR_xc_reserved_op 39 /* reserved for XenClient */
+#define __HYPERVISORleeham_hycall 39 /* reserved for XenClient */
...
- 超级调用表
xen/arch/x86/hypercall.c 增加自定义超级调用的参数个数;
const hypercall_args_t hypercall_args_table[NR_hypercalls] =
{
......
ARGS(leeham_hycall, 2),
......
}
xen\arch\x86\pv\hypercall.c修改超级调用表:
......
HYPERCALL(leeham_hycall),
......
- 修改函数头文件
/asm-x86/hypercall.h
......
extern int
do_leeham_hycall(char* message1,char *message2);
......
- 添加hypercall定义
同Xen4.4.0
4.1.2在DomainU中测试
-
首先修改xen.h
/usr/include/xen/xen.h中 添加的__HYPERVISOR_leeham-hycall的宏定义
-
编写测试程序
同Xen4.4.0
-
make install-xen;重启
一些命令
xl dmesg | less
su reboot