Kernel Connection Multiplexor

Kernel Connection Multiplexor (KCM) 是一種機制,它為通用應用程式協議提供基於訊息的 TCP 介面。 透過 KCM,應用程式可以使用資料報套接字有效地透過 TCP 傳送和接收應用程式協議訊息。

KCM 在核心中實現了一個 NxM 多路複用器,如下圖所示

+------------+   +------------+   +------------+   +------------+
| KCM socket |   | KCM socket |   | KCM socket |   | KCM socket |
+------------+   +------------+   +------------+   +------------+
    |                 |               |                |
    +-----------+     |               |     +----------+
                |     |               |     |
            +----------------------------------+
            |           Multiplexor            |
            +----------------------------------+
                |   |           |           |  |
    +---------+   |           |           |  ------------+
    |             |           |           |              |
+----------+  +----------+  +----------+  +----------+ +----------+
|  Psock   |  |  Psock   |  |  Psock   |  |  Psock   | |  Psock   |
+----------+  +----------+  +----------+  +----------+ +----------+
    |              |           |            |             |
+----------+  +----------+  +----------+  +----------+ +----------+
| TCP sock |  | TCP sock |  | TCP sock |  | TCP sock | | TCP sock |
+----------+  +----------+  +----------+  +----------+ +----------+

KCM 套接字

KCM 套接字為多路複用器提供了使用者介面。 繫結到多路複用器的所有 KCM 套接字都被認為具有相同的功能,並且不同套接字中的 I/O 操作可以並行完成,而無需使用者空間中執行緒之間的同步。

多路複用器

多路複用器提供訊息控制。 在傳送路徑中,在 KCM 套接字上寫入的訊息以原子方式在適當的 TCP 套接字上傳送。 類似地,在接收路徑中,訊息在每個 TCP 套接字 (Psock) 上構建,並且完整的訊息被引導到 KCM 套接字。

TCP 套接字 & Psocks

TCP 套接字可以繫結到 KCM 多路複用器。 為每個繫結的 TCP 套接字分配一個 Psock 結構,該結構儲存用於在接收時構造訊息的狀態以及 KCM 的其他連線特定資訊。

連線模式語義

每個多路複用器都假定所有連線的 TCP 連線都連線到同一目標,並且可以在傳送時使用不同的連線進行負載平衡。 可以使用正常的 send 和 recv 呼叫(包括 sendmmsg 和 recvmmsg)從 KCM 套接字傳送和接收訊息。

套接字型別

KCM 支援 SOCK_DGRAM 和 SOCK_SEQPACKET 套接字型別。

訊息劃分

訊息透過 TCP 流傳送,並帶有某種應用程式協議訊息格式,該格式通常包括一個幀訊息的標頭。 可以從應用程式協議標頭(通常只是一個簡單的長度欄位)推斷出接收到的訊息的長度。

必須解析 TCP 流才能確定訊息邊界。 Berkeley Packet Filter (BPF) 用於此。 將 TCP 套接字附加到多路複用器時,必須指定 BPF 程式。 該程式在開始接收新訊息時被呼叫,並提供一個包含到目前為止收到的位元組的 skbuff。 它解析訊息頭並返回訊息的長度。 鑑於此資訊,KCM 將構造指定長度的訊息並將其傳遞到 KCM 套接字。

TCP 套接字管理

當 TCP 套接字附加到 KCM 多路複用器時,資料就緒 (POLLIN) 和可用的寫入空間 (POLLOUT) 事件由多路複用器處理。 如果 TCP 套接字上發生狀態更改(斷開連線)或其他錯誤,則會在 TCP 套接字上釋出錯誤,以便發生 POLLERR 事件,並且 KCM 停止使用該套接字。 當應用程式收到 TCP 套接字的錯誤通知時,它應該從 KCM 分離該套接字,然後處理錯誤情況(典型的響應是關閉套接字並在必要時建立新連線)。

KCM 將最大接收訊息大小限制為附加 TCP 套接字上接收套接字緩衝區的大小(套接字緩衝區大小可以透過 SO_RCVBUF 設定)。 如果 BPF 程式報告的新訊息的長度大於此限制,則會在 TCP 套接字上釋出相應的錯誤 (EMSGSIZE)。 BPF 程式還可以強制執行最大訊息大小,並在超出該大小時報告錯誤。

可以為在接收套接字上組裝訊息設定超時。 超時值取自附加 TCP 套接字的接收超時(這由 SO_RCVTIMEO 設定)。 如果在組裝完成之前計時器到期,則會在套接字上釋出錯誤 (ETIMEDOUT)。

使用者介面

建立多路複用器

透過套接字呼叫建立新的多路複用器和初始 KCM 套接字

socket(AF_KCM, type, protocol)
  • type 是 SOCK_DGRAM 或 SOCK_SEQPACKET

  • protocol 是 KCMPROTO_CONNECTED

克隆 KCM 套接字

在使用上述套接字呼叫建立第一個 KCM 套接字後,可以透過克隆 KCM 套接字來為多路複用器建立額外的套接字。 這是透過 KCM 套接字上的 ioctl 來完成的

/* From linux/kcm.h */
struct kcm_clone {
      int fd;
};

struct kcm_clone info;

memset(&info, 0, sizeof(info));

