本文以我的视角对 ublk 进行了最基本的分析,希望也为你带来帮助。 ublk ublk 是一个 6.X 内核全新的实现用户态块设备驱动的内核框架,之前的类似框架还有 TCMU、vdpa-user (VDUSE) 和 NBD。ublk 框架中,一个额外的 ublk Server 用户态进程作为 ublk 块设备的服务后端,实现了主要的存储逻辑。区别于其他用户态块设备框架,ublk 采用 io_uring 作为内核于用户态通信的传输机制。ublk 架构图如下: 使用 ublk 框架,内核会多出几种设备,包括一个唯一的 ublk_ctl 设备,多个名为 /dev/ublkcN 的字符设备,以及同样数量的 /dev/ublkbN 块设备。其中, 块设备是实际的存储服务设备,可以格式化文件系统或者作为裸设备使用,这也是 ublk 存在的最终目的; 字符设备是 ublk 框架的数据面接口,主要被用户态 ublk Server 进程用于与内核通信,处理块设备的实际 IO 请求; ublk_ctl 设备(/dev/ublk-control)则可以看作的是 ublk 框架的控制面通道,ublk Server 通过请求 ublk_ctl 设备来创建出多对字符设备和块设备, 类似于其他用户态驱动框架,ublk 为了方便用户态 ublk-server 的开发,也提供了用户态 SDK […]

汇总一些容易混淆的容器领域技术概念,希望对你有所帮助。 技术栈架构 生态对比 Ecosystem Orchestration Service Orchestration Agent Container Engine ContainerRuntime CLItools OCI / CNCF k8s kubelet containerd(CRI runtimes) runc / kata(OCI runtimes) ctr,crictl Docker docker swarm dockerd(docker CLI) containerd runc docker LXD /Canonical clusterd lxd lxd lxd lxc lxc OpenShift / Redhat k8s kubelet CRI-O runc podman 当然,这里列出的只是各个生态的默认技术栈,开源社区中还有各种项目让不同生态的组件互相组合协同工作。比如:Mirantis/cri-dockerd: dockerd as a compliant Container Runtime […]

原文为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 […]

