什么是“Intel VT-d”?

Intel VT-d(以下简称VT-d)”代表“Intel Virtualization Technology for Directed I/O”。“VT(Virtualization Technology)”泛指Intel所有的虚拟化技术,而“VT-d”则是虚拟化技术解决方案中的一种。VT-d的整体思想就是用硬件方式支持隔离和限制对设备的访问。

VT-d有以下主要功能:
a)分配I/O设备:这个功能允许管理员根据需求,灵活地为虚拟机分配I/O设备。
b)DMA remapping:支持针对虚拟机DMA访问的地址转换。
c)Interrupt remapping:支持虚拟机对设备中断的路由和隔离。
d)可靠性功能:记录关于DMAInterrupt的错误访问。

参考资料:
Understanding VT-d: Intel Virtualization Technology for Directed I/O

DMA Remapping简介

DMA(Direct Nemory Access) Remapping是一种用来限制硬件设备只能使用DMA访问预先分配的内存区域(domain or physical memory regions)的技术。DMA Remapping会把DMA请求里的地址转化成正确的物理内存地址,同时还会检查设备是否允许访问指定的内存。请看下图:

dma_remapping

虚拟机的操作系统(Guest OS)所提供的物理地址称为Guest Physical Address (GPA) ,它不一定与实际的物理地址一致,也就是Host Physical Address (HPA),而DMA技术则要求访问真实的物理地址。DMA Remapping技术可以把Guest OS提供的GPA转化成HPA,然后数据就可以直接发送到Guest OS的缓冲区(buffer)了。

主机平台(host platform)可以支持一个或多个DMA remapping硬件单元(hardware unit),每个硬件单元remapping从它控制的作用域内发出的DMA remapping请求。主机固件(BIOS)需要把每个DMA remapping硬件单元报给系统软件(比如操作系统)。

DMA remapping硬件单元使用source-id来标示发出DMA请求的设备。对一个PCI Express设备,source-id就是resource identifier

 ________________________________________________________
|____Bus(8 bits)_________|__Device(5 bits)|_func(3 bits)_|

Root-entry作为最顶层的数据结构,会把某特定PCI总线上的设备映射到对应的domain。一个context-entry会把一个地址总线上的某个具体设备映射到对应的domain。参考下图:

root-entry

每个root-entry会有一个指向一个context-entry的表的指针,而每个context-entry则会包含如何用来进行地址转化的结构。

Linux kernel 笔记 (1) ——CPU在做什么?

In fact, in Linux, we can generalize that each processor is doing exactly one of three things at any given moment:
a) In user-space, executing user code in a process
b) In kernel-space, in process context, executing on behalf of a specific process
c) In kernel-space, in interrupt context, not associated with a process, handling an
interrupt

Linux中,任何时候,CPU都在做下面三件事中的一件:

a)运行进程的用户空间代码;
b)运行进程的内核空间代码;
c)处理中断(也是工作在内核空间,但不与任何进程关联)。

Go语言字符串浅析

Go语言中,字符串就是一个只读(read-only)的,可以包含任意字节(byte)的切片(slice)。由于Go源码文件都是使用UTF-8编码的,所以直接在源码中输入的字符串也都是用UTF-8编码的(前提是字符串里没有byte-level escapes,即字节层面上的转义)。请看下面这个例子:

package main
import "fmt"

func main() {
    s := "日志log"

    fmt.Println(len(s))
}

执行结果:

9

s是一个UTF-8编码的字符串,“日志”两个汉字各占3个字节,所以一共是9个字节。

再看一个例子:

package main
import "fmt"

func main() {
    s:="\xFF\xFF"

    fmt.Println(len(s))
}

执行结果:

2

虽然0xFF不是一个合法的UTF-8编码字符,但是因为字符串可以包含任意字节,所以s仍是一个合法的字符串。

Go语言引入rune类型(实际就是int32)来表示字符(character),“日志log”这个字符串包含了5个字符,9个字节。 可以用for range循环来遍历UTF-8编码字符串的每个字符:

package main
import "fmt"

func main() {
    s := "日志log"
    for index, runeValue := range s {
        fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
    }
}

执行结果:

