29

xenomai内核解析--实时IPC概述

 3 years ago
source link: http://www.cnblogs.com/wsg1100/p/13758250.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

版权声明:本文为本文为博主原创文章,转载请注明出处。如有问题,欢迎指正。博客地址: https://www.cnblogs.com/wsg1100/

目录

1.概述

Linux系统中常见的进程间通讯方式有管道、FIFO、共享内存、信号、套接字等方式。但在xenomai内核加入后,一个实时任务与非实时(普通Linux任务,如人机交互应用)之间该如何通讯?

虽然xenomai任务本身也是一个linux任务,能够无障碍地使用linux提供的进程间通讯方式,但是当实时任务调用这些服务接口的时候会触发任务迁移,迁移到linux核,由linux接管调度并提供服务,Linux内核本身就只是软实时内核,这样必然会严重影响了xenomai实时任务实时性。

实时任务除了可以使用Linux的进程间通讯外(当然不建议使用),xenomai也提供了针对实时任务的进程间通讯方式( Real-time IPC ),其中包含一种跨域通讯方式---XDDP(cross-domain datagram protocol跨域数据报协议)。

2.Real-time IPC

RTIPC 以RTDM(实时设备驱动模型)下的 Protocol Devices 来实现,根据进程间通讯情况不同,rtipc提供三种进程间通讯:

  • XDDP ,跨域数据报协议,实时与普通Linux任务之间的通讯 (RT<->non-RT) ,实时Xenomai线程和常规Linux线程通讯时使用,实时任务端不会离开head域,这样就不会影响到实时任务的实时性。
  • IDDP ,实时域内数据报协议,实时任务之间的通讯 (RT<->RT) ,IDDP协议使实时线程可以通过套接字端点在Xenomai域内交换数据报。
  • BUFP ,缓冲区协议,实时任务间批量数据通讯 (RT<->RT) ,所有写入的消息均按照严格的FIFO顺序缓冲到单个存储区中,直到被使用者读取为止。

JfIvEvN.png!mobile

当然,并不是说有了RTIPC,xenomai内核就没有其它通讯方式了,其实大部分posix标准通讯方式xenoma内核均有实现,仅用于实时任务间,如:信号量(sem)、消息队列(mq)、xddp/bufp/iddp、事件(event)、条件变量(cond)....,至于它们的内核实现,与RTIPC不同,可以关注本博客后续文章。

2.内核配置

由于RTIPC以实时内核驱动模块的形式来实现,所以要使用RTIPC,就得在内核构建编译的时候配置,如下:

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

3.应用编程接口

实时应用通过套接字来使用RTIPC,虽然接口与普通套接字接口一样,但是参数需要根据xenomai提供的参数来使用,下面为 官方文档 简单直译。

socket()

​ 创建套接字。

#include <rtdm/ipc.h>
int socket(int domain, int type, int protocol);

参数:

domain: AF_RTIPC 地址族;

type:套接字类型, SOCK_DGRAM (其余无效)

protocol:

  • IPCPROTO_XDDPIPCPROTO_IDDPIPCPROTO_BUFPIPCPROTO_IPC 默认协议(IPCPROTO_IDDP)

返回值:

​ 返回一个套接字,出错:除了用于socket(2)的标准错误代码外,还可能返回以下特定错误代码:

  • ENOPROTOOPT(协议是已知的,但未在RTIPC驱动程序中进行编译)。

close()

​ 关闭一个套接字。

int 	close  (int sockfd)

当套接字关闭并返回错误时,将解除阻塞在sendmsg或recvmsg的阻塞。

setsockopt()

设置套接字选项。

#include <rtdm/ipc.h>
int setsockopt(int 	sockfd,
    int 	level,
    int 	optname,
    const void * 	optval,
    socklen_t 	optlen 
)

