跳转至

接口

目的

创建 iPXE 风格的接口是为了满足传输和转换消息的需求。例如,一个 http_connection 接口会向 tcp_socket 接口发送数据投递消息,而后者随后可以反过来发送一些其他的数据投递消息。

实现

接口既充当消息处理器,又充当载体:http 接口会把任何消息传递给 tcp 接口,即使它本身也可能处理消息。之所以这样做,是因为源对象不需要向自身发送消息(它可以直接执行这项工作)。每个接口都可以看作一个单向消息管道[^1]。两个接口可以互为目的地(dhcp 接口会向 udp 套接字发送消息,而 udp 套接字会把消息发回来)。

消息是强类型的,由通过接口发送消息的工具函数的地址来标识。类型使用 mymessage_TYPE 宏强制执行,该宏为适当的对象类型构建一个函数指针。

接口始终绑定到一个对象,对象包含任意数据。反过来,一个对象可以包含多个接口。对象是结构体,接口是类型为 struct interface 的字段。

每个接口包含一个作用于该对象的消息处理器列表[^2]。

作为一项附加特性,当一个接口没有为某种消息类型实现处理器时,它可以知道同一对象的另一个接口(称为直通接口),用作通向另一个对象的接口的载体,后者有望处理该消息(如果不能,可以重复该过程,直到没有直通接口为止)。

这可用于实现消息透明的消息过滤器。

示例

struct xor_filter {
       /* ... */
       struct interface xfer;
       struct interface backend;
       uint8_t xor_constant;
};

static struct interface_operation xor_xfer_operations[] = {
        INTF_OP(xfer_deliver,  /* ... */  xor_filter_deliver),
        /* ... */
};

static struct interface_descriptor xor_xfer_desc =
        INTF_DESC_PASSTHRU(struct xor_filter *, xfer,
                           xor_xfer_operations, backend);

static struct interface_operation xor_backend_operations[] = {
        /* ... */
};

static struct interface_descriptor xor_backend_desc =
        INTF_DESC_PASSTHRU(struct xor_filter *, backend,
                           xor_backend_operations, xfer);

static int xor_filter_deliver(struct xor_filter *xfilter,
                              struct io_buffer *io_buf,
                              struct xfer_metadata *meta) {
        /* ... */
        xfer_deliver(&xfer->backend, io_buf, meta);
}

int xor_filter(struct interface *xfer, struct interface *backend, uint8_t c) {
        struct xor_filter *xfilter = zalloc(sizeof (*xfilter));
        if (!xfilter)
                return -ENOMEM;

        /* ... */

        intf_init(&xfilter->xfer, &xor_xfer_desc /* ... */);
        intf_init(&xfilter->backend, &xor_backend_desc /* ... */);

        xfilter->xor_constant = c;

        intf_plug_plug(xfer, &xfilter->xfer);
        intf_plug_plug(backend, &xfilter->backend);

        /* ... */

        return 0;
}

常见问题

  • 在前面的示例中,backend 把未知消息直通给 xfer,反之亦然。为什么不会产生循环?

因为消息被传输到直通接口的目的地。如果不是这样,接口就没有用了,因为最初接收消息的接口处理消息的能力会与它转发消息到的接口相同,因为它们都来自同一个对象。

  • 内存是如何管理的?我没有看到任何引用计数。

为了清晰起见,本示例中删掉了任何与引用计数有关的内容。然而,实际的对象确实包含适当的引用计数:任何接口都持有一个绑定的引用计数器[^3],并且每当一个接口被插入另一个接口时,目的地的引用计数就会递增。

为了处理包含引用循环的网络的释放,网络中的任何对象都可以向其邻居发送 intf_close 消息,这将断开其所有接口,并把同样的消息发送给它的所有邻居。

  • 我在哪里可以找到更多示例?

src/net/udp/ntp.c src/net/udp/syslog.c src/net/tcp/syslogs.c src/core/pinger.c

参考资料

src/include/ipxe/interface.h src/core/interface.c

[^1]: 消息与网络无关, 而是可以看作函数调用。 传输消息意味着分派函数调用。

[^2]: 每个接口包含一个到对象开头的偏移量, 用于在调用方法时检索其地址

[^3]: 很可能是包含它的对象的 引用计数器