龙空技术网

脚本语言的虚拟机和操作系统的虚拟机

底层技术栈 501

前言:

此刻大家对“启动脚本未能在虚拟机中成功运行”都比较注意,我们都想要剖析一些“启动脚本未能在虚拟机中成功运行”的相关资讯。那么小编同时在网摘上网罗了一些关于“启动脚本未能在虚拟机中成功运行””的相关资讯,希望你们能喜欢,朋友们一起来学习一下吧!

#头条创作挑战赛#

虚拟机是个用软件实现的CPU,而CPU的权限控制分为系统级和用户级。

例如,Linux内核就运行在CPU的最高优先级(ring0),而普通应用程序则运行在最低优先级(ring3)。

虽然英特尔把CPU的权限分了4个优先级,但实际只用到了2个。

对于虚拟机来说,要想模拟操作系统的运行,也必须进行权限分级。

1,CPU的权限分级,主要是指内存的访问权限。

intel的CPU分为实模式和保护模式,保护模式最主要的作用就是保护内存的访问权限。

内核代码可以访问所有的内存,但是用户代码只能访问进程的用户空间(内存)。

用户空间的内存是通过进程的页表来管理的,而进程的页表只能通过系统内核来修改。

当使用malloc()分配内存的时候,实际上并不是分配一块物理内存,而只是把用户空间的某一个内存范围设置为可用。

只有当进程代码真去读写这个内存范围的时候,操作系统才会给它分配物理内存,即Linux的写时复制和需求加载机制。

所以虚拟机要想“模拟”操作系统的运行,首先要模拟CPU的保护模式。

2,CPU保护模式的实现,靠的就是几个控制寄存器。

对于intel CPU来说,跟保护模式下相关的寄存器是cr0, cr1, cr2, cr3。

其中cr0用于控制分段和分页机制,一旦开启内存的分段机制就进入了保护模式。

一旦开启了内存的分页机制,操作系统可以支持的进程个数就是无限的了。

开启了分页之后,操作系统就可以4096字节的一个页为单位,为进程分配“必需的”内存空间,非常的灵活。

什么时候必需?

当然是写时复制和需求加载的时候必需,所以进程刚创建时除了它的task_struct结构之外,只需要给它分配4096字节做为页目录即可,其他的都可以跟父进程共享。

对于多进程多任务的操作系统来说,内存的分页机制是必需的,因为分段机制太死板了。

cr3就是页目录基地址寄存器,哪个进程运行时它就指向哪个进程的页表,内核运行时它就指向内核页表。

cr2在缺页中断时用于保存进程用户空间的内存地址。在哪个位置出错了,就保存哪个地址,然后操作系统就会为那个位置(所在的内存页)分配内存。

获取一个位置addr所在的内存页非常的简单,把它的最低12位清零就行,addr & ~0xfff[呲牙]

3,虚拟机要想模拟操作系统的运行,必须自己实现MMU的功能。

操作系统的运行,首先要依赖这几个控制寄存器。

这几个控制寄存器的主要作用,其实就是内存管理。

在真实的硬件上,内存管理是通过MMU实现的。MMU可以根据进程的页表实现用户空间的内存地址(线性地址)到物理内存的映射。

如果在虚拟机上,这部分功能就只能通过代码去实现了。

虚拟机要实现三层内存地址的映射:虚拟进程的用户内存地址 --> 虚拟物理内存的物理地址 --> 虚拟机所在的真实进程的用户内存地址。

OS虚拟机的内存映射过程

所以像qemu这种能够直接运行Linux系统的大型虚拟机,是必须要实现CPU的控制寄存器和系统级指令的。

系统级指令,指的是只能在内核代码(或引导扇区)里运行的指令,例如:

pushfl 把标志寄存器压栈,

mov cr2, eax 把导致缺页的内存地址读到eax寄存器,

mov ax, cs 加载段选择符,等等。

4,脚本语言的虚拟机

脚本语言因为是运行在用户进程中,运行的代码也是用户态代码,所以实现起来比qemu这类虚拟机要简单的多。

它只需要解释一些常用指令就行了,不需要处理系统级的指令,也不需要管理复杂的内存映射。

它只需要把编译之后的字节码文件根据程序头的信息加载起来,并且处理动态库函数的调用(动态链接),就可以实现脚本语言的运行了。

最主要的是,脚本语言的字节码和编译器都是脚本语言的作者设计的,作者可以实现字节码和虚拟机的精确匹配,而不需要去实现CPU的整个指令集。

系统级的虚拟机就不得不实现CPU的整个指令集,因为OS内核被编译之后有可能用到CPU的所有指令,其中任何一条指令没被支持都可能导致内核运行失败。

脚本语言的虚拟机怎么写,之前已经说过了,不再细说了。

标签: #启动脚本未能在虚拟机中成功运行