XenStore: 使用,结构和原理

摘要:XenStoreXen提供的一个域间共享的存储系统,它以字符串形式存放了管理程序和前、后端驱动程序的配置信息。Dom0管理所有的数据,而DomU通过共享内存,向Dom0请求与自己相关的键值,以此实现域间通信。Xen提供了多种接口用来操作XenStore:命令行的xenstore-*命令、用户空间的xs_系列函数、内核的XenBus接口,都可以用来方便地操作XenStore的数据。




0. 动机

Xen的半虚拟化(PV)系统中,各个虚拟机(Domain)之间可以利用Xen核心提供的授权表(Grant Table)和事件通道(Event Channel)机制进行域间通信。但是在实际使用中有这样的问题:

  •   Alice使用授权表共享了一个页面,但是Sanae想要拿来用,必须事先知道它的授权引用(gref);
  •   Alice打开一个事件通道(unbound evtchn),但是Sanae想要绑定这个通道,必须事先知道Alice使用的端口号(remote port)。

显然,想要解决这两个问题,需要AliceSanae之间,在建立自定义的授权表、事件通道之前就能够互相通信。

使用XenStore可以很方便地实现这种通信。

1. 快速入门

XenStoreXen提供的一个域间共享的存储系统,它以字符串形式存放了管理程序和前、后端驱动程序的配置信息。Dom0管理所有的数据,而DomU通过共享内存,向Dom0请求与自己相关的键值,以此实现域间通信。

XenStore的存储结构类似于Dom树:每一个节点(Node)有一个字符串值(Value),并且可以有多个子节点。在Dom0启动后,XenStore的结构如下:

/

vm

{uuid}

local

domain

0

vm

device

control

memory

console

limit

type

name

......

1

tool

xenstored

在根(/)下有三个子目录,vmlocal(实际上是/local/domain)和toolvm存放虚拟机管理信息。tool暂时没有数据。而/local/domain存放了活动虚拟机配置和驱动信息。/local/domain中每一个目录项代表一个活动的虚拟机,例如/local/domain/0代表dom0Dom0可以读写XenStore的全部数据,而Dom-x只能访问/local/domain/x的内容。需要在dom0domx之间共享的内容,一般都写在这个目录下,所以/local/domain是最常用的目录。

Xen提供了多种接口用来操作XenStore:命令行的xenstore-*命令、用户空间的xs_系列函数、内核的XenBus接口,都可以用来方便地操作XenStore的数据。

操作

命令行

用户空间

  1. #include <xs.h>  
  2. -lxenstore  

内核空间

  1. #include “xen/xenbus.h”  

列目录

xenstore-ls(递归)

xenstore-list(不递归)

xs_directory

xenbus_directory

读写

xenstore-read

xenstore-write

xs_read

xs_write

xenbus_read

xenbus_write

xenbus_scanf

xenbus_printf

建立/删除目录

xenstore-mkdir

xenstore-rm

xs_mkdir

xs_rm

xenbus_mkdir

xenbus_rm

监视

xenstore-watch

xs_watch

xs_unwatch

xs_read_watch

xenbus_watch

xenbus_unwatch


例子

命令行

[plain]  view plain copy print ?
  1. xenstore-write example/Alice hello!  

用户空间

  1. struct xs_handle *xh = xs_open(0);  
  2. int xt=xs_transaction_begin(xh);  
  3. xs_write(xh,xt,"/local/domain/0/example/Alice""hello!"sizeof("hello!"));  
  4. xs_transaction_end(xh, xt, 0);  
  5. xs_close(xh);  

内核空间

  1. struct xenbus_transaction trans;  
  2. xenbus_transaction_start(&trans);  
  3. xenbus_printf(trans, "example""Alice""Hello! trans_id = %d", trans.id);  
  4. xenbus_transaction_end(trans, 0);  
Note: example/Alice 相当于 /local/domain/{ 当前 domain}/example/Alice

具体的使用方法,可以查看相应的头文件

2.     xenstore结构和通信数据流程

XenStore结构

 

XenStore包含两个模块:Dom0用户空间的xenstored服务主模块,和Dom*里的XenBus内核模块;此外,还有各种通信接口。各个部分的功能如下:

