CMPI中的内存管理及在Open Pegasus 中的实现

本文首先简单介绍了 CMPI(Common Manageability Programming Interface) 规范与 Open Pegasus, 说明了 CMPI 规范中对多线程及内存管理方面的要求,然后以 Open Pegasus 中的实现为例,分析了为实现上述要求所需要的关键数据结构,最后全文进行了总结。

CMPI(Common Manageability Programming Interface) 是由 Open Group(www.opengroup.org) 维护的一套规范。其中定义了一系列基于C语言的编程接口。基于这些编程接口, CMPI 隔离了 CIMOM(CIM Object Manager, 又称 Management Broker )和 CIM Provider (又称 Management Instrumentation, 以下简称 Provider )中的相关实现,从而使 Provider 的开发和运行不再依赖于某种特定的 CIMOM。 因此无需重新编译链接, Provider 可以发布到任何一种支持 CMPI 的 CIMOM 中。

Open Pegasus(www.openpegasus.org, 以下简称 Pegasus )是一个开源的企业级的 CIMOM。它实现了对 CMPI 规范的支持。本文就以 Pegasus (2.7.1版)为例,研究了 CMPI 实现中的多线程内存管理的问题。

本文讨论的默认平台为 Linux。

CMPI 中的线程与内存问题

CMPI 需要同时解决的两个问题就是其所驱动的 Provider 代码的线程安全与可重入性以及 CMPI 对象的内存管理。

线程安全及可重入

CMPI 规范要求 CMPI 的实现是线程安全以及是可重入的。这是因为通常 CIMOM 为了同时响应多个 CIM 的请求,会为每个 CIM 请求生成一个线程。这些线程通过 CMPI 定义的接口来驱动 Provider 的代码。因此同一段 Provider 的代码可能同时被多个线程所执行。为了达到线程安全及可重入,CMPI 规范中提出需要实现一个 CMPI 上下文( CMPIContext )的对象。每个线程拥有不同的 CMPIContext 对象,所有静态的数据都将存储在这个上下文对象之中,从而实现线程相关数据的分离。

内存管理

因为 CMPI 对象相对复杂,为了保证内存性能, CMPI 规范中规定了在 Provider 调用期间所有通过 CMPI 进行的内存分配(除了使用 CMPI 中定义的 clone 方法申请的内存)都应当由 CMPI 负责释放。这极大的降低了 Provider 编程中对内存管理编程的复杂度,使遵守 CMPI 规范的 Provider 开发更简单。但是也正是因为 CMPI 能自动管理内部使用的 CMPI 对象,因此如果希望一个 CMPI 对象的作用域能够超出其所在函数的范围,就必须使用 CMPI 为每个 CMPI 对象定义的 clone 方法。使用 clone 方法创建的 CMPI 对象,脱离了 CMPI 的管理,因此需要显式调用该对象的 release 方法进行释放。





Pegasus 中对 CMPI 多线程内存管理的实现

作为企业级的 CIMOM 实现, Pegasus 为了能够在多线程的环境下实现内存的管理,实现了若干数据结构。图1给出了这些数据结构的关系图。


图 1:Pegasus 中支持 CMPI 的相关数据结构
CMPI中的内存管理及在Open Pegasus 中的实现_第1张图片  

其中关键的数据结构是 CMPI_ThreadContext 。 Pegasus 在启动每个响应 CIM 请求(例如 EnumerateInstances )的线程时,都会在栈上生成一个 CMPI_ThreadContext 对象,以维护对该 CIM 请求的处理过程中动态创建的 CMPI 对象。因为其是在栈上创建的,因此当其作用域退出时,该对象及其管理的所有 CMPI 对象也会随之析构。

一般来讲,一个线程只需要一个 CMPI_ThreadContext 对象就可以完成管理其生命周期内所有 CMPI 对象的任务。

CMPI_ThreadContext 数据结构

每个 CMPI_ThreadContext 对象都将绑定一个线程,并被线程内部所有的 CMPI 对象所共享。因此 CMPI_ThreadContext 实际上就是一个线程私有数据( Thread Specific Data, TSD )。基于 pthread 线程库所提供的支持, CMPI_ThreadContext 类包含一个 pthread_key_t 类型的静态变量。通过 pthread_setspecific 调用, CMPI_ThreadContext 将自身的地址与这个 key 绑定。而线程内的其他 CMPI 对象,则可以通过 pthread_getspecific 调用来得到该线程的 CMPI_ThreadContext 的地址。基于此种机制,不同线程内 CMPI 对象被分别管理成为可能。

