vfio 直通设备的 memory region 初始化

vfio特别用一个数据结构来管理设备的memory region
注意到它还有个域是VFIOMmap结构的指针:
感觉是不是有冗余,那么外层结构里面的mem是不是指向内层中的mem呢?
上面的mem还有个奇怪的注释:slow,难道还有一种快一点的MR?那是不是指这里是IO的MR,另外还有个RAM的mr?

Continue reading “vfio 直通设备的 memory region 初始化”

IOMMU group and ACS cap

VFIO做VM的设备直通过程中,需要把直通设备所在iommu group里面所有的设备都unbind掉,这是为啥呢,iommu group又是啥,木有遇到该问题的小伙伴你们肯定年轻而富有:)你们的设备都是有ACS的呢,这又是啥,咱先来看看官方文档吧:

Continue reading “IOMMU group and ACS cap”

libvirt 向qemu传文件描述符

libvirt创建的qemu进程里面有一些fd的参数,这些文件是libvirt帮qemu打开的一些设备文件句柄等,比如:
qemu … -netdev tap,fd=24,id=hostnet1,vhost=on,vhostfd=25
因为需要libvirt帮忙先配置好后端以及处于安全考虑;但是qemu起来就是另外一个进程,给个fd号就能直接用了吗,显然不是,下面从代码角度分析下

Continue reading “libvirt 向qemu传文件描述符”

sriov vf get iommu group kernel code trace

VF device driver call:
pci_enable_sriov -> sriov_enable -> pci_iov_add_virtfn -> pci_device_add -> device_add ->
pci bus register a iommu notifier will be called when device_add start to notify:

intel_iommu_init -> iommu_bus_init -> “nb->notifier_call = iommu_bus_notifier;”

ops->add_device:

intel_iommu_add_device:

iommu_group_get_for_dev:
this will find or create an iommu group for the VF device

Linux 内核 schedule时的preemption notify机制

内核进行进程切换时,先调用了__schedule,在关抢占后调用context_switch:
prepare_task_switch里面调用fire_sched_out_preempt_notifiers,进而调用prev进程注册的sched_out操作,
这里是分支:如果是新创建的进程被调度了,要调用schedule_tail:
回到主题:finish_task_switch这里会调用fire_sched_in_preempt_notifiers:
preempt_notifiers在哪里注册的呢,对kvm来说是这里:

kvm模块加载的时候vmx_init->kvm_init里面初始化了kvm_preempt_ops

所以kvm_sched_in和kvm_sched_out被调用时的上下文是关抢占的

[MOS] deadlock detection

MOS: Modern Operating Systems

deadlock detection with one resource of each type

如果每种资源都是独一份的,可以用上面这样的图来分析资源竞争情况。
图中方块代表资源,圆形代表进程;指向进程的箭头表示资源被该进程拥有,而从进程出发的箭头表示其需要的资源。
有向图中只要存在一个闭环,就存在一个死锁

找出闭环的方法有很多种,这里给出一个比较直观的方法:
0)首先把图中所有箭头记为unmarked,初始化一个空链表L,并选中一个节点(无论是进程还是资源,这里只考虑抽象出的有向图)为当前节点
1)判断该节有没有出现在L中,如果没有则加入L尾部进入下一步;如果有则说明存在一个闭环,即发现死锁
2)当前节点如果有向外的unmarked箭头就顺着它找到下一个节点,同时将该箭头标记为marked,跳到1);如果没有unmarked箭头进入下一步
3)没有unmarked的箭头表明已经进入死胡同,删掉当前节点,回溯到源节点,将其设置为当前节点,跳到2)

deadlock detection with multiple resources of each type

当一种资源有多个可用实体时,我们使用一种基于矩阵的算法
向量E表示m种资源目前被占用的情况,A表示m种资源剩余量
矩阵C表示n个进程目前分别占用各种资源的情况,每行代表一个进程,每列是一种资源对应的各个进程占用情况,m列求和就等于Em
矩阵R表示n个进程目前对各种资源的需求量,行列意义同上
定义向量X<=Y当且仅当X的每个分量都<=Y的对应分量
算法步骤:
0)所有进程标记为unmarked
1)找到一个unmarked进程i,其占用的资源Ri<=A,下一步;没有满足该条件的进程,跳到3)
2)A=A+Ci,进程i标记为marked,跳到1)
3)如果所有进程都是marked,表示都得到了执行,没有死锁;否则,剩余的所有unmarked进程构成了死锁

