原文为vfio mediated device内核文档[5],我翻译一下。 1. Virtual Function I/O (VFIO) Mediated devices[1] 对没有内置SR-IOV功能的设备进行DMA虚拟化的需求越来越多。以前,为了虚拟化一个这样的设备,开发者需要自己开发管理接口和API,然后把它们集成到用户态应用中。为了简化这种用户空间软件集成,我们找出了这种设备公共需求然后开发了一种统一的接口。 VFIO驱动框架为直接设备访问提供了统一的API。它将设备直接访问安全地(以一种IOMMU保护的环境)暴露给用户,是一种IOMMU/设备无关的框架。此框架用于多种设备,如GPU、网卡和计算加速器等。有了这种直接设备访问,虚拟机或者用户态应用可以直接访问物理设备。Mdeiated devices便是重用了VFIO这种框架。 Mediated core driver为mdiated device提供了一个公共的管理接口,它可以被不同类型的设备驱动所利用。这个模块提供了的通用接口可以进行如下操作: 创建和删除mediated device 把一个mediated deivce加入或移出某个总线驱动 把一个mediated device加入或移出某个IOMMU group Mediated core driver也提供注册总线驱动的接口。比如,一个mediated VFIO mdev驱动就是为mediated devices设计的,并且支持VFIO的API。Mediated bus driver可以将一个mediated device加入或者移出一个VFIO group。 以下的上层图展示了VFIO mediated driver框架的主要组件和接口。这张图展示了NVIDIA、Intel和IBM设备,因为这些设备是首先使用这些模块的。 +—————+ | | | +———–+ | mdev_register_driver() +————–+ | | | +<————————+ | | | mdev | | […]

