本文以我的视角对 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 […]

本文主要探索以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) */ // }; // […]

谁来定义虚拟化? 一个比较抽象的词,很难给出一个比较明确的定义,而且我也一直认为没有人可以对一个抽象的概念给出绝对正确的定义。如果一个公司想争夺一种技术的定义权,我会毫不吝啬的给它扣上“技术独裁者”的帽子。 但是理解一个词在当前这个时代、这个世界的用法还是很有必要的,比如“虚拟化”这个词,对虚拟化开始重点关注以来,对“虚拟化”这个词的理解的确是在逐渐变化的,今天就来说一说我目前为止的理解。 从虚拟机说起 关注虚拟化方向之前,我对虚拟化这个词的理解是经历了几个阶段的。最开始可能“虚拟机”啊,有了虚拟机,大家就能方便地在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) […]

例子 以打印一个蓝色斜体的”hello, world“为例: C printf(“\033[3;34mhello, world\033[0m\n”); python print “\033[3;34mhello, world\033[0m” Shell echo -e ‘\033[3;34mhello, world\033[0m’ 格式 \033[Para0{;Para1…}mYOUR_TEXT\033[0m \033[Para0{;Para1…}m 表示转义开始 *033[0m 作为转义结束 Para0(1,2…) 参数可以为多个,比如上述例子*,3表示为斜体,34表示蓝色 YOUR_TEXT 在例子中就是hello, world 参数 Linux中通过man console_codes命令可查看详细的参数描述*这里写一下常用的格式和颜色: 常用格式: 参数代码 描述 0 重置所有格式 1 粗体(高亮) 2 暗色 3 斜体 4 下划线 5 闪烁 常用颜色: (前景色为30+颜色代码;背景色为40+颜色代码。) 颜色 代码 前景 背景 黑 0 30 40 红 […]

写shell脚本处理文本的时候,经常用到awk来配合shell命令。但是awk的大括号中和shell貌似是两个世界。本文只介绍最容易理解的方法(作者水平有限,复杂的以后可能补充),来实现awk对shell变量的使用和更改。 如果我们将awk看成变成语言中的函数,或者一个封装,那么要使用或者修改外部的变量,其实就是输入参数和输出返回值的问题。对于使用shell变量,其实就是shell变量怎么作为参数传入awk的问题;而对于awk给shell变量赋值,可以看成awk输出返回值的问题。 awk中使用shell变量 awk传入参数的选项是-v [awk_var=$SHELL_VAR],应该加在’ ‘包围的awk主体程序之前。 示例脚本1: #!/bin/bash VAR1=”~~~!” echo hello, world|awk -v awk_var1=$VAR1 ‘{ print $1, “shell”, $2, awk_var1 }’ 输出: hello, shell world ~~~! awk给shell变量赋值

在LWN.net上的一篇文章,The UAPI header file split(By Michael Kerrisk July 25, 2012)。原文链接:https://lwn.net/Articles/507794/ 这个特性已经在3.7版本中被Linus大神接受[详情]。。。Linus大神如是说: the “uapi” include file cleanups. The idea is that the stuff exported to user space should now be found under include/uapi and arch/$(ARCH)/include/uapi. Let’s hope it actually works. Because otherwise this was just a totally pointless pain in the *ss. And regardless, I’m definitely done […]