增强管道数据流转技术(Enhance Pipeline Data Routing,EPDR)是 taskBus 跨平台多进程合作框架创立的开源数据分发技术,在软件无线电方向已经具有了较为完整的应用场景。本文介绍技术的起源,简述从最初的前向数据复制(Forward Data Replication,FDR)发展到EPDR的过程。
FDR技术通过定义一些简单的标记格式,允许通过 A.exe|B.exe|C.exe级联三个可执行文件时,C.exe也能直接获得A.exe的输出(靠B来复制)。
“标准输入输出设备”是在DOS时代就非常常用的系统文件设备。执行一个DOS/Bash命令行时,命令进程内通常会自动打开三个标准文件句柄(指针),即标准输入文件(stdin),通常对应终端的键盘;标准输出文件(stdout)和标准错误输出文件(stderr),这两个文件都对应终端的屏幕。进程将从标准输入文件中得到输入数据,将正常输出数据输出到标准输出文件,而将错误信息送到标准错误文件中。这三个文件是不需要通过“file.open”这样的函数手工打开的,进程一旦启动,其自然存在。因此,可以像读写一般文件一样,读写这三个文件:
#include <stdio.h>
int main(int argc, char *argv[])
{
unsigned int a[4];
//scanf("%u,%u,%u,%u",a,a+1,a+2,a+3);
//等效为
fscanf(stdin,"%u,%u,%u,%u",a,a+1,a+2,a+3);
for(int i = 0; i < 4; ++i)
a[i] = 0xFFFFFFFF ^ a[i];
//printf("%u,%u,%u,%u\n",a[0],a[1],a[2],a[3]);
//等效为
fprintf(stdout,"%u,%u,%u,%u\n",a[0],a[1],a[2],a[3]);
fprintf(stderr,"%u,%u,%u,%u\n",a[0],a[1],a[2],a[3]);
return 0;
}
这种连接着输入输出设备的文件句柄,是一种特殊的文件,管道(pipeline)。管道也是一类流式设备。经常接触的另一种流式的设备是套接字的Stream模式,对应的是TCP协议。管道则是单机版本的底层的流式数据,在层级上和TCP完全不同。在默认状态,如bash交互时,stdin连着键盘缓存,stdout\stderr连着显示缓存。
操作系统允许重定向这些管道。通过符号“<”、”>”、”|”可以进行重定向。其中,
如:
$ ls -na | grep "Sep" | less
则是把列出文件(ls)的结果送给 grep程序,进行字符串“ Sep ” 的搜索匹配,以只列出九月份的文件。而后,如果九月份的文件很多,则进行分屏显示(less)。这里要注意的是一般只能把 stdout 送给 stdin,stderr一直都连着默认的设备。在命令行下,“|” 是可以传输二进制数据的。很多开源的工具借助这种方式完成复杂的任务。如RTL-SDR的广播收听:
$ rtl_fm -f 91.8e6 -s 200000 -r 48000 - | ffplay -f s16le -ar 48000 -showmode 1 -i -
但是要注意的是,在windows下自己写程序时,由于要避免\r\n替换、EOF转义等问题,要指定二进制模式, 见如下代码:
#include <io.h>
#include <fcntl.h>
int main(int argc,char * argv)
{
//用于在windows下吞吐二进制数据。
setmode(fileno(stdout), O_BINARY);
setmode(fileno(stdin), O_BINARY);
unsigned int a[4];
fread(a,sizeof(int),4,stdin);
for(int i = 0; i < 4; ++i)
a[i] = 0xFFFFFFFF ^ a[i];
fwrite(a,sizeof(int),4,stdout);
return 0;
}
在早期进行控制台SDR程序设计时,遇到一种情况需要进行不同层级的数据访问。比如,SDR获取的数据,一方面要进行fm解调,并收听广播;另一方面,又想要进行频谱显示,如下:
由于”|”链接的管道是顺序的、单向的,因此上图中的两路处理必须合成1路,其中前序的某些EXE要为后续处理流程转发数据。
采用的思路是统一各个exe的输入输出格式,给原本无边界的流,切割为包。而后,标记包的ID,并复制到后一级。为了有效的完成这类请求,流式数据被切割为包,并加入头部:
包序号 | 数据类型ID | 长度 | 内容 |
---|---|---|---|
1 | 2字节 | 2字节 | N字节 |
2 | 2字节 | 2字节 | N字节 |
3 | 2字节 | 2字节 | N字节 |
…… |
FDR有几个问题:
管道虽然是内存设备,但是流量也是有上限的. 基于教研室的 i7 6700K @ 4GHz, 内存 64GB DDR4 @ 2666MHz. Linux发行版为 Manjaro,内核5.10, 进行的最大流量测试结果如下表:
操作系统 | 编译器 | 级联层数 | 整体速率 | sc16单路带宽 |
---|---|---|---|---|
windows 10 x64 | Mingw64 | 1 | 7016.842 MB/s | 1754.21 MHz |
windows 10 x64 | Mingw64 | 2 | 3621.510 MB/s | 905.37 MHz |
windows 10 x64 | Mingw64 | 3 | 2419.891 MB/s | 604.97 MHz |
windows 10 x64 | Mingw64 | 4 | 1813.055 MB/s | 453.26 MHz |
windows 10 x64 | vc2022 | 1 | 6843.942 MB/s | 1710.98 MHz |
windows 10 x64 | vc2022 | 2 | 3567.249 MB/s | 891.81 MHz |
windows 10 x64 | vc2022 | 3 | 2429.300 MB/s | 607.325 MHz |
windows 10 x64 | vc2022 | 4 | 1899.506 MB/s | 474.87 MHz |
Linux x64 | gcc | 1 | 4046.54 MB/s | 1011.63 MHz |
Linux x64 | gcc | 2 | 2122.48 MB/s | 530.62 MHz |
Linux x64 | gcc | 3 | 2126.54 MB/s | 531.64 MHz |
Linux x64 | gcc | 4 | 1985.11 MB/s | 496.28 MHz |
因此,如果一味的进行前向复制,很容易塞满系统的IO带宽。
FDR有不足,它的主要限制是BASH和DOS命令行的语法决定的。这种语法是前向(无环)、单列的。于是,自然而然会想到要自己做一个增强的Shell出来,要更加灵活地流转数据。
参考分布式计算和消息队列的概念,把数据分为专题(Subject、Topic之类)、并由一个专门的程序管理他们之间的消费关系。这个程序应是实现EPDR 的关键,它构造了一个总线,或者是交换Hub,用来流转各个进程的包数据。
无论是在windows还是在Linux下,一个父进程启动了某个子进程,便可以调用API获取它的stdin, stdout, stderr三个指针。因此,若管理者进程启动了一堆子进程,则可以用一个矩阵来交换他们的数据。
上表对应的流转图如下:
当我们着手开始把EPDR的想法实现为平台时,遇到了命名的问题。如何命名这个数据交换总线呢?就叫EPDR Platform如何?
团队里的成员都觉得不好。EPDR比较拗口,还是要找一个简明的词。由于我们几个好基友在入门计算机时,接触的第一个DOS命令行操作的编程是在Turbo-BASIC 1.0下完成的, 而第一个管道操作是在Turbo C2.0下完成的,于是自然就想用一个缩写为 TB的词,如Turbo Bus。但是,擅用专属于Borland大神的Turbo这个名讳,显然是对Borland的历史有所冲撞。考虑到计算机里task是与“进程”比较贴切的词语,很自然的想到任务管理器等计算机词汇,便用了 taskBus,任务总线,来命名未来的产品。如此这般,取首字母还是TB,真是亲切。
taskBus的LOGO是一个着了火的兔子。这个Logo是由团队成员的孩子引出的。当吃着一盒巴士饼干,只认得Bus而不识Task的孩子,把白板上taskBus的字母读成了兔巴士,taskBus的中文翻译就定了。同时,也取兔子敏捷、巴士装得多的意义。
而后,孩子们手绘了兔子巴士的样子,并认为应该用纸糊一个。此外,为了突出兔子巴士开的快,就让它的钛合金表面被大气层摩擦出火花来,就有了下图。
基于管道的吞吐,具有很多优势:
但与集约化的平台(如 GNURadio, Pothos FLow)相比,在某些方面有显著的不足。