虚拟机对x2apic destination mode的选择

首先简单介绍下apic的destination mode:

physical mode下高32bit(destination field)是apic id
logical mode下高32bit是MDA,又分为flat和cluster两种:
flat就是一个bitmap
而cluster包含两个部分,高16bit为cluster ID,后16bit为bitmap;这里还分flat和hierarchy;这里就不深究了。

根据intel spec:Flat logicalmode is not supported in the x2APIC mode. Hence the Destination Format Register(DFR) is eliminated in x2APIC mode. 在x2apic已经没有FDR,所以在logical mode下不再支持flat mode了,只有cluster mode由于x2apic在原来apic的8bit apicid基础上扩展到32bit

所以通过ioapic发中断和发MSI(不是MSIX)中断的设备就有问题了;他们需要做IR,interrupt remapping(VT-d提供的一项能力,通过IOMMU来做)
但是,也有些例外,比如使用physical mode且cpu个数小于256时,8bit的apic id已经完全可以cover所有的cpu了,而且这8bit直接可以写入x2apic的32bit的destination field,所以这种情况下可以直接使用x2apic physical mode而无需IR能力

https://patchwork.kernel.org/patch/2826342/这个邮件内容告诉我们,使用x2apic而同时又没有IR能力的情况,只有虚拟化场景(bios不能enable VT-d这种情况好像要被禁止了,enable x2apic一定要开VT-d)

然而,虚拟化场景下为何一定要enable x2apic呢?
IBM给出了答案Red Hat Enterprise Linux 6 implements x2APIC emulation for KVM guests. Advanced programmable interrupt controllers (APICs) are used in symmetric multiprocessor (SMP) computer systems. x2APIC is a machine state register (MSR) interface to a local APIC with performance and scalability enhancements. IBM lab tests show that enabling the x2APIC support for Red Hat Enterprise Linux 6 guests can result in 2% to 5% throughput improvement for many I/O workloads. You can enable the x2APIC support by specifying the -cpu qemu64,+x2apic option on the qemu-kvm command for a KVM guest.

然而这解释当然是隔靴搔痒,真正的解释还是要show me the patch:

 

PTRACE的权限检查,自由是有前提的~

系统调用PTRACE的一切开始于这里:
COMPAT_SYSCALL_DEFINE4(ptrace, compat_long_t, request, compat_long_t, pid,
               compat_long_t, addr, compat_long_t, data)

首先提供了attach的绿色通道:
if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) {
        ret = ptrace_attach(child, request, addr, data);
attach做的事情就是把当前进程变成了被trace进程的父进程,同时把被trace进程加入到当前进程的ptraced链表中;
__ptrace_link(task, current);
list_add(&child->ptrace_entry, &new_parent->ptraced);
    child->parent = new_parent;
但是这样做是有前提的,具体请看后面的分析,这里先按下不表。

attach?wtf?为啥要在用之前attach,搞得这么神秘,难道直接用不可以吗?
不可以,因为你不能直接访问其他进程的地址空间,否则地址空间的隔离不就变成笑话了。
所以这个ptrace系统调用入口做了个很重要的check:
ret = ptrace_check_attach(child, request == PTRACE_KILL ||
                  request == PTRACE_INTERRUPT);
    if (!ret) { //只有返回为0,即判断条件成立,才继续执行对ptrace的request
        ret = compat_arch_ptrace(child, request, addr, data);
        if (ret || request != PTRACE_DETACH)
            ptrace_unfreeze_traced(child);
    }
child->ptrace && child->parent == current
只有过了这一关,才能进行后面的操作(除了kil和interrupt,他们自己的处理逻辑里面有此类的判断或者无需),那难道随便就可以attach,那我就麻烦一点先attach呗;显然不会这么傻,前面卖了个关子,这里来看看attach里面做的权限检查,必须是有权的人才能随便搞的,屁民是要守法的!!

权限管理的水比较深,博主还没吃透,所以这里先简单列一下我能看懂的部分:
etval = __ptrace_may_access(task, PTRACE_MODE_ATTACH);
这里有两个条件,1)我自己人改自己人ok(这里的suid euid等等的请看后面链接);2)我的上层命名空间ok(我理解是,比如像root用户这种大boss)
    tcred = __task_cred(task);
    if (uid_eq(cred->uid, tcred->euid) &&
        uid_eq(cred->uid, tcred->suid) &&
        uid_eq(cred->uid, tcred->uid)  &&
        gid_eq(cred->gid, tcred->egid) &&
        gid_eq(cred->gid, tcred->sgid) &&
        gid_eq(cred->gid, tcred->gid))
        goto ok;
    if (ptrace_has_cap(tcred->user_ns, mode))
        goto ok;
