os & runtime & 虚拟化
- app 如何在 os上运行起来?
- os 如何为 app 提供服务?
- 哪些是 os 提供?哪些由语言 runtime 提供?
OS
系统调用
除内存/文件/进程/外设管理等内部模块以外,os 还提供许多接口 给应用程序使用,就是所谓的 系统调用
DOS 时代系统调用就通过软中断形式提供,著名的 INT 21
- 系统功能号送到寄存器 AH
- 入口參数送到指定的寄存器
- INT 21H 调用
- 依据出口參数分析功能调用运行情况
Hello.asm
msg db 'hello world!',0Dh,0Ah,'$'
org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
int 21H 调用
AH | 功能 | 入口参数 | 出口参数 |
---|---|---|---|
4CH | 返回 DOS | 无 | 无 |
1 | 键盘输入一个字符到AL | fq | AL=字符 |
2 | 输出DL寄存器的字符到显示器 | DL(存一个字符) | 无 |
9 | 输出一个以$ 结尾的字符串到显示器 | DS:字符串段地址DX:字符串首地址 | 无 |
0AH | 从键盘输入一个字符串到指定缓冲区 | DS:缓冲区段地址DX:缓冲区首地址 | 缓冲区对应位置 |
D:\dev\c\BuildTools\VC\Tools\MSVC\14.10.25017\bin\HostX64\x64\nasm hello.asm -o hello.com
dosbox-0.74.conf
MOUNT C D:\tmp
set PATH=$PATH$;D:\tmp
x86 ring
通过特权级(privilege level),os和CPU一起合谋限制 用户模式的应用程序
特权级共 4个,0(最高权 控制中断、修改页表、访问设备)到3(最低),x86 只用到 0 和 3
3 种主要资源受到保护:
- 内存
- I/O端口
- 特殊机器指令
任一时刻,CPU 都只在一个特定特权级下运行
用户模式代码不调用内核情况下,几乎不能与外部世界交互(不能打开文件,发网络数据包,屏幕打印、分配内存)
要做受控操作(访问磁盘,写文件 ...)就要 系统调用(函数)
CPU 运行级别从 ring3 到 ring0 切换,并跳到OS对应的内核代码位置执行,这样内核就为你完成了设备访问,完成之后再从 ring0 返回 ring3( 用户态和内核态的切换 )
一个进程泄漏的内存会在进程结束后被统统回收,打开的文件也会被自动关闭
所有的控制着内存或打开的文件等的数据结构全都不能被用户代码直接使用
Windows 95/98容易死机原因之:
系统一些重要数据结构,出于兼容目的被设计成 可由用户直接访问。当时可能是一个很好的折中,代价也很大
快速系统调用
这做法到奔腾 2(P6) 前都没变,windows 通过 INT 2E、Linux 是 INT 80 提供系统调用,只不过后来的寄存器比以前大一些,可能再多一层跳转表查询
后来 Intel 和 AMD 提供了效率更高的 SYSENTER/SYSEXIT SYSCALL/SYSRET 代替之前中断方式,略过耗时的特权级别检查及寄存器压栈出栈的操作,直接从 RING 3 代码段到 RING 0 代码段的转换
通过引入新的寄存器组 (Model-Specific Register(MSR)) 存放所需信息,进而实现快速跳转
系统调用就是 os 和 app 之间沟通的协议,超出此协议的功能,需 app 去实现
举例
- 内存管理
os 只提供进程级别的内存段的管理
- Windows virtualmemory 系列
- Linux 的 brk
os 不在乎应用如何为新建对象分配内存,或 gc,这些都需要 app 自己去实现
如果超出此协议的功能无法自己实现,那就说该操作系统不支持该功能
例 Linux 2.6 前不支持多线程,无论如何在程序里模拟,都无法做出多个可以同时运行的并符合 POSIX 1003.1c 语义标准的调度单元
运行时
可是我们写程序并不需要调中断或SYSCALL,os提供了一层封装
- Windows
NTDLL.DLL(常说的Native API)
不但不需要直接调INT 2E 或 SYSCALL,准确的说是不能直接去调用,因为Windows并没有公开其调用规范,直接去调用无法保证未来的兼容性
- Linux
没此问题,系统调用列表都公开(400个左右),且Linus非常看重兼容性,不会去做任何更改,glibc 里甚至专门提供了syscall来方便用户直接用编号调用
Linux上还是对部分系统调用做一层封装,VDSO (早期叫linux-gate.so)
- 解决glibc和内核间不同版本兼容性
- 提高某些调用效率(如__NR_ gettimeofday)
可是我们写程序也很少直接调用NTDLL或者VDSO,而是通过更上一层的封装
这一层处理了参数准备和返回值格式转换、以及出错处理和错误代码转换,这就是我们所使用语言的 运行库
- C 语言,Linux 上是 glibc,Windows 上是 kernel32(或调用msvcrt)
- Java 是 JRE,这些 “其他语言” 的运行库通常最终还是调用glibc或kernel32
- .NET 是 CLR
“运行库” 其实不止包括用于和编译后的目标执行程序进行链接的库文件,也包括了脚本语言或字节码解释型语言的运行环境
运行时功能
- 系统调用的封装
- 不需要操作系统支持的功能
- 操作系统支持的功能提供更易用更高级的封装
不需要操作系统支持的功能
- 字符串处理
- 数学计算
- 常用数据结构容器等
易用高级封装例
- 带缓存和格式的IO
- 线程池等
所以说“某某语言新增了某某功能”的时候,通常是这么几种可能:
- 支持新的语义/语法,便于描述和解决问题。如Java泛型、Annotation、lambda
- 提供了新工具或类库,减少了们开发代码量。如Python 2.7 argparse
- 对系统调用有了更良好更全面的封装,做到以前在这个语言环境里做不到或很难做到的事情。如Java NIO
Go提供了更方便更清晰的语义和支持,提高了开发的效率
虚拟化
虚拟化难题
vmware/virtualbox的宿主系统叫 hostos,里面跑的系统叫guest os
host os工作在ring0,guest os就不能也在ring0了,但是它不知道这一点
以前执行什么指令,现在还是执行什么指令,没权限啊,玩不转!
虚拟机管理程序(VMM)
VMM 在ring0上,一般以驱动程序的形式体现(得驱动的了设备)
guest os 执行特权指令时,会触发异常(CPU机制,没权限的指令,触发异常),VMM捕获,在异常里面做翻译,模拟,返回到guest os 内,guest os 认为自己的特权指令工作正常,继续运行
但这个性能损耗非常的大,原来简单的一条指令,却要通过复杂的异常处理过程
- 半虚拟化
让guest os 知道自己是在虚拟机上跑的,工作在非ring0状态,它原先在物理机上执行的一些特权指令,就会修改成其他方式,这种方式是可以和VMM约定好的
相当于通过改代码把os移植到一种新的架构上来
像XEN这种半虚拟化技术,guest os都是有一个专门的定制内核版本,和x86、mips、arm这些内核版本等价
就不会有捕获异常、翻译、模拟的过程了,性能损耗非常低
这也是为什么XEN只支持虚拟化Linux,无法虚拟化windows原因,微软不改代码啊
- 硬件辅助的全虚拟化
CPU 厂商开始支持虚拟化
Intel-VT 技术,VMX root operation 和 VMX non-root operation两种模式,都支持Ring 0 ~ Ring 3
VMM运行在VMX root operation下,guest OS运行在VMX non-root operation
全虚拟化不需要修改guest os
现在XEN也支持硬件辅助的全虚拟化
KVM、VMARE这些一直都是全虚拟化