我这个人不喜欢废话,本文基本都是干货(其实一开始是写给自己看的Cheat Sheet性质的笔记没想到越写越长),没必要展开的地方就不展开讲了,读者可自行参考The Rust Book和Rust Reference。基本上Part I就是搬运的The Rust Book,补上了若干细节,Part II是一些相对更「高级」的特性,可对照Rust Reference食用,Part III是全文的精髓,里面对Rust核心的ownership、borrow和lifetime机制的讨论,目前中文资料里还没有如此深入的(好像英文也没有,大家都是复读机,只会复读最基本的知识)。

Part I. Common Rust

Basics

首先看一下基本语法,在传统的C-like基础上引入了大量ML-Style语法:

  • 注释:// this is a line comment, /* this is a block comment */
    • /// inner line doc, /** inner block doc */: 这种注释用于为紧跟着的对象提供文档
    • //! outer line doc, /*! inner block doc */: 这种注释用于为父对象提供文档
    • 文档注释支持Markdown语法
  • ML风格变量声明
    • let lhs = rhs;: Statement以分号结尾
    • let x: u32;: 默认使用类型推导
    • let mut y = 42;: 默认immutable,加mut表示mutable
    • let x = 3; let x = x * 2;: 支持Variable Shadowing
    • let声明不是Expression,不能作为RHS
  • 单独的const声明,不允许声明为mutable
    • const MAX: u32 = 100;: 必须提供类型annotation
    • const允许在Global Scope声明,而let不允许
    • const只允许赋值Constant Expression,编译时无法确定的值不能赋值
      阅读全文 »

TSC virtualization

Basics

  • tsc_khz为Host的pTSCfreq
  • 用户态通过ioctl(vcpu, KVM_SET_TSC_KHZ)设置vCPU的vTSCfreq,即使KVM不支持TSC Scaling
  • kvm_has_tsc_control表示硬件是否支持TSC Scaling
    • 若不支持,只要vTSCfreq > pTSCfreq,仍可成功设置,此时vcpu->arch.tsc_catchup = 1vcpu->arch.always_catchup = 1
  • vcpu->arch.virtual_tsc_khz为vCPU的vTSCfreq
    • vcpu->arch.virtual_tsc_mult/virtual_tsc_shift用于将nsec转换为tsc value
  • vcpu->arch.tsc_scaling_ratio为vTSCfreq / pTSCfreq,是一定点浮点数(Intel VT-x中高16位为整数部分,低48位为小数部分)

TSC Matching

Host/Guest写入TSC时,会调用kvm_write_tsc

0). 写入的值记为vTSC,则我们要将L1 TSC Offset设置为offset = vTSC - (pTSC * scale)

阅读全文 »

Overview

查看drivers/vfio/mdev/Makefile可以发现,mdev(Mediated Device)实际上是由两个而不是一个模块构成:

1
2
3
4
mdev-y := mdev_core.o mdev_sysfs.o mdev_driver.o

obj-$(CONFIG_VFIO_MDEV) += mdev.o
obj-$(CONFIG_VFIO_MDEV_DEVICE) += vfio_mdev.o

其中mdev.ko是mdev core模块,包括了mdev的绝大多数核心功能。该模块在Device Model中定义了一种新的Bus即mdev总线,而vfio_mdev.ko则是定义了mdev总线上的一种Driver,用来实现和VFIO的对接,换句话说就是起到和vfio-pci驱动相同的作用。

这样设计的好处在于,mdev.ko模块提供的是通用的创建和销毁Mediated Device,以及将Mediated Device加入或移出IOMMU Group的功能,它不一定要和VFIO绑定。vfio_mdev.ko提供了Mediated Device和VFIO的绑定,将来也可以创建新的模块(即新的mdev驱动),提供其他形式的接口。

Mdev Core

阅读全文 »

文中代码基于Linux 5.1 rc6版本

Overview

VFIO提供了两个字符设备文件作为提供给用户程序的入口点,分别是/dev/vfio/vfio/dev/vfio/$GROUP,此外还在sysfs中添加了一些文件。