U+65E5 '日' starts at byte position 0
U+5FD7 '志' starts at byte position 3
U+006C 'l' starts at byte position 6
U+006F 'o' starts at byte position 7
U+0067 'g' starts at byte position 8

参考资料:
Strings, bytes, runes and characters in Go

printk函数简介

printk函数是Linux kernel开发中用来打印log的常用函数,同使用在用户空间程序的printf函数很类似。实现代码如下:

asmlinkage int printk(const char *fmt, ...)
{
    va_list args;
    int r;

#ifdef CONFIG_KGDB_KDB
    if (unlikely(kdb_trap_printk)) {
        va_start(args, fmt);
        r = vkdb_printf(fmt, args);
        va_end(args);
        return r;
    }
#endif
    va_start(args, fmt);
    r = vprintk_emit(0, -1, NULL, 0, fmt, args);
    va_end(args);

    return r;
}

举个例子(注意KERN_ALERT"DEBUG ..."之间没有逗号):

printk(KERN_ALERT "DEBUG: Passed %s %d \n",__FUNCTION__,__LINE__);

KERN_ALERT代表log的级别,参考kern_levels.h

#define KERN_EMERG  KERN_SOH "0"    /* system is unusable */
#define KERN_ALERT  KERN_SOH "1"    /* action must be taken immediately */
#define KERN_CRIT   KERN_SOH "2"    /* critical conditions */
#define KERN_ERR    KERN_SOH "3"    /* error conditions */
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions */
#define KERN_NOTICE KERN_SOH "5"    /* normal but significant condition */
#define KERN_INFO   KERN_SOH "6"    /* informational */
#define KERN_DEBUG  KERN_SOH "7"    /* debug-level messages */ 

数字越大,代表优先级越低。

另外关于printk格式化字符串形式,参考printk-formats.txt

/proc/sys/kernel/printk这个文件显示了和printk相关的log级别设置:

[root@localhost kernel]# cat /proc/sys/kernel/printk
7       4       1       7

4个值的含义依次如下:
console_loglevel:当前consolelog级别,只有更高优先级的log才被允许打印到console
default_message_loglevel:当不指定log级别时,printk默认使用的log级别;
minimum_console_loglevelconsole能设定的最高log级别;
default_console_loglevel:默认的consolelog级别。

请看下面这个例子:

[root@localhost kernel]# echo 8 > /proc/sys/kernel/printk
[root@localhost kernel]# cat /proc/sys/kernel/printk
8       4       1       7

可以看到,console_loglevel的级别修改成了8

参考资料:
Debugging by printing

什么是IOMMU?

在计算机领域,IOMMU(Input/Output Memory Management Unit)是一个内存管理单元(Memory Management Unit),它的作用是连接DMA-capable I/O总线(Direct Memory Access-capable I/O Bus)和主存(main memory)。传统的内存管理单元会把CPU访问的虚拟地址转化成实际的物理地址。而IOMMU则是把设备(device)访问的虚拟地址转化成物理地址。为了防止设备错误地访问内存,有些IOMMU还提供了访问内存保护机制。参考下图:

282px-MMU_and_IOMMU.svg

IOMMU的一个重要用途是在虚拟化技术(virtualization):虚拟机上运行的操作系统(guest OS)通常不知道它所访问的host-physical内存地址。如果要进行DMA操作,就有可能破坏内存,因为实际的硬件(hardware)不知道guest-physicalhost-physical内存地址之间的映射关系。IOMMU根据guest-physicalhost-physical内存地址之间的转换表(translation table),re-mapping硬件访问的地址,就可以解决这个问题。

另外,在AMDVIRTUALIZING IO THROUGH THE IO MEMORY MANAGEMENT UNIT (IOMMU)文档中,也有一个更全面的总结图:

iommu

参考资料:
IOMMU

DMA(Direct Memory Access)简介

DMA(Direct Memory Access)是指在现代计算机系统上,外接设备可以不用CPU干预,直接把数据传输到内存的技术。

DMA控制器(controller)是一种特殊的硬件,它用来管理数据传输和总线仲裁。当要发起数据传输时,它会发一个申请使用系统总线的DMA请求信号给CPUCPU完成当前操作后,就会让出系统总线,同时会发一个DMA确认信号给DMA控制器。接下来,DMA控制器接管系统总线,开始数据传输。数据传输完毕后,DMA控制器会通知CPU重新接管总线。

