Mach的绝大多数通信(包括绝大多数系统调用和任务间信息)都是通过消息传递实现的。

消息被发往邮箱,也从邮箱里接收,在Mach中,邮箱(mailboxes)也叫端口(port).

Mach内核支持创建和销毁多个任务(task),这些任务进程相似,但它可以控制多个线程,并且只需要更少的相关资源

消息被发送到邮箱,并从邮箱接收,邮箱在Mach中被称为端口。端口大小有限,而且是单向;对于双向通信,消息被发送到一个端口,响应被发送到一个单独的reply端口。每个端口可能有多个发送端,但只有一个接收器。Mach使用端口来表示诸如任务、线程、内存和处理器等资源,而消息传递则提供了一种面向对象的方法来与这些系统资源和服务进行交互。消息传递可能发生在同一主机上的任意两个端口或在分布式系统上的单独主机之间。

与每个端口相关联的是一组端口权限,用于标识任务与端口交互所需的功能。例如,对于要从端口接收消息的任务(task),它必须具有该端口的MACH_PORT_RIGHT_RECEIVE功能。创建端口的任务(task)是该端口的所有者,所有者是唯一允许从该端口接收消息的任务(task)。端口所有者还需要拥有操纵端口的功能。这在建立应答端口时最常见。例如,假设任务T1拥有端口P1,并向端口P2发送消息,端口P2属于任务T2。如果T1期望收到T2的回复,那么它必须给予T2关于端口P1的MACH_PORT_RIGHT_RECEIVE功能。端口权限的所有权是任务级别的,这意味着属于同一任务的所有线程共享相同的端口权限。因此,属于同一任务的两个线程可以通过与每个线程关联的每个线程端口交换消息来轻松地进行通信。

在Mach中,当创建任务时,会创建两个特殊的邮箱------任务自端口(Task Self port)和通知端口(Notify port)。内核拥有任务自端口的接收权限,该端口允许任务向内核发送消息。内核可以将事件发生的通知发送到任务的通知端口(Notify port)(当然,任务对其具有接收权限)。

mach_port_allocate()函数调用创建一个新端口并为其消息队列分配空间。它还标识了端口权限。每个端口权限表示该端口的名称(name),一个端口只能通过一个权限访问。端口名称是一个简单的整数值,与UNIX文件描述符非常相似。下面的示例演示如何使用这个API创建一个端口:

mach port t port; // the name of the port right 队列最大值默认为8.
mach port_allocate (
    mach task self(), // a task referring to itself
    MACH PORT RIGHT RECEIVE, // the right for this port
    &port); // the name of the port right

每个任务还可以访问引导端口(bootstrap port),该端口允许任务使用系统范围的引导服务器(bootstrap server)上注册它所创建的端口。在引导服务器上注册了端口之后,其他任务可以在此注册中心中查找端口,并获得向端口发送消息的权限。

与每个端口相关联的队列大小是有限的,并且最初是空的。当消息被发送到端口时,消息被复制到队列中。所有消息都是可靠地交付的,并且具有相同的优先级。Mach确保来自同一个发送者的多个消息按照先进先出(FIFO)顺序排队,但不保证绝对的顺序。例如,来自两个发送者的消息可以以任何顺序排列。

Mach消息包含如下两个属性

  • 一个固定大小的消息头,包含关于消息的元数据,包括消息的大小以及源端口和目的端口。通常,发送线程需要一个应答,因此源端口名被传递给接收该消息的任务,该任务可以将其作为”返回地址“来回复应答。
  • 一个包含数据的可变大小的body体

消息可以是简单的(simple),也可以是复杂的(complex)。一个简单的消息包含普通的、非结构化的用户数据,内核不会解释这些数据。一个复杂的消息可能包含指向包含数据的内存位置的指针(称为“离线”数据),也可能用于将端口权限转移到另一个任务。当消息必须传递大量数据时,离线数据指针特别有用。简单的消息需要复制和打包消息中的数据;离线数据传输只需要一个指向存储数据的内存位置的指针。

函数mach_msg()是发送和接收消息的标准API。函数的一个参数的值——MACH SEND MSG或MACH RCV MSG——表明它是发送还是接收操作。现在,我们将说明当客户机任务如何使用它向服务器任务发送简单消息。假设有两个端口——client和server——分别与客户机和服务器任务相关联。图3.18中的代码显示了客户机任务构造头部并向服务器发送消息,以及服务器任务接收来自客户机的消息。

#include<mach/mach.h>
struct message {
    mach msg header t header;
    int data;
} ;
mach port t client;
mach port t server;
客户端代码
struct message message;
// construct the header
message.header.msgh size = sizeof(message);
message.header.msgh remote port = server;
message.header.msgh local port = client;
// send the message
mach msg (&message.header, // message header
    MACH SEND MSG, // sending a message
    sizeof(message), // size of message sent
    0, // maximum size of received message - unnecessary
    MACH PORT NULL, // name of receive port - unnecessary
    MACH MSG TIMEOUT NONE, // no time outs
    MACH PORT NULL // no notify port
);
服务端代码
struct message message;
// receive the message
mach msg (&message.header, // message header
    MACH RCV MSG, // sending a message
    0, // size of message sent
    sizeof(message), // maximum size of received message
    server, // name of receive port
    MACH MSG TIMEOUT NONE, // no time outs
    MACH PORT NULL // no notify port
);