Pegasus 使用专门的类来封装每种 CIM 的对象,例如 CIMInstance 、 CIMDateTime 等,然后将这些对象再次封装为 CMPI_Object 。 CMPI_Object 的主要的成员就是一个指向被封装对象的指针 hdl 和一个与其对应的函数表。另外就是用以构造一个 CMPI_Object 双向队列的指针 prev 和 next。

CMPI_ThreadContext 中维护着指向这个 CMPI_Object 双向队列头尾的指针 CIMfirst 和 CIMlast 。在该线程内创建的所有的 CMPI 对象都被记录在该队列中。为了能够自动释放该队列中所有的 CMPI 对象, CMPI_ThreadContext 在析构函数中(参见代码清单1),依次调用该队列中的对象的 release 方法。

CMPI_ThreadContext::~CMPI_ThreadContext()
{
    for( CMPI_Object *nxt,*cur=CIMfirst; cur; cur=nxt )
    {
        nxt=cur->next;
        (reinterpret_cast<CMPIInstance*>(cur))->ft->release(
        reinterpret_cast<CMPIInstance*>(cur));	
    }

    TSDKeyType k=getContextKey();
    TSDKey::set_thread_specific(k,prev);
}

代码清单1:CMPI_ThreadContext 的析构函数

在上面代码中,所有的 CMPI_Object 对象都转换成 CMPIInstance 对象后,再调用的 release 成员方法。通常而言这是错误的做法,因为这些 CMPI 对象并不能确保都是 CMPIInstance 对象。上面代码之所以可以这么做,是因为 release 方法总是所有的 CMPI 对象的函数表中的第一个成员函数。因此可以将一个 CMPI 对象转换成为任何一种 CMPI 对象并调用其 release 方法而保证不出现任何问题。

那么一个 CMPI_Object 对象是如何自动的加入到这个双向队列中呢? CMPI_Object 为每一种 CIM 对象定义了相应版本的构造函数。在每个构造函数中, CMPI_Object 都首先得到和当前线程关联的 CMPI_ThreadContext 对象,然后将自身的 this 指针加入到其所管理的 CMPI_Object 双向队列中。

当显式调用一个 CMPI_Object 对象的 release 方法被时,该对象所指向的实际 CIM 对象会被首先删除,然后 release 方法会将该 CMPI_Object 的 this 指针从所属的双向队列中移出并删除。

在 Provider 的代码中使用多线程

对于无需使用多线程的 Provider 开发中, Pegasus 所提供的关于线程以及内存管理方面的支持已经极大简化了程序员的工作量。但是如果因为实际需要, Provider 内部依然要求使用多线程,而且在所启动的线程内部还需要使用 CMPI 所提供的服务,那么只需要遵守如下三个步骤。

在创建新的线程之前调用 CMPI 定义的 prepareAttachThread 方法。该方法的目的是复制当前线程的一些上下文信息并传递给新创建的线程。从而保证新的线程可以继承当前线程中的一些上下文信息。

在新的线程中,首先调用 CMPI 定义的 attchThread 方法。在 attachThread 方法中会 new 一个新的 CMPI_ThreadContext 对象,使其管理该进程内部所有的 CMPI 对象。

在新的线程退出前,调用 CMPI 定义的 detachThread 方法。因为新的线程中的 CMPI_ThreadContext 对象是在堆上创建的,无法自动析构。因此需要在线程退出之前使用 detachThread 方法显式的去析构 CMPI_ThreadContext 对象。





总结

本文阐述了 CMPI 规范中关于线程安全及可重入性和内存管理方面的要求,并分析了其在 Pegasus 中的具体实现,分析了其中使用的关键数据结构。上面的阐述可以帮助 Provider 的开发人员能够更加清晰的认识 CMPI 对象的生存周期,在多线程的环境下正确的使用 CMPI 提供的各种方法。(责任编辑:A6)

你可能感兴趣的:(CMPI中的内存管理及在Open Pegasus 中的实现)