Xenstored:Dom0用户空间的一个服务,负责管理xenstore的数据(tdb),并且处理所有的请求。

XenBus:内核模块,提供Dom*内核访问xenstore的API,提供用户空间的xenfs接口(/proc/xen/xenbus)。Dom0的Xenbus还要初始化xenstored的运行环境。

共享页面:每一个Domain(包括Dom0自身)都有一个与Dom0通信的共享页面(共享环),这个页面同时被映射到了xenstored的内存空间。也就是说这个页面可以同时被xenstored、dom0内核、和对应的DomU内核读写。Xenstored会随时监控页面,只要环被任意一方更新,xenstored就会直接处理消息,不需要内核模块干预。

Xenstore特殊文件:

文件

路径

功能

xenbus

/proc/xen

XenBus用户接口。用户把请求写入该文件,经由内核发送到共享环

以下只在dom0中

 

 

tdb

/var/lib/xenstored

xenstore的数据库,存放在dom0的/var/lib/xenstored/tdb,可以用tdbtool打开

socket, socket_ro

/var/run/xenstored

Dom0直接操纵xenstored的接口

xsd_port, xsd_kva

/proc/xen

用于Dom0初始化

 

XenStore的通信协议:

Xenstore各个模块间的通信内容——请求和回复——都封装成为一种数据包,格式定义在文件xs_wire.h中。

  1. struct xsd_sockmsg  
  2. {  
  3.      uint32_t type;  /* XS_??? */  
  4.    uint32_treq_id;/* Request identifier, echoed in daemon's response.  */  
  5.      uint32_ttx_id; /* Transaction id (0 if not related to a transaction). */  
  6.      uint32_t len;   /* Length of data following this. */  
  7.    
  8.      /* Generally followed bynul-terminated string(s). */  
  9. };  


而共享页面上定义了一个环结构xenstore_domain_interface:

 
 
  1. #define XENSTORE_RING_SIZE 1024  
  2. typedef uint32_t XENSTORE_RING_IDX;  
  3. #define MASK_XENSTORE_IDX(idx) ((idx) & (XENSTORE_RING_SIZE-1))  
  4. struct xenstore_domain_interface {  
  5.      char req[XENSTORE_RING_SIZE]; /* Requests to xenstore daemon. */  
  6.      char rsp[XENSTORE_RING_SIZE]; /* Replies and async watch events. */  
  7.      XENSTORE_RING_IDX req_cons, req_prod;  
  8.      XENSTORE_RING_IDX rsp_cons, rsp_prod;  
  9. };  

DomU通信流程

DomU的内核将请求放入共享环,这时Dom0的xenstored会直接检测到这个改变并执行操作,最后把结果返回共享页面。DomU的用户空间需要通过/proc/xen/xenbus来操作xenstore。

DomU向XenStore写入数据的xs_write函数调用流程:

DomU的内核调用流程:

 

Dom0 User、Kernel 通信流程

Dom0的流程和DomU类似。但是在客户空间,可以通过socket方式直接连接xenstored进行操作,不需要通过xenbus绕圈子。这也就是老版本Xen里面,xs_domain_open和xs_daemon_open的区别。

摘要:XenStore的初始化完全在用户空间的libxl中实现。Dom0包办了所有的操作,使DomU一启动,就可以与Dom0进行xenstore通信,但是程序非常的复杂和难以维护。Xen核心仅仅为xenstore提供了两个辅助数据项,其余的完全在xen已有的架构下实现。

XenStore所进行的域间通信,在底层仍然是通过共享内存和事件通道机制进行的。但是前面已经提到,想要共享内存或者建立事件通道,必须事先进行通信。那么XenStore自身是怎样建立起这些通道的呢?有必要研究一下Xenstore的启动过程。

我们一般使用命令行新建domu。例如:sudo xl create –c ubuntu.cfg 所以我们从xl所在的程序库libxl开始,仔细分析虚拟机启动过程中xenstore的初始化过程。

其中核心的步骤就是:Dom0的libxl调用libxc,替新创建的domu分配了属于DomU的共享页面和事件通道,并且通知Xenstored建立domu的管理数据结构;在分配好以后再由XenStore映射回来。这样DomU一启动,就可以从start_info结构体中获取共享内存的地址,与Dom0进行通信了。然而,这样做就意味着Dom0包办了所有的操作,并且要考虑架构相关的因素,精确地控制DomU的内存分布,同时还要避免C语言的内存泄漏;所以程序变得非常的复杂和难以维护。

