mach ipc 学习

MACH IPC

mach kernel提供面向消息的,基于权能的进程间通信。进程间通信(IPC)原语有效地支持许多不同风格的交互,包括远程过程调用(RPC)、面向对象的分布式编程、数据流和发送大量数据。

IPC原语操作于三个抽象:消息、端口和端口集。用户任务通过IPC原语访问所有其他内核服务和抽象。

消息原语让任务发送和接收消息。任务向端口发送消息。发送到端口的消息被可靠地传递(消息可能不会丢失),并按照发送的顺序接收。消息包含一个固定大小的消息头和消息头后面可变数量的类型化数据。报头描述消息的目的地和大小。

IPC实现利用VM系统高效地传输大量数据。消息体可以包含发送方地址空间中应该作为消息的一部分进行传输的区域的地址。当任务接收到包含行外数据区域的消息时,该数据将出现在接收方地址空间中未使用的部分。这种脱行数据的传输得到了优化,以便发送方和接收方共享数据写时复制的物理页,并且除非写了这些页,否则不会发生实际的数据复制。内存区域的大小不超过一个完整的地址空间可以通过这种方式发送。

端口保存消息队列。通过执行端口的权能,任务对端口进行操作,以发送和接收消息。多个任务可以拥有一个端口的发送权能或权限。任务还可以持有“发送一次”权限,该权限授予发送单个消息的能力。对于端口,只有一个任务可以拥有接收能力或接收权限。可以通过消息在任务之间传输端口权限。消息的发送方可以在消息体中指定消息包含端口权限。如果消息包含端口的接收权限,则将从消息的发送方移除接收权限,并将该权限传输给消息的接收方。当接收权正在传输时,拥有发送权的任务仍然可以向端口发送消息,它们将被排队,直到任务获得接收权并使用它来接收消息。

任务可以从端口和端口集接收消息。端口集抽象允许单个线程等待来自多个端口中的任何一个的消息。任务使用与端口功能相同的空间操作端口或端口集。端口集权限不能在消息中传输。端口集持有接收权限,端口集上的接收操作将阻塞发送到任何组成端口的消息。一个端口不能属于多个端口集,如果一个端口是端口集的成员,则接收权的持有者不能直接从该端口接收。

端口权限是一种安全的、与位置无关的端口命名方式。端口队列是一个受保护的数据结构,只能通过内核导出的消息原语进行访问。权利也受到内核的保护;恶意用户任务无法猜测端口名称并将消息发送到它不应该访问的端口。端口权利不携带任何位置信息。当端口的接收权限从一个任务转移到另一个任务,甚至在不同机器上的任务之间,端口的发送权限保持不变并继续工作。

消息传递接口

本节描述消息如何在Mach IPC系统中组成、发送和接收。

Mach消息调用

mach_msg函数用于发送和接收消息。Mach消息包含类型化数据,可以包括端口权限和对内存区域的引用。MSG是调用者地址空间中的缓冲区地。消息缓冲区应该按长字边界对齐。消息option选项是位值,与位或组合。应该使用MACH_SEND_MSG和MACH_RCV_MSG中的一个或两个。其他选项可作为修饰符,发送消息时,send size指定消息缓冲区的大小。否则应提供零。接收消息时,rcv size指定消息缓冲区的大小。否则应提供零。接收消息时,rcv name指定端口或端口集。否则应该提供MACH_PORT_NULL。当使用MACH_SEND_TIMEOUT和MACH_RCV_TIMEOUT选项时,timeout指定在放弃之前等待的毫秒数。否则应该提 供MACH_MSG_TIMEOUT_NONE。当使用MACH_SEND_NOTIFY、MACH_SEND_CANCEL和MACH_RCV_NOTIFY选项时,notify指定用于通知的端口。否则应该提供MACH_PORT_NULL。

如果option参数为MACH_SEND_MSG,它将发送一条消息。send size参数指定要发送的消息的大小。消息头的msgh_remote_port字段指定消息的目的地。

如果option参数为MACH_RCV_MSG,它将收到一条消息。rcv size参数指定接收消息的消息缓冲区的大小;不接收大于RCV大小的消息。rcv name参数指定接收的端口或端口集。

