继续上文的讲叙,在利用OpenIPMI现成的库和例子程序之前,我们需要理解OpenIPMI 代码的结构和OpenIPMI程序运行的过程和特点。
OpenIPMI的目的就是要屏蔽IPMI协议的细节,给用户提供快速实现SMS的框架和接口。因此,它内部替用户已经实现了IPMI协议里提到的各种类型(数据结构),比如sensor,sdr,sel,mc,sel,entity等等,同时使用面向对象的思想,提供了很多查询和设置这些数据结构的方法。当然,为了屏蔽不同平台和接口的差别,OpenIPMI自己在用户层封装了一层timer机制、锁同步机制。此外,为了向用户提供统一的操作方法,OpenIPMI自定义了一些额外的数据结构,比如domain,connection等等。结合文档和原代码的分析,我们可以看到OpenIPMI的数据结构比较多,相互关联比较复杂:
通过上面的图,我们可以看到最中间的ipmi_domain_t,它关联了和BMC本身相关的mc,sdr,sensor,entity等数据结构,这些成员都是在这个域建立起来后需要初始化的。在上面图最左上侧的os_handler_s数据结构,就是对不同的内存管理、timer机制的封装。而它下面的ipmi_con_s数据结构就是对BMC不同的接口比如KCS/Lan/IPMB的封装,以向用户提供统一的命令发送、链路建立的接口。和BMC本身相关的数据结构主要有ipmi_mc_s,它囊括了SEL/SDR/Sensor/Control信息以及处理OEM差异的函数,而SEL/SDR/Sensor/Control等信息数据又和相应的实体结构对应,每个实体结构包括真实的物理信息,OpenIPMI为此提供了许多设置和查询的操作。以ipmi_sensor_s为例子,它本身包括Lun/ Sensor number等基本信息,OpenIPMI又提供了对它的许多操作:
|| ipmi_sensor_convert_from_raw
|| stand_ipmi_sensor_convert_to_raw
|| stand_ipmi_sensor_get_tolerance
|| stand_ipmi_sensor_get_accuracy
|| stand_ipmi_sensor_reading_name_string
|| ipmi_sensor_get_callbacks
|| ipmi_sensor_set_callbacks
|| ipmi_sensor_set_event_enables
|| ipmi_sensor_enable_events
|| ipmi_sensor_disable_events
|| ipmi_sensor_rearm
|| ipmi_sensor_get_event_enables
|| ipmi_sensor_get_hysteresis
|| ipmi_sensor_set_hysteresis
|| ipmi_sensor_get_thresholds
|| ipmi_sensor_set_thresholds
|| ipmi_sensor_get_reading
|| ipmi_sensor_get_states
用户只需要利用OpenIPMI提供的机制按照函数定义的格式正确调用就能快速高效构建自己的SMS。比如我们需要每隔5秒钟检查系统上的所有sensor的读值是否超出阈值,可以利用OpenIPMI已经提供好的timer机制、回调机制和现成的函数接口实现即可。具体的过程如下:
1. 执行框架代码:建立起来统一的os handler,初始化IPMI 库,关联connection和os handler, open一个domain,同时指定额外的初始化函数;
2. 初始化timer:OpenIPM提供的timer相当于定时触发器,每隔一段时间和它关联的进程就会执行一次。 具体而言,需要先初始化一个timeval和os_hnd_timer_id_t的数据结构,设置超时间隔,指定超时后执行的函数。这里指定扫描系统所有sensor的函数作为超时后执行的函数。然后通过os handler启动定时器。这样设置完成后,一旦定时器超时,指定的函数就会执行 。但需要注意的是,由于定时器超时后不会再自动启动,这个函数只会执行一次 。为了让指定的函数周期地按照固定的间隔执行,需要利用线程。
3.通过os handler调用creat_thread创建一个线程 ,在线程里面执行OpenIPMI事件驱动模型的主要循环,这个循环里面就包括重启定时器。
4. 利用OpenIPMI的回调机制实现sensor扫描:首先在超时后执行的函数里面调用枚举sensor的函数:
ipmi_entity_iterate_sensors(myentity, iterate_sensor, NULL);
注意这个函数的第二个参数是需要用户自定义的回调函数,它有固定的参数格式:
static iterate_sensor(ipmi_entity_t *ent, ipmi_sensor_t *sensor, void * b_data)
特别需要强调的是,并不是ipmi_entity_interate_sensors()函数执行完了后,马上就会执行到了iterate_sensor,从前者都后者在OpenIPMI内部又经历了三四层的回调,并且当执行到后者时,它的前面两个参数刚好指向当前遍历的sensor的ipmi_entity_t 和 ipmi_sensor_t结构,这样用户根据需要直接访问这俩参数就可以了。至于底层如何从/dev/ipmi0设备通过IOCTL发送IPMI sensor reading请求并读取响应值,OpenIPMI都已经处理好,用户不需要再操心。类似的,如果用户需要遍历所有的entity,调用ipmi_domain_iterate_entities()函数并实现对应的handle_entity()回调函数就可以。值得注意的是,用户可以通过枚举函数ipmi_domain_iterate_xxxx的第三个参数 void * cb_data向回调函数传递参数,但是回调函数不能想上层带回信息。
5. 调用OpenIPMI提供的函数来查询sensor的状态:OpenIPMI提供了大量的设置sensor和查询sensor的函数,比如ipmi_is_state_set()、ipmi_set_initial_update_in_progress()等。用户可以直接调用这些函数来检查某些状态位或阈值位是否已经设置。
根据上面的分析我们不难看到,借助OpenIPMI提供的timer方法、回调机制和提供的诸多接口,用户不再需要对驱动、IPMI协议有详尽了解,甚至也不用开发任何访问BMC的函数,就可以快速构建自己的SMS,这在产品更新换代日益加快的今天,无疑将会大大提高生产效率,加快产品上线进度。