本文是VFIO内核文档[1]的翻译。 很多现代系统提供DMA和中断重映射工具来帮助保证IO在被指定的界限中进行。包括x86硬件的AMD-Vi和Intel VT-d,POWER系统的Partitionable Endpoints (PEs)和嵌入式的PowerPC系统(如Freescale PAMU)。VFIO driver是IOMMU/设备不可知的一个框架,它只是专门用于将设备在安全的、IOMMU保护的环境下直接暴露给userspace。换句话说,VFIO允许安全且非特权的用户态驱动。 我们为什么需要VFIO?一个原因是虚拟机经常时用直接设备访问(“device assignment”)来获得尽可能高的IO性能。从设备和host的角度,这其实就是把VM变成了一个用户态驱动,VM也因此获得了这个IO设备的低延迟、高带宽和全虚拟化原生(bare-metal)设备驱动的直接应用。 一些应用场景中(特别是这高性能计算领域),也会从低开销的从用户空间的直接设备访问获得好处。例子包括网卡(通常基于非TCP/IP)和计算加速器等IO设备。在VFIO之前,这些驱动必须经过很长的开发周期来成为上游驱动、单独分支维护,或者使用UIO框架(UIO并不支持IOMMU保护,并且对中断支持有限,还需要root权限来访问PCI配置空间等东西)。 VFIO驱动框架用来将所有东西统一起来,代替KVM PCI设备assignment的代码,并提供一个比UIO更安全、功能更丰富的用户态驱动环境。 Groups、Devices和IOMMUs 设备是任何IO驱动的主要目标。设备一般会创建包括IO访问、中断和DMA在内的编程接口。不讨论每个驱动的细节,DMA通常是保证安全环境的最重要的部分,这是由于如果不对设备向物理内存的读写操作不设限制,将会造成对整个系统造成极大威胁。 为了减小这种风险,很多现代IOMMU将隔离特性加入到负责地址转换的接口中,这解决了设备在受限制的地址空间的寻址问题。有了这种介质,设备之间或者设备和某块内存间可以实现有效的隔离,这也允许了设备到虚拟机的安全直接管理。 这种隔离性的粒度并不总是单个设备,即使IOMMU可以做到这点,设备的属性、连接方式和IOMMU拓扑结构都可能会减弱这种隔离性。比如,一个独立的设备可能是一个更大范围设备集合的子集,那么即使IOMMU可以辨识出在这一集合中的不同设备,这个集合中的transactions也不会需要经过IOMMU。例如,从一个functions之间有后门的多function PCI设备,到一个non-PCI-ACS (Access Control Services)bridge的任何东西都允许不经过IOMMU的重定向。拓扑结构也在隐藏设备这件事中扮演着很重要的角色。一个PCIe-to-PCI的bridge隐藏了它之后的所有设备,让transaction看起来就来自bridge本身。显然,IOMMU也承担了主要的任务。 因此,虽然大多数情况下IOMMU可以达到设备级的隔离粒度,系统一般也是容忍这个粒度被放宽的。IOMMU API也因此支持IOMMU group的概念。一个group就是一组设备的集合,这组设备隔离于系统的其他设备。Group因此也是VFIO所用的ownership的单元。 虽然group是保证用户访问安全的最小粒度,它并不一定是最好的粒度。在使用page tables的IOMMU中,多个groups之间共享一组page table是可能的,这减小了硬件平台的开销(减少TLB thrashing、减少重复page table等),也减小了用户的开销(只编写一个set的转换即可)。因此,VFIO使用了一个container的概念,一个class可以包括一个或者多个groups。创建一个container很简单,只要打开/dev/vfio/vfio字符设备即可。 Container自身只提供很少的功能,。。。 用户需要将group加到container中来获得下一级的功能。要这样做,用户首先需要找到所关心设备所属的group,这可以用下边例子中的sysfs链接办到。通过将设备从host驱动解绑并绑定到VFIO驱动上,一个新的VFIO group会出现为/dev/vfio/$GROUP,其中$GROUP是IOMMU的group number,目标的设备是这个group的成员。如果IOMMU group有多个设备,那么这个VFIO group可用前,每个设备都需要绑定到一个VFIO驱动上(只是将所有设备从host驱动解绑也可以,这也会让group可用,但是没有绑定VFIO设备的特定设备不可用)。待定:禁用驱动probing/locking一个设备的接口。 如果group准备好了,可以通过open这个VFIO group字符设备(/dev/vfio/$GROUP)将这个group加入到container中,并用ioctl的VFIO_GROUP_SET_CONTAINER参数将打开的container文件描述符fd传入。如果需要在多个group间分享IOMMU上下文中,过个group可以被设置(set)到一个相同的container。如果一个group无法被set到一个container,那么一个空的container将被使用。 如果一个或多个group被加入到一个container,那么剩下的ioctl参数就可用了,可以访问VFIO IOMMU接口了。而且,现在在VFIO group的fd上用ioctl可以得到每个设备的文件描述符。 VFIO设备的API包括描述设备、描述IO region、描述设备描述符上read/write/mmap偏移量的ioctl参数,也包括描述和注册中断通知的机制。 VFIO使用实例 假如我们想访问PCI设备0000:06:0d.0: $ readlink /sys/bus/pci/devices/0000:06:0d.0/iommu_group ../../../../kernel/iommu_groups/26 因此这个设备在IOMMU group 26。这个设备在pci bus上,所以用户会用vfio-pci管理这个组: # modprobe vfio-pci 绑定这个设备到vfio-pci驱动并为这个group创建VFIO […]