如果option参数为MACH_SEND_MSG|MACH_RCV_MSG,那么mach_msg将同时执行发送和接收操作。如果发送操作遇到错误(除MACH_MSG_SUCCESS以外的任何返回代码),则调用立即返回,而不通过尝试接收操作。从语义上讲,组合调用等价于单独的发送和接收调用,但是它节省了系统调用并支持其他内部优化。

如果option参数既没有指定MACH_SEND_MSG也没有指定MACH_RCV_MSG,那么mach_msg什么也不做。

一些option,如MACH_SEND_TIMEOUT和MACH_RCV_TIMEOUT共享一个支持的参数。如果这些选项一起使用,它们会独立使用支持参数的值。

mach_msg_timeout_t
这是一个由超时机制使用的natural_t。单位是毫秒。无超时时使用的值为MACH_MSG_TIMEOUT_NONE。

消息格式

Mach消息由一个固定大小的消息头(mach_msg_header_t)和零个或多个数据项组成。数据项是类型化的。每个项都有一个类型描述符,后面跟着实际数据(或者数据的地址,用于行外内存区域)。

与Mach端口相关的数据类型如下:

mach_port_t:mach_port_t数据类型是无符号整数类型,它表示任务端口名称空间中的端口名称。在GNU Mach中,这是一个无符号整数。

以下数据类型与Mach消息相关:

mach_msg_bits_t:mach_msg_bits_t数据类型是一个无符号int,用于存储消息的各种标志。

mach_msg_size_t:mach_msg_size_t数据类型是一个unsigned int,用于存储消息的大小。

mach_msg_id_t:mach_msg_id_t数据类型是一个integer_t,通常用于传递接收方的函数或操作id。

mach_msg_header_t:该结构是Mach IPC系统中每条消息的开始。它有下列成员:
mach_msg_bits_t msgh_bits:msgh_bits字段定义了以下位,所有其他位都应该为零:
MACH_MSGH_BITS_REMOTE_MASK、
MACH_MSGH_BITS_LOCAL_MASK:远程和本地位对mach_msg_type_name_t值进行编码,这些值指定了msgh_remote_port和msgh_local_port字段中的端口权限。远程值必须为消息的目的地指定发送或发送一次的权利。如果本地值没有为消息的应答端口指定发送或发送一次的权利,那么它必须为零,并且msgh本地端口必须为MACH_PORT_NULL。

MACH_MSGH_BITS_COMPLEX:如果消息正文包含包含端口权限或行外内存区域,则必须指定复杂位。如果未指定,则消息体不携带端口权限或内存,而不管类型描述符似乎表明什么。

MACH_MSGH_BITS_REMOTE和MACH_MSGH_BITS_LOCAL宏返回适当的mach_msg_type_name_t值(给定msgh_bits值)。给定两个mach_msg_type_name_t值,MACH_MSGH_BITS宏为msgh_bits构造一个值。

mach_msg_size_t msgh_size:接收到的消息头中的msgh_size字段包含消息的大小。消息大小(字节数量)包括消息头、类型描述符和内联数据。对于行外内存regions,消息大小包括内联地址的大小,而不是实际内存区域的大小。对于Mach消息的大小、消息中数据项的数量或数据项的大小没有任意的限制。

mach_port_t msgh_remote_port:msgh_remote_port字段指定消息的目的端口。该字段必须携带一个合法的发送或发送一次的端口权利。

mach_port_t msgh_local_port:msgh_local_port字段指定了一个辅助端口权限,该端口通常被消息的接收方用作应答端口。该字段必须携带一个发送权,一个发送一次权,MACH_PORT_NULL或MACH_PORT_DEAD。

mach_port_seqno_t msgh_seqno:msgh_seqno字段为消息提供序列号。它只在接收到的消息中有效;它在已发送消息中的值将被覆盖。

mach_msg_id_t msgh_id:mach_msg调用不使用msgh_id字段,但按照惯例传递操作或函数id。

mach_msg_bits_t MACH_MSGH_BITS (mach msg type name t remote,
mach msg type name t local)

这个宏将指定mach_msg调用的msgh_remote_port和msgh_local_port字段中的端口权限的两个mach_msg_type_name_t值组合成适当的mach_msg_bits_t值。

mach_msg_type_name_t MACH_MSGH_BITS_REMOTE(mach msg bits t bits):这个宏从mach_msg_bits_t值中提取远程端口权的mach_msg_type_name_t值。

mach_msg_type_name_t MACH_MSGH_BITS_LOCAL:
这个宏从mach_msg_bits_t值中提取本地端口权限的mach_msg_type_name_t值。