mach_msg()函数调用由用户程序调用,用于执行消息传递。mach_msg()然后调用函数mach_msg_trap(),这是对mach内核的一个系统调用。在内核中,mach_msg_trap()接下来调用mach_msg__overwrite__trap()函数,然后它处理实际传递的消息。

发送和接收操作本身是灵活的。例如,当消息被发送到端口时,它的队列可能是满的。如果队列没有满,则将消息复制到队列,并继续发送任务。如果端口队列满了,发送方有几个选项(通过mach msg()的参数来指定):

  • 无限期地等待直到邮箱的空间可用

  • 最多等待n毫秒,即由超时时间

  • 不等待,直接返回

  • 临时的缓存消息。在这里,即使队列满了,消息还是可以被交给操作系统保存。当消息可以放进邮箱中时,一条通报会发送给发送者。任何时候,对于一个给定的发送线程,任何时候只能挂起一条到完整队列的消息。

最后一个选项是针对服务器任务的。在完成请求之后,服务器任务可能需要向请求服务的任务发送一次应答,但它也必须继续处理其他服务请求,即使客户机的应答端口已满。

消息系统的主要问题通常是由于将消息从发送方的端口复制到接收方端口而导致的性能不佳。Mach消息系统试图通过使用虚拟内存管理技术来避免复制操作(第10章)。实际上,Mach将包含发送者消息的地址空间映射到接收者的地址空间。因此,消息本身永远不会被复制,因为发送方和接收方都访问相同的内存。这种消息管理技术提供了很大的性能提升,但只适用于系统内部消息。

PS:下面是第七版的翻译,上面是第十版的翻译

消息传递只需要三个系统调用:

  • msg_send():向邮箱发送消息
  • msg_receive():接收消息
  • msg_rpc():远程过程调用,发送消息,并等待确个返回消息。在这种情况下,RPC模拟了典型的子程序过程调用,但它还可以在系统之间工作------这也就解释了remote的语义

port_allocate()系统调用创建一个新的邮箱,并为消息队列分配空间。

创建邮箱的任务就是该邮箱默认的拥有者。该拥有者允许从该邮箱接收消息。

每次只能有一个任务可以从邮箱中获取或接收,但是这些权限可以发送到其他任务中。

初始的邮箱的消息队列是空的。当消息被发送到邮箱,消息被复制进邮箱。所有的消息都有相同的优先级

Mach保证从同一个发送者发送的多个消息按照 FIFO的顺序排列,但并不能保证绝对顺序,比如来自两个发送者的消息可以按照任意顺序排队。

消息自身由定长的头信息和不定长的数据部分组成。消息头包括消息的长度和两个邮箱的名字,其中一个指定消息被发送到的邮箱。通常,发送消息的线程也期待一个回复,因此发送者的邮箱名称被传递给接收的任务,接收任务可以使用它作为“返回地址(return address)”。

消息的可变部分是具有类型的数据条目的列表。在列表中的每一个条目/项都有类型、大小和值。在消息中指定的对象的类型是很重要的,因为对象是由操作系统定义的——比如所有权或获得访问权、任务状态和内存段-----都可以在消息中被发送。

发送和接收操作本身是很灵活的。比如,当消息被发送到邮箱,邮箱可能是满的。如果不是满的,那么消息被复制进邮箱,发送线程继续工作。如果邮箱是满的,发送线程有四种可选的操作:

  • 无限期地等待直到邮箱的空间可用
  • 最多等待n毫秒,即由超时时间

  • 不等待,直接返回

  • 临时的缓存消息。在这里,即使邮箱满了,消息还是可以被交给操作系统保存。当消息可以放进邮箱中时,一条通报会发送给发送者。任何时候,对于一个给定的发送线程,只能有一条发送到一个满的邮箱的消息。

最后一个选项用于服务器任务,例如行打印机驱动程序。在结束请求之后,这样的任务需要发送一个一次性的应答,即使客户端的回复邮箱满了。

接收操作必须指明从哪个邮箱或者邮箱集合(mailbox set)来接收消息。邮箱集合是由任务所声明的,可以组合在一起作为一个邮箱以满足任务的一组邮箱。

任务线程只能从任务具有接收权的邮箱或者邮箱集合中接收消息。一个prot_status()系统调用返回给定邮箱中的消息数量。接收操作试图从如下两处来接收消息:

  • 邮箱集合内的任何邮箱
  • 特定的邮箱

如果没有消息等待接收,则接收线程最多只能等待n毫秒,或者不等待。

消息系统的主要问题是消息的双重复制,导致性能差,即消息首先从发送方复制到邮箱,再从邮箱复制到接收方。

通过使用虚拟内存管理技术(第九章)可以避免双重复制。其关键在意Mach将发送者的地址空间映射到接收者的地址空间,消息本身并不真正复制。

results matching ""

    No results matching ""