QNX----第3章 进程间通信(2部分)

时间:2024-05-22 08:00:35

QNX----第3章 进程间通信(2部分)

事件

 

QNX中微子内核设计的一个重大进展是事件处理子系统。POSIX及其实时扩展定义了许多异步通知方法(例如,不排队或传递数据的UNIX信号、可能排队和传递数据的POSIX实时信号等)。

内核还定义了额外的QNX特定于中微子的通知技术,如脉冲。实现所有这些事件机制可能会占用大量的代码空间,因此我们的实现策略是在单个、丰富的事件子系统上构建所有这些通知方法。

这种方法的一个好处是,一个通知技术独有的功能可以被其他技术使用。例如,应用程序可以将POSIX实时信号的队列服务应用于UNIX信号。这可以简化应用程序中信号处理程序的健壮实现。

执行线程遇到的事件可以来自以下三个来源:

•由线程调用的MsgDeliverEvent()内核调用

•一个中断处理程序

•定时器超时

事件本身可以是许多不同类型中的任何一种:QNX中微子脉冲、中断、各种形式的信号,以及强制的“解除阻塞”事件。“Unblock”是一种方法,通过这种方法,线程可以从故意阻塞的状态中释放出来,而不需要实际交付任何显式事件。

考虑到事件类型的多样性,以及应用程序需要能够请求最适合它们需要的异步通知技术,要求服务器进程(上一节中的高级线程)携带支持所有这些选项的代码将会很尴尬。

相反,客户端线程可以向服务器提供一个数据结构(或“cookie”),以便稍后继续使用。当服务器需要通知客户机线程时,它将调用MsgDeliverEvent(),微内核将在客户机线程上设置cookie中编码的事件类型。

QNX----第3章 进程间通信(2部分)

图27:客户机向服务器发送sigevent

I / O通知

许多POSIX异步服务(例如mq_notify()和select()的客户端)都构建在ionotify()之上。当在文件描述符(fd)上执行I/O时,线程可能选择等待I/O事件完成(对于write()案例),或者等待数据到达(对于read()案例)。在为读写请求提供服务的资源管理器进程上没有线程块,而ionotify()允许客户端线程向资源管理器发布一个事件,当出现指示的I/O条件时,客户端线程希望接收这个事件。以这种方式等待允许线程继续执行和响应事件源,而不仅仅是单个I/O请求。

select()调用是使用I/O通知实现的,允许线程阻塞和等待多个fd上的I/O事件的混合,同时继续响应其他形式的IPC。以下是提交请求事件的条件:

•_NOTIFY_COND_OUTPUT -输出缓冲区中有空间容纳更多数据。

•_NOTIFY_COND_INPUT- resource manager定义的数据量可以读取。

•_NOTIFY_COND_OBAND - resource-manager定义的“带外”数据可用。

信号

OS支持32个标准的POSIX信号(与UNIX一样)以及POSIX实时信号,它们都是由内核实现的64个具有统一功能的信号集编号的。而POSIX标准定义了实时信号从unix形式不同的信号(可能包含数据的四个字节,字节代码和可能排队等候交付),这个功能可以显式地选中或者去掉的时候在一个信号的基础上,允许这个聚合实现仍然符合标准。

顺便说一句,如果应用程序需要的话,unix风格的信号可以选择POSIX实时信号队列。QNX中微子RTOS还扩展了POSIX的信号传递机制,允许信号以特定的线程为目标,而不是简单地以包含线程的进程为目标。由于信号是异步事件,它们也通过事件传递机制实现。

QNX----第3章 进程间通信(2部分)

最初的POSIX规范只定义进程上的信号操作。在多线程进程中,遵循以下规则:

•信号操作在进程级别进行维护。如果线程忽略或捕获信号,它将影响进程中的所有线程。

•信号掩码保持在线程级别。如果一个线程阻塞了一个信号,它只影响那个线程。

•一个未被忽略的针对线程的信号将被单独发送到该线程。