mach_msg_bits_t MACH_MSGH_BITS_PORTS:
这个宏提取mach_msg_bits_t组件,该组件由远程和本地端口权限的mach_msg_type_name_t值组成,该值为mach_msg_bits_t值。

mach_msg_bits_t MACH_MSGH_BITS_OTHER:
这个宏提取mach_msg_bits_t组件,该组件包含除mach_msg_type_name_t值之外的所有远程和本地端口权的mach_msg_bits_t值。

每个数据项都有一个类型描述符,即mach_msg_type_t或mach_msg_type_long_t。mach_msg_type_long_t类型描述符允许某些字段使用更大的值。长描述符中的msgtl_header字段仅用于其内联、长形式和释放位。

mach_msg_type_name_t:
这是一个无符号整数,可用于保存mach_msg_type_t和mach_msg_type_long_t结构的msgt_name组件。

mach_msg_type_size_t:
这是一个无符号整数,可用于保存mach_msg_type_t和mach_msg_type_long_t结构的msgt_size组件。

mach_msg_type_number_t:
这是一个natural_t,可用于保存mach_msg_type_t和mach_msg_type_long_t结构的msgt_number组件。

mach_msg_type_t:
该结构有以下成员:

unsigned int msgt_name : 8
msgt_name字段指定数据的类型。以下类型是预定义的:
MACH_MSG_TYPE_UNSTRUCTURED
MACH_MSG_TYPE_BIT
MACH_MSG_TYPE_BOOLEAN
MACH_MSG_TYPE_INTEGER_16
MACH_MSG_TYPE_INTEGER_32
MACH_MSG_TYPE_CHAR
MACH_MSG_TYPE_BYTE
MACH_MSG_TYPE_INTEGER_8
MACH_MSG_TYPE_REAL
MACH_MSG_TYPE_STRING
MACH_MSG_TYPE_STRING_C
MACH_MSG_TYPE_PORT_NAME

以下预定义类型指定端口权限,并接受特殊处理。下一节将详细讨论这些类型。
类型MACH_MSG_TYPE_PORT_NAME描述了端口权限名称,此时没有权限被转移,而只有名称。为此目的,应该优先使用它,而不是MACH_MSG_TYPE_INTEGER_32。

MACH_MSG_TYPE_MOVE_RECEIVE
MACH_MSG_TYPE_MOVE_SEND
MACH_MSG_TYPE_MOVE_SEND_ONCE
MACH_MSG_TYPE_COPY_SEND
MACH_MSG_TYPE_MAKE_SEND
MACH_MSG_TYPE_MAKE_SEND_ONCE

msgt_size : 8
msgt_size字段指定每个数据的大小,以比特为单位。例如,MACH_MSG_TYPE_INTEGER_32数据的msgt大小为32。

msgt_number : 12
msgt_number字段指定由多少个数据元素组成的数据项。零是一个合法的数字。类型描述符指定的总长度是(msgt_size * msgt_number),向上取整为整数字节数。然后将内联数据填充为整数个长单词。这确保了类型描述符总是从长字边界开始。这意味着消息大小总是长单词大小的整数倍。

msgt_inline : 1
当为FALSE时,msgt_inline位指定数据实际驻留在一个出线区域。内存区域的地址(vm_offset_t或vm_address_t)位于消息体中的类型描述符之后。
msgt_name、msgt_size和msgt_number字段描述内存区域,而不是地址。

msgt_longform : 1
当为TRUE时,msgt_longform位指定此类型描述符为mach_msg_type_long_t而不是mach_msg_type_t。msgt_name、msgt_size和msgt_number字段应为零。相反,mach_msg使用以下msgtl_name、msgtl_size和msgtl_number字段。

msgt_deallocate : 1
msgt_deallocate位与行外区域一起使用。当为TRUE时,它指定在发送消息时应该从发送方的地址空间释放内存区域(就像使用vm_deallocate一样)。

msgt_unused : 1
msgt_unused位应该为零。

交换端口的权利

每个任务都有自己的端口权限空间。端口权限以正整数命名。除了保留值MACH_PORT_NULL(0)1和MACH_PORT_DEAD(~0)外,这是一个完整的32位名称空间。当内核为一个新的权限选择一个名称时,它可以自由地在空间中选择任何未使用的名称。