原文是性能优化大神Brendan Gregg的一篇17年的博客”CPU Utilization is Wrong”[1],写的很有参考价值,我翻译一下。Youtube上也有与之对应的一个presentation: 我们大家用的“CPU占用率”这个指标存在很大的误导性,而且这种误导近年来越来越严重。什么是CPU占用率,它是指你的处理器到底多忙吗?不,那并不是它所能反映出的。是的,我正要讨论%CPU这个大家都处都在用的性能指标(包括每一种的性能检测软件,包括top工具)。 你以为90%的CPU占用率是这样时, 其实它有可能是这样的: Stalled指的是处理器不能继续执行指令,这基本是由于CPU在等待内存IO。上边我所画的busy和stalled的比例正是我在真实的生产环境所见的。所以,很有可能你在内存stall时,并没有察觉到。 对我们来说这意味这什么?明白了我们的CPU有多少被stall可以让我们直接将性能优化的努力放到减少不必要的代码和内存IO。能搞清楚CPU占用率中有多少比例的stall成分,任何关注CPU性能的人(尤其当程序运行在是根据CPU来自动扩展的云环境中时)都能获得好处。 到底什么是CPU占用率 CPU占用率这个指标其实就是“非空闲时间”,即CPU没有在跑idle线程。OS内核经常会在上下文切换时进行追踪操作,如果非空闲线程开始跑并在100毫秒后停下来,那么内核认为CPU被全部占用。 这个指标和分时系统一样古老。阿波罗登月舱的指挥计算机(分时系统的先驱)把它的idle线程称为”哑任务(dummy job)”,工程师们通过追踪运行哑任务和其他运行真实任务的cycles来得到这一重要的计算机占用率指标。(就像我以前所写一样。) 那么问题出在哪呢? 当今,CPU比主存更快,因此“等待内存”成为了“CPU占用率”的主要部分。当你在top工具中看到高的%CPU时,你可能会认为处理器(在散热器和风扇下的CPU封装)是瓶颈,不过其实都是那些DRAM内存的问题。 这种情况越来越糟,一直以来,处理器时钟的增速都要大于DRAM访问延迟的增速(“CPU-DRAM鸿沟”,译注:也叫“内存墙”)。这种局面持续到2005年的3 GHz处理器,从那以后,处理器往多核、超线程、多socket发展,这些都提升了对内存子系统的需求。处理器厂商尝试用更智能的CPU cache、更快的内存总线和互联网络来解决这些问题。但是我们仍然经常面临stall问题。 怎么知道CPU正在干什么 通过使用性能监测计数器(Performance Monitoring Counter, PMC) :一种可以用Linux perf或其他工具读取的硬件计数器。比如我们可以这样监测整个系统10秒钟: # perf stat -a — sleep 10 Performance counter stats for ‘system wide’: 641398.723351 task-clock (msec) # 64.116 CPUs utilized (100.00%) 379,651 context-switches # 0.592 K/sec (100.00%) 51,546 cpu-migrations […]