首先看/dev/vfio/vfio,它是一个misc device,在vfio模块的初始化函数vfio_init中注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static struct miscdevice vfio_dev = {
.minor = VFIO_MINOR,
.name = "vfio",
.fops = &vfio_fops,
.nodename = "vfio/vfio",
.mode = S_IRUGO | S_IWUGO,
};

static int __init vfio_init(void) {
int ret;
/* ... */
ret = misc_register(&vfio_dev);
/* ... */
}

每次打开/dev/vfio/vfio文件,都会创建一个对应的Container即struct vfio_container

阅读全文 »

文中代码基于Linux 5.1 rc6版本

MODULE_DEVICE_TABLE

在讨论PCI驱动之前,我们先考察一下PCI驱动的Kernel Module是如何被自动加载的。首先,每个PCI Function有5个(或3个)Configuration寄存器,即Device ID、Vendor ID、Class Code(由Class、Subclass、Interface三个Byte构成)以及Subsystem ID、Subsystem Vendor ID(前三个必选,后两个可选),在Linux Kernel中用pci_device_id表示:

1
2
3
4
5
6
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
__u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data; /* Data private to the driver */
};

每个非内置的PCI驱动,都要声明一个pci_device_id列表(以数组的形式),用于表示其支持的PCI设备,我们假设列表为id_tbl,则使用宏MODULE_DEVICE_TABLE(pci, id_tbl)声明该列表,这个信息最终会编译到.ko文件中。我们来看一下具体过程:

首先,通过MODULE_DEVICE_TABLE宏,我们声明了id_tbl的一个别名__mod_pci__id_tbl_device_table

阅读全文 »

文中代码基于Linux 5.1 rc6版本

Major and Minor Number

我们知道,在类Unix操作系统中都有Major和Minor Number的概念,一对(Major, Minor)对应于一个设备。对于同一个设备,实际上可以通过mknod命令创建任意多个特殊的设备文件(Device File),对这些设备文件的读写操作都会由同一个驱动处理。另外,设备被分为块设备(Block Device)和字符设备(Character Device),区分标准是块设备只能以固定大小的块(例如512B或4K)为单位读写数据,块设备和字符设备各自拥有一个命名空间(Namespace)。也就是说,对于同一对Major、Minor号,可以存在两个不同的设备文件,一个对应于块设备,另一个对应于字符设备。

在早期,Major号通常对应于一个驱动,而Minor号用于区分该驱动支持的不同型号的设备,但现在不同驱动也可以共用一个Major号,一个驱动也可以使用多个Major号,故此说法已不具参考意义。在Linux 2.6以前,不提供动态分配Major、Minor号的功能,当时所有设备的Major、Minor号由专门的机构LANANA(Linux Assigned Names and Numbers Authority)管理,LANANA在 www.lanana.org/docs/device-list/ 维护了一份列表。在Linux 2.6以后,内核提供了动态分配Major、Minor号的功能,不再需要专门维护一份列表,继承自LANANA的列表现在位于Linux内核中的Documentation/admin-guide/devices.rstDocumentation/admin-guide/devices.txt,而LANANA网站则不再维护。

在早期,Major和Minor Number各占8位,合起来可以用一个16位整数表示。随着设备的种类和数量越来越多,人们发现编号开始不够用了,于是自Linux 2.6起改为了Major为12位,Minor为20位,合起来用一个32位整数(dev_t类型)表示。并且,为了将来的兼容性,禁止对dev_t类型变量的直接操作,所有操作都要通过helper函数或宏进行。

Character Device

阅读全文 »

文中代码基于Linux 5.1 rc6版本

Introduction to kobject