有五种基本类型的权利:接收权利、发送权利、一次发送权利、端口设置权利和死名称。死掉的名字不是能力。它们充当占位符,防止名字被其他人使用。

当端口的接收权限被释放时,端口将被销毁或死亡。当端口失效时,该端口的send和send-once权限将变成失效名称。端口上排队的任何消息都会被销毁,这将释放消息中的端口权限和行外内存。

任务可能包含多个用户引用的发送权限和死名称。当一个任务接收到它已经拥有的发送权限时,内核将增加该权限的用户引用计数。当一个任务释放发送权时,内核将减少它的用户引用计数,只有当计数为0时,任务才会失去发送权。

发送一次权限的用户引用计数总是为1,尽管端口可以有多个发送一次权限,因为任务持有的每个发送一次权限具有不同的名称。
相反,当一个任务拥有一个端口的发送权限或接收权限时,这些权限共享一个名称。

消息体可以携带端口权限;类型描述符中的msgt_name (msgtl_name)字段指定了端口权限的类型以及如何从调用者中提取端口权限。值MACH_PORT_NULL和MACH_PORT_DEAD在消息体中总是有效的。在发送的消息中,以下msgt_name值表示端口权限:

MACH_MSG_TYPE_MAKE_SEND:消息将携带发送权,但调用者必须提供接收权。发送权是从接收权创建的,并且接收权的make发送计数将递增。

MACH_MSG_TYPE_COPY_SEND:消息将携带发送权,而调用者应该提供发送权。
提供的发送权限的用户引用计数不会改变。调用者也可以提供一个死名称,而接收任务将获得MACH_PORT_DEAD。

MACH_MSG_TYPE_MOVE_SEND:消息将携带发送权,而调用者应该提供发送权。提供的发送权的用户引用计数将减少,如果计数变为零,则取消发送权。除非接受权仍然存在,否则该名称可被回收利用。调用者还可以提供一个死名称,这将丢失用户引用,而接收任务将获得MACH_PORT_DEAD。

MACH_MSG_TYPE_MAKE_SEND_ONCE:消息将携带发送一次的权利,但调用者必须提供接收权利。发送一次权限是从接收权限创建的。

MACH_MSG_TYPE_MOVE_SEND_ONCE:该消息将携带一个发送一次权利,并且调用者应该提供一个发送一次权利。调用者失去提供的发送一次的权利。调用者还可以提供一个死名称,这将丢失用户引用,而接收任务将获得MACH_PORT_DEAD。

MACH_MSG_TYPE_MOVE_RECEIVE:消息将携带接收权,而调用者应该提供一个接收权。调用者失去了提供的接收权限,但是保留了相同名称的发送权限。

如果消息带有发送或发送一次的权限,并且在消息传输期间端口终止,那么接收任务将获得MACH_PORT_DEAD而不是权限。在接收到的消息中,以下msgt_name值表示它携带端口权限:

MACH_MSG_TYPE_PORT_SEND:此名称是MACH_MSG_TYPE_MOVE_SEND的别名。这条消息带有一种发送权。如果接收任务已经拥有该端口的发送和/或接收权限,那么该端口的名称将被重用。否则,新的权利将有一个新的名称。如果任务已经有发送权限,它将获得该权限的用户引用(除非这会导致用户引用计数溢出)。
否则,它获取发送权,用户引用计数为1。

MACH_MSG_TYPE_PORT_SEND_ONCE:此名称是MACH_MSG_TYPE_MOVE_SEND_ONCE的别名。消息带来了一次发送权利。权利将有一个新名字。

MACH_MSG_TYPE_PORT_RECEIVE:此名称是MACH_MSG_TYPE_MOVE_RECEIVE的别名。该信息带有接收权。如果接收任务已经拥有该端口的发送权限,那么该端口的名称将被重用。否则,权利将有一个新名称。接收权的make-send计数将重置为零,但端口保留其他属性,如排队消息、现有的发送和发送一次权限,以及对端口破坏和无发送方通知的请求。

当内核为端口权限选择一个新名称时,它可以选择任何名称,除了MACH_PORT_NULL和MACH_PORT_DEAD,这两个名称目前没有用于端口权限或死名称。它可能选择一个名称,在以前的某个时间表示端口权限,但目前未使用。

内存

消息体可以包含发送方地址空间中应作为消息的一部分进行传输的区域的地址。消息携带内存的逻辑副本,但是内核使用VM技术延迟任何实际的页面副本。除非发送方或接收方修改数据,否则物理页仍然是共享的。

