版权声明:本文为本文为博主原创文章,转载请注明出处。若有问题,欢迎指正。博客地址:https://www.cnblogs.com/wsg1100/
html
Linux系统中常见的进程间通信方式有管道、FIFO、共享内存、信号、套接字等方式。但在xenomai内核加入后,一个实时任务与非实时(普通Linux任务,如人机交互应用)之间该如何通信?linux
虽然xenomai任务自己也是一个linux任务,可以无障碍地使用linux提供的进程间通信方式,可是当实时任务调用这些服务接口的时候会触发任务迁移,迁移到linux核,由linux接管调度并提供服务,Linux内核自己就只是软实时内核,这样必然会严重影响了xenomai实时任务实时性。编程
实时任务除了可使用Linux的进程间通信外(固然不建议使用),xenomai也提供了针对实时任务的进程间通信方式(Real-time IPC),其中包含一种跨域通信方式---XDDP(cross-domain datagram protocol跨域数据报协议)。跨域
RTIPC
以RTDM(实时设备驱动模型)下的Protocol Devices来实现,根据进程间通信状况不一样,rtipc提供三种进程间通信:dom
固然,并非说有了RTIPC,xenomai内核就没有其它通信方式了,其实大部分posix标准通信方式xenoma内核均有实现,仅用于实时任务间,如:信号量(sem)、消息队列(mq)、xddp/bufp/iddp、事件(event)、条件变量(cond)....,至于它们的内核实现,与RTIPC不一样,能够关注本博客后续文章。异步
因为RTIPC以实时内核驱动模块的形式来实现,因此要使用RTIPC,就得在内核构建编译的时候配置,以下:socket
Xenomai/cobalt ---> Drivers ---> Real-time IPC drivers ---> <*> RTIPC protocol family [*] XDDP cross-domain datagram protocol [*] IDDP intra-domain datagram protocol (32) Number of IDDP communication ports [*] Buffer protocol (32) Number of BUFP communication ports
实时应用经过套接字来使用RTIPC,虽然接口与普通套接字接口同样,可是参数须要根据xenomai提供的参数来使用,下面为官方文档简单直译。函数
建立套接字。线程
#include <rtdm/ipc.h> int socket(int domain, int type, int protocol);
参数:指针
domain:AF_RTIPC
地址族;
type:套接字类型,SOCK_DGRAM
(其他无效)
protocol:
IPCPROTO_XDDP
、IPCPROTO_IDDP
、IPCPROTO_BUFP
、IPCPROTO_IPC
默认协议(IPCPROTO_IDDP)。返回值:
返回一个套接字,出错:除了用于socket(2)的标准错误代码外,还可能返回如下特定错误代码:
关闭一个套接字。
int close (int sockfd)
当套接字关闭并返回错误时,将解除阻塞在sendmsg或recvmsg的阻塞。
设置套接字选项。
#include <rtdm/ipc.h> int setsockopt(int sockfd, int level, int optname, const void * optval, socklen_t optlen )
针对XDDP套接字选项说明及参数配置以下:
/proc/xenomai/registry/rtipc/xddp/%s
)来打开通信端点,而不是用设备路径名(/dev/rtpN
)
level
: SOL_XDDP
optname
:XDDP_LABEL
optval
:rtipc_port_label
指针optlen
:sizeof(struct rtipc_port_label)
struct rtipc_port_label { /** 端口标签字符串,以null结尾。 */ char label[XNOBJECT_NAME_LEN]; };
XDDP_POLLSZ:XDDP本地内存池大小配置。默认状况下,传输数据所需的内存是从xenomai的系统内存池中提取的,设定本地池大小会覆盖默认大小。若是配置了非零大小,则在bind时才进行分配实际内存。 该池将为未决数据提供存储。绑定套接字后,不容许配置本地池大小。 可是,绑定以前容许进行多个配置调用。 将使用最后设置的值。
level
: SOL_XDDP
optname
:XDDP_POLLSZ
optval
:指向类型为size_t的变量的指针,该变量表示绑定时保留的本地池大小,单位:字节。optlen
:sizeof(size_tl)
XDDP_BUFSZ :XDDP流缓冲区大小配置。除了发送数据报外,实时线程还能够经过端口以面向字节的模式传输数据。为套接字设置非零缓冲区大小时,启用此功能。这样,当任何发送函数使用MSG_MORE标志时,实时数据会累积到流缓冲区中,发生如下状况时缓冲区数据会被发送出去:
Linux域中接收器被唤醒接收数据,
发送标志中没有MSG_MORE,
缓冲区已满。(以先到者为准)。
将* optval
设置为0将禁用流缓冲区,在这种状况下,全部发送都将在单独的数据报中传输,而与MSG_MORE
无关。
注意:每一个套接字只有一个流缓冲区。当该缓冲区满时,实时数据将中止积累,而且仅在数据报模式恢复发送操做。从Linux域端点消耗了流缓冲区中的部分或所有数据以后,可能会再次发生累积。在套接字生存期中,能够屡次调整流缓冲区的大小;在刷新前一个缓冲区后恢复累积时,最新的配置更改将生效。
level
: SOL_XDDP
optname
:XDDP_BUFSZ
optval
:指向类型为size_t的变量的指针,该变量表示绑定时保留的本地池大小,单位:字节。optlen
:sizeof(size_t)
XDDP_MONITOR:XDDP监视回调。对套接字安插用户定义的回调函数,以便收集通道上发生的特定事件。此机制对于在执行其余任务时异步监视通道特别有用。仅适用于内核空间任务。
level
: SOL_XDDP
optname
:XDDP_MONITOR
optval
:指向类型为int (*)(int fd, int event, long arg)的函数的指针,其中包含用户定义的回调函数的地址。在optval中传递NULL回调指针将禁用该功能。
optlen
:sizeof(size_t)
针对IDDP套接字选项说明及参数配置以下:
level
: SOL_IDDP
optname
:IDDP_LABEL
optval
:rtipc_port_label
指针optlen
:sizeof(struct rtipc_port_label)
struct rtipc_port_label { /** 端口标签字符串,以null结尾。 */ char label[XNOBJECT_NAME_LEN]; };
level
: SOL_IDDP
optname
:IDDP_POLLSZ
optval
:指向类型为size_t的变量的指针,该变量表示绑定时保留的本地池大小,单位:字节。`optlen
:sizeof(size_tl)
针对BUFP套接字选项说明及参数配置以下:
BUFP_BUFSZ:配置BUFP缓冲区大小,写入BUFP的数据都被缓冲在每一个套接字的存储区域中,必须配置该大小。绑定套接字后,不容许配置本地池大小。 可是,绑定以前容许进行多个配置调用。 将使用最后设置的值。
level
: SOL_BUFP
optname
:BUFP_BUFSZ
optval
:指向类型为size_t的变量的指针,该变量表示绑定时保留的本地池大小,单位:字节。`optlen
:sizeof(size_tl)
BUFP_LABEL:设置BUFP端口标签。以便以比使用普通数字端口值更具描述性的方式来链接套接字。
绑定套接字后,不容许分配标签。 可是,在绑定以前容许屡次分配调用。 最后一个标签集将被使用。
绑定一个RTIPC socket到一个端口。
int bind(int sockfd, const struct sockaddr_ipc *addr, socklen_t addrlen)
将套接字绑定到目标端口。
sockfd
:套接字文件描述符。
addr
:绑定套接字的地址(请参见struct sockaddr_ipc)。 该地址的含义取决于套接字所使用的RTIPC协议:
sipc_family
:必须是AF_RTIPC,sipc_port
为-1或者0到CONFIG_XENO_OPT_PIPE_NRDEV-1之间的有效空闲端口号。若是sipc_port为-1,bind将自动为其分配一个空闲端口。
成功后,将为该通讯通道保留伪设备/dev /rtpN
,其中N是分配的端口号。 非实时端应打开此设备以经过绑定的套接字交换数据。
若是使用了label,非实时经过伪设备/proc/xenomai/registry/rtipc/xddp/label
来与实时通信。
sipc_family
:必须是AF_RTIPC,sipc_port
为-1或者0到CONFIG_XENO_OPT_IDDP_NRPORT-1之间的有效空闲端口号。若是sipc_port为-1,bind将自动为其分配一个空闲端口。
sipc_family
:必须是AF_RTIPC,sipc_port
为-1或者0到CONFIG_XENO_OPT_BUFP_NRPORT-1之间的有效空闲端口号。若是sipc_port为-1,bind将自动为其分配一个空闲端口。
addrlen
:addr指向的结构体大小。
数据发送与接收。
ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数:
sockfd
:socket()建立的套接字.
buf
:发送/接收的数据;
len
:发送/接收的数据长度;
flags
:MSG_MORE发送标志位,将带有该标志的数据包累积到缓冲区,而不是当即发出数据报,仅用于XDDP协议。
数据发送与接收。recvmsg()能作全部read()、sendto()能作到的事,一样sendmsg()能作全部read()、sendto()能作到的事,具体使用方法查阅Linux相关资料。
recvmsg()从RTIPC套接字接收消息。
#include <rtdm/ipc.h> struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags (unused) */ }; ssize_t recvmsg (int sockfd, struct msghdr *msg, int flags)
参数:
sockfd
: socket()建立的套接字。
msg
:消息头将被复制到该地址,具体查阅资料。
flasgs
:MSG_DONTWAIT 非阻塞操做,若是没有消息可接收时,不会阻塞,当即返回EWOULDBLOCK,只有实时应用能使用该标志。
sendmsg()在RTIPC套接字上发送消息
#include <rtdm/ipc.h> ssize_t sendmsg (int sockfd, const struct msghdr *msg, int flags)
参数:
sockfd
: socket()建立的套接字。
msg
:传达数据报的消息头的地址,,具体查阅资料。
flasgs
:MSG_OOB给发送带外消息;(带外数据:容许发送端将传送的数据标记为高优先级)。
MSG_DONTWAIT 非阻塞操做,当没法当即发送消息时(如内存不足),不会阻塞,而是当即返回EWOULDBLOCK。
MSG_MORE发送前先累积数据到缓冲区,而不是当即发出数据报,仅用于IPCPROTO_XDDP协议。只有实时应用能使用该标志。
IPCPROTO_XDDP
:跨域数据报协议(RT<->NRT),实时Xenomai线程和常规Linux线程通信时使用,linux端经过read()、write()
读写/dev/rtp <minor>
来通信,Xenomai端经过套接字recvfrom()或read()
来接收数据,sendto()或write()
来发送数据。
一个LLinux任务与一个实时任务使用XDDP进行通信,实时任务向Linux任务发送消息,Linux任务收到后原样发送出去,实时任务将收到的消息显示出来(xenomai示例:xenomai3.0.8\demo\posix\cobalt\xddp-echo.c
)。
对于linux可经过打开固定rtipc端口的设备节点来与实时任务固定端口通信,这个端口是全局的,被使用了另外一个实时任务就没法再使用。另外一种方式是设置XDDP端口标签。实时程序设定XDDP端口的ASCII字符串名称,设定后在非实时端,可经过设备名称(/proc/xenomai/registry/rtipc/xddp/%s)来打开通信端点,而不是用设备路径名(/dev/rtpN),其中的端口xenomai会自动分配。(xenomai示例:xenomai3.0.8\demo\posix\cobalt\xddp-label.c
)
同一系统的两种方式尽可能不要混合使用,否则会发生以下状况,程序1使用XDDP端口标签配置了XDDP socket,此bind时系统为该socket分配的是端口1,接着另外一个程序2开始建立另外一个XDDP socket,因为指定了用端0来通信,但该端口已经被程序1占用,就会绑定端口失败,致使程序没法正常运行。下面例子使用固定端口通信:
使用带缓冲区方式与非实时应用通信,使用端口0,实时端:
#define XDDP_PORT 0 /*通信端口0*/ ..... /*1.建立一个XDDP(rt<->nrt)通信socket,AF_RTIPC、SOCK_DGRAM为固定参数*/ s = socket(AF_RTIPC, SOCK_DGRAM, IPCPROTO_XDDP); if (s < 0) { perror("socket"); exit(EXIT_FAILURE); } /*2.配置socket s为流缓冲通信,缓冲区大小为1KB,设置为零将禁用流缓冲,每次数据发送将单独传输*/ streamsz = 1024; /* bytes */ ret = setsockopt(s, SOL_XDDP, XDDP_BUFSZ, &streamsz, sizeof(streamsz)); if (ret) fail("setsockopt"); /*3.将套接字s绑定到端口0*/ memset(&saddr, 0, sizeof(saddr)); saddr.sipc_family = AF_RTIPC; //固定参数 saddr.sipc_port = XDDP_PORT; //端口0 对应非实时读写的设备节点/dev/rtp0 ret = bind(s, (struct sockaddr *)&saddr, sizeof(saddr)); for (;;) { /*4.发送*/ for (b = 0; b < len; b++) { /*MSG_MORE表示:一字节一字节的将数据存到缓冲区*/ ret = sendto(s, msg[n] + b, 1, MSG_MORE, NULL, 0); if (ret != 1) fail("sendto"); /*若是不使用MSG_MORE,每一个字母将做为一个数据包。Linux端段每次读取只能读取到一个字母,且符合FIFO*/ ret = sendto(s, msg[n] + b, 1, 0, NULL, 0); if (ret != 1) fail("sendto"); } /*4.接收数据*/ ret = recvfrom(s, buf, sizeof(buf), 0, NULL, 0); if (ret <= 0) fail("recvfrom"); } /* 5.关闭套接字*/ close(s);
非实时端:
#define _GNU_SOURCE /*使用asprintf()函数须要该宏*/ #include <stdio.h> #include <stdlib.h> #define XDDP_PORT 0 /*通信端口0*/ char buf[128],*devname; if (asprintf(&devname, "/dev/rtp%d", XDDP_PORT) < 0)/* /dev/rtp0 */ fail("asprintf"); /*1.打开设备 /dev/rtp0*/ fd = open(devname, O_RDWR); free(devname); for (;;) { /*2.读/dev/rtp0*/ ret = read(fd, buf, sizeof(buf)); if (ret <= 0) fail("read"); /*3.写/dev/rtp0来发送数据*/ ret = write(fd, buf, ret); if (ret <= 0) fail("write"); } close(fd);