Persistent Memory (pmem, PM) 是很特殊的设备,它既是内存接口,又有非易失的存储特性。因此:作为内存,它即面临内存所特有的cache coherence等问题;作为存储,它也面临外存系统所特有的崩溃一致性、持久化等问题。 因此,构建一个基于PM的存储系统也所要考虑的问题是比较多的。近年来,学术界大量文章都在关注PM存储系统或者PM编程模型,比如PM感知文件系统、PM KV存储、PM事务框架等。 本文分为持久化、原子性和崩溃一致性三小节,讨论了PM系统编程的一些必须注意的点。文章整体基于我之前所写调研综述的相关部分,并进行了精简和修改。本文参考了很多文章并加入了自己的理解,若有问题,恳请指正。 1. 持久化 (Durability) 然而,对PM的store操作后并不能保证数据肯定存储到PM上,这是因为CPU和PM之间还有几层cache,要保证持久化,我们需要用flush / fence相关的指令。 当使用PM时,CPU对内存的store操作可能会暂存在硬件管理的cache或者write buffer中,若用户不强制刷cacheline到内存,无法保证store操作的数据何时写到内存中。在原来的情况下,内存为掉电易失的DRAM,所以刷或者不刷cacheline只可能牵扯到系统的性能,而不会影响系统的正确性;但是在使用PM时,由于持久化的存储是我们的目标之一,我们就要额外注重数据持久化的时机,以进行更精确的控制。 在使用块设备时,类似的问题同样存在。写操作一般会被缓存到系统的page cache中,因此需要用户调用fsync等函数来进行磁盘同步,让数据真正持久化地存储到磁盘上。解决PM cache数据写回的问题和块设备同步磁盘问题的思路是类似的,只不过方法不同。如下图,在x86平台中,当数据离开CPU cache进入PM或者进入电容保护的持久区(虚线框),便意味着数据已经被持久化,因此只要使用将数据刷出cache并写回PM的指令,就可以保证相应数据持久化存储到PM了。 由于一般在使用PM时,是通过内存映射的方式进行的,所以使用操作系统实现的msync函数是可行的(msync和fsync具有相同的语义)。除此之外,用户也可以直接调用x86平台的cache刷新指令进行数据同步。 指令 说明 CLFLUSH 几乎所有CPU都支持,但没有任何并发性,串行地执行 CLFLUSHOPT + SFENCE 比CLFLUSH新,但是不是串行执行的,因此需要SFENCE CLWB + SFENCE 相对CLFLUSHOPT,可以在刷入PM后仍然让cache保持有效,局部性较好的数据使用此命令可以提升性能 NT stores + SFENCE 即non-temporal store,直接跳过cache的store命令,因此不需要刷新cache WBINVD 只能在内核模式用,将所有CPU的cache 刷入PM,一般不用,太影响性能 上表列出了其他的cache刷新指令,它们的行为各有不同,需要依据场景进行选择。除表中最后一项外,其他指令都是可以在用户空间直接使用的。在用户空间调用cacheline刷新指令的好处是不用切换到内核态,且用户能更清楚地知道哪块数据需要马上写回PM,所以用户的控制更精细,刷新指令的性能也要好于msync。但是,一些PM感知文件系统也需要msync的控制权,因为数据刷入PM若需要造成PM感知文件系统的metadata改变,那么用户空间使用cacheline刷新指令将导致PM感知文件系统metadata的不一致。所以用户程序应该仅在确保文件系统安全的情况下才使用cacheline刷新指令。 2. 原子性 (Atomic Updating) 这里的原子性指的是原子更新粒度(原子操作)或并发时的原子可见性(隔离性),而非ACID事务的原子性。虽然ACID事务的原子性也是需要借助原子操作实现,这里的原子性更类似与ACID中的I(isolation, 隔离性)。 PM是内存接口,其原子性操作和内存类似,因此其访问原子性也和内存一样,在无锁保护的情况下, x86 平台上支持8、16 或 64 […]

本文主要探索以mmap接口访问文件时,文件自身大小、mmap映射范围和我们所能访问区间之间的关系。主要通过几个小的实验程序来说明。 本文假定读者了解mmap可以作为文件访问的接口,若没有用过可以在Linux中直接man mmap看相关说明,或者去网上搜索其他资料。简单来说,文件的某一段经过mmap系统调用映射后会返回一个地址,这样我们可以像操纵内存一样操纵磁盘上的数据,因此”open +mmap+memcpy+msync “这套文件操作可以在很多的时候代替”open+read/write+fsync“这套文件操作。 但是相比write进行追加写的操作,被mmap映射的地址是无法做到改变被映射文件大小的,那么我们如果想改变文件大小怎么办?如果我们写的地址大于实际文件大小会出现什么情况?如果我们写的地址大于所映射的地址范围会有什么情况? 通过两组简单的测试,我们可以探究这个问题: 测试1:”文件范围内, mmap范围外” 会产生SIGSEGV段错误 测试1是简单的情况,我们mmap映射的范围小于文件的实际大小,那么当我们访问在文件范围内但不是映射区范围内的地址时,会产生”segmentation fault”(SIGSEGV)错误!这很好理解,因为我们访问了非法的内存地址。 如下图,具体的,我们创建一个1 MB的文件,然后将其前512 KB用mmap映射,然后尝试访问文件第800 KB,第800 KB虽然在文件的范围内,但是不在映射范围内。结果是产生segmentation fault (SIGSEGV) 段错误。 +——————+——————+ file_testmap: | mmapped | not mmapped | +——————+——————+ 0 (KB) 512 ^ 1024 | we try to access here –+ (it will cause seg. fault) 程序1: #define _GNU_SOURCE #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> […]