(翻译自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 […]

我们应该怎么分析QEMU代码中某段代码的性能呢?除了比较复杂的trace-event功能(我的博客中翻译过qemu tracing的文档),其实在QEMU自带有一个简单的profiler实现,它是一个简单的计时器封装。这篇博客主要介绍怎么在编译时开启、使用QEMU profiler,并说明怎么利用这个功能添加一个自己的计时器来分析QEMU中某段代码的性能。 1. 编译 我的代码是QEMU 2.12.0。要开启profiler功能,在编译前进行运行configure的时候,只要加入–enable-profiler选项就可以了,它会加入CONFIG_PROFILER这个宏定义。比如我用如下选项进行编译: cd QEMU_SRC_PATH ./configure –prefix=/PATH/TO/QEMU_BIN_DIR –target-list=x86_64-softmmu –enable-profiler make -j make install 将其编译到QEMU后,无须改动启动参数,它是默认启用的,但是我们一般需要用QMP shell来查看它帮我们计时的信息。 2. 使用 在启动QEMU虚拟机的命令中,我们需要加入一个QMP socket,用于一会接入我们的QMP shell: # -qmp后边接我们要创建的QMP socket文件的信息,这里我们将它创建在本目录(./qmp-sock文件) /PATH/TO/QEMU_BIN_DIR/bin/qemu-system-x86_64 … -qmp unix:./qmp-sock,server,nowait 在虚拟机启动后,用QEMU源码中自带的QMP shell脚本连接QMP socket: # 这个脚本就是QEMU源码中的scripts/qmp/qmp-shell文件 # 加-H是为了以HMP命令进行交互,否则就得使用json格式,不方便 /PATH/TO/QEMU_SRC/scripts/qmp/qmp-shell -H ./qmp-sock 之后出现以下欢迎界面: Welcome to the HMP shell! Connected to QEMU 2.12.0 (QEMU) 我们在(QEMU)后面输入info profile就可以看到QEMU自带的两个计时器的数值,每次查看完这个数值,数值会清零。 Welcome […]

日记式的个人胡扯,没有一句话保证正确,谢谢围观,欢迎指正。。 通用与专用,没有好坏 首先举两个极端的例子:深度学习应用十分重要,因此人们愿意专门为它花钱设计新型专用芯片和硬件架构,甚至可能为了它的性能,没准以后会重新写了一个叫”DnnOS”(我自己瞎编的名字)操作系统;而Jc写的helloworld.py程序无论在Linux, Windows 95还是Win 10上怎么折腾,都是打印一行hello, world,没有任何区别的。 哪个更好的问题没有绝对的答案,也许深度学习的新型硬件没有键盘鼠标和显示器,DnnOS也只要实现一套新型硬件的驱动,复杂程度远远小于Linux和Windows。但是Jc的helloworld.py无论运行在多么复杂的系统上,都无法到一个好的深度学习模型所带来的价值;反过来说,即使这套新型深度学习工具再牛逼,可能都无法运行Jc的helloworld.py程序。 所以,废话就是,当然是各有利弊。 什么应用需要专用系统, 首先当然是嵌入式设备,有些嵌入式设备并没有操作系统,有些有经过精简的操作系统。为实际应用量身定做的硬件和软件,对权衡性能和成本应该是最有利的! 另一种是,在我看来,是专用服务器,比如一台专门的数据库服务器,那么首先它的硬件选型是很重要的,比如存储设备要选高端的,显卡就不要了;它的操作系统的很多东西就用不上了,比如DBMS通常会关掉OS的文件缓存page cache,甚至不用OS的文件系统,来自己管理缓存和裸块设备,包括很多多余的硬件驱动都是不必要的。 什么应用需要通用系统 最易想到应该使用通用系统的例子,就是每个人的手机还有个人电脑了,由于每个人的爱好和工作习惯不同,它们的任务多种多样,资源利用的特点也各不相同。80年代的PC、00年代的手机,都是以计数器、画图、记事本功能齐全作为卖点,后来随着系统的发展,甚至会搞出应用商店这种来增加系统的功能,这时,工作负载就更不唯一。这样,在为每个用户量身定做系统不现实的情况下,只有系统更通用才会更满足消费者的多样需求(比如有人爱拍照,有人爱打王者荣耀)。 另一种,可能是云,也就是各种虚拟化和分布式技术。原因很简单,租用云服务的用户所需要的不只是一种服务(可能是云主机、云数据库或者云存储),更重要的是负载也是无法预测的。最典型的例子是云虚拟机,这时,一个“需要通用”的操作系统运行在云上,那么这个云更通用,比通用OS还要通用。比如,一个可以称为“云操作系统”的系统,要保证计算、存储、内存的可伸缩性,这时一般OS是不提供的,还要保证运行和数据可靠,这都是通用(通用虚拟机)之外的通用(伸缩性、可靠性)。 通用也是专用 讨论了什么时候应该通用和专用,及其原因之后,突然觉得:通用也是专用,因为不管是通用的还是专用的。因为专用的本来就是专用的;而通用的,开发它们多数都是 专用 来赚钱的,不管是手机还是云。

谁来定义虚拟化? 一个比较抽象的词,很难给出一个比较明确的定义,而且我也一直认为没有人可以对一个抽象的概念给出绝对正确的定义。如果一个公司想争夺一种技术的定义权,我会毫不吝啬的给它扣上“技术独裁者”的帽子。 但是理解一个词在当前这个时代、这个世界的用法还是很有必要的,比如“虚拟化”这个词,对虚拟化开始重点关注以来,对“虚拟化”这个词的理解的确是在逐渐变化的,今天就来说一说我目前为止的理解。 从虚拟机说起 关注虚拟化方向之前,我对虚拟化这个词的理解是经历了几个阶段的。最开始可能“虚拟机”啊,有了虚拟机,大家就能方便地在Windows下用Linux系统啦。后来云计算火起来了,云服务器的底层技术是什么啊,是虚拟机和虚拟化啊,所以感觉虚拟化真是很牛逼。后来Docker火了,Docker是什么,人家都说是“轻量级的虚拟化”,哇,虚拟化这么牛逼,虚拟机、云服务器、Docker都用了虚拟化啊。 虚拟机就像下面的图中,如果把一个计算机软硬件系统比作一个从地基垒起的金字塔(用户和应用在金字塔顶),那么虚拟机就像一个倒立的金字塔,虚拟机的用户就像是倒立的金字塔上的金字塔。要让一个虚拟机正常运转,就要在软件层面上模拟各种计算机硬件,涉及到计算,存储,网络等各种设备。 以QEMU为例,虚拟机的CPU是以QEMU创建的vCPU线程进行模拟的,这些线程仍然由Host操作系统进行调度;虚拟机的网络和存储IO是通过virtio这种半虚拟化机制达成的,Guest的IO通过内存的循环队列传递数据和消息;虚拟机的存储设备通过Host中的一个文件来进行模拟……但是虚拟机(Virtual Machine)所用的技术就是虚拟化技术(Virtualization)吗?我认为不一样。 虚拟机之外的虚拟化 以存储虚拟化(Storage Virtualization)为例,Linux逻辑卷管理技术(LVM),有了这种技术,我们可以将多个分区虚拟成一个大的逻辑分区,或者将一个分区虚拟成多个小的逻辑分区。LVM之外,Linux中的loop回环设备(将一个文件虚拟成一个块设备)、tmpfs(将一段内存虚拟成一个文件系统分区)、RAM DISK(将一段内存虚拟成一个块设备)、UnionFS(将多个文件系统虚拟成一个文件系统)在我理解都应该属于虚拟化之列。而QEMU虚拟机中,我们多是把一个文件虚拟成一个磁盘,其灵活性无法代表整个存储虚拟化技术,或者说仅关注QEMU,我们不能关注到所有的存储虚拟化技术。 QEMU VM的vCPU是Host操作系统中的线程,可以看做是计算的虚拟化。其实按照广义的虚拟化,Intel的超线程(Hyper-threading)技术岂不更加“虚拟”?比如它可以将一个核抽象成操作系统看来的两个核。 虚拟化,无处不在 更广义(准确)的,其实操作系统技术本身就是虚拟化技术,没有操作系统的进程调度时间片切换,也许CPU是1核的就只能同时跑1个程序,但有了操作系统,你不仅能同时照着网页抄作业、还能看着paper听着歌。这一切都是系统将计算资源虚拟化的结果啊。 再说编程语言,程序员之所以不用编写二进制代码,便是因为有工具可以将高级语言翻译成机器码,如果不去研究编译器,这种翻译也是透明的;编程语言,形形色色,不管你用哪种,其实背后都是0,1在执行(电位在变化)。我可以在这篇博客里写一串二进制码011110100110101001100011,你看到的0和1并不是底层的0和1,但还是0和1,不也是类似虚拟机软件反向搭建金字塔的过程吗?同样,人类语言也可以看成是思想的“虚拟化”。 所以,什么是虚拟化?虚拟化,道也,什么是道?老子说,“道可道,非常道”。

0. GCC的attribute关键字 这是GCC的一个特性,gcc可以使用attribute关键字,格式如下: __attribute__((attribute_name)) 其中attribute_name中有两类constructor和destructor类似C++中类的构造和析构的概念,只不过是相对main()函数来说的。简单说,__attribute__((constructor))定义的函数在main前执行,__attribute__((destructor))定义的函数在main后执行。 1. 以qcow2为例 QEMU中有很多”module”的初始化使用了__attribute__((constructor))这个特性,来在main前完成init函数的注册过程。使用方法举例具体如下: // QEMU_2.10_SRC/include/qemu/module.h中: #define module_init(function, type) \ static void __attribute__((constructor)) do_qemu_init_ ## function(void) \ { \ register_module_init(function, type); \ } #endif typedef enum { MODULE_INIT_BLOCK, MODULE_INIT_OPTS, MODULE_INIT_QOM, MODULE_INIT_TRACE, MODULE_INIT_MAX } module_init_type; // 比如这里block_init函数被用在QEMU_SRC/block/*的qcow2等Format Driver中广泛应用, // 其实就是间接调用了被__attribute__((constructor))调用的register_module_init() #define block_init(function) module_init(function, MODULE_INIT_BLOCK) #define opts_init(function) module_init(function, MODULE_INIT_OPTS) #define type_init(function) module_init(function, MODULE_INIT_QOM) […]