•一个未被忽略的针对进程的信号被发送到没有阻塞信号的第一个线程。如果所有线程都阻塞了信号,信号将在进程中排队,直到任何线程忽略或解除阻塞信号。如果忽略,进程上的信号将被删除。如果解除阻塞,信号将从进程移动到解除阻塞的线程。

当一个信号针对具有大量线程的进程时,必须扫描线程表,寻找信号未阻塞的线程。大多数多线程进程的标准实践是在除一个线程外的所有线程中屏蔽信号,该线程专门处理这些信号。为了提高进程信号传递的效率,内核会缓存最后一个接受信号的线程,并总是试图先将信号传递给它。

QNX----第3章 进程间通信(2部分)

图28:信号传递

POSIX标准包括排队实时信号的概念。QNX中微子RTOS支持任意信号的可选排队,而不仅仅是实时信号。排队可以在一个进程中以一个信号一个信号地指定。每个信号可以有一个相关的8位代码和一个32位值。

这与前面描述的消息脉冲非常相似。内核利用了这种相似性,并使用通用代码来管理信号和脉冲。使用_SIGMAX - signo将信号编号映射到脉冲优先级。因此,信号以优先级顺序传递,较低的信号编号具有较高的优先级。这符合POSIX标准,该标准规定现有信号优先于新的实时信号。

特殊的信号

如前所述,操作系统总共定义了64个信号。其范围如下:

QNX----第3章 进程间通信(2部分)

这八种特殊信号不能被忽略或捕捉。尝试调用signal()或sigaction()函数或SignalAction()内核调用来更改它们将失败,错误为EINVAL。

此外,这些信号总是被阻塞并启用了信号排队。通过sigprocmask()函数或SignalProcmask()内核调用释放这些信号的尝试将被忽略。

可以使用以下标准信号调用对常规信号进行编程。特殊的信号可以防止程序员编写这段代码,并保护信号不受这种行为的意外变化的影响。

QNX----第3章 进程间通信(2部分)

这种配置使这些信号适合于使用sigwaitinfo()函数或SignalWaitinfo()内核调用的同步通知。以下代码将阻塞,直到接收到第8个特殊信号:

QNX----第3章 进程间通信(2部分)

由于信号总是被阻塞,如果特殊信号被发送到sigwaitinfo()函数之外,程序就不能被中断或终止。由于始终启用了信号排队,因此信号不会丢失——它们将排队等待下一个sigwaitinfo()调用。

这些信号的设计目的是解决常见的IPC需求,其中服务器希望通知客户机它有可供客户机使用的信息。服务器将使用MsgDeliverEvent()调用通知客户机。对于通知中的事件有两个合理的选择:脉冲或信号。

脉冲是客户机的首选方法,客户机也可以是其他客户机的服务器。在这种情况下,客户机将创建一个接收消息的通道,还可以接收脉冲。