近年,一些本为内核处理的任务,分别出现用户态的实现,有的是为了提升开发灵活性(FUSE、userfaultfd),有的则是为了提高与外设通信的性能(SPDK、DPDK)。本系列文章对我所了解到的用户空间实现的内核机制进行使用介绍或原理分析。第一篇文章介绍用户态的缺页处理 — userfaultfd机制,以后还可能根据我的学习进度介绍userfaultfd的内核实现原理、FUSE的使用和原理、SPDK等内容。文章若有错误,恳请指正。 userfaultfd 机制让在用户控制缺页处理提供可能,进程可以在用户空间为自己的程序定义page fault handler,增加了灵活性,但也可能由于类似FUSE之于内核FS的问题(调用层次加深)而影响性能。 1. 基本使用步骤 以最基本的用户空间进行匿名页缺页处理为例,(例子代码基本来自userfaultfd的man page[1],)步骤大致如下: STEP 1. 创建一个描述符uffd 要使用此功能,首先应该用userfaultfd调用[1]来创建一个fd,例如: // userfaultfd系统调用创建并返回一个uffd,类似一个文件的fd uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); 然后,所有的注册内存区间、配置和最终的缺页处理等就都需要用ioctl来对这个uffd操作。ioctl-userfaultfd[2]支持UFFDIO_API、UFFDIO_REGISTER、UFFDIO_UNREGISTER、UFFDIO_COPY、UFFDIO_ZEROPAGE、UFFDIO_WAKE等选项。比如UFFDIO_REGISTER用来向userfaultfd机制注册一个监视区域,这个区域发生缺页时,需要用UFFDIO_COPY来向缺页的地址拷贝自定义数据。 STEP 2. 用ioctl的UFFDIO_REGISTER选项注册监视区域 比如,UFFDIO_REGISTER对应的注册操作如下: // 注册时要用一个struct uffdio_register结构传递注册信息: // struct uffdio_range { // __u64 start; /* Start of range */ // __u64 len; /* Length of range (bytes) */ // }; // […]

(翻译自qemu 3.0的文档) qemu-img [standard options] command [command options] qemu-img 允许你离线地创建、转换或者修改镜像文件。它可以处理所有QEMU支持的镜像格式。 注意: 千万不要用 qemu-img 修改虚拟机(或者其他程序)正使用着的镜像,因为那可能会破坏镜像;对应的,如果读正在修改的镜像,也可能会有不一致的状态产生。 1. Standard options: ‘-h, –help’ 显示帮助信息。 ‘-V, –version’ 显示版本信息。 ‘-T, –trace [[enable=]pattern][,events=file][,file=file]’ Specify tracing options. 指定tracing选项: ‘[enable=]pattern’ 以parttern来指定启动的events。这个选项仅在编译了simple、log或ftrace等3个tracing后端时才能用。要启用多个events或者patterns,用多个-trace即可,用-trace help可以打印列出trace points名称。 ‘events=file’ 马上开启file中列出了的events名称,file中每行为一项event名称或者patterns (存在于trace-events-all文件中的)。同样,这个选项仅在编译了simple、log或ftrace等3个tracing后端时才能用。 ‘file=file’ 将traces的输出打印到log文件中。同样,这个选项仅在编译了simple、log或ftrace等3个tracing后端时才能用。 2. Command: 以下是被支持的命令: amend [–object objectdef] [–image-opts] [-p] [-q] [-f fmt] [-t cache] -o options filename […]