为了提高域之间的作业协作能力,域间通信(Inter-Domains Communication)工具被引入到XtratuM Hypervisor系统中。当前,XtratuM系统中存在两种数据通信工具,一种是针对数据流的命名管道(FIFO),另外一种是针对块数据的共享内存。命名管道是一种简单的流数据传输工具,采用先进先出的策略,不同于PIPE(管道),命名管道有固定的存储介质和名称,它可以被任何域通过名称打开和关闭。而管道只有存储介质,没有名称。管道的存在依附于创建管道的任务,当任务退出时,管道也就相应的被销毁了。而命名管道一经创建将会永久存在,知道用户明显的将其删除[37]。
共享内存是不同于命名管道的数据通信模式,用户可以像打开普通文件一样打开共享内存,并且可以指定共享内存大小。与命名管道相比,共享内存是块设备,即任务可以随机的访问共享内存区间的任何地址的数据,并且不会因为地址的不同而使访问时间有所差别。但是FIFO类似的流数据通信工具,数据的传输和访问必须有一个先后顺序。因此,命名管道与共享内存虽然目的相同,但是在使用时一定要正确评估应用场景。
基于XtratuM系统,FIFO/XM V1.0,V2.0,V3.0已经先后被发布。其中V1.0采用了阻塞性同步竞争条件和系统调用的方法实现,在后续的版本中,V2.0和V3.0就分别针对这两种技术的缺陷,通过Lock-Free机制和内存映射机制解决相应问题。本节将详细介绍三个版本的FIFO实现过程以及Lock-Free机制[38]。
FIFO/XM主要包含共享FIFO,Linux FIFO设备驱动和PaRTiKle FIFO设备驱动。共享FIFO在V1.0中是被集成到XtratuM模块里面的。并且FIFO的访问是非阻塞型,即当FIFO没有数据时,如果任务试图从里面读取数据时,任务会直接返回。当FIFO被写满后,继续执行写操作的任务也会直接返回。FIFO可以是双向操作,即任何域可以对同一个FIFO进行读或者写操作。图2-10给出了FIFO/XM V 1.0的架构模型。
图2-1. FIFO/XM V1.0架构模型
从图2-10中可以清晰的看到Linux和PaRTiKle使用不同的FIFO设备驱动。Linux和普通的域通过不同的接口访问底层的共享FIFO。在FIFO/XM的开发和测试环境中,PaRTiKle是普通的任务域。
当XtratuM模块被加载的时候,FIFO内存被静态分配并且被锁在物理内存中,这样可以防止FIFO内存空间的换入或换出。默认情况下,16个大小为PAGE SIZE的FIFO被建立。Linux系统中FIFO设备驱动通过两个导出全局函数链接访问FIFO数据。但是,不同于Linux系统任务,PaRTiKle通过XtratuM在Hypercall层提供的两个Hypercalls对FIFO进行访问,xm_fifo_read() 和 xm_fifo_write()。当PaRTiKle要访问底层的FIFO设备时,PaRTiKle首先要调用xm_fifo_read()函数,该函数会执行”int 0x82”指令,从而触发Hypercall异常,系统由PaRTiKle空间陷入XtratuM内核空间,然后调用xm_fifo_read_sys()函数对FIFO进行读操作。熟悉Linux系统调度的读者很容易明确这一部分的执行过程。PaRTiKle系统中的用户态线程和Linux用户态进程都通过POSIX APIs对FIFO设备进行访问和操作,例如open(),close(),read(),write()等。在Linux和PaRTiKle系统中,FIFO设备的名称为/dev/rtf0,…,/dev/rtf15,并且设备的最大号与最小号遵循Linux系统对命名管道的规定。图2-11和图2-12分别给出了FIFO/XM在Linux系统和PaRTiKle系统中读操作的函数调用树。
图2-11. Linux读FIFO/XM设备函数调用树
图2-12. PaRTiKle读FIFO/XM设备函数调用树
从图2-11和图2-12中可以看到底层从FIFO中读数据函数都是xmf_read(),这也意味着在xmf_read()函数中会出现资源竞争访问,包含实时域和非实时域之间的竞争。因此,为了提高竞争资源的安全性,同步机制被引入到该函数中。在当前XtratuM版本中,除了屏蔽中断,系统没有提供其它的竞争条件方法。为此,新的机制应该被引入到域间资源的竞争中。下面给出了xm_read()函数的算法。
int xmf_read(int index, char *dst, int size)
BEGIN
...
hw_save_flags_and_cli(&flags);
read_data();
hw_restore_flags(flags);
...
END
竞争条件并不仅仅存在于域之间对FIFO/XM的访问中,并且存在于域内任务对设备的访问。在Linux和PaRTiKle系统中,为了避免内部的进程和线程对FIFO设备的竞争,它们采用了不同的函数避免竞争条件。在Linux系统中,FIFO/XM采用读信号量和写信号量对FIFO设备的访问。而PaRTiKle中,由于提供的信号量功能相对比较少,读写FIFO设备将会采用同一种信号量。下面分别给出了Linux和PaRTiKle针对FIFO设备竞争条件的同步算法。
int linf_read(int fd, char *dest, int size)
BEGIN
...
down_read(&fifo.rw_semphore);
read_data();
up_read(&fifo_rw_semphore);
...
END
int linf_write(int fd, const char *src, int size)
BEGIN
...
down_write(&fifo.rw_semphore);
write_data();
up_wirte (&fifo_rw_semphore);
...
END
int prtkf_read(int fd, char *dest, int size)
BEGIN
...
sem_wait_sys (&fifo.rw_semphore);
read_data();
sem_post_sys(&fifo_rw_semphore);
...
END
上面的内容已经简单的介绍了FIFO/XM V1.0的实现,希望读者对FIFO设备能够理解。但是,在FIFO/XM V1.0中存在一些缺陷。
l 中断屏蔽机制可以解决多个域对FIFO资源的竞争条件。但是,其余的任务甚至是中断服务程序都无法得到执行。从而很大的影响了系统的实时行为,尤其是当具有低优先级的域首先获取资源时。尤其是在多核系统上,这种策略具有严重缺陷。
l 采用信号量机制解决竞争条件。信号量是一种基于阻塞的机制,并且会引起优先级倒置。并且,信号量的获取和释放会影响系统的性能。因此,新的同步机制需要创建和应用。
l 在Linux系统和PaRTiKle系统中,对FIFO数据的访问采用的系统调用或Hypercall,提高了系统延迟。
l 缺乏FIFO控制能力。在FIFO/XM 1.0中,仅仅提供了对FIFO读和写这两种操作。没有相应的控制功能和状态检查功能。
鉴于FIFO/XM 1.0存在的多种缺陷,进一步提高FIFO设备的效率和稳定性,在FIFO/XM V2.0和FIFO/XM V3.0中引入了新的方法和技术。
当前,有多种传统的方式在同步领域得到实现和应用,例如spin-lock,信号量,BKL等。但是这些机制都是等待阻塞型机制,并且会引起优先级倒置问题。因此,Lock-Free机制应该被引入到FIFO设备访问过程中。Lock-Free机制可以避免由传统基于阻塞的条件竞争解决方案带来的一些问题[39]:
l 优先级倒置:当高优先级任务请求被低优先级任务持有的锁时发生[40];
l 死锁问题:不同的任务依照不同的序列试图获取同一个资源集中资源锁时发生;
l 抢占容忍:如果持锁去睡眠,但是另外一个进程要去获取该锁。从而导致新任务去等待睡眠者,也就意味着新任务在无用的运行。
Lock-Free机制的目标是在不阻塞其它任务的前提下可以安全的访问竞争资源,并且要保证资源操作的一致性。为了实现Lock-Free机制,通常采用的基本方法是CAS(Compare-and-Swap)操作。CAS是严重依赖底层计算机平台的一个原子操作。x86平台采用cmpxchg指令实现CAS操作,该指令出现于X486等后继的X86平台中。下面列出了两端代码,一段是用C语言实现的CAS操作算法,另外一段是基于X86平台实现的CAS操作[41]。
int cas(int *addr, int old_value, int new_value)
{
if(*addr == old_value) {
*addr = new_value;
return 1;
}
return 0;
}
#define CAS(adr, ov, nv) ({ /
__typeof__(ov) ret; /
__asm__ __volatile__( /
"cmpxchg %3, %1" /
:"=a"(ret),
"+m"(*(volatile unsigned int *)(adr)) /
:"a"(ov),"r"(nv)); /
ret == ov; /
})
在PowerPC平台上,由于缺乏cmpxchg指令,为了实现CAS功能,系统采用了Fetch-Store指令,下面是基于PowerPC 405 的伪cmpxchg() 指令代码段。
static __inline__ unsigned long
cmpxchg(volatile int *p, int old, int new)
{
int prev;
__asm__ __volatile__ ("/n/
1: lwarx %0,0,%2 /n/
cmpw 0,%0,%3 /n/
bne 2f /n"
PPC405_ERR77(0,%2)
" stwcx. %4,0,%2 /n/
bne- 1b/n"
"2:"
: "=&r" (prev), "=m" (*p)
: "r" (p), "r" (old), "r" (new), "m" (*p)
: "cc", "memory");
return prev;
}
上面简单的介绍了Lock-Free机制和CAS操作在x86和PowerPC平台的实现,Lock-Free机制在FIFO/XM中的使用和实现将会在下面的部分介绍。