QNX开发之ECAT专用网卡驱动ecpkt · 01-背景
QNX开发之ECAT专用网卡驱动ecpkt · 01-背景QNX上使用网络通信框架iopkt来统一管理网络通信应用程序通过通用socket接口向iopkt发起通信请求网卡驱动则由系统预先注册到iopkt框架中iopkt再通过这些注册进来的驱动控制网卡硬件进行报文收发从而完成网络通信。这是iopkt的框架图EtherCAT本质上还是网络通信所以按照传统方法在QNX上运行ECAT主站报文管理还是要交给iopkt。主站这里以SOEM为例在上图中扮演的就是Application的角色SOEM调用socket接口发起通信请求被转交给iopkt微内核体系下它同样是一个进程iopkt再通过Drivers控制网卡硬件完成报文收发最后数据仍经由socket接口回到SOEM。使用这种方法实现主站是最快捷的开发人员直接使用socket接口完全不用关心协议栈和硬件控制很快就能实现一个可用的主站。但这种方法有几个问题-iopkt原生不支持RAW SOCKET通信EtherCAT通信本质上是链路层通信通信报文的组包和解包完全由主站自己完成不需要TCP/IP协议栈介入所以主站在socket层面一般直接使用RAW SOCKET进行通信。但iopkt不支持RAW SOCKET要在iopkt框架下进行链路层的RAW SOCKET通信需要使用BPF接口。直接使用BPF原始接口进行网络报文收发开发门槛极高调试也非常困难好在QNX自带基于BPF的pcap支持可以使用pcap封装的API来代替。下面是我使用pcap进行链路层通信的代码作为示例供参考pcap 链路层socket的创建和配置*psock pcap_create(ifname, errbuf); if (*psock NULL) { fprintf(stderr, failed to create pcap handler: %s\n, errbuf); return -1; } if (pcap_set_buffer_size(*psock, 65536) ! 0) { fprintf(stderr, failed to set pcap buffer size: %s\n,pcap_geterr(*psock)); pcap_close(*psock); return -1; } if (pcap_set_timeout(*psock, ECAT_TICK_PERIOD) ! 0) { fprintf(stderr, failed to set pcap timeout: %s\n, pcap_geterr(*psock)); pcap_close(*psock); return -1; } // Optional // - PCAP_OPENFLAG_PROMISCUOUSpcap_set_promisc(handle, 1); 1on0off // - filter pcap_set_promisc(*psock, 1); pcap_set_immediate_mode(*psock, 1); if (pcap_activate(*psock) ! 0) { fprintf(stderr, failed to active pcap handler: %s\n, pcap_geterr(*psock)); pcap_close(*psock); return -1; } if (pcap_lookupnet(ifname, net, mask, errbuf) -1) { perror(lookup net);} if (pcap_compile(*psock, fp, filter_exp, 0, net) -1) { perror(compile);} if (pcap_setfilter(*psock, fp) -1) { perror(set filter);} if (NULL *psock) { printf(interface %s could not open with pcap\n, ifname); return 0; }pcap 链路层socket的收发rval pcap_sendpacket(*stack-sock, (*stack-txbuf)[idx], lp); res pcap_next_ex(*stack-sock, header, pkt_data);-iopkt处理报文的路径过长会给实时通信引入抖动EtherCAT报文的收发路径是应用程序 → socket → iopkt 进程 → 协议栈 → mbuf → devnp 驱动 → GEM 硬件对普通TCP/IP流量这条路径毫无问题但对EtherCAT这种周期性硬实时报文毫秒级周期、抖动敏感它引入了三类不可控延迟mbuf管理——每帧都要经过mbuf池的分配、链式组织、释放协议栈穿行——裸L2帧也要在栈的输入/输出队列里排队work thread调度——iopkt用多线程处理驱动事件报文何时被处理取决于线程调度而不是硬件何时到达。-EtherCAT和Ethernet报文混杂在一起由iopkt处理Ethernet报文吞吐量大时会严重影响EtherCAT实时性这才是最致命的问题。在iopkt框架下所有的socket通信都会统一交给iopkt管理。虽然iopkt内部会通过多线程并行来降低报文阻塞的风险但它的多线程模型无法进行精细管理。比如我们可以在应用层通过提高EtherCAT进程优先级、指定进程CPU亲和性等方式保证EtherCAT报文优先提交给iopkt但iopkt内部的报文处理线程无法绑定到应用程序上也就无法在iopkt内部保证接收到的EtherCAT报文被优先处理。这样一来Ethernet报文吞吐量大时EtherCAT的实时性必然会受到影响。其实QNX对这个问题有一个折中方案运行多个iopkt实例分别管理指定的网卡不同实例可以使用不同的进程优先级以此处理不同优先级的通信。但这种做法有个限制多个iopkt实例之间有点类似于用户组的概念同一个用户进程只能使用一个用户组下的资源。简单来说EtherCAT应用只能使用管理EtherCAT网卡的iopkt实例的网络资源Ethernet应用只能使用管理Ethernet网卡的实例的网络资源这就要求系统必须将EtherCAT功能和Ethernet功能拆分为独立进程两者通过IPC进行数据交互。这样一来一是IPC的效率低于进程内线程间的数据交互二是给用户系统额外增加了一层限制所以并不是一种最优的方案。总结一下上面这些问题的根源是把为TCP/IP设计的iopkt框架强行套在了EtherCAT身上而EtherCAT本质是链路层通信对通信框架的依赖非常低——它完全不需要TCP/IP协议栈的参与。所以我们考虑将EtherCAT通信从iopkt中分离出来通过专用的网卡驱动框架ecpkt直接驱动硬件彻底绕开iopkt框架。使用ecpkt后通信链路会变成下面这样应用程序 → write/read → ecpkt → GEM 硬件系统层面上的混合通信使用iopkt时是这样使用ecpkt之后会是这样ecpkt将作为一个Resource Manager独立运行在系统里。改造也有现成的起点iopkt本身已经包含了一个适配自己框架的可用网卡驱动具备完整的初始化、数据收发管理、中断管理等功能。我们将会基于这个驱动改造出一个满足我们需求的功能完备的专用驱动——这也是本系列后续章节的主线。