针对 XDDP 套接字选项说明及参数配置如下:

  • XDDP_LABEL :设置XDDP端口标签。设定XDDP端口的ASCII字符串名称,设定后在非实时端,可通过设备名称( /proc/xenomai/registry/rtipc/xddp/%s )来打开通讯端点,而不是用设备路径名( /dev/rtpN
    • levelSOL_XDDP
    • optnameXDDP_LABEL
    • optvalrtipc_port_label 指针
    • optlensizeof(struct rtipc_port_label)
struct rtipc_port_label {
	/** 端口标签字符串,以null结尾。 */
	char label[XNOBJECT_NAME_LEN];
};
  • XDDP_POLLSZ:XDDP本地内存池大小配置。默认情况下,传输数据所需的内存是从xenomai的系统内存池中提取的,设定本地池大小会覆盖默认大小。如果配置了非零大小,则在bind时才进行分配实际内存。 该池将为未决数据提供存储。绑定套接字后,不允许配置本地池大小。 但是,绑定之前允许进行多个配置调用。 将使用最后设置的值。

    • levelSOL_XDDP
    • optnameXDDP_POLLSZ
    • optval :指向类型为size_t的变量的指针,该变量表示绑定时保留的本地池大小,单位:字节。
    • optlensizeof(size_tl)
  • XDDP_BUFSZ:XDDP流缓冲区大小配置。除了发送数据报外,实时线程还可以通过端口以面向字节的模式传输数据。为套接字设置非零缓冲区大小时,启用此功能。这样,当任何发送函数使用MSG_MORE标志时,实时数据会累积到流缓冲区中,发生以下情况时缓冲区数据会被发送出去:

  • Linux域中接收器被唤醒接收数据,

    • 不同的源端口尝试将数据发送到相同的目标端口,
  • 发送标志中没有MSG_MORE,

  • 缓冲区已满。(以先到者为准)。

* optval 设置为0将禁用流缓冲区,在这种情况下,所有发送都将在单独的数据报中传输,而与 MSG_MORE 无关。

注意:每个套接字只有一个流缓冲区。当该缓冲区满时,实时数据将停止积累,并且仅在数据报模式恢复发送操作。从Linux域端点消耗了流缓冲区中的部分或全部数据之后,可能会再次发生累积。在套接字生存期中,可以多次调整流缓冲区的大小;在刷新前一个缓冲区后恢复累积时,最新的配置更改将生效。

    • levelSOL_XDDP
    • optnameXDDP_BUFSZ
    • optval :指向类型为size_t的变量的指针,该变量表示绑定时保留的本地池大小,单位:字节。
    • optlensizeof(size_t)
  • XDDP_MONITOR:XDDP监视回调。对套接字安插用户定义的回调函数,以便收集通道上发生的特定事件。此机制对于在执行其他任务时异步监视通道特别有用。 仅适用于内核空间任务

    • levelSOL_XDDP

    • optnameXDDP_MONITOR

    • optval :指向类型为int (*)(int fd, int event, long arg)的函数的指针,其中包含用户定义的回调函数的地址。在optval中传递NULL回调指针将禁用该功能。

    • optlensizeof(size_t)

针对 IDDP 套接字选项说明及参数配置如下:

  • IDDP_LABEL :设置IDDP端口标签。设定IDDP端口的ASCII字符串名称,以便使用比数字端口更具描述性的方式来与套接字连接。设置label后,标签将在bind()时注册,在bind()前可多次设置,bind()前的最后一次设置生效。
    • levelSOL_IDDP
    • optnameIDDP_LABEL
    • optvalrtipc_port_label 指针
    • optlensizeof(struct rtipc_port_label)
struct rtipc_port_label {
	/** 端口标签字符串,以null结尾。 */
	char label[XNOBJECT_NAME_LEN];
};
  • **IDDP_POOLSZ **:配置IDDP本地内存池大小。默认情况下,传输数据所需的内存是从xenomai的系统内存池中提取的,设定本地池大小会覆盖默认大小。如果配置了非零大小,则在bind时才进行分配实际内存。传输数据占用的内存将从该池内分配。绑定套接字后,不允许配置本地池大小。 但是,绑定之前允许进行多个配置调用。 将使用最后设置的值。
  • levelSOL_IDDP
    • optnameIDDP_POLLSZ
    • optval :指向类型为size_t的变量的指针,该变量表示绑定时保留的本地池大小,单位:字节。`
    • optlensizeof(size_tl)

针对 BUFP 套接字选项说明及参数配置如下:

  • BUFP_BUFSZ:配置BUFP缓冲区大小,写入BUFP的数据都被缓冲在每个套接字的存储区域中,必须配置该大小。绑定套接字后,不允许配置本地池大小。 但是,绑定之前允许进行多个配置调用。 将使用最后设置的值。

    • levelSOL_BUFP
    • optnameBUFP_BUFSZ
    • optval :指向类型为size_t的变量的指针,该变量表示绑定时保留的本地池大小,单位:字节。`
    • optlensizeof(size_tl)
  • BUFP_LABEL:设置BUFP端口标签。以便以比使用普通数字端口值更具描述性的方式来连接套接字。

    绑定套接字后,不允许分配标签。 但是,在绑定之前允许多次分配调用。 最后一个标签集将被使用。

bind()

绑定一个RTIPC socket到一个端口。

int bind(int sockfd, const struct sockaddr_ipc *addr, socklen_t addrlen)

将套接字绑定到目标端口。

  • sockfd :套接字文件描述符。

  • addr :绑定套接字的地址(请参见struct sockaddr_ipc)。 该地址的含义取决于套接字所使用的RTIPC协议:

    • IPCPROTO_XDDP

    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 来与实时通讯。

    • IPCPROTO_IDDP

    sipc_family :必须是AF_RTIPC, sipc_port 为-1或者0到CONFIG_XENO_OPT_IDDP_NRPORT-1之间的有效空闲端口号。如果sipc_port为-1,bind将自动为其分配一个空闲端口。

    • IPCPROTO_BUFP

    sipc_family :必须是AF_RTIPC, sipc_port 为-1或者0到CONFIG_XENO_OPT_BUFP_NRPORT-1之间的有效空闲端口号。如果sipc_port为-1,bind将自动为其分配一个空闲端口。

  • addrlen :addr指向的结构体大小。

sendto()与recvfrom()

数据发送与接收。

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()与sendmsg()

数据发送与接收。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协议。只有 实时应用 能使用该标志。

4.实时与非实时间通讯XDDP示例

IPCPROTO_XDDP :跨域数据报协议(RT<->NRT),实时Xenomai线程和常规Linux线程通讯时使用,linux端通过 read()、write() 读写 /dev/rtp <minor> 来通讯,Xenomai端通过套接字 recvfrom()或read() 来接收数据, sendto()或write() 来发送数据。

z2Uj632.png!mobile

XDDP应用示例:

一个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);

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK