【原創】xenomai內核解析--實時IPC概述

版權聲明:本文爲本文爲博主原創文章,轉載請註明出處。如有問題,歡迎指正。博客地址: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順序緩衝到單個存儲區中,直到被使用者讀取爲止。

rtipc-arch

當然,並不是說有了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);

參數:

domainAF_RTIPC地址族;

type:套接字類型,SOCK_DGRAM(其餘無效)

protocol

  • IPCPROTO_XDDPIPCPROTO_IDDP IPCPROTO_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
    • level SOL_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時才進行分配實際內存。 該池將爲未決數據提供存儲。綁定套接字後,不允許配置本地池大小。 但是,綁定之前允許進行多個配置調用。 將使用最後設置的值。

    • level SOL_XDDP
    • optnameXDDP_POLLSZ
    • optval:指向類型爲size_t的變量的指針,該變量表示綁定時保留的本地池大小,單位:字節。
    • optlensizeof(size_tl)
  • XDDP_BUFSZ :XDDP流緩衝區大小配置。除了發送數據報外,實時線程還可以通過端口以面向字節的模式傳輸數據。爲套接字設置非零緩衝區大小時,啓用此功能。這樣,當任何發送函數使用MSG_MORE標誌時,實時數據會累積到流緩衝區中,發生以下情況時緩衝區數據會被髮送出去:

  • Linux域中接收器被喚醒接收數據,

    • 不同的源端口嘗試將數據發送到相同的目標端口,
  • 發送標誌中沒有MSG_MORE,

  • 緩衝區已滿。(以先到者爲準)。

* optval設置爲0將禁用流緩衝區,在這種情況下,所有發送都將在單獨的數據報中傳輸,而與MSG_MORE無關。

注意:每個套接字只有一個流緩衝區。當該緩衝區滿時,實時數據將停止積累,並且僅在數據報模式恢復發送操作。從Linux域端點消耗了流緩衝區中的部分或全部數據之後,可能會再次發生累積。在套接字生存期中,可以多次調整流緩衝區的大小;在刷新前一個緩衝區後恢復累積時,最新的配置更改將生效。

    • level SOL_XDDP
    • optnameXDDP_BUFSZ
    • optval:指向類型爲size_t的變量的指針,該變量表示綁定時保留的本地池大小,單位:字節。
    • optlensizeof(size_t)
  • XDDP_MONITOR:XDDP監視回調。對套接字安插用戶定義的回調函數,以便收集通道上發生的特定事件。此機制對於在執行其他任務時異步監視通道特別有用。僅適用於內核空間任務

    • level SOL_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()前的最後一次設置生效。
    • level SOL_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時才進行分配實際內存。傳輸數據佔用的內存將從該池內分配。綁定套接字後,不允許配置本地池大小。 但是,綁定之前允許進行多個配置調用。 將使用最後設置的值
  • level SOL_IDDP
    • optnameIDDP_POLLSZ
    • optval:指向類型爲size_t的變量的指針,該變量表示綁定時保留的本地池大小,單位:字節。`
    • optlensizeof(size_tl)

針對BUFP套接字選項說明及參數配置如下:

  • BUFP_BUFSZ:配置BUFP緩衝區大小,寫入BUFP的數據都被緩衝在每個套接字的存儲區域中,必須配置該大小。綁定套接字後,不允許配置本地池大小。 但是,綁定之前允許進行多個配置調用。 將使用最後設置的值

    • level SOL_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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章