目录
构建一个xenomai 3 应用
编译一个基于rtdm的模块
运行应用程序
运行一个xenomai 3应用
实时的I / O的支持
关于RTnet
RTnet相关特性
RTnet 基本要素
RTnet 配置文件
RTnet 测试
RTnet编程代码
详细的构建与安装见https://gitlab.denx.de/Xenomai/xenomai/-/wikis/App_Setup_And_Init
总而言之,您应该使用xeno-config脚本来获取适当的编译和链接器标志相关的Xenomai,以便
构建您的应用程序的Cobalt or Mercury core.
xeno-config脚本的完整用法可在以下https://xenomai.org/documentation/xenomai-3/html/man1/xeno-config/index.html找到。
对于没有耐心的人,这里有一个简单的Makefile片段,用于检索编译器和用于构建单文件应用程序vxapp.c的标志,通过VxWorks仿真API:
XENO_CONFIG := /usr/xenomai/bin/xeno-config
CFLAGS := $(shell $(XENO_CONFIG) --vxworks --cflags)
LDFLAGS := $(shell $(XENO_CONFIG) --vxworks --ldflags)
CC := $(shell $(XENO_CONFIG) --cc) EXECUTABLE := vxapp
all: $(EXECUTABLE)
%: %.c
$(CC) -o $@ $< $(CFLAGS) $(LDFLAGS)
构建常规内核模块/驱动程序的规则也适用于基于rtdm的驱动程序,不需要额外的要求。
例如,用于构建some_driver.ko的Makefile,由foo.c和bar.c两个文件组成,基于RTDM API:
obj-y += some_driver.o
some_driver-y := foo.o bar.o
从内核树构建这个模块应该从包含模块源的目录中完成,如下所示:
$ make -C /path/to/kernel/tree M=$PWD modules
从内核树构建这个模块应该在包含驱动程序模块的目录中完成,在为双重内核配置构建驱动程序模块之前,目标内核树必须已经准备好并按照这里的文档进行构建。
对于Cobalt 内核来说,您需要将实时内核构建到目标Linux内核中,安装描述步骤:https://gitlab.denx.de/Xenomai/xenomai/-/wikis/Installing_Xenomai_3
对于Mercury 内核来说,到目前为止,您不需要特定于xenomai的内核支持,除了您的主机Linux内核已经提供的。您的内核至少应该提供高分辨率计时器支持(CONFIG_HIGH_RES_TIMERS)
如果您的应用程序需要短且有限制的延迟,则可能需要完全抢占(PREEMPT_RT)。任何基于xenomai的应用程序都可以识别一组可以在命令行上传递的标准选项,如本文档所述。
此外,运行在Xenomai核心上的Alchemy、pSOS和VxWorks™api可以定义要使用的时钟分辨率,以纳米秒为单位,即HZ=(1000000000 / ns),由--{Alchemy / pSOS / VxWorks}-clock-resolution=
如果您的应用程序组合了多个api,您可以通过几个时钟分辨率开关来设置它们。默认值取决于所考虑的API。例如,VxWorks™和pSOS™模拟器默认为毫秒时钟速率。Alchemy API默认是无标记的,即--Alchemy -clock-resolution=1。指定大于1纳秒的分辨率需要Xenomai库提供低分辨率时钟支持(请参阅--enable-lores-clock configuration开关)。
Real-time networking (RTnet)
RTnet的设计目的是为RTAl和Xenomai环境提供实时网络功能。本文件旨在为使用Xenomai进入RTnet的初学者提供一个跳板。
RTnet项目的代码以模块化的方式组织。通常的biq图像是TDMA实时现场总线,但是您也可以使用RTnet执行更简单的任务,比如实时点对点通信。对于更简单的任务,可能不需要全部RTnet。
安装好RTnet后有两个文件夹需要注意:
默认地址:_/usr/src/rtnet-x.y.z .
特别需要注意:
*/Documentation :包含RTnet功能的结构和使用的基本信息
*/example:包含已编译并放在该文件的的实例的源代码
默认地址:/usr/local/rtnet.
安装文件夹包含运行RTnet所需的所有已编译的可执行文件和内核模块
etc:RTnet的两个安装配置文件位于此文件夹中。rtnet.conf和tdma.conf设置。
examples: 编译后的RTnet示例放在这里
sbin:在此文件夹中,有各种用于运行和配置RTnet的脚本。
include:该文件夹包含将用于编译任何RTnet程序的包含文件。
modules: 为RTnet编译的模块和使它与实时设备驱动程序一起高效运行的所有组件都位于这里
使用以太网时,通信是不确定的,因为在网络上有一个集线器的几个主机之间可能会发生冲突,或者由于交换机的延迟未知以。
太网协议不允许确定性通信,因为可能会发生冲突,并由CSMA/CD机制(载波感知多址/冲突检测)支持。
RTnet是一个协议栈,它运行在以太网层和应用层(或IP层)之间。它的目的是通过使用时间间隔(时隙)来进行确定性通信,通过禁用碰撞检测CSMA/CD,防止网络中的缓冲包。RTnet是一款在Linux内核上运行的软件,具有RTAl或Xenomai实时扩展。它利用实时内核扩展来保证通信栈的确定性。在这个目标中,所有与此协议相关的指令都使用实时内核函数,而不是Linux函数,这些函数将延迟绑定到执行时间和中断延迟,从而提供确定性的通信
通信分时段TDMA(时分多址)是协议决定论的基础。使用分配给主机发送数据的时间间隔(时间槽)不能再使用冲突检测机制(CDMA / CD),因此限制了消息的发送和接收之间的时间间隔。基于主从操作来同步网络中每台主机的时钟并为每台主机分配时隙,协议主要依赖于定期发出同步消息的主机。Wiki: tdma.png ?600
Rtnet采用时分多路复用TDMA(时分多址),避免了通信冲突,在每个周期的开始,主机发送一个包含全局时钟信息的帧进行同步。同步帧在网络和包含一个周期的定期发送4个字节编码数量,大师的参考时间的价值在纳秒和传输参考时间的值,这个值有助于减少全球时钟的偏差。同步消息包含以下数据:为了弥补集中式主从系统的不足,可以通过为备份主系统预订额外的插槽来建立备用主系统(备份主系统)。如果主主系统崩溃,则在考虑到前一帧同步的变化之后,将激活辅助主系统。当主主服务器恢复服务时,它与活动的辅助主服务器同步,然后禁用辅助主服务器。
Rtnet协议栈包含OSI模型传输层的物理部分。它是专门针对实时内核RTAl和Xenomai开发的。为了保证通信应用的确定性,所有功能使用的以太网控制器应用软件都必须满足硬实时约束。因此,为了使用Linux的实时内核而不是通常使用的内核,必须实现网络laver协议,特别是传输协议。Wiki: pile.png
物理层由以太网控制器支持,需要使用从Linux派生的特定驱动程序。处理内存、中断处理机制或屏蔽中断需要实时内核执行操作,以确保确定性。还要考虑在以太网驱动程序中最准确地检测发射和接收的可能瞬间。从材料的角度来看,可以使用任何以太网控制器,并支持其他物理层(如火线)。管理控制器的初始化、设置和关机是不实时的,因此要特别注意收发功能。在发送时,保存全局时钟的值,在接收时,需要保存接收时的全局时钟的值,以便精确控制。
以太网作为一种标准操作,不能提供硬实时通信,无论是在基于中继器的网络上,还是在存在冲突风险的交换机上。IEEE 802.1q提供的服务质量为这个问题提供了一个很好的解决方案,但是交换机的集中是昂贵的布线。Rtnet的数据链路协议是由内核模块Rtmac提供的,它包含一个模块TDMA。TDMA模块管理网络上的通信(同步、周期时间等)。虽然这一层是stack Rtnet的可选扩展,但它带来了基于这些服务的确定性媒体访问协议:
*将特定封包传送至适当的服务
*使用高层协议交换控制消息和数据。
*在使用多个网络接口的情况下,可以定义特定于媒体访问控制的接口。
*通过服务隧道与非实时网络交换数据。
Rtmac层是一个提供以下服务的模块:
*截获包信息,将它们重定向到适当的服务。
*上层并行数据应用程序中控制数据或特定信息的交换。
*通过建立隧道与非实时系统进行通信
*使用一个虚拟接口并添加一个标头来区分这个流。
Rtnet在数据链路层采用时分接入网,防止发生数据冲突。该协议利用主从通信对网段节点的时钟进行同步,并对周期性传输的同步消息定义了数据的传输时间。如果一个节点知道自己的时间访问权限,那么它甚至可以在网络启动时加入网络,并在该节点上手动配置或在网络节点(rtcfa)上设置服务器配置。一旦获得该参数,节点将通过向主同步发送查询来评估返回网络的时间,主同步将通过在发送的包中包含可以计算返回时间的计时信息来响应。
通过简化路由过程和碎片优化,支持UDP/ IP和TCP/ IP网络和传输层。地址解析协议(ARP)只在初始化期间进行操作。在总线上的通信环境中,这些协议可能是不必要的,但是与外部网络的通信相关,通常是非实时的。IP协议实现在实时内核上运行,并支持发送大小超过MTU(最大传输单元)的数据包的分段。为了执行路由,需要使用两个表,第一个表是任务的路由表,它包含在LAN上可以访问的主机的IP地址。第二个表是可选的,它将网关地址实时地用于其他网络,允许建立结构更复杂的网络。路由表的内容类似于标准IP栈的ARP表。对于Rtnet,此表用于路由过程,而对于标准电池则不一定如此。UDP和TCP在IP层(版本4)之上实现,UDP用于硬实时传输。反过来,TCP最近已经实现,其目的是与不运行Rtnet的网络元素进行通信,TCP要复杂得多(在一百多个RFC中进行了描述)。因此,由于其用途有限,它的实现已大大简化。UDP则是默认协议,用于在两个Rtnet应用程序之间建立实时通信。Rtcfg是为Rtnet有效配置网络而定义的协议。为此,将网络节点用作服务器并将配置信息发送给其他节点(客户端)。由于虚拟接口的存在,可以运行所有网络层协议并在更高标准的Linux内核中实现。也有可能对于有限的通信网络节点不使用路由协议和传输(socket类型RAW)。
。当启动rtnet启动脚本时,将读取rtnet.conf文件,并在启动时设置rtnet环境。下面讨论这个文件中的条目:(**********重要**********)
RT_DRIVER:RT驱动程序:此条目指定用于运行网卡的实时驱动程序。不是所有的网卡都支持RTnet,要找到支持的驱动程序,请查看/drivers下驱动程序的源代码。编译后的驱动程序位于/modules…
RT_DRIVER_OPTIONS:RT驱动程序选项:系统上可能安装多个网卡。要识别RTnet应该使用哪张卡,此条目用1指定它。为了找出哪张卡是由设置配置中的哪一个位置表示的,请更改1的位置并在主模式下启动rtnet。当你把网线插入集线器时,活动卡的LED会不停地闪烁,就像它在寻找从机一样……
IPADDR:主机IP
NETMASK:子网掩码
RT_LOOPBACK:如果在RTnet的安装设置中选择了LOOPBACK选项,那么您可以使用此条目指定环回设备是否处于活动状态。此环回用于测试RTnet的正确工作方式,请参阅安装说明了解更多信息
使用细节:
RTCAP :RTCAP:为了能够使用带有RTnet的以太网络分析仪,使用此条目。这也只有在实时捕获支持在RTnet安装时被选中时才有效。更多信息参见 /Documentation/README.rtcap 或https://www.rts.uni-hannover.de/rtnet/download/RTnet-ETFA05.pdf
TDMA_MODE :指定当前系统是主服务器还是客户机
TDMA_SLAVES : 当这个系统被设置为主机时,那么网络上的从机必须列在这里。如果该系统被设置为从属系统,请保留空白
TDMA_CYCLE : TDMA周期:对于简单的TMDA设置,这里列出了以微秒为单位的TDMA周期时间,仅当系统设置为主系统时使用。
TDMA_OFFSET :。TDMA偏移量:对于简单的TMDA设置,这里列出了以微秒为单位的TDMA偏移时间,仅当系统设置为主系统时使用。
TDMA_CONFIG : TDMA配置:当没有使用简单的TDMA设置,而需要更复杂的设置时,该条目指定要使用的TDMA配置文件。它通常位于/etc/tdma.conf。更多设置细节/Documentation/README.rtmac
检测程序如下:
从正常的非实时ehternet网络中断开RTnet节点的网络电缆
重新启动系统到补丁的内核模式
使用该命令显示网络设置
ifconfig
这将显示你有运行的etho(这是你的网卡)和运行的lo(这是本地环回)
您需要禁用网卡的非实时网络操作如下:
ifconfig eth0 down
然后,卸载网卡的设备驱动程序。
sudo rmmod 8139too
加载RTnet操作所需的实时Linux扩展所需的模块,如果你在xenomai2.x 构建内核时支持了RTDM,或者你正在运行xenomai3.x,跳过此步骤
在位于/usr/ocal/rtnet/etc/rtnet.conf的RTnet配置文件中编辑以下参数:
sudo ./rtnet start
RTnet正在等待连接。cntl +C结束等待。通过调用Ismod并在加载的模块列表中查找网卡的实时驱动程序,可以看到RTnet正在运行。如果未列出,则RTnet没有运行
按如下方式Ping局部环回
sudo ./rtping 127.0.0.1
若要停止RTnet传输,请运行:
sudo ./rtnet stop
测试程序如下:
从普通的非实时以太网网络中断开RTnet节点。
使用开关(或集线器)将RTnet节点彼此连接。
重新启动系统到补丁的内核模式。
修改rtnet配置文件/usr/local/rtnet/etc/rtnet.conf。你应该检查以下内容:
sudo ./rtnet start
使用以下命令检查节点之间的通信,其中为远程节点的IP地址:
sudo ./rtping IP
停止命令 ctrl+C
卸载RTnet模块
sudo ./rtnet stop
如果RTnet不能工作,或者只能间歇性地工作,那么很可能是由于这些已知的问题之一造成的
①Xenomai在你的硬件上不能正常工作。
②将用于实时网络设备的Linux驱动程序构建到内核中,并阻塞硬件。
③IRQ冲突。Xenomai能够检测冲突并将它们报告到内核控制台。
④您使用的是rt e1000驱动程序和RTnet 0.9.9或更老版本。请参阅RTnet: rte1000查看解决方案。
解决问题的步骤:。
①检查内核控制台或系统日志是否有可疑消息。
②验证实时扩展的基本测试是否正常工作。Xenomai 还有一个基本的测试叫 latency
③收集有关您的设置(版本、配置、输出消息)的信息,并在rtnet用户上发布支持请求
如果您的系统上有两条(或更多)以太网线路可用,您可以确定其中一条使用RTnet(实时),另一条使用非实时网络:
①卸载非实时驱动程序
② 加载网卡驱动并指定网卡参数(e.g. insmod rt_e1000.ko cards=1,0).
③加载非实时模块并启动它(使用insmod和ifup的标准方式)。
网卡 参数接受一个由0和1组成的数组。例如insmod rt e1000.ko card=0,1,0 使用3的“中间”网卡。
$ sudo insmod /usr/local/rtnet/modules/rt_e1000.ko cards=1,0 $ sudo insmod e1000 $ sudo ifup eth1
介绍
一旦您理解了RTnet如何与RTOS相关的概念,在RTnet中进行编程就非常简单了。
RTnet本身实际上只是一个内核模块,它与实时以太网设备驱动程序通信RTnet和RTOS之间的接口是由Xenomai实现的实时驱动程序Modell提供的。该接口在Xenomai文档中有完整的文档记录。
RTDM规范位于Xenomai API树的模块/实时驱动程序模型/用户APl部分。
基础引导
为了在RTnet中编程,遵循了标准的Linux套接字编程方法。如果您不熟悉Linux套接字编程,下面的链接将提供关于这个主题的一些额外信息。
本机rt dev xxx与POSIX服务调用(使用Xenomai)的区别:
Xenomai应用程序独立于RTnet,可以调用Linux的正常网络服务。他们只是在那一点上失去了时间保证。
POSI皮肤的编程模型允许您像编写普通的Linux应用程序一样使用套接字函数。如果您的呼叫地址是RTnet提供的服务(UDP或AF包),并且加载了RTnet,它将在实时约束下为您处理。RTnet不知道的服务被传递到Linux,没有时间隔离。顺便说一句,同样的模式也适用于AF CAN。
(两点从RTnet邮件列表中复制)
RTnet中的示例项目提供了在这种环境中进行编程的基本介绍。例子中的一般步骤如下:
声明两个套接字描述符结构,一个用于本地套接字,另一个用于远程套接字。
static struct sockaddr_in local_addr;
static struct sockaddr_in server_addr;
在用值填充这些结构之前清除它们
memset(&local_addr, 0, sizeof(struct sockaddr_in));
memset(&server_addr, 0, sizeof(struct sockaddr_in));
将地址格式设置为Internet (IP)
local_addr.sin_family = AF_INET;
将本地地址设置为主机地址
local_addr.sin_addr.s_addr = INADDR_ANY;
将sting转换为端口号并将其存储在结构中
local_addr.sin_port = htons(atoi(argv[1]));
对于远程端口设置,再次将设备格式设置为IP
server_addr.sin_family = AF_INET;
将IP地址sting转换为网络格式
server_addr.sin_addr.s_addr = rt_inet_aton(argv[2]);
设置远程端口号
server_addr.sin_port = htons(atoi(argv[3]));
创建一个新的套接字来管理连接。
sockfd = rt_dev_socket(AF_INET, SOCK_DGRAM, 0);
将新的套接字绑定到本地端口
ret = rt_dev_bind(sockfd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in));
将套接字连接到远程端口
rt_dev_connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in));
然后发送信息。
rt_dev_send(sockfd, msg, sizeof(msg), 0);
在接收端接收消息
ret = rt_dev_recv(sockfd, msg, sizeof(msg), 0);
* Close the socket after transmission
--------------------
rt_dev_close(sockfd);
---------------------