当数据的类型描述符将msgt_inline指定为FALSE时,就会发生出线传输。内存区域的地址(vm_offset_t或vm_address_t)应该跟在消息体中的类型描述符后面。类型描述符和地址决定了消息的大小(send_size,msgh_size)。出线数据不会影响消息的大小。

类型描述符中的name、size和number字段描述了出线数据的类型和长度,而不是内联地址。行外内存经常需要较长的类型描述符(mach_msg_type_long_t),因为msgt_number字段太小,无法描述4K字节的页面。

行外内存到达接收方地址空间的某个地方作为新内存。它具有与新vm_allocate内存相同的继承和保护属性。当不再需要内存时,接收方负责(通过vm_deallocate)释放内存。具有安全意识的接收者在使用来自不可靠来源的out- out- line内存时应谨慎,因为内存可能由不可靠的内存管理器支持。

空的行外内存是合法的。如果行外区域大小为零(例如,因为msgtl_number为零),那么该区域的指定地址将被忽略。接收到的空行外内存区域总是有一个零地址。

不对齐的地址和区域大小不是页面的倍数是合法的。接收到的消息还可能包含地址不对齐和大小奇怪的内存。一般情况下,接收端新存储区域中的第一页和最后一页并不只包含来自发送端的数据,而是部分为零。接收到的地址指向第一页中的数据的开始。这种可能性并不会使释放变得复杂,因为vm_deallocate做了正确的事情,向下舍入起始地址和向上舍入结束地址以释放所有到达的页面。

行外内存有一个释放选项,由msgt_deallocate位控制。如果该值为TRUE且超线内存区域不为null,则该区域将从发送方隐式释放,就像通过vm_deallocate释放一样。特别是,起始地址和结束地址被舍入,以便内存区域重叠的每个页都被释放。使用msgt_deallocate可以有效地将内存副本更改为内存移动。在接收到的消息中,msgt_deallocate在用于行外内存的类型描述符中为TRUE。
行外内存可以携带端口权限。

消息发送

发送操作将消息队列发送到端口。该消息携带调用者数据的副本。发送后,调用者可以自由修改消息缓冲区或出线内存区域,而消息内容将保持不变。

消息传递是可靠的和有序的。消息不会丢失,并且从单个线程发送到端口的消息将按照发送的顺序接收。

如果目标端口的队列已满,那么可能会发生几种情况。如果消息被发送到一个send-once right (msgh_remote_port携带一个send-once right),那么内核将忽略队列限制并传递消息。否则调用者阻塞,直到队列中有空间,除非使用了MACH_SEND_TIMEOUT或MACH_SEND_NOTIFY选项。如果一个端口有几个被阻塞的发送方,那么当队列中的空间可用时,它们中的任何一个都可以将下一条消息排队,前提是被阻塞的发送方不会无限期地饿死。

这些选项修改MACH_SEND_MSG。如果未指定MACH_SEND_MSG,则忽略它们。

MACH_SEND_TIMEOUT:
timeout参数应该指定在放弃之前阻塞调用的最大时间(以毫秒为单位)。如果消息不能在超时时间结束前排队,那么调用将返回MACH_SEND_TIMED_OUT。零超时是合法的。

MACH_SEND_NOTIFY:
notify参数应该为通知端口指定接收权限。如果发送被阻塞,则将消息排队,返回MACH_SEND_WILL_NOTIFY,并请求接受msg的通知。如果还指定了MACH_SEND_TIMEOUT,那么MACH_SEND_NOTIFY在超时时间过后才生效。
使用MACH_SEND_NOTIFY,任务可以强制队列一次发送一条消息。当另一个消息可以被强制排队时,一个接受了msg的通知被发送到通知端口。如果在此之前尝试使用MACH_SEND_NOTIFY,则调用返回一个MACH_SEND_NOTIFY_IN_PROGRESS错误。接受msg的通知携带发送权的名称,如果在生成接受了msg的通知之前释放了发送权,那么接受了msg的通知携带的值为MACH_PORT_NULL。如果目标端口在生成通知之前被销毁,那么将生成一个send-once通知。

MACH_SEND_INTERRUPT:
如果指定,如果软件中断中止调用,mach_msg调用将返回MACH_SEND_INTERRUPTED。否则,将重试发送操作。