err = ioctl(kcmfd, SIOCKCMCLONE, &info);

if (!err)
  newkcmfd = info.fd;

附加傳輸套接字

透過在多路複用器的 KCM 套接字上呼叫 ioctl 來執行將傳輸套接字附加到多路複用器。 例如

/* From linux/kcm.h */
struct kcm_attach {
      int fd;
      int bpf_fd;
};

struct kcm_attach info;

memset(&info, 0, sizeof(info));

info.fd = tcpfd;
info.bpf_fd = bpf_prog_fd;

ioctl(kcmfd, SIOCKCMATTACH, &info);

kcm_attach 結構包含

  • fd:正在附加的 TCP 套接字的檔案描述符

  • bpf_prog_fd:已下載的已編譯 BPF 程式的檔案描述符

分離傳輸套接字

從多路複用器分離傳輸套接字非常簡單。 使用 kcm_unattach 結構作為引數完成“分離” ioctl

/* From linux/kcm.h */
struct kcm_unattach {
      int fd;
};

struct kcm_unattach info;

memset(&info, 0, sizeof(info));

info.fd = cfd;

ioctl(fd, SIOCKCMUNATTACH, &info);

停用 KCM 套接字上的接收

使用 setsockopt 來停用或啟用 KCM 套接字上的接收。 停用接收時,套接字的接收緩衝區中的任何掛起訊息都會移動到其他套接字。 如果應用程式執行緒知道它將對一個請求執行大量工作並且暫時無法為新訊息提供服務,則此功能非常有用。 使用示例

int val = 1;

setsockopt(kcmfd, SOL_KCM, KCM_RECV_DISABLE, &val, sizeof(val))

用於訊息劃分的 BPF 程式

可以使用 BPF LLVM 後端編譯 BPF 程式。 例如,用於解析 Thrift 的 BPF 程式是

#include "bpf.h" /* for __sk_buff */
#include "bpf_helpers.h" /* for load_word intrinsic */

SEC("socket_kcm")
int bpf_prog1(struct __sk_buff *skb)
{
     return load_word(skb, 0) + 4;
}

char _license[] SEC("license") = "GPL";

在應用程式中使用

KCM 加速應用層協議。 具體來說,它允許應用程式使用基於訊息的介面來發送和接收訊息。 核心提供了必要的保證,即訊息以原子方式傳送和接收。 這減輕了應用程式在將基於訊息的協議對映到 TCP 流上的許多負擔。 KCM 還使應用層訊息成為核心中的工作單元,用於控制和排程,這反過來又允許在多執行緒應用程式中使用更簡單的網路模型。

配置

在 Nx1 配置中,KCM 在邏輯上為同一 TCP 連線提供多個套接字控制代碼。 這允許 TCP 套接字上的 I/O 操作之間存在並行性(例如,資料的 copyin 和 copyout 是並行化的)。 在應用程式中,可以為每個處理執行緒開啟一個 KCM 套接字並將其插入 epoll 中(類似於使用 SO_REUSEPORT 來允許同一埠上的多個偵聽器套接字)。

在 MxN 配置中,與同一目標建立多個連線。 這些用於簡單的負載平衡。

訊息批處理

KCM 的主要目的是在 KCM 套接字和執行緒之間進行負載平衡,因此在正常用例中。 完美的負載平衡(即將接收到的每條訊息引導到不同的 KCM 套接字,或者將每條傳送的訊息引導到不同的 TCP 套接字)可能會對效能產生負面影響,因為這不允許建立親和性。 基於組或批次訊息進行平衡可能有利於效能。

在傳送時,應用程式可以透過三種方式在 KCM 套接字上批次處理(流水線化)訊息。

  1. 在單個 sendmmsg 中傳送多條訊息。

  2. 傳送一組訊息,每條訊息都有一個 sendmsg 呼叫,其中除最後一條訊息外的所有訊息在 sendmsg 呼叫的標誌中都有 MSG_BATCH。

  3. 建立由多條訊息組成的“超級訊息”,並使用單個 sendmsg 傳送它。

在接收時,KCM 模組嘗試在每個 TCP 就緒回撥期間將同一 KCM 套接字上接收到的訊息排隊。 目標 KCM 套接字在 KCM 套接字上的每次接收就緒回撥時都會更改。 應用程式不需要配置此項。

錯誤處理

應用程式應包含一個執行緒來監視 TCP 連線上引發的錯誤。 通常,這將透過將附加到 KCM 多路複用器的每個 TCP 套接字放置在 epoll 中設定為 POLLERR 事件來完成。 如果附加的 TCP 套接字上發生錯誤,KCM 會在該套接字上設定 EPIPE,從而喚醒應用程式執行緒。 當應用程式看到錯誤(可能只是斷開連線)時,它應該從 KCM 中分離該套接字,然後將其關閉。 假設一旦在 TCP 套接字上釋出了錯誤,資料流就無法恢復(即,可能在接收訊息的中間發生了錯誤)。

TCP 連線監控

在 KCM 中,沒有辦法將訊息與用於傳送或接收該訊息的 TCP 套接字相關聯(除非只有連線了一個 TCP 套接字)。 但是,應用程式確實保留了套接字的開啟檔案描述符,因此它將能夠從套接字獲取統計資訊,這些資訊可用於檢測問題(例如套接字上的高重傳)。