1、背景介绍
目前主流的服务器上都包含BMC芯片,用来实现对服务器的板级监控和管理。X86/飞腾可以配AST2500系列BMC芯片,鲲鹏可以配1711 BMC芯片。主CPU与BMC芯片之间可以通过标准的IPMI协议进行通讯,Linux中也提供了openipmi设备驱动以及ipmitool工具方便用户直接在终端中输入指令实现数据传输。
如果是x86搭配AST2500,在安装了openipmi设备驱动和ipmitool工具后,打开终端输入
ipmitool raw 6 4,此时显示返回值会是55 00,表明主CPU与BMC之间数据通路正常。
2、用户接口实现
当然,除了直接用system来调用终端中输入的ipmitool命令脚本外,也可以通过popen方式来操作,以下就是一个例子
int getSlot()
{FILE *fstream=NULL;char buff[1024];int errno=0;int temp1,temp2;memset(buff,0,sizeof(buff));if(NULL==(fstream=popen("ipmitool raw 6 0x2a","r"))){fprintf(stderr,"execute command failed:%s",strerror(errno));return -1;}while(NULL!=fgets(buff,sizeof(buff),fstream)){sscanf(buff, " %x %x", &temp1,&temp2);// printf("temp1 is %d temp2 is %d %d\n",temp1,temp2,(~temp2)&0x1F);}gslot=(~temp2)&0x1F;gchasis=(~temp1)&0x7F;return 1;
}
不过无论是system("ipmitool raw 6 0x2a")还是上面这种popen方式,都需要执行shell命令,开销都不会太小,执行速度往往比较慢。
在Linux系统编程中,
system
调用和通过fstream
(特别是std::ifstream
和std::ofstream
,但更直接相关的是popen
)调用外部脚本或程序,是两种常见的与外部程序交互的方式。它们各有特点,适用于不同的场景。下面详细解释这两种方式的区别:1.
system
调用
- 定义:
system
函数是C标准库中的一个函数,用于执行一个命令字符串。它启动一个新的shell来执行指定的命令,并等待命令执行完成。- 用法:
int system(const char *command);
- 特点:
- 简单易用,适合执行简单的外部命令。
- 它会启动一个新的shell进程,因此会有额外的开销。
- 不能直接获取命令的输出或输入,除非通过重定向(如
system("command > output.txt")
)。- 难以处理复杂的交互或实时数据流。
- 返回值是命令的退出状态,而不是命令的输出。
2.
popen
调用
- 定义:
popen
函数是POSIX标准定义的,用于创建一个管道,并通过该管道执行一个shell命令,同时允许程序与这个命令进行输入输出交互。- 用法:
FILE *popen(const char *command, const char *type);
- 特点:
- 允许程序与执行的命令进行输入输出交互。
- 可以通过
fread
、fwrite
等标准I/O函数读取命令的输出或向命令发送输入。- 适用于需要处理命令输出或输入的复杂场景。
- 相比
system
,popen
提供了更细粒度的控制。- 仍然需要处理shell注入等安全问题。
3. 两者之间的主要区别
- 交互性:
system
不提供直接的输入输出交互能力,而popen
允许程序与执行的命令进行输入输出交互。- 开销:
system
通过启动新的shell来执行命令,因此通常比popen
有更高的开销。- 用途:
system
适合执行简单的、不需要交互的命令;而popen
适合需要处理命令输出或输入的复杂场景。- 安全性:两者都需要注意避免shell注入等安全问题,但
popen
由于允许更细粒度的控制,可能更容易实现安全的命令执行。
如果需要更快的执行速度,可以采用ioctl方式进行操作,这个可以从ipmitool这个工具的源码中去找到具体的实现方法,以下就是用ioctl方式实现ipmitool raw 6 4的示例代码。
/** Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.* * Redistribution and use in source and binary forms, with or without* modification, are permitted provided that the following conditions* are met:* * Redistribution of source code must retain the above copyright* notice, this list of conditions and the following disclaimer.* * Redistribution in binary form must reproduce the above copyright* notice, this list of conditions and the following disclaimer in the* documentation and/or other materials provided with the distribution.* * Neither the name of Sun Microsystems, Inc. or the names of* contributors may be used to endorse or promote products derived* from this software without specific prior written permission.* * This software is provided "AS IS," without a warranty of any kind.* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.* SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE* FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING* OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL* SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA,* OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR* PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF* LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.*/
#define _POSIX_C_SOURCE 1#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/time.h>#define IPMI_BMC_CHANNEL 0xf
#define IPMI_SYSTEM_INTERFACE_ADDR_TYPE 0x0c
#define IPMI_BUF_SIZE 1024
#define IPMI_MAX_MD_SIZE 0x20
#define IPMI_OPENIPMI_READ_TIMEOUT 1
#define IPMI_MAX_ADDR_SIZE 0x20typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;struct ipmi_msg {unsigned char netfn;unsigned char cmd;unsigned short data_len;unsigned char *data;
};struct ipmi_req {unsigned char *addr;unsigned int addr_len;long msgid;struct ipmi_msg msg;
};struct ipmi_recv {int recv_type;unsigned char *addr;unsigned int addr_len;long msgid;struct ipmi_msg msg;
};struct ipmi_system_interface_addr {int addr_type;short channel;unsigned char lun;
};struct ipmi_rs {uint8_t ccode;uint8_t data[IPMI_BUF_SIZE];/** Looks like this is the length of the entire packet, including the RMCP* stuff, then modified to be the length of the extra IPMI message data*/int data_len;struct {uint8_t netfn;uint8_t cmd;uint8_t seq;uint8_t lun;} msg;struct {uint8_t authtype;uint32_t seq;uint32_t id;uint8_t bEncrypted; /* IPMI v2 only */uint8_t bAuthenticated; /* IPMI v2 only */uint8_t payloadtype; /* IPMI v2 only *//* This is the total length of the payload orIPMI message. IPMI v2.0 requires this tobe 2 bytes. Not really used for much. */uint16_t msglen;} session;/** A union of the different possible payload meta-data*/union {struct {uint8_t rq_addr;uint8_t netfn;uint8_t rq_lun;uint8_t rs_addr;uint8_t rq_seq;uint8_t rs_lun;uint8_t cmd;} ipmi_response;struct {uint8_t message_tag;uint8_t rakp_return_code;uint8_t max_priv_level;uint32_t console_id;uint32_t bmc_id;uint8_t auth_alg;uint8_t integrity_alg;uint8_t crypt_alg;} open_session_response;struct {uint8_t message_tag;uint8_t rakp_return_code;uint32_t console_id;uint8_t bmc_rand[16]; /* Random number generated by the BMC */uint8_t bmc_guid[16];uint8_t key_exchange_auth_code[IPMI_MAX_MD_SIZE];} rakp2_message;struct {uint8_t message_tag;uint8_t rakp_return_code;uint32_t console_id;uint8_t integrity_check_value[IPMI_MAX_MD_SIZE];} rakp4_message;struct {uint8_t packet_sequence_number;uint8_t acked_packet_number;uint8_t accepted_character_count;uint8_t is_nack; /* bool */uint8_t transfer_unavailable; /* bool */uint8_t sol_inactive; /* bool */uint8_t transmit_overrun; /* bool */uint8_t break_detected; /* bool */} sol_packet;} payload;
};struct ipmi_addr {int addr_type;short channel;char data[IPMI_MAX_ADDR_SIZE];
};#define IPMI_IOC_MAGIC 'i'
#define IPMICTL_SEND_COMMAND _IOR(IPMI_IOC_MAGIC, 13, struct ipmi_req)
#define IPMICTL_RECEIVE_MSG_TRUNC _IOWR(IPMI_IOC_MAGIC, 11, struct ipmi_recv)int fd=-1;int ipmidev_open()
{fd=open("/dev/ipmi0", O_RDWR);if(fd<0){printf("open ipmidev failed\n");return -1;}return fd;
}void ipmidev_close()
{if (fd >= 0){close(fd);}
}int ipmi_cmd_test(int netfun,int cmd)
{struct ipmi_system_interface_addr bmc_addr = {.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE,.channel = IPMI_BMC_CHANNEL,};struct ipmi_req _req;struct ipmi_recv _recv;static struct ipmi_rs rsp;struct ipmi_addr addr;struct timeval read_timeout;int rset;int i=0;int retval=0;memset(&_req, 0, sizeof(struct ipmi_req));bmc_addr.lun =0;_req.addr = (unsigned char *) &bmc_addr;_req.addr_len = sizeof(bmc_addr);_req.msgid = 0;_req.msg.data = 0;_req.msg.data_len = 0x0;_req.msg.netfn =netfun;_req.msg.cmd =cmd;if(ioctl(fd, IPMICTL_SEND_COMMAND, &_req) < 0){printf( "Unable to send command\n");return -1;}read_timeout.tv_sec = IPMI_OPENIPMI_READ_TIMEOUT;read_timeout.tv_usec = 0;retval = select(fd, &rset, NULL, NULL, &read_timeout);_recv.addr = (unsigned char *) &addr;_recv.addr_len = sizeof(addr);_recv.msg.data = rsp.data;_recv.msg.data_len = sizeof(rsp.data);/* get data */if (ioctl(fd, IPMICTL_RECEIVE_MSG_TRUNC, &_recv) < 0) {printf( "Unable to recv command\n");return -1;}for (i = 0; i<_recv.msg.data_len ; i++)printf("%02x ", _recv.msg.data[i]);printf("\n");return 1;
}int main()
{ipmidev_open();ipmi_cmd_test(6,4);ipmidev_close();return 0;
}
上述代码中首先需要打开Ipmi设备,只要驱动加载了/dev下面就能找到ipmi0设备,然后调用ioctl给该设备发送数据并等待回应数据,注意这里需要延迟一段时间等待文件可读
read_timeout.tv_sec = IPMI_OPENIPMI_READ_TIMEOUT;read_timeout.tv_usec = 0;retval = select(fd, &rset, NULL, NULL, &read_timeout);
这段代码是在使用
select
函数来监视一个或多个文件描述符(在这个例子中为fd
),以等待文件描述符变得可读,同时设置了一个超时时间。下面是对这段代码的详细解释:
- 设置超时时间:
read_timeout.tv_sec = 10;
:这行代码设置超时时间的秒数为10秒。read_timeout.tv_usec = 0;
:这行代码设置超时时间的微秒数为0。微秒数(tv_usec
)和秒数(tv_sec
)共同构成了超时时间的总长度。因此,这里的超时时间总共是10秒。select
函数调用:
retval = select(fd, &rset, NULL, NULL, &read_timeout);
:这行代码调用了select
函数,用于监视文件描述符集合rset
中的文件描述符是否变得可读。
当文件可读后再调用ioctl获取返回值即可,注意ipmi协议中返回值第一个字节是completion code,正常情况下是0,所以这里打印出来的返回值会比直接在shell中输入ipmitool 命令返回值多一个字节。
这里拿鲲鹏平台进行测试验证,shell中输入ipmitool 命令返回值如下:
执行上述程序返回值如下:
可以看到返回值是正确的,这样就可以不需要执行shell脚本实现主芯片与BMC芯片的交互。