MACH_SEND_CANCEL:
notify参数应该为通知端口指定接收权限。如果发送操作从调用者中移除目标端口权利,并且被移除的权利注册了一个dead-name请求,而notify是dead-name请求的notify端口,那么dead-name请求可能会被静默取消(而不是导致端口删除通知)。
此选项通常用于取消使用MACH_RCV_NOTIFY选项发出的dead-name请求。它只能作为一种优化使用。

发送操作可以生成以下返回码。这些返回码暗示调用什么都没做:

MACH_SEND_MSG_TOO_SMALL:
日志含义指定的发送大小小于消息的最小大小。

MACH_SEND_NO_BUFFER:
资源短缺使内核无法分配消息缓冲区。

MACH_SEND_INVALID_DATA:
提供的消息缓冲区不可读。

MACH_SEND_INVALID_HEADER:
msgh_bits值无效。

MACH_SEND_INVALID_DEST:
msgh_remote_port值无效。

MACH_SEND_INVALID_REPLY:
msgh_local_port值无效。

MACH_SEND_INVALID_NOTIFY:
当使用MACH_SEND_CANCEL时,notify参数没有表示有效的接收权。

这些返回码意味着部分或全部消息已被销毁:

MACH_SEND_INVALID_MEMORY:
消息体指定了不可读的行外数据。

MACH_SEND_INVALID_RIGHT:
消息体指定了调用者不拥有的端口权限。

MACH_SEND_INVALID_TYPE:
类型描述符无效。

MACH_SEND_MSG_TOO_SMALL:
消息中的最后一个数据项在消息的末尾运行。

这些返回码意味着消息是通过一个伪接收操作返回给调用者的:

MACH_SEND_TIMED_OUT:
超时时间已过。

MACH_SEND_INTERRUPTED:
软件中断。

MACH_SEND_INVALID_NOTIFY:
当使用MACH_SEND_NOTIFY时,notify参数没有表示有效的接收权。

MACH_SEND_NO_NOTIFY:
资源短缺导致内核无法设置接受msg的通知。

MACH_SEND_NOTIFY_IN_PROGRESS:
已经请求了一个接受短信的通知,但尚未生成。

这些返回码意味着消息被排队了:

MACH_SEND_WILL_NOTIFY:
消息被强制排队,并且请求了一个接受msg的通知。

MACH_MSG_SUCCESS:
消息被排队。

一些返回代码,如MACH_SEND_TIMED_OUT,意味着消息几乎已经发送,但不能排队。在这些情况下,内核尝试使用伪接收操作将消息内容返回给调用者。这可以防止只存在于消息中的端口权限或内存的丢失。例如,一个被移动到消息中的接收权,或与释放位一起发送的出线内存。

伪接收操作与正常的接收操作非常相似。伪receive处理消息头中的端口权限,就像它们在消息体中一样。它们没有颠倒过来。在伪接收之后,可以重新发送消息。如果不重新发送消息,请注意,行外内存区域可能已经移动,一些端口权限可能已经更改了名称。

伪接收操作可能遇到资源短缺。这类似于接收操作的MACH_RCV_BODY_ERROR返回代码。当这种情况发生时,normal发送返回码将用MACH_MSG_IPC_SPACE、MACH_MSG_VM_SPACE、MACH_MSG_IPC_KERNEL和MACH_MSG_VM_KERNEL位来表示资源短缺的性质。

携带接收权限的消息的队列可能会创建一个接收权限和消息的循环循环,这将永远无法被接收。例如,可以将带有接收权的消息发送到该接收权。这种情况不是错误,但是内核将对这种循环进行垃圾收集,破坏相关的消息和端口。

4.2.6 消息接收

接收操作将消息从端口中退出队列。接收任务获取端口权限和消息中携带的超线内存区域。

rcv_name参数指定接收的端口或端口集。如果指定了端口,则调用者必须拥有该端口的接收权,且该端口不能是端口集的成员。如果没有消息出现,那么调用就会阻塞,这取决于MACH_RCV_TIMEOUT选项。

如果指定了端口集,则调用将接收发送到任何成员端口的消息。允许端口集没有成员端口,并且在从端口集接收时可以添加和删除端口。接收到的消息可以来自任何有消息的成员端口,前提是有消息的成员端口不会被无限期地耗尽。接收到的消息头中的msgh_local_port字段指定消息来自端口集中的哪个端口。