Linux Device Model的基本构成元素是kobject,通常每个kobject对应于sysfs(/sys)中的一个目录,其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct kobject {
const char *name; /* sysfs中的目录名 */
struct list_head entry; /* 同一kset下的kobject构成一个链表 */
struct kobject *parent; /* 父目录的kobject对象 */
struct kset *kset; /* 所属的kobject集合(kset),可以不设置 */
struct kobj_type *ktype; /* 提供目录中的文件、namespace、ownership等 */
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref; /* 提供引用计数功能 */
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1; /* 是否初始化完毕 */
unsigned int state_in_sysfs:1; /* 是否在sysfs中注册完毕 */
unsigned int state_add_uevent_sent:1; /* 是否发送过KOBJ_ADD uevent消息 */
unsigned int state_remove_uevent_sent:1; /* 是否发送过KOBJ_REMOVE uevent消息 */
unsigned int uevent_suppress:1; /* 是否禁止uevent的发送 */
};

首先,kobject提供引用计数功能(通过kobj->kref),用户应使用kobject_get/kobject_put获取、释放kobject的引用,当引用计数降到0时,则会调用kobj->ktype->release进行析构工作(注意析构函数定义在ktype中,因此kobject必须设置其ktype)。

使用kobject,实际上就是将其初始化并加入sysfs的过程:

阅读全文 »

文中代码基于Linux 5.1 rc6版本

Static Initialization (i.e. initcall)

对于bulit-in的Driver,往往使用device_initcall进行初始化。对于Kernel Module,如果用户没有将其编译为module而是编译成了built-in(CONFIG_XXX = y而不是CONFIG_XXX = m),则module_init最终也会展开为device_initcall

1
2
3
4
/* from include/linux/module.h */
#define module_init(x) __initcall(x)
/* from include/linux/init.h */
#define __initcall(fn) device_initcall(fn)

这实际上属于一个initcall系列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* Early initcalls run before initializing SMP.
*
* Only for built-in code, not modules.
*/
#define early_initcall(fn) __define_initcall(fn, early)

/*
* A "pure" initcall has no dependencies on anything else, and purely
* initializes variables that couldn't be statically initialized.
*
* This only exists for built-in code, not for modules.
* Keep main.c:initcall_level_names[] in sync.
*/
#define pure_initcall(fn) __define_initcall(fn, 0)

#define core_initcall(fn) __define_initcall(fn, 1)
#define core_initcall_sync(fn) __define_initcall(fn, 1s)
#define postcore_initcall(fn) __define_initcall(fn, 2)
#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
#define arch_initcall(fn) __define_initcall(fn, 3)
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
#define subsys_initcall(fn) __define_initcall(fn, 4)
#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
#define fs_initcall(fn) __define_initcall(fn, 5)
#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
#define device_initcall(fn) __define_initcall(fn, 6)
#define device_initcall_sync(fn) __define_initcall(fn, 6s)
#define late_initcall(fn) __define_initcall(fn, 7)
#define late_initcall_sync(fn) __define_initcall(fn, 7s)
阅读全文 »

文中代码基于Linux 5.1 rc6版本

Overview

Kbuild系统终究还是由Makefile构成的,系统主要包括以下部分:

  • Makefile,即根目录下的Makefile
  • .config,即make configmake menuconfig生成的配置文件
  • arch/$(ARCH)/Makefile,该文件会被根Makefile所include
  • scripts/Makefile.*,用于定义Kbuild的规则,它其实是编译时实际用到的Makefile
  • kbuild Makefiles,即各个目录下的Makefile,其文件名可以为Makefile,也可以为Kbuild(e.g. drivers/gpu/drm/nouveau/nvkm/subdev/pci/Kbuild),它其实只是被scripts下的Makefile模板include,因此可以写得很简单

此外还有各级目录下的Kconfig文件,不过它们主要是在用户进行配置(例如make menuconfig)时起一个提示以及约束的作用,另外也提供配置选项的默认值。最终的make过程,只依赖生成的.config文件。

顶层Makefile中定义了一些变量,例如$(MODLIB)定义了modules安装的地址,其默认值为$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE),其中$(INSTALL_MOD_PATH)需要由用户提供,$(KERNELRELEASE)是一个表示kernel版本号的字符串,相当于uname -r的输出。因此,modules默认的安装地址为/lib/modules/*/

阅读全文 »