正常情况下,CPU全权负责内存的读写操作,而DMA技术可以把CPU解放出来,这将使计算机性能得到显著改善。

参考资料:
DMA (Direct Memory Access)

硬件虚拟化(hardware virtualization)浅析

硬件虚拟化(hardware virtualization)可以创建出多个系统虚拟机实例(system virtual machine instance),这些虚拟机可以运行整个操作系统(包括它们的内核)。硬件虚拟化分为以下几种:

a)Full virtualization - binary translation:提供一个由虚拟化硬件部件组成完整的虚拟化系统,可以在上面安装一个不需修改的,完整的操作系统。这项技术结合了直接的处理器执行和必要时指令的二进制转化(binary translation)。

b)Full virtualization - hardware-assisted:提供一个由虚拟化硬件部件组成完整的虚拟化系统,可以在上面安装一个不需修改的,完整的操作系统。这项技术利用了处理器的支持,使得执行虚拟机更加有效率(比如AMD-VIntel-VT扩展)。

c)Paravirtualization:提供一个支持接口(interface)的虚拟系统,虚拟机操作系统(guest OS)利用这个接口就可以有效地利用宿主机(host)资源(通过hypercalls),而不需要所有组件的完全虚拟化。

还有一种hybrid virtualization,利用hardware-assisted virtualization加上一些高效的paravirtualization调用,可以提供更好的性能(performance)。

Hypervisor(或被称为Virtual Machine Monitor (VMM))是用来创建虚拟机的,它可以由软件(software),硬件(hardware)或固件(firmware)实现。有2种类型的hypervisor,请参考下图:

hypervisor

类型1)这种hypervisor直接运行在处理器上 (例如:hyper-VKVM ,也被称之为native hypervisorbare-metal hypervisorHypervisor的管理工作是通过一个享有特权模式的guest OS来进行(在上图中,为Guest OS #0),这个guest OS可以创建和启动其它的guest OS

类型2)这种hypervisor运行在宿主机操作系统上 (例如:VirtualBox 。由宿主机操作系统负责管理hypervisor和启动新的guest OS

参考资料:
Systems Performance: Enterprise and the Cloud

Scala笔记(2)——val和var

Scala中,用val声明一个变量是不可改变的(immutable,read-only),而用var声明一个变量是可改变的(immutable,read-write)。同Java类似,Scala中许多变量实质上对分配在堆(heap)上的对象(object)的引用(reference),所以可变或不可变指的是引用可不可以指向不同的对象,而并非指引用的对象本身是否可以改变。看下面这个例子:

scala> val array: Array[String] = new Array(5)
array: Array[String] = Array(null, null, null, null, null)

scala> array[0] = "Hello"
<console>:1: error: identifier expected but integer literal found.
       array[0] = "Hello"
             ^

scala> array = new Array(2)
<console>:8: error: reassignment to val
       array = new Array(2)
             ^

array是一个val类型变量,array所指向数组的第一个元素可以改变(array[0] = "Hello"),但是array不能指向一个新的数组(array = new Array(2))。

BPF(BSD Packet Filter)简介

BPF(BSD Packet Filter)是一种抓取并过滤网络数据包(capture and filter packet)的内核结构(kernel architecture)。BPF包含2个重要的组成部分:网络分流器(network tap)和包过滤器(packet filter)。网络分流器负责从网络驱动拷贝数据包,而包过滤器则过滤掉不符合条件的数据包,只把符合需求的数据包上报给应用程序。下图摘自UNP

BPF

当数据包到达网卡时,正常情况下,数据链路层的驱动程序会把包转给协议栈。但当有BPF监听网卡时,驱动程序会首先把包发给BPFBPF会把包发给不同程序的包过滤器,再由过滤器决定哪些包并且包里的哪些内容应该保存下来。对于符合条件的数据包,BPF会把数据拷贝到和包过滤器对应的缓存(buffer)。然后驱动程序继续执行。

在用tcpdump命令过滤数据包时:

tcpdump -p -ni eth0 -d "ip and udp"

实际就用到了BPF