另外还要做命名空间能力的检测,看看目标进程的命名空间有没有被ptrace的能力:
ns_capable(__task_cred(task)->user_ns, CAP_SYS_PTRACE)

参考链接

修改运行中代码之神器——PTRACE的秘诀

ptrace是个神奇的系统调用,是gdb等工具的实现基础,它可以用来监控进程的执行,读取或修改进程的寄存器和memory内容包括代码段。有的底层码农老司机甚至也会觉得奇怪,代码段不都是不可写的吗,为啥ptrace就能修改呢?

下面解释一下,老司机直接看后面代码秒懂:
  • 首先,代码段不可写,其实是在页表中对代码段相应的页设置了不可写的flag,而页表只能控制进程地址空间的访问权限。
  • 其次,这里的不可写只对用户态进程起作用。当然,进程的地址空间是隔离的,而进程在用户态只能通过自己地址空间的虚拟地址访问内存,所以当前进程在用户态也无法访问其他进程的地址空间。
  • 最后,在内核态就能随便修改其他进程的代码段了?是的,但是你得先找到目标代码段地址空间对应的物理页,然后再映射到内核地址空间,然后新的地址空间的访问权限决定了你能如何访问这些物理页;ptrace中如下这段代码实现了这些功能,如何能执行到这些代码又是另外一个话题了;这里面牵涉到权限管理等等的问题,ptrace系统调用已经考虑的很周全了,博主会在下一篇博客里面详解ptrace系统调用的权限检查~
ptrace_request()
    case PTRACE_POKETEXT:
    case PTRACE_POKEDATA:
        return generic_ptrace_pokedata(child, addr, data); //调用__access_remote_vm
 最后的关键代码:
ret = get_user_pages(tsk, mm, addr, 1,
                write, 1, &page, &vma);
……
maddr = kmap(page);
            if (write) {
                copy_to_user_page(vma, page, addr,
                          maddr + offset, buf, bytes);
                set_page_dirty_lock(page);
            } else {
                copy_from_user_page(vma, page, addr,
                            buf, maddr + offset, bytes);
            }
            kunmap(page);

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.


Example: Add file kvmopts.conf to /etc/modprobe.d/ with contents:
 options kvm_intel ple_gap=0
Restart the kvm_intel module if it is already running.
 # modprobe -r kvm_intel
 # modprobe kvm_intel
or
insmod kvm-intel ple_gap=0

From Intel spec:
If the “PAUSE exiting” VM-execution control is 0 and the “PAUSE-loop exiting” VM-execution control is 1, the following treatment applies.
The processor determines the amount of time between this execution of PAUSE and the previous
execution of PAUSE at CPL 0. If this amount of time exceeds the value of the VM-execution control field
PLE_Gap, the processor considers this execution to be the first execution of PAUSE in a loop. (It also
does so for the first execution of PAUSE at CPL 0 after VM entry.)
Otherwise, the processor determines the amount of time since the most recent execution of PAUSE that
was considered to be the first in a loop. If this amount of time exceeds the value of the VM-execution
control field PLE_Window, a VM exit occurs.
For purposes of these computations, time is measured based on a counter that runs at the same rate as
the timestamp counter (TSC).

【人话总结】

永远把两次间隔超过ple_gap的pause指令其中的后一条作为第一次loop内pause,从这个第一次loop内pause开始计算次数直到超过PLE_window就触发exit
也就是间隔在ple_gap之内的连续pause指令构成了pause loop,大于PLE_window的pause loop将触发退出