1.UIO
VFIO用来取代UIO的框架,允许用户端直接访问设备细节,也就是说让用户端设备驱动成为可能。其主要的工作成果是用户端可以配置IOMMU,从而让用户端也可以在编程时使用DMA。不过由于是新事物,其目前还仅支持PCI设备的驱动访问(vfio-pci模块),另外对CPU的IOMMU配置,也只实现了x86和PowerPC两种。
用户端的设备文件是/dev/vfio/N,用户可以使用这个设备文件实现完全的设备驱动程序,目前的主要用途是虚拟机时的设备驱动透明访问。
UIO是一个在用户端实现内核驱动的机制。其在内核中有一个UIO模块,目前该模块只支持字符设备。用户可以添加多个UIO设备(用户端的设备驱动),每个设备在/dev/uioX,X为数字,第一个为0,依次类推。我们知道设备都是靠中断来响应的,响应UIO设备中断的方法是读取/dev/uioX文件,没有中断的时候读取会阻塞,来中断的时候会读取到整数值,代表已经发生的中断的次数。
有的设备有多个中断,有的没有中断。针对这些情况UIO模块也实现了对应的机制,但是称不上完美。对于多个中断的情况,对/dev/uioX进行write()系统调用可以打开或者关闭内核的中断处理,以便驱动可以手动处理中断。在没有中断的情况下,UIO模块提供了一个定时器接口,通过设置这个接口可以人工地让这个设备定时产生中断。
UIO用户定义的驱动类似于内核驱动,一般会有一些需要通过sys文件系统访问的全局变量。UIO模块不支持调用sysctl更改这些值,但是可以在sys文件系统找到这些对应的文件,从而进行修改:/sys/class/uio/uioX。如图2-6所示。
图2-6
每个UIO设备都有name、version、event这3个定义属性,还有一个maps文件夹(有内存映射的时候才存在),其他为sys文件系统自带的uevent模型。name表示这个UIO设备的名字;version用于表示当前的UIO内核模块的版本;event与read()设备获得的值一样,是当前已经发生过中断的次数。maps文件夹服务于硬件的数据处理。大部分硬件都需要操作内存,UIO用户驱动如果要映射设备内存到用户端操作,需要使用mmap系统调用,每使用一次系统调用会在maps目录下生成一个目录map[digital],里面有4个固定的文件用来描述映射的内存的信息,如图2-7所示。
图2-7
其中addr是映射的基地址;offset是偏移量;name是映射时给这段映射起的名字;size是映射的内存的大小。如果不能映射内存,还可以通过x86的端口操作系统调用ioperm()、iopl()、inb()、outb()等对某个硬件端口进行读写。在这种情况下,UIO模块还添加了/sys/class/uio/uioX/portio/目录。
由于UIO在用户空间写驱动的便利性,所以对FPGA提供了很好的支持,甚至DPDK这种将内核数据包导出到用户空间的机制中的kni网卡驱动也使用UIO。Open Source Automation Development Lab等使用机器人编程的组织也喜欢UIO。
2.VFIO
VFIO则是软件对硬件设备内存暴露在用户空间的支持,是对UIO的升级。DMA内存直接被映射到用户进程空间,使用这个驱动需要将设备与操作系统原来的驱动解绑。目前仅实现了支持vfio-pci模块和PCI设备的映射。这对于在虚拟机和用户空间设备驱动有重要意义。访问设备内存的机制被单独称为IOMMU。IOMMU本来是为虚拟化而设计的,使用场景是如果驱动在用户态(比如虚拟机),没有高效的使用设备IO内存的方法,内核要在用户内存空间到设备内存空间做额外的转换,IOMMU可以直接将设备的内存空间映射到用户进程空间。用户可以通过直接操作硬件的内存空间来操作硬件。
3.SysRq
SysRq机制很容易被人忽视,使用此机制解决特殊问题是非常有效的。SysRq类似Windows的“Ctrl+Alt+Del”组合键的效果,只要系统不是处于完全被锁死的状态,就会优先响应这个命令。在Linux中这个功能本身是可以打开或关闭配置的,使用echo "1" >/proc/sys/kernel/sysrq打开SysRq机制。在Linux中调用该系列命令的方式是“SysRq键+命令”。SysRq在大部分键盘上一般是Print Screen按键的副功能,需要使用Alt键调用。与Windows操作系统不同的是,Windows操作系统一定是在按键后跳出图形界面,而Linux允许直接使用按键命令执行特定的操作,下面列出常用的一些功能。
· SysRq+b:立即重启系统。
· SysRq+c:立即产生一个系统级的crash dump。
· SysRq+d:显示当前使用中的所有锁。
· SysRq+e:发送SIGTERM给出init之外的全部进程。
· SysRq+f:手动调用oom killer杀死当前OOM打分最高的进程。
· SysRq+g:被kgdb使用。
· SysRq+h:显示SysRq的使用帮助。
· SysRq+i:发送SIGKILL信号给除了init外的所有进程。
· SysRq+j:相当于调用ioctl(fd,FITHAW,0);让一个被冰冻的文件系统(ioctl(fd,FIFREEZE,0);)解冻。
· SysRq+k:杀掉当前虚拟终端上开启的所有进程。
· SysRq+l:显示所有CPU的调用栈。
· SysRq+m:导出当前的内存信息。
· SysRq+n:使采用实时调度算法的进程可以设置nice值。
· SysRq+o:关机。
· SysRq+p:打印当前寄存器。
· SysRq+q:打印当前各个时间装置(如图2-8所示)。
图2-8
· SysRq+r:将键盘输入设置为XLATE模式。Raw模式是平时使用的,在这种模式下,键盘返回。输入键位编码给计算机,而在XLATE模式下,键盘直接输出ASCII字符给计算机。
· SysRq+s:同步所有已经加载的文件系统。
· SysRq+t:打印当前所有的任务和详细信息。
· SysRq+u:把所有的文件系统都unmount,然后用只读的权限重新mount。
· SysRq+w:打印所有处于阻塞状态的进程。
· SysRq+y:打印所有寄存器。
· SysRq+z:导出ftrace buffer。
· SysRq+0~9:设置内核的log级别。
这些命令视内核的配置而部分有效,这在运维工作中也是相当有用的。我们可能会遇到Shell登录上去只能输入命令,但是大部分命令不会被执行的情况,笔者在Ubuntu 12上调试DPDK的kni模块时就频繁地遇到此问题。这个时候只能重启机器,而重启的方法要么是让运维人员重启,要么是自己去实验室重启。但是使用SysRq就能解决这个问题,因为它提供了不需要执行外部命令的重启方式。
4.其他机制
有一些逻辑虽然是串行的,但是可以分成不同的部分,每一部分可以分别执行。PADATA就是这样可以在多个CPU上同时执行不同的逻辑,但是又能保证逻辑的顺序机制,这个机制最早是为IPSec开发的。
namespace机制在出现很长时间后都没有被重视,直至虚拟化成为重要需求。现在pid、net、ipc、mount、user等都逐渐支持namespace,并且逐步诞生了cgroup,从而使得docker成为了可能。
内核中很多地方都有使用引用计数的需求,涉及资源回收和资源竞争,或者是访问统计等。这种需求一般是使用一个整数,自己编写的时候控制其增加或减少。而控制的时候又要考虑并发冲突等很多情况,通常要自己封装函数。Linux就实现了一种通用的数据结构和相关函数调用,使用者直接使用接口即可。示例如下:
我们在Shell中敲入的命令必须是Shell解析器内置的,或者是位于PATH环境变量设置的路径中可以找到的ELF格式的可执行文件,例如dd命令一般位于/bin/dd,当在Shell中输入dd的时候,Shell解析器就会去/bin文件夹下寻找dd二进制,而/bin一般是在PATH环境变量中设置的。Linux不仅可以支持ELF格式的二进制文件,其他格式的程序执行也可以通过其他的解析器来支持。例如通过Python解释器可以执行Python的程序,通过Perl解析器可以执行Perl程序,通过PHP解析器可以执行PHP程序,甚至Shell脚本本身也是一种程序,可以用Shell解析器(例如bash程序)解析执行。
内核提供了一种方法允许将例如Java的程序与ELF二进制一致看待。用户只需要在Shell中敲入Java程序名(或者Python程序名),只要该程序在PATH下就可以像ELF格式可执行程序一样被执行。做到这样的方式是使用binfmt_misc机制,该机制通过proc文件系统操作,若要使用首先要先mount上去:mount binfmt_misc-t binfmt_misc/proc/sys/fs/binfmt_misc。然后向/proc/sys/fs/binfmt_misc/register中写入规定格式的字符串即可,如下所示。
其中冒号是必须的,不同需求替代不同的字符串。例如对Wine程序的支持使用::Wine:M::MZ::/usr/bin/wine:写入register文件。
Shell解析器本身也提供了很好的调用其他解析器的能力,而不需要使用binfmt_misc。例如Bash解析器就支持在文件的第一行使用#!/bin/python 作为第一行,从而就会调用/bin/python来作为解析器解析后文的脚本程序。但是这种方式对于不同的二进制格式就无能为力,一般用于支持不同解析器的脚本。这种首行设置解析器的功能已经成为Linux的所有Shell约定俗成的规定了。