- 首先,代码段不可写,其实是在页表中对代码段相应的页设置了不可写的flag,而页表只能控制进程地址空间的访问权限。
- 其次,这里的不可写只对用户态进程起作用。当然,进程的地址空间是隔离的,而进程在用户态只能通过自己地址空间的虚拟地址访问内存,所以当前进程在用户态也无法访问其他进程的地址空间。
- 最后,在内核态就能随便修改其他进程的代码段了?是的,但是你得先找到目标代码段地址空间对应的物理页,然后再映射到内核地址空间,然后新的地址空间的访问权限决定了你能如何访问这些物理页;ptrace中如下这段代码实现了这些功能,如何能执行到这些代码又是另外一个话题了;这里面牵涉到权限管理等等的问题,ptrace系统调用已经考虑的很周全了,博主会在下一篇博客里面详解ptrace系统调用的权限检查~
Category: 内核和虚拟化
pause loop exiting & ple gap for KVM performance tunning
A system feature called Pause-Loop Exiting can cause instability and performance degradation on a virtualized system that will have a web server running in a heavily utilized guest. Turn it off by setting the ple_gap module parameter to 0 for kvm_intel.
【人话总结】
Linux 内存回收机制
【在active和inactive之间流动的核心为:】
【思考】
【shrink list的顺序?】
【什么样的page可以被回收 】
【参考文献】
PAUSE指令
内核中的嵌入式汇编代码”rep;nop”会被编译为PAUSE指令,Intel Pentium4以后的CPU支持,之前的就相当于NOP。为啥rep;nop不是指循环执行nop同时递减ecx的值呢,这还真是之前困扰过我的问题,有篇文章用代码解释了这个问题-链接-。而博主自己也在虚拟机里面将cpu_relax()中的rep;nop替换为nop,前后均在host上抓取vmexit和trace kvm_exit数据,结果显示,PAUSE_INSTRUCTION造成的退出消失了。
上文中还翻译了Intel的spec,大概说了下pause指令有两种功能,一方面是可以解决memory order violation问题,另一方面可以降低循环等待的能耗。
什么是memory order violation?看这里。简单说下就是cpu的pipeline会根据执行代码的情况来预测即将执行的指令,提前将这些指令放入流水线中,达到一定的并行计算优化性能的目的,但是总会事与愿违,在spinlock代码实现中,如果不加入pause指令,很容易造成pipeline被“读入lockvar,比较lockvar是否为0”这样指令刷屏了,这就造成了即使在lockvar已经被别的CPU更新为非0值的时候,pipeline中出现了无效指令,这种情况就是memory order violation,即本应在写内存后读取内存值的动作发生在了写入之前,于是cpu就暴力的把pipeline全部flush掉,这样就造成了性能损失,因为这里是在等待一个lockvar被改变,只要及时的对这个动作做出反应就可以了。
pause指令的出现可以给cpu一个提示,这里不要给我缓存指令,等前面的执行完再看后面的,于是大大的减少了出现无效指令的可能性(此时出现这个情况的时间窗口为:读取了lockvar,但是cmp还没有执行;与之前比起来,时间窗口大大大的缩小了,之前是预读取了很多次的“读取lockvar,与0比较,跳入再一次的读取比较的分支”这三个动作,最后一个跳转也是预测的)。
节能的效果更好理解,其实这里就是在原地踏步,不是要求一定时间内踏的次数多,而是要对出现的情况及时处理。所以加入pause可以让处理更及时,而且不必把能量浪费在多出来的无意义的指令上。
梳理一下EPT表项的建立
KVM直通设备的配置空间访问
KVM提供了设备直通的方式:kvm assgined device。很多人认为直通设备是直接分配给虚拟机的,那么它的配置空间也应该由虚拟机直接访问,但这是个误区,即使很多从事虚拟化多年的老司机也有这个误解。
实际上,从qemu代码里面可以看到,直通设备是由qemu模拟的一个特殊的pci设备,对这个设备的配置空间读写都由qemu代理。这样做的原因在笔者看来,一方面是考虑到安全因素,不放心让虚拟机直接操作硬件,因为虚拟机里面的设备驱动是不可控的;另一方面,qemu需要在必要的时候截获对配置空间的访问来完成一些对hypervisor的配置,比如对msix entry page的配置,qemu必须拿到guest分配的中断vector,这样才能配置好hypervisor(具体是KVM模块实例中的数据结构)完成对直通设备中断的路由,这个内容我们以后详述,本文重点是直通设备的配置空间访问。
qemu中的pci-assign.c是直通设备模拟的代码所在。其中assigned_initfn函数是直通设备模型实例的初始化函数,这里需要啰嗦一下,qemu1.0对设备模拟代码框架进行了重构,融入了面向对象的编程理念,所有的设备都进行了分门别类,一层层的定义了很多设备的抽象类型以及由这些类型继承而来的子类型,比如virtio-net设备的继承链是object->device->pci->virtio-pci->virtio-net。这些类型就是一个个对象,他们的实例化由自己的初始化函数来做。回到assigned_initfn,get_real_device函数通过sysfs打开了直通设备的配置空间(比如/sys/bus/pci/devices/0000:00:01.0/config)并读取其内容,同时打开了BAR指向的资源(类似/sys/bus/pci/0000:00:01.0/resource0)文件,并在assigned_dev_register_regions函数里将resource文件做了mmap,然后组装好MemoryRegion(qemu对虚拟机物理地址空间管理使用的数据结构,主要用来处理虚拟机对设备的控制层面的PIO和mmio访问),这里面会注册对这一段地址访问的操作函数,调用的时机就是在虚拟机访问这些内存地址发生退出的时候。虚拟机访问MemoryRegion包含的地址时,发生EPT_VIOLATION,进而退出到qemu中,qemu根据其所属的memoryregion来决定如何处理这个退出,比如调用这个region注册的读写函数。而这里所注册的读写函数就是读写真实设备BAR指向的配置寄存器了。