参数rcv_size指定调用者的消息缓冲区的大小。mach_msg调用不会收到大于rcv_size的消息。过大的消息将被销毁,除非使用MACH_RCV_LARGE选项。

在接收到的消息报头中,目的地和应答端口反转。msgh_local_port字段指定接收消息的目的端口,msgh_remote_port字段指定应答端口的权限。msgh_bits中的位也是反向的。如果消息被发送到发送权,则MACH_MSGH_BITS_LOCAL位的值为MACH_MSG_TYPE_PORT_SEND;如果消息被发送到发送一次权,则MACH_MSG_TYPE_PORT_SEND_ONCE。MACH_MSGH_BITS_REMOTE位描述了应答端口权限。

接收到的消息可以包含端口权限和行外内存。msgh_local_port字段没有接收端口权限;接收消息的行为破坏了目的端口的发送或发送一次的权利。如果MACH_MSGH_BITS_COMPLEX以msgh位出现,则msgh远端端口字段将指定接收端口权限、应答端口权限,并且消息体可以携带端口权限和内存。应该以某种方式消耗或释放接收到的端口权限和内存。

在几乎所有情况下,msgh_local_port将指定接收权的名称,或者rcv_name,或者如果rcv_name是一个端口集,则是rcv_name的成员。如果其他线程正在并发地操作接收权,情况就更复杂了。如果在调用期间重命名了接收权限,那么msgh_local_port将指定该权限的新名称。如果调用方在消息从队列中退出后丢失了接收,那么mach_msg将继续执行,而不是返回MACH_RCV_PORT_DIED。如果接收权限被销毁,那么msgh_local_port指定MACH_PORT_DEAD。如果接收权仍然存在,但不是由调用者持有,则msgh_local_port指定MACH_PORT_NULL。

接收到的消息被打上序列号,序列号是从接收消息的端口获取的。(从端口集接收到的消息将印有来自适当成员端口的序列号。)新创建的端口以零序列号开始,当端口的接收在任务之间移动时,序列号将重置为零。当消息从端口中退出队列时,它将被标记为端口的序列号,然后该端口的序列号将被递增。出队列和递增操作是原子操作,因此从一个端口接收消息的多个线程可以使用msgh_seqno字段来重建消息的原始顺序。

这些选项将修改MACH_RCV_MSG。如果未指定MACH_RCV_MSG,则忽略它们。

MACH_RCV_TIMEOUT:
timeout参数应该指定在放弃之前阻塞调用的最大时间(以毫秒为单位)。
如果在超时时间过后之前没有消息到达,那么调用将返回MACH_RCV_TIMED_OUT。零超时是合法的。

MACH_RCV_NOTIFY:
notify参数应该为通知端口指定接收权限。如果接收应答端口在调用者中创建了一个新的端口权限,那么notify端口将用于为新端口权限请求一个死名通知。

MACH_RCV_INTERRUPT:
如果指定,如果软件中断中止调用,mach_msg调用将返回MACH_RCV_INTERRUPTED。否则,将重试接收操作。

MACH_RCV_LARGE:
如果消息大于rcv_size,则消息将保持在队列中,而不是被销毁。该调用返回MACH_RCV_TOO_LARGE,在消息头的msgh_size字段中返回消息的实际大小。

接收操作可以生成以下返回代码。这些返回码暗示调用没有将消息从队列中取出:

MACH_RCV_INVALID_NAME:指定的rcv_name无效。

MACH_RCV_IN_SET:指定的端口是端口集的成员。

MACH_RCV_TIMED_OUT:超时时间已过。

MACH_RCV_INTERRUPTED:软件中断。

MACH_RCV_PORT_DIED:调用者失去了rcv_name指定的权限。

MACH_RCV_PORT_CHANGED:Rcv_name指定了一个接收权限,该权限在调用期间被移动到端口集中。

MACH_RCV_TOO_LARGE:当使用MACH_RCV_LARGE时,消息大于rcv_size。消息留在队列中,其实际大小在消息缓冲区的msgh_size字段中返回。

这些返回码意味着消息被退出队列并销毁:

MACH_RCV_HEADER_ERROR:资源短缺导致无法接收消息头中的端口权限。

MACH_RCV_INVALID_NOTIFY:当使用MACH_RCV_NOTIFY时,notify参数没有表示有效的接收权。