对于大多数简单的客户端来说,情况就不一样了。为了接收一个脉冲,一个简单的客户端将*为这个表达目的创建一个通道。如果信号被配置为同步(即这正是特殊信号的配置方式。客户机将用一个简单的sigwaitinfo()调用替换用来等待通道上的脉冲的MsgReceive()调用,以等待信号。

信号归类

这个表格描述了每个信号的含义。

信号

描述

SIGABRT

异常终止信号,例如abort()函数发出的终止信号。

SIGALRM

alarm ()函数发出的信号。

SIGBUS

指示内存奇偶校验错误(特定于中微子的解释)。请注意,如果在您的进程处于该故障的信号处理程序中时发生了第二个故障,该进程将被终止。

SIGCHLD (or SIGCLD)

子进程终止。默认操作是忽略信号。

SIGCONT

如果HELD继续。如果进程不是HELD,默认操作是忽略信号。

SIGDEADLK

互斥死锁发生。如果一个进程在持有互斥锁时死亡,并且您还没有调用SyncMutexEvent()来设置一个事件,以便在互斥锁死亡时将其传递给互斥锁的所有者,那么内核会将SIGDEADLK传递给所有正在等待互斥锁的线程,而不会超时。

注意,SIGDEADLKSIGEMT指的是同一个信号。有些实用程序(例如gdbkshslaykill)知道SIGEMT,但是不知道SIGDEADLCK

SIGEMT

EMT指令(模拟器陷阱)。注意,SIGEMTSIGDEADLK指的是同一个信号。

SIGFPE

错误的算术运算(整数或浮点数),如除零或导致溢出的运算。请注意,如果在您的进程处于该故障的信号处理程序中时发生了第二个故障,该进程将被终止。

SIGHUP

死亡的会话的领导者,或挂起检测到控制终端。

SIGILL

检测无效的硬件指令。请注意,如果在您的进程处于该故障的信号处理程序中时发生了第二个故障,该进程将被终止。造成此信号的一个可能原因是试图执行需要I/O权限的操作。线程可以通过以下方式请求这些特权:

1。启用PROCMGR_AID_IO能力。有关更多信息,请参见procmgr_ability()

2。调用ThreadCtl(),指定_NTO_TCTL_IO标志:

ThreadCtl(_NTO_TCTL_IO 0);

SIGINT

Interactive attention signal (Break)

SIGIOT

IOT指令(不是在x86硬件上生成的)

SIGKILL

终止信号——仅适用于紧急情况。这个信号不能被捕获或忽略。

SIGPIPE

Attempt to write on a pipe with no readers.

 

POSIX消息队列

POSIX定义了一组非阻塞消息传递工具,称为消息队列。与管道一样,消息队列是使用“阅读器”和“写入器”操作的命名对象。“作为离散消息的优先队列,消息队列比管道有更多的结构,并为应用程序提供更多的通信控制。

为什么要使用POSIX消息队列?

POSIX消息队列为许多实时程序员提供了一个熟悉的接口。它们类似于许多实时高管使用的“邮箱”。

QNX消息和POSIX消息队列之间有一个基本的区别。QNX消息块——它们直接在发送消息的进程的地址空间之间复制数据。另一方面,POSIX消息队列实现了一种存储转发设计,在这种设计中发送方不需要阻塞,并且可能有许多未完成的消息排队。POSIX消息队列独立于使用它们的进程而存在。可能会在设计中使用消息队列,在这种设计中,随着时间的推移,许多命名队列将由各种进程操作。

对于原始性能,POSIX消息队列传输数据的速度将比QNX中微子本地消息慢。然而,队列的灵活性可能使这种小小的性能损失值得付出代价。

接口类文件

消息队列类似于文件,至少就其接口而言如此。可以使用mq_open()打开消息队列,使用mq_close()关闭它,并使用mq_unlink()销毁它。要将数据放入(“write”)并将其从(“read”)消息队列中取出,可以使用mq_send()和mq_receive()。

对于严格的POSIX一致性,应该创建以单斜杠(/)开头的消息队列,并且不包含其他斜杠。注意,我们通过支持可能包含多个斜杠的路径名来扩展POSIX标准。例如,这允许公司将所有消息队列放在公司名称下,并以增强的信心分发产品,相信队列名称不会与另一家公司的名称冲突。在QNX中微子中,创建的所有消息队列都将出现在目录下的文件名空间中:

QNX----第3章 进程间通信(2部分)

可以使用ls命令显示系统中的所有消息队列如下:

QNX----第3章 进程间通信(2部分)

消息队列函数

POSIX消息队列通过以下功能进行管理:

QNX----第3章 进程间通信(2部分)

共享内存

一旦创建了共享内存对象,访问对象的进程就可以使用指针直接对其进行读写操作。这意味着对共享内存的访问本身是不同步的。如果进程正在更新共享内存的某个区域,则必须小心防止另一个进程读取或更新相同的区域。即使在简单的读取情况下,另一个进程也可能获得不稳定且不一致的信息。

为了解决这些问题,共享内存通常与同步原语一起使用,以便在进程之间进行原子更新。如果更新的粒度很小,那么同步原语本身就会限制使用共享内存固有的高带宽。因此,当将大量数据作为一个块进行更新时,共享内存是最有效的。

信号量和互斥量都是适用于共享内存的同步原语。引入了用于进程间同步的POSIX实时标准信号量。线程同步的POSIX线程标准引入了互斥对象。互斥也可以在不同进程中的线程之间使用。POSIX认为这是一个可选的功能;我们支持它。通常,互斥对象比信号量更有效。

带有消息传递的共享内存

共享内存和消息传递可以结合起来提供IPC:

•非常高性能(共享内存)

•同步(消息传递)

•网络透明度(消息传递)

通过消息传递,客户机向服务器发送请求并阻塞。服务器按照优先级顺序从客户端接收消息,并在满足请求时进行处理和应答。此时,客户端被解除阻塞并继续执行。发送消息的行为在客户机和服务器之间提供了自然的同步。与通过消息传递复制所有数据不同,消息可以包含对共享内存区域的引用,因此服务器可以直接读写数据。最好用一个简单的例子来解释。

让我们假设一个图形服务器接受来自客户机的绘制图像请求,并将它们呈现到图形卡上的帧缓冲区中。仅使用消息传递,客户机将向服务器发送包含图像数据的消息。这将导致图像数据从客户机地址空间复制到服务器地址空间。然后服务器将呈现图像并发出简短的回复。

如果客户机没有发送与消息内联的图像数据,而是发送对包含图像数据的共享内存区域的引用,那么服务器可以直接访问客户机的数据。

由于客户机在服务器上由于发送消息而被阻塞,因此服务器知道共享内存中的数据是稳定的,在服务器响应之前不会更改。这种消息传递和共享内存的结合实现了自然的同步和非常高性能。

这个操作模型也可以反过来——服务器可以生成数据并将其提供给客户端。例如,假设客户机向服务器发送一条消息,服务器将直接从CD-ROM中读取视频数据,并将其读入客户机提供的共享内存缓冲区。当共享内存被更改时,客户机将在服务器上被阻塞。当服务器响应并客户机继续运行时,共享内存将保持稳定,以便客户机访问。这种类型的设计可以使用多个共享内存区域进行流水线操作。

简单的共享内存不能在通过网络连接的不同计算机上的进程之间使用。另一方面,消息传递是网络透明的。服务器可以为本地客户机使用共享内存,为远程客户机使用数据的完整消息传递。这允许您提供一个高性能的服务器,它也是网络透明的。

在实践中,消息传递原语的速度足以满足大多数IPC需求。只有在带宽非常高的特殊应用程序中,才需要考虑组合方法的复杂性。

创建共享内存对象

进程中的多个线程共享该进程的内存。在进程间共享内存时,必须首先创建共享内存区域,然后将该区域映射到进程的地址空间。共享内存区域使用以下调用创建和操作:

QNX----第3章 进程间通信(2部分)

QNX----第3章 进程间通信(2部分)POSIX共享内存是通过进程管理器(procnto)在QNX Neutrino RTOS中实现的。

shm_open()函数接受与open()相同的参数,并向对象返回文件描述符。与常规文件一样,这个函数允许创建新的共享内存对象或打开现有的共享内存对象。必须打开文件描述符进行读取;如果要在内存对象中写入,还需要写访问权限,除非指定了私有映射(MAP_PRIVATE)。

当创建新的共享内存对象时,对象的大小设置为零。要设置大小,可以使用ftruncate()——与设置文件大小的函数完全相同——或者使用shm_ctl()。

mmap()

一旦有了共享内存对象的文件描述符,就可以使用mmap()函数将对象或它的一部分映射到进程的地址空间。mmap()函数是QNX Neutrino RTOS内存管理的基础,值得详细讨论。还可以使用mmap()将文件和键入的内存对象映射到进程的地址空间。

mmap()函数的定义如下:

QNX----第3章 进程间通信(2部分)

简单来说,这表示:“在与fd关联的共享内存对象中,以offset_within_shared_memory的共享内存的长度字节映射。” mmap()函数将尝试将内存放在地址空间中的地址where_i_want_it。内存将受到memory_protection指定的保护,映射将根据mapping_flags进行。

fd、offset_within_shared_memory和length这三个参数定义要映射的特定共享对象的一部分。映射整个共享对象是很常见的,在这种情况下,偏移量将为零,长度将为共享对象的大小(以字节为单位)。在Intel处理器上,长度将是页面大小的倍数,即4096字节。

QNX----第3章 进程间通信(2部分)

图29:mmap()映射内存。

mmap()的返回值将是映射对象的进程地址空间中的地址。参数where_i_want_it被系统用作对象放置位置的提示。如果可能,对象将被放置在请求的地址。大多数应用程序都指定一个地址为零,这样系统就可以*地将对象放置在它想要的位置。

可以为memory_protection指定以下保护类型:

QNX----第3章 进程间通信(2部分)

在使用共享内存区域访问可能被硬件修改的双端口内存时(例如,视频帧缓冲区或内存映射网络或通信板),应该使用PROT_NOCACHE清单。如果没有这个清单,处理器可能会从先前缓存的读取中返回“陈旧”的数据。

mapping_flags决定如何映射内存。这些标志分为两个部分—第一部分是类型,必须指定为以下内容之一:

QNX----第3章 进程间通信(2部分)

MAP_SHARED类型用于在进程之间设置共享内存;MAP_PRIVATE有更专门的用途。可以或将许多标记放入上述类型中,以进一步定义映射。这些在QNX Neutrino RTOS C库引用中的mmap()条目中详细描述。一些更感兴趣的标志是:

MAP_ANON

映射不与任何文件描述符关联的匿名内存;必须将fd参数设置为NOFD。mmap()函数分配内存,默认情况下,用0填充分配的内存。

通常在MAP_PRIVATE中使用MAP_ANON,可以在MAP_SHARED中使用它来为分支应用程序创建共享内存区域。可以使用MAP_ANON作为页面级内存分配器的基础。

MAP_FIXED

将对象映射到由where_i_want_it指定的地址。如果共享内存区域内包含指针,那么可能需要在映射该区域的所有进程中强制该区域位于同一个地址。这可以避免通过在区域内使用偏移量来代替直接指针。

MAP_PHYS

此标志表示希望处理物理内存。fd参数应该设置为NOFD。在不使用MAP_ANON时,offset_within_shared_memory指定要映射的确切物理地址(例如,用于视频帧缓冲区)。如果与MAP_ANON一起使用,就会分配物理上连续的内存(例如,DMA缓冲区)。可以使用MAP_NOX64K和MAP_BELOW16M进一步定义MAP_ANON分配的内存和一些DMA形式的地址限制。

MAP_NOX64K

与MAP_PHYS | MAP_ANON一起使用。分配的内存区域不会跨越64 kb的边界。这是旧的16位PC DMA所必需的。

MAP_BELOW16M

与MAP_PHYS | MAP_ANON一起使用。分配的内存区域将驻留在低于16mb的物理内存中。当使用ISA总线设备使用DMA时,这是必要的。

MAP_NOINIT

将POSIX需求放宽到零分配内存。

使用上面描述的映射标志,进程可以很容易地在进程之间共享内存:

QNX----第3章 进程间通信(2部分)或者为总线主控PCI网卡分配DMA缓冲区:

QNX----第3章 进程间通信(2部分)

可以使用munmap()从地址空间取消共享内存对象的全部或部分映射。这个原语并不局限于取消对共享内存的映射——它可以用于取消进程中任何内存区域的映射。当将AP_ANON标记与mmap()结合使用时,可以轻松实现一个私有的页级分配器。

可以使用mprotect()更改内存映射区域上的保护。与munmap()一样,mprotect()并不局限于共享内存区域——它可以更改进程中任何内存区域的保护。

初始化分配的内存

POSIX要求mmap()为它分配的任何内存零。初始化内存可能需要一段时间,因此QNX Neutrino RTOS提供了一种松耦合POSIX需求的方法。这允许更快的启动,但可能是一个安全问题。

避免初始化内存需要进行解映射的过程和进行映射的过程的合作:

•munmap_flags()函数是非posix函数,类似于munmap(),但它允许控制下一个映射内存时会发生什么:

QNX----第3章 进程间通信(2部分)

如果指定一个flags参数为0,munmap_flags()的行为与munmap()相同。以下位控制分配时的内存清除:

UNMAP_INIT_REQUIRED:下一次分配底层物理内存时,需要将页面初始化到所有0。

UNMAP_INIT_OPTIONAL:初始化底层物理内存以使其下次分配为零是可选的。

如果将MAP_NOINIT标记指定为mmap(),并且映射的物理内存以前是用UNMAP_INIT_ OPTIONAL未映射的,那么POSIX要求内存为零的要求就会放宽。

默认情况下,内核初始化内存,但是您可以使用-m选项来控制它。这个选项的参数是一个字符串,允许您启用或禁用内存管理器的某些方面:

i:munmap()的作用就好像UNMAP_INIT_REQUIRED被指定一样。

~i:munmap()的作用就好像UNMAP_INIT_OPTIONAL是指定的一样。

默认情况下,当释放内存供以后重用时,该内存的内容将保持不变;无论拥有遗留内存的应用程序是什么,它都将保持完整,直到下一次由另一个进程分配内存为止。在QNX Neutrino 6.6或更高版本中,procnto的-m选项允许您在取消映射时控制默认行为:

-mc:释放内存时清除内存。

-m~c:当内存被释放时不要清除它(默认)。当释放内存供以后重用时,该内存的内容将保持不变;无论拥有遗留内存的应用程序是什么,它都将保持完整,直到下一次由另一个进程分配内存为止。在这一点上,在将内存传递给下一个进程之前,它是零。

内存类型

类型化内存是1003.1规范中定义的POSIX功能。它是高级实时扩展的一部分,位于<sys/mman.h>头文件。

类型化内存向C库添加了以下功能:

posix_typed_mem_open():打开一个类型化内存对象。这个函数返回一个文件描述符,然后您可以将其传递给mmap(),以建立类型内存对象的内存映射。

posix_typed_mem_get_info():获取关于类型化内存对象的信息(当前可用内存数量)。

POSIX类型化内存提供了一个接口来打开内存对象(以特定于操作系统的方式定义)并对其执行映射操作。它有助于在特定于BSP或板的地址布局和设备驱动程序或用户代码之间提供抽象。

 POSIX指定以特定于实现的方式创建和定义类型化内存池(或对象)。

类型内存区域的划分

在QNX Neutrino下,类型化内存对象是从系统页面asinfo部分指定的内存区域中定义的。因此,类型化内存对象直接映射到启动时定义的地址空间层次结构(asinfo段)。类型化内存对象还继承asinfo中定义的属性,即内存段的物理地址(或界限)。

一般来说,asinfo条目的命名和属性是任意的,完全由用户控制。然而,有一些强制性的条目:

Memory:处理器的物理可寻址性,通常是32位CPU上的4 GB(更多是物理寻址扩展)。

Ram:系统上所有的内存。这可能由多个条目组成。

Sysram:系统内存,将内存分配给操作系统进行管理。这也可能由多个条目组成。

由于sysram是分配给操作系统的内存,所以这个池与操作系统用来满足匿名mmap()和malloc()请求的池相同。可以创建额外的条目,但只能在启动时使用as_add()函数。

类型内存区域的命名

类型内存区域的名称直接从asinfo段的名称派生而来。asinfo部分本身描述了一个层次结构,因此类型化内存对象的命名是一个层次结构。下面是一个示例系统配置:

QNX----第3章 进程间通信(2部分)

传递给posix_typed_mem_open()的名称遵循上述命名约定。POSIX允许实现定义名称没有以斜杠(/)开头时会发生什么。规则如下:

1。如果名称以前导/开头,则完成精确匹配。

2。名称可以包含中间/字符。它们被认为是路径组件分隔符。如果指定了多个路径组件,则从下往上匹配它们(与解析文件名的方式相反)。

3。如果名称没有以前导/开头,则在指定的路径名组件上进行尾部匹配。

下面是一些关于posix_typed_mem_open()如何使用上面的示例配置解析名称的示例:

QNX----第3章 进程间通信(2部分)

路径名空间和类型内存

类型化内存名称层次结构通过/dev/ tymem下的process manager命名空间导出。应用程序可以列出这个层次结构,并查看系统页面中的asinfo条目,以获得关于类型化内存的信息。

与共享内存对象不同,不能通过名称空间接口打开类型化内存,因为posix_typed_mem_ open()接受额外的参数tflag,这是必需的,在open() API中没有提供。

mmap()分配标志和类型化内存对象

对于类型化内存,一般考虑了以下分配和映射的情况:

1、从(POSIX_TYPED_MEM_ALLOCATE和POSIX_TYPED_MEM_ALLOCATE_CONTIG)显式地分配类型化内存池。这种情况就像一个普通的匿名对象的MAP_SHARED:

QNX----第3章 进程间通信(2部分)

内存被分配,不能用于其他分配,但是如果对进程进行分支,子进程也可以访问它。当最后一个映射被删除时,内存被释放。注意,就像有人使用mem_offset()和MAP_PHYS来访问以前分配的内存一样,其他人可以使用POSIX_TYPED_MEM_MAP_ALLOCATABLE(或不带标志)打开类型化内存对象,并以这种方式访问相同的物理内存。POSIX_TYPED_MEM_ALLOCATE_CONTIG类似于MAP_ANON | MAP_SHARED,因为它会导致一个连续的分配。

2、POSIX_TYPED_MEM_MAP_ALLOCATABLE用例,它用于创建到对象的映射,而不需要分配或重分配。这相当于到物理内存的共享映射。

应该只使用MAP_SHARED mappings,因为对MAP_PRIVATE映射的写操作会(像往常一样)在普通的匿名内存中为进程创建一个私有副本。

如果没有指定标志,或者指定POSIX_TYPED_MEM_MAP_ALLOCATABLE,则mmap()的偏移参数将指定类型化内存区域中的起始物理地址;如果类型化内存区域是不连续的(多个asinfo条目),则允许的偏移值也是不连续的,并且不像共享内存对象那样从零开始。如果指定的[paddr, paddr + size]区域位于类型化内存对象的允许地址之外,那么mmap()将在ENXIO中失败。

权限和类型内存对象

类型化内存对象的权限由UNIX权限控制。posix_typed_mem_open()的oflags参数指定了所需的访问权限,并且根据类型化内存对象的权限掩码检查这些标记。

POSIX没有指定如何给类型化内存对象分配权限。在QNX Neutrino下,系统启动时将分配默认权限。默认情况下,root是所有者和组,具有读写权限;其他人没有任何权限。目前,没有改变对象权限的机制。将来,可以扩展该实现,以允许chmod()和chown()修改权限。

对象长度和偏移量定义

可以使用posix_typed_mem_get_info()检索对象的大小。posix_typed_mem_get_info()调用填充了一个posix_typed_mem_info结构,该结构包含posix_tmi_length字段,该字段包含类型化内存对象的大小。

正如POSIX指定的,length字段是动态的,并且包含该对象的当前可分配大小(实际上,POSIX_TYPED_MEM_ALLOCATE和POSIX_TYPED_MEM_ALLOCATE_CONTIG对象的空闲字节大小)。如果使用tflag为0或POSIX_TYPED_MEM_MAP_ALLOCATABLE打开对象,则length字段设置为0。

在类型化内存对象中映射时,通常会向mmap()传递一个偏移量。偏移量是映射应该开始的对象中位置的物理地址。只有当使用tflag为0或POSIX_TYPED_MEM_MAP_ALLOCATABLE打开对象时,偏移量才合适。如果使用POSIX_TYPED_MEM_ALLOCATE或POSIX_TYPED_ MEM_ALLOCATE_CONTIG打开类型化内存对象,一个非零偏移量会导致对mmap()的调用失败,错误为EINVAL。

与其他POSIX API的交互

类型化内存可以与其他POSIX api交互.

rlimits

POSIX setrlimit() API提供了对进程可以使用的虚拟和物理内存设置限制的能力。由于类型化内存操作可能在普通RAM (sysram)上操作,并且会在进程的地址空间中创建映射,所以在进行rlimit核算时需要考虑到它们。特别适用下列规则:

•mmap()为类型化内存对象创建的任何映射都被计算在进程的RLIMIT_VMEM或RLIMIT_AS限制中。

•类型化内存从不对RLIMIT_DATA计数。

POSIX文件描述符函数

可以使用posix_typed_memory_open()返回的文件描述符和所选的基于POSIX fd的调用,如下所示:

•fstat(fd,..),它填充了stat结构,就像它填充共享内存对象一样,只是size字段不包含类型化内存对象的大小。

•关闭(fd)关闭文件描述符。

•dup()和dup2()复制文件句柄。

•posix_mem_offset()的行为如POSIX规范中记录的那样。

实际的例子

下面是一些如何使用类型化内存的例子。

  1. 从系统RAM分配连续内存

下面是一个从系统RAM中分配连续内存的代码片段:

QNX----第3章 进程间通信(2部分)

  1. 定义包内存并从中分配

假设有特殊的内存(比如fast SRAM),想要将其用于包内存。这个SRAM没有放入全局系统RAM池中。相反,在启动时,使用as_add()(参见构建嵌入式系统的自定义映像启动程序章节)为包内存添加asinfo条目:

QNX----第3章 进程间通信(2部分)

其中phys_addr是SRAM的物理地址,size是SRAM大小,mem_id是父进程的ID(通常是内存,由as_default()返回)。

这段代码为packet_memory创建了asinfo条目,可以将其用作POSIX类型化内存。以下代码允许不同的应用程序从packet_memory分配页面:

QNX----第3章 进程间通信(2部分)

或者,可能希望将包内存用作直接共享的物理缓冲区。在这种情况下,应用程序将使用它如下:

QNX----第3章 进程间通信(2部分)

  1. 定义dma安全区域

在某些硬件上,由于芯片组或内存控制器的限制,可能无法在系统中对任意地址执行DMA。在某些情况下,芯片组只有DMA到所有物理RAM的子集的能力。传统上,如果不静态地保留部分驱动程序DMA缓冲区的RAM(这可能会造成浪费),就很难解决这个问题。类型化内存提供了一个干净的抽象来解决这个问题。这里有一个例子:

在启动时,使用as_add_contains() 来定义用于dma安全内存的asinfo条目。让这个条目成为ram的子条目:

QNX----第3章 进程间通信(2部分)

其中dma_addr是dma安全RAM的开始,size是dma安全区域的大小。

这段代码为dma创建了一个asinfo条目,dma是ram的子元素。然后驱动程序可以使用它来分配dma安全的缓冲区:

QNX----第3章 进程间通信(2部分)

管道和FIFOs

管道和FIFOs都是连接进程的队列形式。

管道

管道是一个未命名的文件,它充当两个或多个合作进程之间的I/O通道:一个进程写入管道,另一个进程从管道中读取。

管道管理器负责缓冲数据。PIPE_BUF缓冲区大小在<limit.h>文件。 函数pathconf()返回极限的值。当两个进程想要并行运行时,数据从一个进程移动到另一个进程时,通常使用管道。(如果需要双向通信,则应该使用消息。)

管道的典型应用程序是将一个程序的输出连接到另一个程序的输入。这种连接通常由shell完成。例如:

QNX----第3章 进程间通信(2部分)

通过管道将ls实用程序的标准输出定向到more实用程序的标准输入。

QNX----第3章 进程间通信(2部分)

FIFOs

FIFOs本质上与管道相同,除了FIFOs是存储在文件系统目录中的永久文件。

QNX----第3章 进程间通信(2部分)

QNX----第3章 进程间通信(2部分)