一、QNX PPS
qnx pps 官方链接:http://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.pps.developer/topic/about.html
ps:刚刚进入项目,需要熟悉QNX的PPS,查询资料找到该文章,转载经过同意;
转载文章出自作者:水无声风无痕
QNX(Persistent Publish Subscribe)持久发布/订阅(PPS)服务是一个小型的、可扩展的发布和订阅服务,它提供了跨重启的持久性。它旨在为嵌入式系统中的发布/订阅和持久性提供简单易用的解决方案,满足使用异步发布和通知构建松散连接系统的需求。
使用PPS,发布是异步的:订阅者不必等待发布者。事实上,发行商和订阅者彼此并不了解;它们唯一的连接是一个对发布者和订阅者都具有意义和目的的对象。
使用PPS在组件之间共享信息的复杂应用程序
二、运行PPS
The PPS service can be run from the command line with the options listed below.
Syntax:
pps [options]
Options:
-A path
(QNX Neutrino 6.6 or later) Set the path to an Access Control List (ACL) configuration file. You can use more than one instance of this option. In the event of contradictory permissions, the permissions in the last configuration file listed take precedence. See “Access Control List configuration file.”
-a num
(QNX Neutrino 7.0 or later) The maximum number of open file handles allowed for the .all objects. The default and minimum value is 32.
-b
Don't run in the background. Useful for debugging.
-C
(QNX Neutrino 6.6 or later) Convert between root and non-root persistence formats, to correspond to the -U option.
-D dir
(QNX Neutrino 7.0 or later) Specify the directory to put core files in. The default is none.
-d backlog
Specify the default delta backlog, in kilobytes. The default is 256 bytes.
-g
(QNX Neutrino 7.0 or later) Enable debugging output. Additional -g options increase the level of the output.
-l argument
(“el”) Set the object load behavior, as follows:
- 0 — load directory names and objects on demand. Default.
- 1 — load all directory and object names on startup, but don't load the object contents. Load the object contents on demand.
- 2 — load directories, objects, and object contents on startup.
-m mount
Specify the mountpath for PPS. The default is /pps/.
-P priority
(QNX Neutrino 6.6 or later) Specify the priority of the persistence thread. The default is 10.
-p path
Set the path for backing up the persistent storage. The default is /var/pps.
-T tolerance
(QNX Neutrino 6.6 or later) The periodic persistence flush interval tolerance, in milliseconds. The default is off.
-t period
Specify the periodicity of the forced persistence, in milliseconds. For example, -t 5000 forces the PPS service to write to persistent storage every five seconds. The default is no forced persistence.
-U uid[:gid[,sup_gid]*]
(QNX Neutrino 6.6 or later) Once running, run as the specified user (and optionally groups), so that the program doesn't need to run as root.
-v
Enable verbose mode. Increase the number of “v”s to increase verbosity.
三、对象及其属性
QNX中PPS服务是一个具有发布者可以修改其属性的对象的系统。
订阅对象的客户端在对象更改时(即发布者修改对象时)接收更新。
使用PPS,你的应用可以:
- 向对象发布更改
- 订阅对象以接收更改通知
- 发布和订阅
对象文件
PPS 对象被实现为特殊的 PPS 文件系统中的文件。默认情况下,PPS 对象显示在 /PPS下,但是这个路径取决于启动 PPS 时使用的 -m 选项。PPS 对象的实例不会多于一个,因此对该对象的更改对订阅者是立即可见的。
更改通知
PPS 在创建、删除或截断对象时通知发布者和订阅者。
对象语法
在 PPS 文件系统的清单中,PPS 对象没有特殊的标识符。也就是说,它们将像清单中的任何其他文件一样显示。例如,/PPS/media 目录下的PPS对象 "PlayCurrent" 将在文件列表中显示为 "/PPS /media/PlayCurrent"。
属性语法
PPS 对象具有用户定义的属性。属性列在PPS对象的对象名称之后。
1 对象文件
PPS 对象被实现为特殊的PPS文件系统中的文件。默认情况下,PPS 对象显示在/ PPS下,但是这个路径取决于启动 PPS 时使用的 -m 选项。PPS 对象的实例不会多于一个,因此对该对象的更改对订阅者是立即可见的。
对象可以包含属性。每个属性都由对象文件中的一行文本表示。例如,您可以发布一个名为Time的对象,该对象表示当天的时间,并具有表示当前时、分、秒的整数属性,如下所示:
@Time hour::17 minute::04 second::38
在本例中,文件名是Time,每个属性是该文件中的一个文本字符串。
因为PPS对象是由文件表示的,你可以:
- 创建目录,并通过在这些目录中创建文件来填充PPS对象。
为了创建对象,您需要在PPS目录或适当的子目录中拥有写权限。
- 使用open(),然后使用read()和write()函数来查询和更改PPS对象。
- 使用标准工具作为简单的调试工具。
为了避免在不同组织的应用程序集成使用相同的PPS文件系统的情况下可能出现的混淆或冲突,我们建议您使用您组织的web域名在PPS目录中创建您的目录。因此,黑莓QNX,其互联网域名为“qnx.com”应使用/pps/ QNX,而域名为“example.net”的组织应使用/pps/example。
PPS对象是通过文件系统访问的,看起来像普通的POSIX文件。但是,它们不是标准POSIX文件,有些PPS行为与标准POSIX行为不同。例如,如果分配的读取缓冲区对于正在读取的数据来说太小,则读取不会返回部分结果;它失败。
属性的顺序
PPS不保证读取属性的顺序与写入对象的顺序相同。也就是说,发布者可以这样写:
@Time hour::17 minute::04 second::38
这可以由订阅者读入,例如:
@Time second::38 hour::17 minute::04
特殊的对象
PPS目录可以包含特殊的对象,您可以打开这些对象来促进订阅行为。下表列出了这些特殊对象:
Object | Use |
---|---|
.all | Open to receive notification of changes to any object in this directory. |
.notify | Open a notification file descriptor in the PPS filesystem root. |
对象和目录大小
因为 PPS 将对象保存在内存中,所以它们很小。每个对象分配 32kb。这并不意味着每个对象在运行时使用32 kb的内存;它只使用内部表示其当前属性所需的内存量。
PPS 目录和对象的数量仅受可用内存的限制。PPS 目录的深度受到对象的完整路径名作为文件存储在持久目录这一事实的限制;这些路径名的大小受所使用的持久文件系统支持的最大文件名大小的限制。
- PPS 对象不应该用作大量数据的转储场所。大多数PPS对象的大小应该以数百字节而不是千字节为单位进行测量。
- PPS 已经用 Power-Safe 文件系统(fs-qnx6.so)测试过,使用默认的PPS选项。该配置支持总路径名长度为517字节,单个路径名元素的长度不超过508字节。也就是说,在 fs-qnx6上可以有50个嵌套深度。假设路径的总长度小于517字节。
2 改变通知
PPS在创建、删除或截断对象时通知发布者和订阅者。
当PPS创建、删除或截断一个对象(文件或目录)时,它将一个通知字符串放入任何已打开该对象或具有被修改对象的目录的.all特殊对象的订阅者或发布者的队列中。
该通知字符串的语法是一个特殊字符前缀,后跟对象标识符“@”,然后是对象名称,如下所示:
Prefix | Example | Meaning |
---|---|---|
+ | +@objectname | PPS created the object. To know if a created object is a file or a directory, call stat() or fstat(). |
- | -@objectname | PPS deleted the object. |
# | #@objectname | PPS truncated the object. |
* | *@objectname | The object has lost a critical publisher. All nonpersistent attributes have been deleted. For more information, see Options and Qualifiers. |
此外,当删除一个对象时,PPS会向打开该对象的任何应用程序发送一个-@objectname。收到此通知的应用程序的典型行为是关闭打开的文件描述符,因为文件在文件系统中不再可见(POSIX行为)。
3 对象语法
在PPS文件系统的清单中,PPS对象没有特殊的标识符。也就是说,它们将像清单中的任何其他文件一样显示。例如,/ PPS /media目录下的PPS对象"PlayCurrent"将在文件列表中显示为/ PPS /media/PlayCurrent。
在读取PPS文件的结果中,第一行标识对象。这一行以“@”字符作为前缀,以识别它是对象名。下面的行定义了对象的属性。这些行没有特殊的前缀。
假设上面示例中的PPS对象“PlayCurrent”包含描述多媒体应用程序中当前播放的歌曲的元数据的属性。让我们假设属性具有以下格式:
@PlayCurrent author::[Presentation text for track author] album::[Presentation text for album name] title::[Presentation text for track title] duration::[Track duration, floating point number of seconds] time::[Track position, floating point number of seconds]
对该文件的open()调用后跟read()调用将返回对象的名称(文件名,前缀为"@"),后跟对象的属性及其值:
@PlayCurrent author::Beatles album::Abbey Road title::Come Together duration::3.45 time::1.24
- 对象名不能包含以下任何字符:"@" (@),"?"(问号)、“/”(正斜杠)、换行符(ASCII LF)或ASCII NUL。
- PPS对象中的每一行都以换行符(在C中是“\n”,或十六进制0A)结束,因此您必须以合作客户端应用程序商定的方式对这个字符进行编码。也就是说,任何包含ASCII LF或NUL字符的值都必须被编码。编码字段可用于协助协作应用程序确定在值字段中使用什么编码。
4 属性语法
PPS对象具有用户定义的属性。属性列在PPS对象的对象名称之后。
属性名称可以由字母、数字、下划线和句点组成,但必须以字母或下划线开头。PPS对象文件中的属性行是attrname:encoding:value\n的形式,其中attrname是属性名,encoding定义了值的编码类型。属性名的末尾和编码的末尾用冒号(":")标记。后面的冒号将被忽略。
PPS不解释编码;它只是将编码从发布者传递给订阅者。因此,发布者和订阅者可以自由地定义他们自己的编码来满足他们的需要。下表描述了可能的编码类型:
Symbol | Encoding |
---|---|
:: | Also referred to as null encoding. Simple text terminated by a linefeed. |
:c: | C language escape sequences, such as "\t" and "\n". Note that "\n" or "\t" in this encoding is a "\" character followed by an "n" or "t"; in a C string this would be "\\n\\t" |
:b: | Boolean |
:n: | Numeric |
:b64: | Base64 encoding |
:json: | JavaScript Object Notation encoding |
属性的值可以是任何字符序列,除了:
- 空值(C中的“\0”,或十六进制0x00)
- 换行符(“\n”在C中,或十六进制0x0A)
四、 发布
要发布到一个PPS对象,发布者只需对对象文件调用open(),使用O_WRONLY只发布,或O_RDWR发布和订阅。发布者可以调用write()来修改对象的属性。该操作是非阻塞的。
有关简单示例,请参阅示例附录中的“Publishers”。
向文件写入属性时,只需在一个操作中完成。需要对一个对象使用一个write()来确保可以正确地处理来自多个发布者的同时写操作。例如,与其这样:
write( fd, "state::", 7); if ( state == 0 ) write( fd, "off", 3); else write( fd, "on", 2);
像这样做:
snprintf( buf, sizeof(buf), "state::%s", state ? "on" : "off"); write( fd, buf, strlen(buf) );
创建、修改、删除对象和属性
您可以对对象和属性进行创建、修改、删除操作,如下表所示:
If you want to: | Do this: |
---|---|
Create a new object | Create a file with the name of the object. The new object will come into existence with no attributes. You can then write attributes to the object, as required. |
Delete an object | Delete the object file. |
Create a new attribute | Write the attribute to the object file. |
Modify an attribute | Write the new attribute value to the object file. Note that there's no need to lseek() to the attribute's position in the file first; seeking has no meaning in PPS. |
Delete all existing attributes | Open the object with O_TRUNC. |
Delete one attribute | Prefix its name with a minus sign, then call write(). For example: // Delete the "url" attribute sprintf( ppsobj, "-url\n" ); write( ppsobj-fd, ppsobj, strlen( ppsobj ) ); |
删除属性需要注意以下事项:
- 在一个对象文件上调用ftruncate()将删除该对象的所有属性,无论长度参数的值是多少。
- 从命令行输入一个简单的Bourne shell重定向指令(例如echo attr::hello > /pps/object),打开一个带有O_TRUNC的对象,并删除所有属性。
多重发布
PPS支持向同一个PPS对象发布的多个发布者。之所以需要此功能,是因为不同的发布者可以访问应用于同一对象的不同属性的数据。
多个发布者写入对象是安全的,因为pps管理器保证每个pps write()都是原子的。
例如,在一个具有PlayCurrent对象的多媒体系统中,io-media可能是time::value属性的来源,而HMI可能是duration::value属性的来源。仅更改time属性的发布者将在写入对象时仅更新该属性。它将保持其他属性不变。
在上面的例子中,假设PlayCurrent对象有以下属性值:
@PlayCurrent author::Beatles album::Abbey Road title::Come Together duration::3.45 time::1.24
如果io-media更新PlayCurrent对象的时间属性如下:
// Update the "time" attribute sprintf( ppsobj, "time::2.32\n" ); write( ppsobj-fd, ppsobj, strlen( ppsobj ) );
则HMI对duration属性进行如下更新:
// Update the "duration" attribute sprintf( ppsobj, "duration::4.02\n" ); write( ppsobj-fd, ppsobj, strlen( ppsobj ) );
结果将是:
@PlayCurrent author::Beatles album::Abbey Road title::Come Together duration::4.02 time::2.32
五 订阅
PPS 客户端可以订阅多个对象,PPS 对象可以有多个订阅者。当发布者更改对象时,将通知订阅该对象的所有客户端该更改。
要订阅一个对象,客户端只需为对象调用 open(),使用O_RDONLY只订阅,或使用 O_RDWR 发布和订阅。然后订阅者可以使用 read() 调用查询对象。read 返回所读数据的长度,以字节为单位。
- 针对 PPS 每个PPS 对象只能打开 200 个文件描述符。
- PPS 读取的行为与标准 POSIX 行为不同。在 PPS 中,如果分配的读缓冲区对于正在读入的数据来说太小,读不会返回部分结果;它失败。
多个订阅者从对象中读取数据是安全的,因为 pps 管理器保证每个pps read()都是原子的。
属性顺序
PPS 不保证读取属性的顺序与写入对象的顺序相同。也就是说,发布者可以这样写:
@Time hour::17 minute::04 second::38
订阅者可以这样读入,例如:
@Time second::38 hour::17 minute::04
等待文件描述符上的数据
通常情况下,对PPS数据进行轮询不是一个好主意。最好是在文件描述符上等待PPS数据,使用以下机制之一:
- 一个阻塞read ()
- 用于接收指定事件的 ionotify() 机制
- poll() 函数(尽管名称如此,但它并不总是轮询)
阻塞型read()是最简单的机制。通常,如果您想在同一进程中将来自文件描述符的输入与QNX中微子消息组合在一起,您可以使用ionotify()。当处理来自套接字、管道、串口等的多个文件描述符时,请使用poll()。这些机制说明如下;有关示例,请参阅示例附录中的“订阅用户”。
默认情况下,对PPS对象的读取是非阻塞的;也就是说,PPS默认一个普通的open()到O_NONBLOCK,这样打开对象的客户端所做的读取不会阻塞。
对于大多数文件系统来说,这种行为并不常见。这样做是为了使标准实用程序在对文件进行read()调用时不会挂起等待更改。例如,使用默认行为,可以使用标准tar实用程序对PPS的整个状态进行tar处理。如果没有这种默认行为,tar将永远不会超过第一个打开并读取的文件。
使用阻塞读
阻塞型read()会等待对象或其属性发生变化,然后返回数据。要获得read块,您需要使用 "?wait pathname open" 选项打开对象,该选项作为对象的路径名的后缀。例如,要打开PlayList对象:
对于默认的非阻塞读取,使用路径名:"/pps/media/PlayList"
对于阻塞读取,使用路径名加上选项:"/pps/media/PlayList?等待”
有关?wait选项的更多信息,请参见”路径名打开选项。
订阅者中的典型循环将存在于其自己的线程中。对于使用?wait选项打开对象的订阅者,该循环可能执行以下操作:
-
/* Assume that the object was opened with the ?wait option
-
No error checking in this example. */
-
for(;;) {
-
read(fd, buf, sizeof(buf)); // Read waits until the object changes.
-
process(buf);
-
}
如果你打开一个没有?wait选项的对象,并且想要改变为阻塞读取,你可以使用fcntl()来清除O_NONBLOCK位:
-
flags = fcntl(fd, F_GETFL);
-
flags &= ~O_NONBLOCK;
-
fcntl(fd, F_SETFL, flags);
或者 ioctl():
-
int i=0;
-
ioctl(fd,FIONBIO,&i);
使用ionotify()
PPS 服务实现了ionotify()功能,允许订阅者通过脉冲、信号、信号量等请求通知。在更改通知时,必须向对象文件发出read()来获取对象的内容。例如:
-
/* Process events while there are some */
-
while ( ( flags = ionotify( fd, _NOTIFY_ACTION_POLLARM,
-
_NOTIFY_COND_INPUT, event ) != -1 )
-
&& (flags & _NOTIFY_COND_INPUT) )
-
{
-
nbytes = read(fd, buf, sizeof(buf));
-
if ( nbytes > 0 )
-
process(buf);
-
}
-
/* If flags != -1, the event will be triggered in the future to get
-
our attention */
要了解更多信息,请参见QNX中C库参考中关于 inotify() 的条目。
使用poll()
poll() 函数检查一组文件描述符,看它们是否准备好读取或写入。要在PPS中使用poll(),请设置一个包含PPS对象的文件描述符的结构pollfd数组。您可以选择设置时间限制。例如:
readfds[0].fd = pps_fd; readfds[0].events = POLLIN; switch ( ret = poll(readfds, 1, timeout ) ) { case -1: /* An error occurred. */ break; case 0: /* poll() timed out. */ break; default: if( readfds[0].revents & POLLRDNORM ) { num_bytes = read( pps_fd, buf, sizeof(buf) ); if (num_bytes > 0) { process(buf); } } }
有关更多信息,请参阅C库参考资料中的poll()条目。
订阅模式
订阅者可以同时以full模式、delta模式或者full模式和delta模式同时打开对象。默认为full模式。
要在delta模式中打开一个对象,您需要使用?delta 路径名打开选项打开该对象,该选项作为对象的路径名的后缀。
有关?delta和其他路径名打开选项的信息,请参见选项和限定符章节。
Full 模式
在full模式(默认)中,订阅者总是在被请求时接收到整个对象的一个单一的、一致的版本。
如果发布者在订阅者请求之前多次更改对象,订阅者只在请求时接收对象的状态。如果对象再次更改,则再次将更改通知订阅者。因此,在完全模式下,订阅者可能会错过对对象的多个更改—在订阅者请求之前发生的对对象的更改。
Delta 模式
在delta模式中,订阅者只接收对对象属性的更改(但所有更改)。
在第一次读取时,由于订阅者不知道对象的状态,PPS假定所有内容都已更改。因此,订阅者在delta 模式中的第一次读取将返回对象的所有属性,而后续读取只返回自该订阅者上一次读取以来的更改。
因此,在 delta 模式中,订阅者总是接收到对对象的所有更改。
下图说明了发送给以full 模式和delta模式打开PPS对象的订阅者的不同信息。
在所有情况下,PPS 都维护具有状态的持久对象—始终存在一个对象。用于打开对象的模式不会改变对象;它只确定订阅者对对象更改的视图。
Delta 模式队列
当订阅者以 delta 模式打开对象时,PPS服务创建一个对象更改的新队列。也就是说,如果多个订阅者以 delta 模式打开一个对象,每个订阅者都有自己的对象更改队列,并且 PPS 服务向每个订阅者发送自己的更改副本。如果没有订阅者以 delta 模式打开对象,则PPS服务不会维护该对象的任何更改队列。
在关闭时,PPS服务保存其对象,但对象的 delta 队列将丢失。
对多个属性的更改
如果发布者通过一次 write() 调用更改多个属性,则PPS将 deltas 保存在一起,并通过订阅者的read()调用将它们返回到同一组中。换句话说,PPS delta同时维护了更改的时间和原子性。例如:
-
write() write()
-
time::1.23 time::1.24
-
duration::4.2 write()
-
duration::4.2
-
read() read()
-
@objname @objname
-
time::1.23 time:1.24
-
duration::4.2 @objname
-
duration::4.2
订阅多个对象
PPS支持一些特殊的对象,方便订阅多个对象:
Open this special object: | To receive notification of changes to: |
---|---|
.all | Any object in the directory |
.notify | Any object associated with a notification group |
订阅目录中的所有对象
PPS 使用目录作为一种自然的分组机制,以简化和提高订阅多个对象的效率。您可以通过调用open(),然后在它们的文件描述符上调用poll()来打开多个对象。更容易的是,您可以打开特殊的.all对象,该对象合并其目录中的所有对象。
例如,假设/pps下的对象文件结构如下:
rear/left/PlayCurrent rear/left/Time rear/left/PlayError
如果你打开 "rear/left/.all",当“rear/left”目录中的任何对象发生变化时,你会收到一个通知。Full 模式下的读取每次最多返回一个对象。
read() @Time position::18 duration::300 read() @PlayCurrent artist::The Beatles genre::Pop ... the full set of attributes for the object
但是,如果您以delta模式打开.all对象,您将收到一个包含目录中任何对象中更改的每个属性的队列。在这种情况下,一个read()调用可能包含多个对象。
read() @Time position::18 @Time position::19 @PlayCurrent artist::The Beatles genre::Pop
通知组
PPS 提供了一种将一组文件描述符与通知组关联的机制。此机制允许您仅读取PPS特殊通知对象,以便接收与该通知组关联的任何对象的更改通知。
创建通知组
- 在PPS文件系统的根目录中打开.notify对象。
- 读取.notify对象; 对该文件的第一次读取将返回一个短字符串(小于16个字符),该字符串带有其他文件描述符应该关联到的组的名称。
要关联一个文件描述符和一个组,在一个打开,指定路径名打开选项?notify=group:value,其中:
- Group是第一次读取.notify文件时返回的字符串
- Value是任意字符串;订阅者将使用此字符串确定绑定到通知组的哪些对象具有可读取的数据
返回的通知组字符串有一个末尾换行符,您必须在使用该字符串之前删除该换行符。
使用通知组
创建通知组及其关联的文件描述符之后,可以使用此组了解与之关联的任何对象的更改。
只要在任何组的文件描述符上有数据可供读取,就读取通知对象的文件描述符,并返回在?notify=group:value路径名选项中传递的字符串。
例如,如果PPS安装在/ PPS上,你可以这样写:
-
char noid[16], buf[128];
-
int notify_fd, fd1, fd2;
-
notify_fd = open("/pps/.notify", O_RDONLY);
-
read(notify_fd, &noid[0], sizeof(noid));
-
sprintf(buf, "/pps/fish?notify=%s:water", noid);
-
fd1 = open(buf, O_RDONLY);
-
sprintf(buf, "/pps/dir/birds?notify=%s:air", noid);
-
fd2 = open(buf, O_RDONLY);
-
while(read(notify_fd, &buf, sizeof(buf) > 0) {
-
printf("Notify %s\n", buf);
-
}
在上面的例子中,从" while "循环中打印出来的数据看起来像这样:
Notify 243:water Notify 243:water Notify 243:air Notify 243:water Notify 243:air
从绑定到通知组的对象读取时,订阅者应该对指示的每个更改执行多次读取。一个项上可能有多个更改,但不能保证每个更改都将在通知组的文件描述符上指示。
对象的关闭文件描述符的通知
如果关闭了属于通知组的对象的文件描述符,则传递更改通知的字符串前加上一个减号(“-”)。例如:
-243:air