Netlink作为一种进程间通信机制, 除了提供内核不同模块间通信, 还能实现内核态与用户态进程间通信. 相较于传统的用户进程与内核通信的机制(如ioctl方法和proc文件系统)的单向性, Netlink可以实现全双工通信[1].
1553B总线全称为数字时分命令/响应型多路传输数据总线. 最早应用于美军航空电子综合系统之上, 各种航电设备基于MIL-STD-1553B总线完成设备间通信[2]. 但随着其不断的发展, 线性局域网络结构使得1553B总线成为航空系统或地面车辆系统中分布式设备的理想连接方式. 与点对点连接相比, 它减少了所需电缆、所需空间和系统的重量. 便于维护, 易于增加或删除节点, 提高了设计灵活性, 冗余容错能力等特点, 在航空、航天、航海和民用等领域都得到广泛的使用[3].
Socket 1553B协议是在Linux下1553B协议实现的一种方法. Linux下一般使用1553B协议的方法是基于字符设备来实现的, 与之不同的是Socket 1553B使用Netlink的socket接口和linux网络协议栈, 这种方法使得1553B设备驱动可以通过网络接口来调用. Socket 1553B的接口被设计的尽量接近TCP/IP的协议, 让那些熟悉网络编程的程序员能够比较容易的学习和使用.
1 系统总体架构设计整个系统环境的最底层是PCIE-1553B接口卡, 在操作系统中为1553B设备创建网络驱动, 负责配置和收发操作. 在收发网络包过程中对其进行解析报文, 根据协议类型判断是否为1553B数据包, 递交给Netlink内核层中的接收函数处理[4]. 利用内核提供的Netlink接口层, 创建新的网络协议, 屏蔽了底层实现的差异, 为上层socket应用提供了统一的应用接口. 系统总体架构如图1.
2 1553B的接口设计 2.1 1553B协议介绍
1553B总线系统包括串行传输电缆、总线控制器BC、远程终端RT、总线监控器MT[5]. 其组成如图2所示.
本文以BC与RT之间的通信为例, 来说明该总线系统的工作过程. 该过程如下:
(1) 总线控制器BC通过初始化完成数据传输的准备工作.
(2) BC通过向总线发送命令字来决定参与本次数据传输的远程终端和采用的消息格式. 命令字分为发送命令字(要求RT向总线上发送数据), 接收命令字(要求RT接收总线上的数据), 方式代码命令字(要求RT完成特殊操作).
(3) RT检测到BC传来的命令字与自己地址匹配时, 将按照命令字的要求参与到数据传输过程中, 完成数据传输后RT将在规定时间内返回状态字[6].
(4) BC通过检测RT返回的状态字来判断本次数据传输完成情况. 如果数据正确传输将结束本消息传输.
其中字格式如图3所示.
1553B协议驱动实现BC与RT之间的通信. BC和RT的通信过程可以分为BC发送RT接收和RT发送BC接收两种情况.
2.1.1 BC读RTBC读RT表示RT发送BC接收, 如图4所示.
如图4所示, 整个过程如下:
(1) RT将数据写入本地DMA发送缓存, 等待BC来读取.
(2) BC发送BC读RT的命令给目的RT, 告诉目的RT, BC需要从RT的哪个子地址(sa)读取多长的数据(len), 同时BC开始计时, 在一定的时间内若没有收到RT返回的状态(帧), 则产生超时中断.
(3) RT接收到BC的命令后RT启动本地定时器(RT需要在规定的时间内向BC发送状态帧), 根据本地情况返回不同的状态帧给BC, 具体情况如下:
1) 如果BC要求读取的目的子地址没有数据,则返回busy帧给BC, 并产生一次正常数据发送中断.
2) 如果BC要求读取的目的子地址有数据,则返回“状态+数据”给BC, 并产生一次正常数据发送中断.
3) 在1), 2)的过程中, 如果定时时间到, 则会产生一个“RT返回状态超时”的中断[7].
(4) BC在规定的时间内收到RT返回的状态(busy帧或数据帧), BC将RT返回的状态写入特定寄存器保存起来, 如果返回的状态后面跟有数据, 则将数据存入BC特定的DMA接收缓存中, 供驱动程序获取, 并产生一次正常数据接收中断.
(5) 驱动程序根据返回的状态内容, 将busy状态或接收到的数据返回给用户.
2.1.2 BC写RTBC写RT表示BC发送RT接收, 如图5所示.
如图5所示, 整个过程如下:
(1) BC将用户数据写入本地特定DMA发送缓存.
(2) BC将目的RT号、目的子地址号、数据长度信息写入特定寄存器, 供硬件组“命令字”使用.
(3) 启动命令发送.
(4) BC以命令字+数据字(可以有多个)的形式往外发送数据, 在最后一个数据字发送完后, 启动定时.
(5) RT收到BC写命令进行解析, 当BC向RT所写的子地址中有数据时, 返回busy帧发送BC; 当BC向RT所写的子地址中无数据时, RT接收数据, 并产生正常接收的状态发送BC[8].
(6) 如果在一定时间内没有收到RT返回的状态(表征busy或正常接收), 则产生一次发送超时; 否则产生一次正常发送中断.
(7) RT将根据状态, 反馈给用户.
2.2 socket接口层Netlink机制用户层的套接字采用标准的套接字接口来定义的, 其中包括socket、bind、sendto、recvfrom、close等[9].
int socket( int domain, int type, int protocol);
socket函数, 指定期望的通信协议类型和传输方式. domain: 地址域, 在Netlink机制下应为AF_NETLINK; type: SOCK_DGRAM或SOCK_RAW两种类型; protocol:协议使用NETLINK_1553B_EVENT, 自定义协议号为NETLINK_1553B_EVENT=17.
int bind ( int sockfd, struct sockaddr *maddr, socklen_t addrlen);
bind函数, 用来绑定一个套接字的地址、协议、端口号[10]. 在Netlink机制下绑定地址的结构体如下:
struct sockaddr_nl
{
sa_family_t nl_family; /*设置为AF_NETLINK* /
unsigned short nl_pad; /*填充* /
_u32 nl_pid; /*进程号* /
_u32 nl_groups; /*多播地址掩码* /
} nladdr;
int sendto( int sockfd, void *buf, size_t len, unsigned flags, struct sockaddr * addr, int addrlen)
sendto是向套接字发送消息给内核的Netlink层. Netlink套接字消息包括消息头和数据两部分. Netlink套接字所传递的消息都有一个固定的消息头, 结构体如下[11]:
struct nlmsghdr
{
_u32 nlmsg_len;/*消息长度* /
_u16 nlmsg_type; /*消息类型* /
_u16 nlmsg_flags; /*控制信息* /
_u32 nlmsg_seq; /*序列号* /
_u32 nlmsg_pid; /*进程号* /
};
nlmsg_len表示该消息的总长度; nlmsg_type表示消息类型, 该值与Netlink选用的通信协议相关, 本文设置为NLMSG_1553B_TYPE=0x12, 表示1553B协议消息类型; nlmsg_flags为控制信息, 如标志设为NLM_F_REQUEST或NLM_F_ACK, NLM_F_ACK表示要求消息接收方发回一个确认消息给消息发送方; nlmsg_seq表示序列号; nlmsg_pid表示发出消息进程的进程号.
int recvfrom(int sockfd, void* buf, size_t size, int flags, struct sockaddr *addr, int addr_len);
recvfrom表示接收来自内核Netlink层的消息, 该函数阻塞等待底层有数据到来.
int close( int sockfd);
close表示关闭一个Netlink网络套接字, 具体操作是把套接字链表中对应的套接字删除, 同时释放套接字的接收缓存和发送缓存, 清除套接字的使用内存,设置套接字的传输状态为关闭.
2.3 Netlink内核接口层在Netlink内核初始化时, 通过netlink_kernel_create函数来创建内核套接字, 并将自定义协议号为NETLINK_ 1553B_EVENT=17, 消息类型为NLMSG_1553B_TYPE=0x12进行注册.
struct sock* netlink_kernel_create(struct net *net, int unit, void (* input)(struct sk_buff *skb), struct mutex *cb_mutex, struct module *module);
net表示网络名字空间, 一般使用init_net这个全局变量; unit表示使用的协议号, 这里为NETLINK_1553B_EVENT=17; cb_mutex为NULL; module为THIS_MODULE.
input()函数为具体的消息处理函数[12]. 内核中为某一个通信协议使用netlink_kernel_create函数创建了一个Netlink套接字, 所有该通信协议的用户方Netlink消息都将发送给内核空间的这个Netlink套接字, 由该套接字再调用创建时注册的input()函数来统一处理所有的消息. 这里在自定义消息处理函数为msg1553b_receive中, 会判断消息类型、长度等, 以及1553B协议整个收发过程, 包含BC读RT, BC写RT, RT读数据, RT写数据等.
基于Netlink机制的用户态与内核态之间的通信如图6所示.
用户态中使用Netlink套接字socket创建时, 需要设置协议号为NETLINK_1553B_EVENT=17. 在Netlink套接字封装消息时, 需要将消息类型设置为NLMSG_1553B_TYPE=0x12. 并且在消息数据中规定了以下几种模式:
DEV_INIT: 表示设备的初始化, 规定该设备的角色, 是BC还是RT.
BC_RD_RT: 表示BC读RT, 即BC需要从RT的某个子地址中读取数据.
BC_WR_RT: 表示BC写RT, 即BC需要向RT的某个子地址中写入数据.
RT_RD_DATA: 表示RT读数据, 即RT从某个子地址中读取数据.
RT_WR_DATA: 表示RT写数据, 即RT向某个子地址中写入数据.
内核态中使用Netlink套接字socket创建时, 需要注册协议号NETLINK_1553B_EVENT=17, 并指明接收处理函数msg1553b_receive. 在msg1553b_receive函数中会判断消息类型是否为NLMSG_1553B_TYPE, 如果是, 继续处理消息; 如果不是则返回错误. 对应的消息处理函数中针对以上这几种模式会做出不同的响应:
(1) 当收到DEV_INIT模式时, 将判断是BC还是RT, 分别进行对应的初始化.
(2) 当收到BC_RD_RT模式时, 将1553B的状态字发送给RT, 并读取RT返回的数据或状态, 再返回给用户态. 若超时或无数据, 则返回对应的状态给用户态.
(3) 当收到BC_WR_RT模式时, 将1553B的“状态字+数据”发送给RT, 并读取RT返回状态, 再返回给用户态. 若超时或无法写数据, 则返回对应的状态给用户态.
(4) 当收到RT_RD_DATA模式时, 将从底层RT对应子地址中的读取数据, 并返回给用户态. 若超时或无数据, 则返回对应的状态给用户态.
(5) 当收到RT_WR_DATA模式时, 将向底层RT对应子地址中的写入数据, 并返回给用户态. 若超时或无法写入数据, 则返回对应的状态给用户态.
3 实验测试搭建测试环境, 测试设备连线示意图如图7所示.
按图7所示连接开发机、测试计算机、1553B监控设备(监控1553B总线上的数据). 测试计算机上, 通过测试软件打开1553B监控设备, 对整个通信过程进行监控; Linux软件开发机上加载1553B驱动模块; 分别打开测试机上RT设备Netlink套接字测试程序和Linux开发机上BC设备Netlink套接字测试程序, 进行BC和RT设备间1553B数据通信测试. 本测试以BC读RT数据为例, 具体测试步骤如下.
(1) BC向0号RT设备发送请求模式字命令, 以获知当前0号RT设备的哪些子地址拥有数据.
(2) RT在收到BC的矢量请求命令后回复一个数据字, 告知BC此时自身拥有数据的子地址号, 如果没有返回0.
(3) BC根据RT返回的结果, 向当前RT的拥有数据的某个子地址发送“RT发送数据”命令字, 来获取该子地址的数据.
(4) RT在收到“RT发送数据”命令字后, 将相应子地址的数据发送给BC, 记录此时发送的数据.
测试结果如下表所示.
通过表1可见, BC发送给RT命令为0x0410, 而RT可以接收到0x0410; RT回复状态和数据表示1子地址有数据; BC发送给RT命令0x0412表示从RT的1子地址上取两个数据; RT回复状态和数据表示1子地址上的1, 2数据传给了BC. 以上的每个步骤在记录表中的数据与1553B监控设备监控的数据一致, 由此可见, 利用Netlink套接字编程可以实现BC与RT之间正常通信.
4 结束语
针对1553B协议提供了基于Netlink机制的通用socket接口, 给熟悉网络编程的程序员带来了极大的方便, 并且移植性好. 本文采用的是基于linux的1553B协议的实现, 但是就协议本身而言, 与操作系统相关性不是很大. 下一步工作是对1553B协议的优化以及多平台, 多系统上的移植, 进一步完成在工程上的实现.
[1] |
陈莉君. Linux操作系统内核分析. 北京: 人民邮电出版社, 2000.
|
[2] |
王鹏. 基于1553B总线监视与仿真软件的设计和研究[硕士学位论文]. 北京: 中国地质大学(北京), 2017.
|
[3] |
朱正国. 基于1553B通信协议的总线模块设计与实现[硕士学位论文]. 西安: 西安电子科技大学, 2013.
|
[4] |
蒋伟. 基于PCI总线的高速1553B总线通信卡的设计与实现. 中国新通信, 2017, 19(14): 32. |
[5] |
徐丽清. 1553B总线接口技术研究及FPGA实现[硕士学位论文]. 西安: 西北工业大学, 2006.
|
[6] |
张浩. 嵌入式1553B总线通信卡的设计与研究[硕士学位论文]. 南京: 南京理工大学, 2008.
|
[7] |
樊江锋, 黄意, 姚莉娟. 1553B总线接口模块测试设备的设计与实现. 现代电子技术, 2015, 38(15): 22-24, 28. |
[8] |
张晓敏. 基于ICD的1553B通信技术研究[硕士学位论文]. 北京: 北京理工大学, 2016.
|
[9] |
周莉, 柯健, 顾小晶. Netlink套接字在Linux系统通信中的应用研究. 计算机与现代化, 2007(3): 109-111. |
[10] |
熊伟, 丁涵, 罗云锋. 支持多线程并发与消息异步处理的Linux Netlink通信机制研究. 软件导刊, 2017, 16(10): 99-103. |
[11] |
黄超, 赵建平, 韦勇钢. Netlink消息通信机制的IPSec VPN实现研究. 网络空间安全, 2017, 8(Z5): 41-44. |
[12] |
汪敏. 基于Netlink的linux服务器集群统一外设事件监听机制. 电子设计工程, 2016, 24(23): 76-78. |