zoukankan      html  css  js  c++  java
  • xen hypercall 的应用层实现

    一句话描述: xen hypercall 在应用层的实现,最终都变成对  /proc/xen/privcmd 的 ioctl 系统调用

    我们知道,xen 在应用层最上层的接口是 libxl , 基本上所以应用程序对xen的操作都通过 libxl 提供的API实现。 这里我们也从 libxl 入口探讨 hypercall 的实现,主要涉及的是 libxl context 初始化部分。所有的xl 调用,如 xl create/ xl list/ xl destroy 都会创建一个上下文环境,这个上下文环境对所有 Xl 进程都是一致的(所以才能叫 context 嘛),什么意思呢 ? 比如你在一台设备上调用多次 Xl  create 启动多台虚拟机,那么这多个xl 进程共享一个context , 这究竟是怎么实现的 ?一般,跨进程的context实现,就是在应用层维护一个基于共享内存的控制结构,当然要辅助各种同步机制,libxl 的上下文没有这么复杂,因为它本质上不是在应用层维护的,而是在domain0的内核维护的,为什么这么说呢? 看下面xl 初始化代码:

    libxl/xl.c 
    int main(int argc, char **argv)
    {
    。。。。。。。
    xl_ctx_alloc(); // xl 的main函数,初始化context
    。。。。
    }
    
    libxl/libxl.c
    
    int libxl_ctx_alloc(libxl_ctx **pctx, int version,
                        unsigned flags, xentoollog_logger * lg)                                                                                  
    {   
        libxl_ctx *ctx = NULL;                                                                                                                   
        struct stat stat_buf;                                                                                                                    
        int rc;                                                                                                                                  
        
        if (version != LIBXL_VERSION) { rc = ERROR_VERSION; goto out; }                                                                          
        
        ctx = malloc(sizeof(*ctx));
    memset(ctx, 0, sizeof(libxl_ctx));                                                                                                       
        ctx->lg = lg;                                                                                                                            
        
        /* First initialise pointers etc. (cannot fail) */                                                                                       
        
        ctx->nogc_gc.alloc_maxsize = -1;                                                                                                         
        ctx->nogc_gc.owner = ctx;                                                                                                                
        
        LIBXL_TAILQ_INIT(&ctx->occurred);                                                                                                        
        
        ctx->osevent_hooks = 0;                                                                                                                  
        
        LIBXL_LIST_INIT(&ctx->pollers_event);                                                                                                    
        LIBXL_LIST_INIT(&ctx->pollers_idle);                                                                                                     
        
        LIBXL_LIST_INIT(&ctx->efds);
        LIBXL_TAILQ_INIT(&ctx->etimes);                                                                                                          
        
        ctx->watch_slots = 0;
        LIBXL_SLIST_INIT(&ctx->watch_freeslots);
        libxl__ev_fd_init(&ctx->watch_efd);
    
        LIBXL_TAILQ_INIT(&ctx->death_list);
        libxl__ev_xswatch_init(&ctx->death_watch);
    
        ctx->childproc_hooks = &libxl__childproc_default_hooks;
        ctx->childproc_user = 0;
    
        ctx->sigchld_selfpipe[0] = -1;
    
        /* The mutex is special because we can't idempotently destroy it */
    
        if (libxl__init_recursive_mutex(ctx, &ctx->lock) < 0) {
            LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "Failed to initialize mutex");
            free(ctx);
            ctx = 0;
        }
    rc = libxl__atfork_init(ctx);
        if (rc) goto out;
    
        rc = libxl__poller_init(ctx, &ctx->poller_app);
        if (rc) goto out;
    
        if ( stat(XENSTORE_PID_FILE, &stat_buf) != 0 ) {
            LIBXL__LOG_ERRNO(ctx, LIBXL__LOG_ERROR, "Is xenstore daemon running?
    "
                         "failed to stat %s", XENSTORE_PID_FILE);
            rc = ERROR_FAIL; goto out;
        }
    
        ctx->xch = xc_interface_open(lg,lg,0);  // 获取 xc 控制接口
        if (!ctx->xch) {
            LIBXL__LOG_ERRNOVAL(ctx, LIBXL__LOG_ERROR, errno,
                            "cannot open libxc handle");
            rc = ERROR_FAIL; goto out;
        }
    
        ctx->xsh = xs_daemon_open(); // 获取 xs 控制接口
        if (!ctx->xsh)
            ctx->xsh = xs_domain_open();
        if (!ctx->xsh) {
            LIBXL__LOG_ERRNOVAL(ctx, LIBXL__LOG_ERROR, errno,
                            "cannot connect to xenstore");
            rc = ERROR_FAIL; goto out;
        }
    
        *pctx = ctx;
        return 0;
    }

    可以看到, xl 的main函数里会调用 xl_ctx_alloc, 后者的具体实现在 libxl_ctx_alloc 函数里,这个函数除了初始化一堆 list 和 tailq 之外,有实质性的调用是  xc_interface_open 和 xs_daemon_open , 即获取对 xc 接口和 xs 接口的控制体,而这两种控制结构的获取最终都是通过打开文件系统获取某个fd来实现, 我们知道,文件系统是kernel维护的,所以说,libxl 的上下文环境更确切说是在 domain0 的 kernel 内部维护的。其中 ,xc 接口主要对应各种hypercall , xs 接口主要对应 xenstore 共享内存机制和事件机制。这里先不管xenstore机制,因为xen hypercall主要涉及的是xc接口的实现。

    xc_interface_open的具体实现在 xc_interface_open_common: 

    // libxc/xc_private.c
    static struct xc_interface_core *xc_interface_open_common(xentoollog_logger *logger,
                                                              xentoollog_logger *dombuild_logger,
                                                              unsigned open_flags,
                                                              enum xc_osdep_type type)
    {
    。。。。。
     xch = malloc(sizeof(*xch));
        if (!xch) {
            xch = &xch_buf;
            PERROR("Could not allocate new xc_interface struct");
            goto err;
        }
        *xch = xch_buf;
    
        if (!(open_flags & XC_OPENFLAG_DUMMY)) {
            if ( xc_osdep_get_info(xch, &xch->osdep) < 0 ) // 打开动态库文件并获取符号地址
                goto err;
    
            xch->ops = xch->osdep.init(xch, type);// 调用xc控制器的初始化函数,获取真正的xc 控制结构
            if ( xch->ops == NULL )
            {
                DPRINTF("OSDEP: interface %d (%s) not supported on this platform",
                      type, xc_osdep_type_name(type));
                goto err_put_iface;
            }
    
            xch->ops_handle = xch->ops->open(xch);// 调用 xc 控制结构的open函数获取 ioctl 作用的 fd 文件描述符
            if (xch->ops_handle == XC_OSDEP_OPEN_ERROR)
                goto err_put_iface;
        }
    
        return xch;
    }
    static int xc_osdep_get_info(xc_interface *xch, xc_osdep_info_t *info)
    {
        int rc = -1;
        const char *lib = getenv(XENCTRL_OSDEP);// 获取环境变量的值,该值执行 libxc 动态链接库文件位置
        xc_osdep_info_t *pinfo;
        void *dl_handle = NULL;
    
        if ( lib != NULL )
        {
            if ( getuid() != geteuid() )
            {
                if ( xch ) ERROR("cannot use %s=%s with setuid application", XENCTRL_OSDEP, lib);
                abort();
            }
            if ( getgid() != getegid() )
            {
                if ( xch ) ERROR("cannot use %s=%s with setgid application", XENCTRL_OSDEP, lib);
                abort();
            }
    
            dl_handle = dlopen(lib, RTLD_LAZY|RTLD_LOCAL);// dlopen动态加载 libxl 动态库文件
            if ( !dl_handle )
            {
                if ( xch ) ERROR("unable to open osdep library %s: %s", lib, dlerror());
                goto out;
            }
    
            pinfo = dlsym(dl_handle, "xc_osdep_info");// dlsym 获取符号 xc_osdep_info
            if ( !pinfo )
            {
                if ( xch ) ERROR("unable to find xc_osinteface_info in %s: %s", lib, dlerror());
                goto out;
            }
    
            *info = *pinfo;
            info->dl_handle = dl_handle;
        }
    }

    我们看到,xc_interface_open_common的实现是打开环境变量 XENCTRL_OSDEP 指向的动态库文件,并dlsym获取里边的 xc_osdep_info 符号,然后调用符号地址的init函数,并将返回值赋值给 xch->ops ,最后调用 xch->ops->open函数,并将返回值赋值给 xch->ops_handle .

    下面分析这段代码:

     首先要理解,xc 接口主要用于实现 hypercall , 而系统调用要依赖于具体的domain0操作系统,如 linux / bsd / solaris 等多种OS 对应的实现是有所不同的,所以domain0为不同OS的情况下,返回的xc接口其内部实现必定不同,这种情况下,一般的做法是不同OS封装自己的实现,然后在xc_interface_open_common函数里判断OS类型并根据类型返回具体的实现,这是一种运行时做判断的方法,libxc 库没有这么做,它实际上是在编译前就做了判断,其实现如下: 

    首先,OS相关的代码被抽离出来,作为单独的文件,如xc_linux_osdep.c, xc_solaris.c, xc_netbsd.c ,其他文件是通用的代码,
    
    其次, 在 configure 的时候检测具体的OS类型,然后编译对应的osdep代码,不匹配的osdep代码根本不编译
    
    最后,不管哪种OS,最终libxc 库都编译为动态库 : libxenctrl.so.4.2.0,并置 XENCTRL_OSDEP 环境变量指向得到的动态库。 

    所以就看到了前面的实现:直接动态加载XENCTRL_OSDEP环境变量指向的动态库,并获取符号 "xc_osdep_info" 符号, 下面以 domain0 是 linux 系统为例,则获取的动态库符号对应的地址里的内容就是:

    xc_osdep_info_t xc_osdep_info = { // linux 系统的 xc 接口
        .name = "Linux Native OS interface",
        .init = &linux_osdep_init,
        .fake = 0,
    };
    则 xc_interface_open_common 函数调用的 init 实际调用的是 linux_osdep_init 函数,该函数实现如下:
    libxc/xc_linux_osdep.c
    
    static struct xc_osdep_ops *linux_osdep_init(xc_interface *xch, enum xc_osdep_type type)
    {
        switch ( type )
        {   
        case XC_OSDEP_PRIVCMD:
            return &linux_privcmd_ops;  // hypercall 通过这个结构体实现
        case XC_OSDEP_EVTCHN:
            return &linux_evtchn_ops;
        case XC_OSDEP_GNTTAB:
            return &linux_gnttab_ops;
        case XC_OSDEP_GNTSHR:
            return &linux_gntshr_ops;
        default:
            return NULL;
        }   
    }

    可以看到,init函数主要是根据type类型返回一个控制结构体,如果是hypercall类型(对应XC_OSDEP_PRIVCMD),则返回的是 linux_privcmd_ops 这个结构体的指针,这个结构体的内容如下:

    static struct xc_osdep_ops linux_privcmd_ops = {                                                                                             
        .open = &linux_privcmd_open, // 这里主要干的是:fd = open("/proc/xen/privcmd", O_RDWR);
        .close = &linux_privcmd_close,                                                                                                           
        
        .u.privcmd = {
            .alloc_hypercall_buffer = &linux_privcmd_alloc_hypercall_buffer,                                                                     
            .free_hypercall_buffer = &linux_privcmd_free_hypercall_buffer,                                                                       
            
            .hypercall = &linux_privcmd_hypercall,    // hypercall 调用                                                                                 
            .map_foreign_batch = &linux_privcmd_map_foreign_batch,                                                                               
            .map_foreign_bulk = &linux_privcmd_map_foreign_bulk,
            .map_foreign_range = &linux_privcmd_map_foreign_range,
            .map_foreign_ranges = &linux_privcmd_map_foreign_ranges,                                                                             
        },                                                                                                                                       
    };
    其中, .u.privcmd.hypercall 函数就是Xen hypercall 在应用层的实现,linux os dep 的实现如下:
    static int linux_privcmd_hypercall(xc_interface *xch, xc_osdep_handle h, privcmd_hypercall_t *hypercall)
    {
        int fd = (int)h;
        return ioctl(fd, IOCTL_PRIVCMD_HYPERCALL, hypercall); // 变成 ioctl 系统调用
    }
    可以看到,最终是转换为对参数 h 这个Fd 的 ioctl 调用,那么这个h是怎么来的?

    回到 xc_interface_open_common函数的实现, 其获取动态库符号后调用的init函数其实是 linux_osdep_init 函数,则保留在 ctx->xch->ops 变量上的是 linux_privcmd_ops 结构体,随即调用 ctx->xch->ops_handle = xch->ops->open(xch) 其实就是调用 linux_privcmd_open 函数,该函数主要代码是 fd = open("/proc/xen/privcmd", O_RDWR); 所以,保留在 ctx->xch->ops_handle上的其实是 /proc/xen/privcmd 打开后的文件描述符。这样,后续进程其他地方需要调用hypercall,则通过 ctx->xch 结构体上的 ops 和 ops_handle , 就可以将各种 hypercall 调用变成对 /proc/xen/privcmd 文件描述符的 ioctl 调用
     
  • 相关阅读:
    SecureCRT 安装及初始化配置
    企业生产环境中linux系统分区的几种方案
    Django之验证码 + session 认证
    Django之上传文件
    Django之Cookie与Session
    Django之CSRF 跨站请求伪造
    web前端之 DOM
    c++ 之 字符和字符串
    web前端
    调用线程无法访问此对象,因为另一个线程拥有该对象
  • 原文地址:https://www.cnblogs.com/jiayy/p/3751368.html
Copyright © 2011-2022 走看看