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

简介: 本文介绍了在Xenomai实时内核下,实时任务与非实时任务如何进行通讯

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

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顺序缓冲到单个存储区中,直到被使用者读取为止。

rtipc-arch

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

XDDP xenomai内核详细实现解析:
【原创】xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程
【原创】xenomai与普通linux进程之间通讯XDDP(二)--实时与非实时关联(bind流程)
【原创】xenomai与普通linux进程之间通讯XDDP(三)--实时与非实时数据交互

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

参数:

domainAF_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()来发送数据。

xddp-ipc

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);
目录
相关文章
|
7天前
|
存储 物联网 调度
操作系统的心脏:内核深度解析
在数字世界的构建中,操作系统扮演着基石的角色,而其核心—内核,则是这一复杂系统的灵魂。本文将深入探讨操作系统内核的工作原理,揭示它是如何管理硬件资源、运行程序以及提供系统服务的。通过理解内核的结构和功能,我们可以更好地把握计算机系统的运作机制,进而优化和创新我们的技术实践。
|
3月前
|
存储 人工智能 安全
操作系统的心脏:内核深度解析
【8月更文挑战第13天】 在数字世界的每一次跳动中,都能感受到操作系统内核的强大生命力。本文将带你走进操作系统的核心——内核,揭示它如何协调和管理计算机硬件资源,保证软件运行的高效和稳定。从内核的定义和功能,到它的结构和设计哲学,再到现代操作系统中的创新与挑战,我们将一起探索这个让计算机系统“活着”的秘密所在。
58 3
|
5天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
27 4
|
6天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
1月前
|
安全 中间件 人机交互
探索操作系统:从内核到用户界面的全面解析
本文旨在深入探讨操作系统的本质、核心组件及其功能。通过分析操作系统的各个层次,包括内核、驱动程序、中间件及用户界面,揭示其背后的技术原理和设计思想。此外,本文还将讨论操作系统在现代计算中的重要性及其未来发展趋势。
|
2月前
|
存储 算法 安全
操作系统的心脏:内核深入解析
本文将带您走进计算机的大脑—操作系统内核,探索它如何管理硬件资源、提供系统服务,并确保多任务高效运行。文章以浅显易懂的语言,逐步揭示内核的神秘面纱,从基础概念到实际应用,让您对操作系统的核心组件有更深的理解。
94 5
|
2月前
|
存储 资源调度 监控
操作系统的心脏:内核深度解析
在数字世界的庞大机器中,操作系统扮演着至关重要的角色。而作为操作系统核心的内核,其重要性不言而喻。本文将深入浅出地探讨操作系统内核的基本概念、主要功能和工作原理,以及它如何影响计算机的整体性能和稳定性。我们将从内核的设计哲学出发,逐步深入到内核的各个组成部分,包括进程管理、内存管理、文件系统和设备驱动等关键模块。最后,文章还将讨论当前操作系统内核面临的挑战和未来的发展趋势。通过这篇文章,读者将获得对操作系统内核更深层次的理解,从而更好地把握计算机系统的运行机制。
65 1
|
2月前
|
存储 资源调度 算法
操作系统的心脏:内核深入解析
本文将带你走进操作系统的核心—内核,通过浅显易懂的语言解释什么是内核、它如何工作以及为什么它对整个系统至关重要。我们将从内核的定义和功能出发,逐步深入到它的结构和设计哲学,最后探讨内核在现代计算环境中面临的挑战和未来发展方向。无论你是计算机新手还是有一定基础的学习者,这篇文章都会为你揭开操作系统内核的神秘面纱。
63 3
|
2月前
|
人工智能 并行计算 安全
探索操作系统的心脏:内核深度解析
在数字世界的每一次跳动中,都能感受到一个强大而隐形的力量在默默支撑着一切——这就是操作系统的内核。本文将带你走进这个神秘而又强大的核心世界,从内核的设计哲学到它的架构布局,再到它如何与硬件、软件协同工作,以及面对现代挑战时的应对策略。我们将一起探索那些让操作系统能够高效、安全运行的秘密,解锁内核的奥秘,理解它对整个计算生态的重要性。准备好跟随我们的脚步,深入操作系统的核心,一窥究竟吧!
43 0
|
3月前
|
存储 资源调度 安全
操作系统的心脏:内核深度解析
本文将带你深入操作系统的核心—内核,探索其如何作为系统的中枢神经,协调硬件与软件之间的复杂交互。我们将从内核的基本概念出发,逐步揭示其设计哲学、关键组件以及在现代计算环境中的作用。通过这篇文章,你将获得对操作系统内核工作原理的深刻理解,并认识到它在维护系统稳定性和性能中的关键角色。

推荐镜像

更多