MACH_RCV_TOO_LARGE:当不使用MACH_RCV_LARGE时,大于rcv_size的消息将被放到队列中并销毁。

在这些情况下,当消息被出队列然后销毁时,应答端口以及消息体中的所有端口权限和内存都将被销毁。但是,调用者接收消息的头,所有字段都是正确的,包括目标端口,但应答端口除外,该端口为MACH_PORT_NULL。

这些返回码表示已收到消息:

MACH_RCV_BODY_ERROR:资源短缺导致无法接收消息正文中的端口权或出线内存区域。消息头(包括应答端口)是正确的。内核尝试传输体内的所有端口权限和内存区域,只销毁那些不能传输的。

MACH_RCV_INVALID_DATA:指定的消息缓冲区不可写。调用任务未成功接收消息中的端口权限和脱机内存区域。

MACH_MSG_SUCCESS

在将端口权限和行外内存区域传输给接收任务时,将消息从队列中取出后可能发生资源短缺。在这种情况下,mach_msg调用返回MACH_RCV_HEADER_ERROR或MACH_RCV_BODY_ERROR。这些返回码总是携带额外的位(按位或),表示资源短缺的性质:

MACH_MSG_IPC_SPACE:任务的IPC名称空间中没有其他端口名称的空间。

MACH_MSG_VM_SPACE:在任务的VM地址空间中没有用于行外内存区域的空间。

MACH_MSG_IPC_KERNEL:内核资源短缺阻碍了端口权利的接收。

MACH_MSG_VM_KERNEL:内核资源短缺导致无法接收出线内存区域。

如果资源短缺阻止接收端口权限,则端口权限将被销毁,调用者将看到名称MACH_PORT_NULL。如果资源短缺阻止接收出线内存区域,该区域将被销毁,调用者将收到一个零地址。此外,数据类型描述符中的msgt_size (msgtl_size)字段被更改为零。如果资源短缺阻止接收带有端口权限的离线内存,那么如果内存区域不能接收,端口权限总是被销毁。一个任务永远不会收到它没有被告知的端口权限或内存区域。

4.2.7 原子性

mach_msg调用原子地处理消息头中的端口权限。消息体中的端口权限和行外内存不享有这种原子性保证。消息体可以从前到后、从后到前、先处理出线内存,然后再处理端口权限(以某种随机顺序,甚至原子方式)。

例如,考虑使用指定为MACH_MSG_TYPE_MOVE_SEND的目标端口和指定为MACH_MSG_TYPE_COPY_SEND的应答端口发送消息。对msgh_remote_port和msgh_local_port字段都提供了相同的发送权限,并使用了一个用户引用。因为mach_msg原子地处理消息头,所以这成功了。如果msgh_remote_port在msgh_local_port之前被处理,那么在这种情况下,mach_msg将返回MACH_SEND_INVALID_REPLY。

另一方面,假设目的地和应答端口都指定为MACH_MSG_TYPE_MOVE_SEND,并且同样为两者提供相同的发送权和一个用户引用。现在发送操作失败了,但是因为它原子地处理报头,所以mach msg可以返回MACH_SEND_INVALID_DEST或MACH_SEND_INVALID_REPLY。

例如,考虑在接收消息的同时,另一个线程正在释放目标接收权。假设应答端口字段携带对目的端口的发送权限。如果重新分配发生在退出队列之前,则接收方获得MACH_RCV_PORT_DIED。如果回收是在接收之后进行的,那么msgh_local_port和msgh_remote_port字段都指定了相同的权限,当回收接收权限时,该权限将成为无效名称。如果回收发生在出队列和接收之间,那么msgh_local_port和msgh_remote_port字段都指定了MACH_PORT_DEAD。因为头是原子处理的,所以两个字段中的一个不可能保存MACH_PORT_DEAD。

MACH_RCV_NOTIFY选项提供了一个更有可能的示例。假设在销毁应答端口的同时,通过MACH_RCV_NOTIFY接收了一个带有发送一次的正确应答端口的消息。如果先销毁应答端口,则msgh_remote_port指定MACH_PORT_DEAD,并且内核不会生成dead-name通知。如果应答端口在收到后被销毁,那么msgh_remote_port将指定一个死名,内核将为其生成一个死名通知。在请求死名通知之前,不可能接收到正确的应答端口并将其转换为死名;作为消息头的一部分,应答端口是原子式接收的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值