这种做法的另一个特点是:实现代码完全在用户空间的libxl和libxc中,完全在xen已有的架构下实现。Xen核心唯一需要做的,就是在start_info结构体中增加两个数据项store_mfn和store_port。这也说明xen与kvm相比,核心提供更多的功能。

Xenstore的监视(watch)功能很实用,在xenstore监视目标文件夹里发生的任何修改,都会通知watch的注册者。xen虚拟机的后端驱动程序,就是通过watch来检测前端设备的改变。

需要注意的:

(1)注册watch不需要开始一个transaction,只要用xs_open打开连接就行了。内核可以直接调用register_xenbus_watch

(2)watch在注册时,xenstored会马上产生一个事件。它就是这么设计的。如果不想处理这个时间,需要设法把它忽略掉。

watch的用法:

内核空间:(转自http://wiki.xen.org/xenwiki/XenBus

  1. static struct xenbus_watch xb_watch = {  
  2.     .node = "memory",  
  3.     .callback = watch_target;  
  4. };  
  5. ret = register_xenbus_watch(&xb_watch);  
  6.     if(IS_ERR(ret)) {  
  7.       IPRINTK("Failed to initialize balloon watcher\n");  
  8.     } else {  
  9.       IPRINTK("Balloon xenbus watcher initialized\n");  
  10.     }  


用户空间:用户空间和内核稍有不同。用户空间可以用xs_watch函数定义一个监视,但是不能指定回调函数,而是需要调用xs_read_watch进行阻塞式的读取,像这样:

  1. xs_handle *xh=xs_open(0);  
  1. assert(xs_watch(xh, path, token));  
  2. char **result = 0;  
  3. result = xs_read_watch(xh, &num);             //会阻塞  
  4. free(result);  
  5. xs_unwatch(xh, path, token);  

如果不想阻塞,就得自己开一个线程用来监听。但是这里有一个bug(4.1.3版本xen):xs_read_watch不是线程安全的。如果在xs_read_watch阻塞的时候,使用pthread_cancel来终止线程,xs_read_watch不会释放所有的资源(锁)。而主线程如果调用xs_close关闭与xenstore的连接,则会因为不能得到这些资源而死锁。具体分析如下:

tools/xenstore/xs.c

  1. char **xs_read_watch(struct xs_handle *h, unsigned int *num)  
  2. {  
  3.         struct xs_stored_msg *msg;  
  4.         char **ret, *strings, c = 0;  
  5.         unsigned int num_strings, i;  
  6.   
  7.         mutex_lock(&h->watch_mutex);                 //------------------>加锁  
  8.                                               //此处应该加一个pthread_cleanup_push(pthread_mutex_unlock, &h->watch_mutex)  
  9. #ifdef USE_PTHREAD  
  10.         /* Wait on the condition variable for a watch to fire. 
  11.          * If the reader thread doesn't exist yet, then that's because 
  12.          * we haven't called xs_watch.  Presumably the application 
  13.          * will do so later; in the meantime we just block. 
  14.          */  
  15.         while (list_empty(&h->watch_list) && h->fd != -1)  
  16.                 condvar_wait(&h->watch_condvar, &h->watch_mutex);  
  17. #else /* !defined(USE_PTHREAD) */  
  18.         /* Read from comms channel ourselves if there are no threads 
  19.          * and therefore no reader thread. */  
  20.   
  21.         assert(!read_thread_exists(h)); /* not threadsafe but worth a check */  
  22.         if ((read_message(h) == -1))                //----------------------->没有消息时阻塞在这里  
  23.                 return NULL;  


xs_read_watch一开始加锁,紧接着调用read_message。read_message会阻塞调用read函数。如果这时主线程调用pthread_cancel来取消运行xs_read_watch的线程,h->watch_mutex就不会被释放,会造成后面的死锁。

很挫的解决办法:在调用xs_unwatch和xs_close之前,检查xs_handle内部锁的情况,强行解锁资源。

xs_handle这个结构体在头文件里只有类型声明,具体的定义在xs.c文件里,仅供xenstore内部使用。要调用xs_handle内部的对象,首先得把xs_handle的详细声明提取出来:

  1. //used to extract (>_<) xs_handle internal members.  
  2. typedef struct list_head {  
  3.         struct list_head *next, *prev;  
  4. }list_head_struct;  
  5. typedef struct  
  6. {  
  7.     int fd;  
  8.     pthread_t read_thr;  
  9.     int read_thr_exists;  
  10.     struct list_head watch_list;  
  11.     pthread_mutex_t watch_mutex;//监视信号量,可能死锁  
  12.     pthread_cond_t watch_condvar;  
  13.     int watch_pipe[2];  
  14.     struct list_head reply_list;  
  15.     pthread_mutex_t reply_mutex;//回复信号量  
  16.     pthread_cond_t reply_condvar;  
  17.     pthread_mutex_t request_mutex;//请求信号量  
  18. }my_xs_handle;  
  19.   
  20. #if __GNUC__ > 3  
  21. #define offsetof(a,b) __builtin_offsetof(a,b)//提取偏移量的宏。  
  22. #else  
  23. #define offsetof(a,b) ((unsigned long)&(((a *)0)->b))  
  24. #endif  

然后像这样提取xh内部成员:

pthread_mutex_t *pm_watch = (pthread_mutex_t *)(((void *)xh) + offsetof(my_xs_handle, watch_mutex));

解锁的宏。其实写成个函数也行

  1. #define check_xh_lock(x) do {\  
  2.         pthread_mutex_t *pm = (pthread_mutex_t *)(((void *)pthis->xh) + offsetof(my_xs_handle, x));  \  
  3.         if (pthread_mutex_trylock(pm) == EBUSY){            \           //pthread_mutex_trylock 如果没上锁,则加锁;否则返回EBUSY  
  4.             cout << "thread_cleanup -> " #x " is already locked!" << endl;   \  
  5.             if (0 != pthread_mutex_unlock(pm))              \         
  6.             cout << "thread_cleanup -> error unlocking!" << endl;            \  
  7.             else cout << "thread_cleanup -> unlocking " #x << endl;          \  
  8.         } else assert(pthread_mutex_unlock(pm)==0);             \  
  9.     } while (0)  
  1. check_xh_lock(watch_mutex);  
  2. check_xh_lock(request_mutex);  
  3. check_xh_lock(reply_mutex);  
  4. cout << "----- unwatch -----" << endl;  
  5. xs_unwatch(pthis->xh, pthis->path.c_str(), map_path("watch", pthis->path).c_str());  
  6.   
  7. check_xh_lock(watch_mutex);  
  8. check_xh_lock(request_mutex);  
  9. check_xh_lock(reply_mutex);  
  10. cout << "-----  close  -----" << endl;  
  11. xs_close(pthis->xh);  
  12. pthis->xh=0;  


测试如下:

[plain]  view plain copy print ?
  1. viktor@buxiang-OptiPlex-330:~/proj/xc_map$ sudo ./domu domu.cpp  
  2. watch -> add watch on path mmap/domu-cpp callback func 0x8049aad                       //主线程打开监视线程  
  3.   
  4. thread_func -> begin watch thread path= mmap/domu-cpp                                  //监视线程注册watch  
  5. thread_func -> watch event path= mmap/domu-cpp token= watch/mmap/domu-cpp            //注册时会发生一次事件,忽略之  
  6. watch_callback -> entering  
  7.                                          //(在这里等待,此时按下Ctrl+C)  
  8. ^Cmain received signal Interrupt  
  9. unwatch -> **stopping work thread. waiting here...work thread already stopped.          //主线程用pthread_cancel关闭监视线程  
  10. thread_cleanup -> path= mmap/domu-cpp thread=b6d6eb70                                    //监视线程退出,主线程开始清理  
  11. ----- unwatch -----  
  12. map_path -> watch/mmap/domu-cpp  
  13. thread_cleanup -> watch_mutex is already locked!                                   //此时watch_mutex上锁了,说明xs_read_watch没有释放资源  
  14. thread_cleanup -> unlocking watch_mutex                                          //强行解锁  
  15. -----  close  -----                 //调用xs_unwatch和xs_close关闭连接。  
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值