浅谈 NCSI 及其在 Linux 上的实现(4)
- UID
- 1066743
|
浅谈 NCSI 及其在 Linux 上的实现(4)
NCSI 在 Linux 上的简单实现硬件支持要实现 NCSI,首先需要选择支持 NCSI 的 NIC,目前大部分网络芯片厂商都有支持 NCSI 的产品,比如 Broadcom 的 BCM57710,Intel 的 Intel82576 等。
在 Linux 网络协议栈中初始化 NCSI 协议族在 Linux 的网络协议栈中初始化 NCSI 协议族需要了解 Linux 的网络协议栈,这不是本文讨论的重点,本节只是简单介绍在 Linux 网络协议栈的三个层面(Socket 层、Sock 层和 sk_buff 层)上初始化 NCSI 接口。之所以只是介绍这三层,是因为 socket{}、sock{}、sk_buff{} 是 Linux 网络协议栈中最重要的数据结构,也是数据流的连接通道。比如,发送报文时,数据会由 socket{} 通过相应的 proto_ops{} 把数据传给 sock{},sock{} 又通过 proto{} 把数据传到 sk_buff;反过来,当收到报文时,sk_buff{} 通过 net_protocol{} 把数据传给 sock{},后者又通过 proto{} 把数据传给 socket{},socket{} 最后把数据传给用户层,有关这方面具体详细细节在这里不做过多赘述。
- Socket 层,注册 net_proto_family 结构体
清单 1. 注册 net_proto_family 结构体到全局数组 net_families 中1
2
3
4
5
6
7
| #define PF_NCSI 27 /* NCSI Address Family */
static struct net_proto_family ncsi_family_ops = {
.family = PF_NCSI,
.create = ncsi_create,
.owner = THIS_MODULE,
};
sock_register(&ncsi_family_ops);
|
- Sock 层,填充 proto_ops 结构体,该结构体的成员变量是一些函数指针,分别对应了 NCSI 操作的函数。
清单 2. 填充 proto_ops 结构体1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| static struct proto_ops ncsi_ops = {
.family = PF_NCSI,
.owner = THIS_MODULE,
.release = ncsi_release,
.bind = sock_no_bind,
.connect = sock_no_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = sock_no_getname,
.poll = sock_no_poll,
.ioctl = sock_no_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = sock_no_setsockopt,
.getsockopt = sock_no_getsockopt,
.sendmsg = ncsi_sendmsg,
.recvmsg = ncsi_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
|
- sk_buff 层,实现从 NIC 接收 NCSI packet 的功能,其中 ncsi_rcv 是从 NIC 中接收 RAW packet 的接口。
清单 3. 实现从 NIC 接收 NCSI packet 的功能1
2
3
4
5
6
| #define NCSI_PROTOCOL 0x88F8
static struct packet_type ncsi_packet_type = {
.type = __constant_htons(NCSI_PROTOCOL),
.func = ncsi_rcv,
};
dev_add_pack(&ncsi_packet_type);
|
利用套接字 socket 实现 NCSI 的操作在 Linux 的网络协议栈中初始化了 NCSI socket 的三元组:< 地址族,类型,具体协议 >,也正好是调用 socket 系统函数的 3 个参数。内核中这 3 个数据结构,就可以创建 sock{} 结构。
首先,在利用套接字实现 NCSI 操作之前,为了提高程序的通用性和易读性、减少不一致性,需要对 NCSI 协议族进行宏定义:
清单 4. 对 NCSI 协议族进行宏定义1
| #define AF_NCSI 27 /* NCSI 地址族定义 */
|
其次,为了便于对 NCSI 包头进行整体性操作,这里根据前文所述的 NCSI 包格式,定义了 NCSI 的包头结构 ncsihdr:
清单 5. NCSI 包头结构的定义1
2
3
4
5
6
7
8
9
10
11
12
| /* NCSI 包头结构的定义 */
struct ncsihdr {
unsigned char mc_id;
unsigned char hdr_rev;
unsigned char reserved0;
unsigned char cmd_iid;
unsigned char cmd;
unsigned char chnl_id;
unsigned short payload_len;
unsigned int reserved2;
unsigned int reserved3;
};
|
从根本上说,NCSI 主要包括 4 种原子操作:创建 NCSI socket、关闭 NCSI socket、向已创建的 NCSI socket 发送 NCSI 命令以及从已创建的 NCSI socket 获得 NCSI 响应。一个最简单的 NCSI 命令操作流程如图 7 所示:
图 7. 一个最简单的 NCSI 命令操作流程图其他任意操作都可以通过使用这 4 种原子操作的组合来实现。为简单起见,本文仅对这 4 中最基本的 NCSI 操作给出了在 linux 下的相应实现,而对于由这 4 种操作引申而出的其它复杂操作就不做过多的赘述了。
清单 6. 创建 NCSI socket1
2
3
4
5
6
7
8
9
10
11
| int OpenNCSISocket ()
{
// 调用 socket 函数创建一个能够进行网络通信的套接字:协议族为 AF_NCSI,套接字类型为 SOCK_RAW
sd = socket(AF_NCSI, SOCK_RAW, 0);
if (sd < 0)
{
cout << "NCSI socket creation failed" << endl;
return -1;
}
return sd;
}
|
清单 7. 关闭 NCSI socket1
2
3
4
5
| int CloseNCSISocket(int sd)
{
close(sd); // 调用 close 函数关闭已创建的套接字
return 0;
}
|
- (3) 向已创建的 NCSI socket 发送 NCSI 命令:
清单 8. 向已创建的 NCSI socket 发送 NCSI 命令1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| int SendRawNCSIPacket( int sd, // 已创建的 NCSI socket
int Port_Num, //NCSI 命令发送到的以太网接口号
unsigned char cmd, //NCSI 命令号
unsigned char channel_id, // 承载 NCSI 命令的信道号
unsigned char *pu8Data, // 指向 NCSI 载荷的指针
unsigned short data_length//NCSI 载荷的长度
)
{
int status;
struct ncsihdr *ncsih; //NCSI 包头
struct iovec xmit_iovec[2]; // 创建 linux I/O 向量 iovec 结构数组
struct msghdr xmit_message; // 创建 linux 信息头 msghdr 结构数组
unsigned char pkt_buffer[60]; // 发送缓冲区
unsigned char *ncsi_payload ; //NCSI 载荷指针
// 封装以太网帧头
memset(pkt_buffer, 0, 60);
memset(pkt_buffer, 0xff, 12); // 目的地址和源地址均为 0xFF FF FF FF FF FF
pkt_buffer[12] = 0x88;
pkt_buffer[13] = 0xf8; // 以太网帧头类型字段为 0x88f8,表示 NCSI 命令
// 封装 NCSI 包头
ncsih = (struct ncsihdr *) (pkt_buffer+14);
ncsih->mc_id = 0x0; // 设置管理控制器 ID 为 0x0
ncsih->hdr_rev = 0x1; // 设置 NCSI 头版本号围 0x1
ncsih->cmd_iid = inciid(); // inciid() 是一个函数,用于获得当前发送数据包的 instance id
ncsih->cmd = cmd; // 设置 NCSI 命令的命令号
ncsih->chnl_id = channel_id; // 设置 NCSI 命令传输的信道号
// 设置 NCSI 载荷的长度。由于网络字节序使用的都是大端模式,所以要调用 htons 进行相应的转换
ncsih->payload_len = htons(data_length);
// 封装 NCSI 载荷
ncsi_payload = (unsigned char *)(pkt_buffer + 14 + sizeof(struct ncsihdr));
if ( (pu8Data!=NULL) && (data_length!=0) )
{
memcpy(ncsi_payload, pu8Data, data_length);
}
// 封装 xmit_message
xmit_message.msg_name = NULL;
xmit_message.msg_namelen = 0;
xmit_message.msg_iov = xmit_iovec;
xmit_message.msg_iov[0].iov_base = (void *)&Port_Num;
xmit_message.msg_iov[0].iov_len = sizeof(int);
xmit_message.msg_iov[1].iov_base = (void *)pkt_buffer;
xmit_message.msg_iov[1].iov_len = 60;
xmit_message.msg_iovlen = 2;
xmit_message.msg_control = NULL;
xmit_message.msg_controllen = 0;
xmit_message.msg_flags = 0;
// 调用 linux 的 sendmsg 发送 NCSI 请求
status = sendmsg(sd, &xmit_message, 0);
return status;
}
|
- (4) 从已创建的 NCSI socket 获得 NCSI 响应:
清单 9. 从已创建的 NCSI socket 获得 NCSI 响应1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| int RecvRawNCSIPacket( int sd, // 已创建的 NCSI socket
unsigned char *pu8NCSIpacket, // 用于存放接收数据的缓冲区
int buf_size, // 接收缓冲区的大小
int *Port_Num, // 接收 NCSI 响应的以太网接口号
int wait_time // 等待时间,单位毫秒
)
{
int status;
struct msghdr recv_message; // 创建 linux 信息头 msghdr 结构数组
struct iovec recv_iovec[3]; // 创建 linux I/O 向量 iovec 结构数组
if ( (NULL == pu8NCSIpacket) || (0 == buf_size )
return -1;
// 封装 recv_message
recv_message.msg_name = NULL;
recv_message.msg_namelen = 0;
recv_message.msg_iov = recv_iovec;
recv_message.msg_iov[0].iov_base = (void *)&wait_time;
recv_message.msg_iov[0].iov_len = sizeof(int);
recv_message.msg_iov[1].iov_base = (void *) pu8NCSIpacket;
recv_message.msg_iov[1].iov_len = buf_size;
recv_message.msg_iov[2].iov_base = (void *)Port_Num;
recv_message.msg_iov[2].iov_len = sizeof (int *);
recv_message.msg_iovlen = 3;
recv_message.msg_control = NULL;
recv_message.msg_controllen = 0;
recv_message.msg_flags = 0;
// 调用 linux 的 recvmsg 接收 NCSI 响应
status = recvmsg(sd, &recv_message, 0);
return status;
}
|
|
|
|
|
|
|