時間戳

1. 控制介面

用於接收網路包時間戳的介面包括

SO_TIMESTAMP

為每個傳入資料包生成一個時間戳,該時間戳以(不一定是單調的)系統時間表示。透過 recvmsg() 在控制訊息中以微秒解析度報告時間戳。SO_TIMESTAMP 根據架構型別和 libc 的 time_t 表示定義為 SO_TIMESTAMP_NEW 或 SO_TIMESTAMP_OLD。對於 SO_TIMESTAMP_OLD,控制訊息格式為 struct __kernel_old_timeval,對於 SO_TIMESTAMP_NEW 選項,控制訊息格式為 struct __kernel_sock_timeval。

SO_TIMESTAMPNS

與 SO_TIMESTAMP 相同的時間戳機制,但以 struct timespec 形式,以納秒解析度報告時間戳。SO_TIMESTAMPNS 根據架構型別和 libc 的 time_t 表示定義為 SO_TIMESTAMPNS_NEW 或 SO_TIMESTAMPNS_OLD。對於 SO_TIMESTAMPNS_OLD,控制訊息格式為 struct timespec,對於 SO_TIMESTAMPNS_NEW 選項,控制訊息格式為 struct __kernel_timespec。

IP_MULTICAST_LOOP + SO_TIMESTAMP[NS]

僅用於多播:透過讀取環回資料包接收時間戳獲得的近似傳輸時間戳。

SO_TIMESTAMPING

在接收、傳輸或兩者上生成時間戳。支援多個時間戳源,包括硬體。支援為流套接字生成時間戳。

1.1 SO_TIMESTAMP(也包括 SO_TIMESTAMP_OLD 和 SO_TIMESTAMP_NEW)

此套接字選項啟用資料報在接收路徑上的時間戳。由於目標套接字(如果有)在網路堆疊中無法提前知曉,因此必須為所有資料包啟用此功能。所有早期接收時間戳選項也是如此。

有關介面詳細資訊,請參閱 man 7 socket

始終使用 SO_TIMESTAMP_NEW 時間戳,以始終獲取 struct __kernel_sock_timeval 格式的時間戳。

SO_TIMESTAMP_OLD 在 32 位機器上 2038 年後返回不正確的時間戳。

1.2 SO_TIMESTAMPNS(也包括 SO_TIMESTAMPNS_OLD 和 SO_TIMESTAMPNS_NEW)

此選項與 SO_TIMESTAMP 相同,只是返回的資料型別不同。其 struct timespec 允許比 SO_TIMESTAMP (ms) 的 timeval 更高的解析度 (ns) 時間戳。

始終使用 SO_TIMESTAMPNS_NEW 時間戳,以始終獲取 struct __kernel_timespec 格式的時間戳。

SO_TIMESTAMPNS_OLD 在 32 位機器上 2038 年後返回不正確的時間戳。

1.3 SO_TIMESTAMPING(也包括 SO_TIMESTAMPING_OLD 和 SO_TIMESTAMPING_NEW)

支援多種型別的時間戳請求。因此,此套接字選項採用標誌點陣圖,而不是布林值。在

err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val));

val 是一個整數,其中設定了以下任何位。設定其他位將返回 EINVAL,並且不會更改當前狀態。

套接字選項配置單個 sk_buffs (1.3.1) 的時間戳生成、到套接字錯誤佇列的時間戳報告 (1.3.2) 和選項 (1.3.3)。還可以使用 cmsg (1.3.4) 為單個 sendmsg 呼叫啟用時間戳生成。

1.3.1 時間戳生成

某些位是堆疊嘗試生成時間戳的請求。它們的任何組合都是有效的。對這些位的更改適用於新建立的資料包,而不適用於堆疊中已有的資料包。因此,可以透過在兩個 setsockopt 呼叫中嵌入一個 send() 呼叫,一個用於啟用時間戳生成,另一個用於停用時間戳生成,從而有選擇地請求資料包子集的時間戳(例如,用於取樣)。時間戳的生成也可能是出於特定套接字請求之外的原因,例如,如前所述,系統範圍啟用了接收時間戳時。

SOF_TIMESTAMPING_RX_HARDWARE

請求由網路介面卡生成的 rx 時間戳。

SOF_TIMESTAMPING_RX_SOFTWARE

請求資料進入核心時生成的 rx 時間戳。這些時間戳是在裝置驅動程式將資料包交給核心接收堆疊後立即生成的。

SOF_TIMESTAMPING_TX_HARDWARE

請求由網路介面卡生成的 tx 時間戳。可以透過套接字選項和控制訊息啟用此標誌。

SOF_TIMESTAMPING_TX_SOFTWARE

請求資料離開核心時生成的 tx 時間戳。這些時間戳是在裝置驅動程式中儘可能接近網路介面的情況下生成的,但始終在其之前。因此,它們需要驅動程式支援,並且可能並非適用於所有裝置。可以透過套接字選項和控制訊息啟用此標誌。

SOF_TIMESTAMPING_TX_SCHED

請求在進入資料包排程程式之前生成的 tx 時間戳。核心傳輸延遲(如果時間很長)通常由排隊延遲決定。此時間戳與在 SOF_TIMESTAMPING_TX_SOFTWARE 處獲取的時間戳之間的差異將顯示此延遲,而與協議處理無關。協議處理中產生的任何延遲都可以透過從此時間戳中減去在 send() 之前立即獲取的使用者空間時間戳來計算。在具有虛擬裝置的機器上,傳輸的資料包會透過多個裝置,從而透過多個數據包排程程式,每個層都會生成一個時間戳。這允許對排隊延遲進行細粒度測量。可以透過套接字選項和控制訊息啟用此標誌。

SOF_TIMESTAMPING_TX_ACK

請求傳送緩衝區中的所有資料都已確認時生成的 tx 時間戳。這僅對可靠協議有意義。目前僅針對 TCP 實施。對於該協議,它可能會過度報告測量值,因為時間戳是在確認 send() 時的緩衝區及其之前的所有資料時生成的:累計確認。該機制忽略 SACK 和 FACK。可以透過套接字選項和控制訊息啟用此標誌。

SOF_TIMESTAMPING_TX_COMPLETION

請求資料包 tx 完成時生成的 tx 時間戳。完成時間戳由核心在收到來自硬體的資料包完成報告時生成。硬體可能會一次報告多個數據包,並且完成時間戳反映報告的計時,而不是實際的 tx 時間。可以透過套接字選項和控制訊息啟用此標誌。

1.3.2 時間戳報告

其他三個位控制將在生成的控制訊息中報告哪些時間戳。對這些位的更改會在堆疊中的時間戳報告位置立即生效。時間戳僅針對也設定了相關時間戳生成請求的資料包報告。

SOF_TIMESTAMPING_SOFTWARE

在可用時報告任何軟體時間戳。

SOF_TIMESTAMPING_SYS_HARDWARE

此選項已棄用且被忽略。

SOF_TIMESTAMPING_RAW_HARDWARE

在可用時,報告由 SOF_TIMESTAMPING_TX_HARDWARE 或 SOF_TIMESTAMPING_RX_HARDWARE 生成的硬體時間戳。

1.3.3 時間戳選項

該介面支援以下選項

SOF_TIMESTAMPING_OPT_ID

為每個資料包生成一個唯一識別符號。一個程序可以有多個併發的待處理時間戳請求。資料包可以在傳輸路徑中重新排序,例如在資料包排程程式中。在這種情況下,時間戳將以與原始 send() 呼叫不同的順序排隊到錯誤佇列中。並非總是可以僅基於時間戳順序或有效負載檢查將時間戳唯一地匹配到原始 send() 呼叫。

此選項將 send() 處的每個資料包與唯一識別符號相關聯,並將其與時間戳一起返回。識別符號是從每個套接字的 u32 計數器(迴圈)派生的。對於資料報套接字,計數器隨著每個傳送的資料包而遞增。對於流套接字,它隨著每個位元組而遞增。對於流套接字,還請設定 SOF_TIMESTAMPING_OPT_ID_TCP,請參閱下面的部分。

計數器從零開始。它在首次啟用套接字選項時初始化。每次在停用該選項後啟用該選項時,都會重置它。重置計數器不會更改系統中現有資料包的識別符號。

此選項僅針對傳輸時間戳實施。在那裡,時間戳始終與 struct sock_extended_err 一起迴圈。該選項修改欄位 ee_data 以傳遞一個 ID,該 ID 在該套接字的所有可能併發的待處理時間戳請求中是唯一的。

程序可以選擇透過控制訊息 SCM_TS_OPT_ID(TCP 套接字不支援)傳遞特定 ID 來覆蓋預設生成的 ID。

struct msghdr *msg;
...
cmsg                         = CMSG_FIRSTHDR(msg);
cmsg->cmsg_level             = SOL_SOCKET;
cmsg->cmsg_type              = SCM_TS_OPT_ID;
cmsg->cmsg_len               = CMSG_LEN(sizeof(__u32));
*((__u32 *) CMSG_DATA(cmsg)) = opt_id;
err = sendmsg(fd, msg, 0);
SOF_TIMESTAMPING_OPT_ID_TCP

將此修飾符與新的 TCP 時間戳應用程式的 SOF_TIMESTAMPING_OPT_ID 一起傳遞。SOF_TIMESTAMPING_OPT_ID 定義了流套接字的計數器如何遞增,但其起點並非完全微不足道。此選項修復了該問題。

對於流套接字,如果設定了 SOF_TIMESTAMPING_OPT_ID,則應始終設定此項。在資料報套接字上,該選項不起作用。

一個合理的預期是,計數器透過系統呼叫重置為零,因此後續 N 位元組的 write() 生成一個時間戳,計數器為 N-1。SOF_TIMESTAMPING_OPT_ID_TCP 在所有條件下都實施此行為。

SOF_TIMESTAMPING_OPT_ID 在沒有修飾符的情況下通常會報告相同的內容,尤其是在沒有資料傳輸時設定套接字選項時。如果正在傳輸資料,則可能會因輸出佇列 (SIOCOUTQ) 的長度而有所偏差。

差異是由於基於 snd_una 而不是 write_seq。snd_una 是對等方確認的流中的偏移量。這取決於程序控制之外的因素,例如網路 RTT。write_seq 是程序寫入的最後一個位元組。此偏移量不受外部輸入的影響。

當在初始套接字建立時配置時,這種差異是微妙的,不太可能被注意到,此時沒有資料排隊或傳送。但 SOF_TIMESTAMPING_OPT_ID_TCP 行為無論何時設定套接字選項都更強大。

SOF_TIMESTAMPING_OPT_CMSG

支援 recv() cmsg 以用於所有帶有時間戳的資料包。控制訊息已無條件地在所有具有接收時間戳的資料包和具有傳輸時間戳的 IPv6 資料包上支援。此選項將它們擴充套件到具有傳輸時間戳的 IPv4 資料包。一個用例是透過同時啟用套接字選項 IP_PKTINFO,將資料包與其出口裝置相關聯。

SOF_TIMESTAMPING_OPT_TSONLY

僅適用於傳輸時間戳。使核心將時間戳作為 cmsg 與空資料包一起返回,而不是與原始資料包一起返回。這減少了向套接字的接收預算 (SO_RCVBUF) 收取的記憶體量,並且即使 sysctl net.core.tstamp_allow_data 為 0,也會傳遞時間戳。此選項停用 SOF_TIMESTAMPING_OPT_CMSG。

SOF_TIMESTAMPING_OPT_STATS

與傳輸時間戳一起獲取的可選統計資訊。它必須與 SOF_TIMESTAMPING_OPT_TSONLY 一起使用。當傳輸時間戳可用時,統計資訊可在單獨的 SCM_TIMESTAMPING_OPT_STATS 型別的控制訊息中獲得,作為 TLV(struct nlattr)型別的列表。這些統計資訊允許應用程式將各種傳輸層統計資訊與傳輸時間戳相關聯,例如,某個資料塊被對等方接收視窗限制的時間。

SOF_TIMESTAMPING_OPT_PKTINFO

為帶有硬體時間戳的傳入資料包啟用 SCM_TIMESTAMPING_PKTINFO 控制訊息。該訊息包含 struct scm_ts_pktinfo,它提供接收資料包的真實介面的索引及其在第 2 層的長度。僅當啟用 CONFIG_NET_RX_BUSY_POLL 且驅動程式使用 NAPI 時,才會返回有效的(非零)介面索引。該結構還包含其他兩個欄位,但它們是保留的且未定義。

SOF_TIMESTAMPING_OPT_TX_SWHW

當 SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE 同時啟用時,請求傳出資料包的硬體和軟體時間戳。如果生成了兩個時間戳,則會將兩個單獨的訊息迴圈到套接字的錯誤佇列,每個訊息僅包含一個時間戳。

SOF_TIMESTAMPING_OPT_RX_FILTER

過濾掉虛假的接收時間戳:僅當啟用了匹配的時間戳生成標誌時,才報告接收時間戳。

接收時間戳在入口路徑的早期生成,在知曉資料包的目標套接字之前。如果任何套接字啟用了接收時間戳,則所有套接字的資料包都將收到帶有時間戳的資料包。包括那些使用 SOF_TIMESTAMPING_SOFTWARE 和/或 SOF_TIMESTAMPING_RAW_HARDWARE 請求時間戳報告,但不請求接收時間戳生成的套接字。當僅請求傳輸時間戳時,可能會發生這種情況。

接收虛假時間戳通常是良性的。程序可以忽略意外的非零值。但這使得行為微妙地依賴於其他套接字。此標誌隔離套接字以獲得更具確定性的行為。

鼓勵新應用程式傳遞 SOF_TIMESTAMPING_OPT_ID 以消除時間戳的歧義,並傳遞 SOF_TIMESTAMPING_OPT_TSONLY 以便與 sysctl net.core.tstamp_allow_data 的設定無關地執行。

例外情況是當程序需要額外的 cmsg 資料時,例如 SOL_IP/IP_PKTINFO 以檢測出口網路介面。然後傳遞選項 SOF_TIMESTAMPING_OPT_CMSG。此選項取決於對原始資料包內容的訪問許可權,因此無法與 SOF_TIMESTAMPING_OPT_TSONLY 組合使用。

1.3.4. 透過控制訊息啟用時間戳

除了套接字選項外,還可以透過 cmsg 為每次寫入請求時間戳生成,僅適用於 SOF_TIMESTAMPING_TX_*(請參閱第 1.3.1 節)。使用此功能,應用程式可以按 sendmsg() 對時間戳進行取樣,而無需支付透過 setsockopt 啟用和停用時間戳的開銷

struct msghdr *msg;
...
cmsg                         = CMSG_FIRSTHDR(msg);
cmsg->cmsg_level             = SOL_SOCKET;
cmsg->cmsg_type              = SO_TIMESTAMPING;
cmsg->cmsg_len               = CMSG_LEN(sizeof(__u32));
*((__u32 *) CMSG_DATA(cmsg)) = SOF_TIMESTAMPING_TX_SCHED |
                               SOF_TIMESTAMPING_TX_SOFTWARE |
                               SOF_TIMESTAMPING_TX_ACK;
err = sendmsg(fd, msg, 0);

透過 cmsg 設定的 SOF_TIMESTAMPING_TX_* 標誌將覆蓋透過 setsockopt 設定的 SOF_TIMESTAMPING_TX_* 標誌。

此外,應用程式仍必須透過 setsockopt 啟用時間戳報告才能接收時間戳

__u32 val = SOF_TIMESTAMPING_SOFTWARE |
            SOF_TIMESTAMPING_OPT_ID /* or any other flag */;
err = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &val, sizeof(val));

1.4 位元組流時間戳

SO_TIMESTAMPING 介面支援位元組流中的位元組時間戳。每個請求都解釋為請求緩衝區的全部內容何時透過時間戳點。也就是說,對於流選項 SOF_TIMESTAMPING_TX_SOFTWARE 將記錄所有位元組何時到達裝置驅動程式,而不管資料已轉換為多少個數據包。

通常,位元組流沒有自然的定界符,因此將時間戳與資料相關聯並非易事。一定範圍的位元組可能會在段之間拆分,任何段都可能會合並(可能會合並與獨立 send() 呼叫關聯的先前分段緩衝區的各個部分)。段可以重新排序,並且相同的位元組範圍可以在多個段中共存,以用於實施重傳的協議。

至關重要的是,所有時間戳都應實施相同的語義,而不管這些可能的轉換如何,否則它們將無法比較。以不同於簡單情況(從緩衝區到 skb 的 1:1 對映)的方式處理“罕見”的極端情況是不夠的,因為效能除錯通常需要關注此類異常值。

實際上,如果正確選擇時間戳的語義和測量時間,則時間戳可以始終如一地與位元組流的段相關聯。此挑戰與決定 IP 分片策略沒有什麼不同。在那裡,定義是僅對第一個片段進行時間戳。對於位元組流,我們選擇僅當所有位元組都透過一個點時才生成時間戳。如定義的 SOF_TIMESTAMPING_TX_ACK 易於實施和推理。由於可能的傳輸漏洞和亂序到達,必須考慮 SACK 的實施將更加複雜。

在主機上,由於 Nagle、cork、autocork、分段和 GSO,TCP 也可能會破壞從緩衝區到 skbuff 的簡單 1:1 對映。該實施透過跟蹤傳遞給 send() 的每個最後一個位元組來確保在所有情況下都正確,即使它在 skbuff 擴充套件或合併操作後不再是最後一個位元組。它將相關序列號儲存在 skb_shinfo(skb)->tskey 中。由於 skbuff 只有一個此類欄位,因此只能生成一個時間戳。

在極少數情況下,如果兩個請求摺疊到同一個 skb 上,則可能會錯過時間戳請求。程序可以透過啟用 SOF_TIMESTAMPING_OPT_ID 並比較傳送時的位元組偏移量與為每個時間戳返回的值來檢測這種情況。它可以透過始終重新整理請求之間的 TCP 堆疊來防止這種情況,例如,透過啟用 TCP_NODELAY 並停用 TCP_CORK 和 autocork。在 linux-4.7 之後,防止合併的更好方法是在 sendmsg() 時使用 MSG_EOR 標誌。

這些預防措施確保僅當所有位元組都透過時間戳點時才生成時間戳,前提是網路堆疊本身不重新排序段。堆疊確實嘗試避免重新排序。唯一例外情況是在管理員控制下:可以構建一個數據包排程程式配置,以不同方式延遲來自同一流的段。這樣的設定是不尋常的。

2 資料介面

時間戳是使用 recvmsg() 的輔助資料功能讀取的。有關此介面的詳細資訊,請參閱 man 3 cmsg。套接字手冊頁 (man 7 socket) 描述瞭如何檢索使用 SO_TIMESTAMP 和 SO_TIMESTAMPNS 記錄生成的時間戳。

2.1 SCM_TIMESTAMPING 記錄

這些時間戳在控制訊息中返回,其中 cmsg_level 為 SOL_SOCKET,cmsg_type 為 SCM_TIMESTAMPING,有效負載型別為

對於 SO_TIMESTAMPING_OLD

struct scm_timestamping {
        struct timespec ts[3];
};

對於 SO_TIMESTAMPING_NEW

struct scm_timestamping64 {
        struct __kernel_timespec ts[3];

始終使用 SO_TIMESTAMPING_NEW 時間戳,以始終獲取 struct scm_timestamping64 格式的時間戳。

SO_TIMESTAMPING_OLD 在 32 位機器上 2038 年後返回不正確的時間戳。

該結構最多可以返回三個時間戳。這是一個遺留功能。任何時候至少一個欄位為非零。大多數時間戳在 ts[0] 中傳遞。硬體時間戳在 ts[2] 中傳遞。

ts[1] 過去用於儲存轉換為系統時間的硬體時間戳。相反,直接在 NIC 上公開硬體時鐘裝置作為 HW PTP 時鐘源,以允許使用者空間中的時間轉換並可選擇地將系統時間與使用者空間 PTP 堆疊(例如 linuxptp)同步。對於 PTP 時鐘 API,請參閱 Linux 的 PTP 硬體時鐘基礎設施

請注意,如果在啟用 SOF_TIMESTAMPING_SOFTWARE 的情況下同時啟用 SO_TIMESTAMP 或 SO_TIMESTAMPNS 選項以及 SO_TIMESTAMPING,則在 recvmsg() 呼叫中將生成一個虛假的軟體時間戳,並在缺少真正的軟體時間戳時在 ts[0] 中傳遞。硬體傳輸時間戳也會發生這種情況。

2.1.1 帶有 MSG_ERRQUEUE 的傳輸時間戳

對於傳輸時間戳,傳出的資料包將迴圈回到套接字的錯誤佇列,並附加發送時間戳。程序透過呼叫 recvmsg() 並設定標誌 MSG_ERRQUEUE,以及具有足夠大的 msg_control 緩衝區以接收相關元資料結構來接收時間戳。recvmsg 呼叫返回帶有兩個附加輔助訊息的原始傳出資料包。

cm_level SOL_IP(V6) 和 cm_type IP(V6)_RECVERR 的訊息嵌入 struct sock_extended_err。這定義了錯誤型別。對於時間戳,ee_errno 欄位為 ENOMSG。另一個輔助訊息將具有 cm_level SOL_SOCKET 和 cm_type SCM_TIMESTAMPING。這嵌入了 struct scm_timestamping。

2.1.1.2 時間戳型別

三個 struct timespec 的語義由擴充套件錯誤結構中的欄位 ee_info 定義。它包含 SCM_TSTAMP_* 型別的值,以定義 scm_timestamping 中傳遞的實際時間戳。

SCM_TSTAMP_* 型別與前面討論的 SOF_TIMESTAMPING_* 控制欄位 1:1 匹配,但有一個例外。由於遺留原因,SCM_TSTAMP_SND 等於零,並且可以為 SOF_TIMESTAMPING_TX_HARDWARE 和 SOF_TIMESTAMPING_TX_SOFTWARE 設定。如果 ts[2] 非零,則為第一個;否則為第二個,在這種情況下,時間戳儲存在 ts[0] 中。

2.1.1.3 分片

傳出資料報的分片很少見,但有可能,例如,透過顯式停用 PMTU 發現。如果傳出資料包被分片,則只有第一個片段被加上時間戳並返回到傳送套接字。

2.1.1.4 資料包有效負載

呼叫應用程式通常不感興趣接收它最初傳遞給堆疊的整個資料包有效負載:套接字錯誤佇列機制只是一種在時間戳上搭載的方法。在這種情況下,應用程式可以選擇使用較小的緩衝區讀取資料報,甚至可能長度為 0。有效負載會相應地被截斷。但是,在程序在錯誤佇列上呼叫 recvmsg() 之前,完整的資料包會被排隊,佔用 SO_RCVBUF 的預算。

2.1.1.5 阻塞讀取

從錯誤佇列讀取始終是非阻塞操作。要阻塞等待時間戳,請使用 poll 或 select。如果在錯誤佇列上有任何資料準備就緒,poll() 將在 pollfd.revents 中返回 POLLERR。無需在 pollfd.events 中傳遞此標誌。此標誌在請求時被忽略。另請參閱 man 2 poll

2.1.2 接收時間戳

在接收時,沒有理由從套接字錯誤佇列讀取。SCM_TIMESTAMPING 輔助資料與資料包資料一起在正常的 recvmsg() 上傳送。由於這不是套接字錯誤,因此它沒有伴隨訊息 SOL_IP(V6)/IP(V6)_RECVERROR。在這種情況下,隱式定義了 struct scm_timestamping 中三個欄位的含義。如果設定了 ts[0],則它儲存一個軟體時間戳,ts[1] 再次被棄用,如果設定了 ts[2],則它儲存一個硬體時間戳。

3. 硬體時間戳配置:ETHTOOL_MSG_TSCONFIG_SET/GET

還必須為預計進行硬體時間戳的每個裝置驅動程式初始化硬體時間戳。該引數在 include/uapi/linux/net_tstamp.h 中定義為

struct hwtstamp_config {
        int flags;      /* no flags defined right now, must be zero */
        int tx_type;    /* HWTSTAMP_TX_* */
        int rx_filter;  /* HWTSTAMP_FILTER_* */
};

所需的行為透過呼叫 tsconfig netlink 套接字 ETHTOOL_MSG_TSCONFIG_SET 傳遞到核心和特定裝置。然後使用 ETHTOOL_A_TSCONFIG_TX_TYPESETHTOOL_A_TSCONFIG_RX_FILTERSETHTOOL_A_TSCONFIG_HWTSTAMP_FLAGS netlink 屬性相應地設定 struct hwtstamp_config。

ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER netlink 巢狀屬性用於選擇硬體時間戳的源。它由裝置源的索引和時間戳型別的限定符組成。

驅動程式可以自由使用比請求的配置更寬鬆的配置。預計驅動程式應僅直接實施可以支援的最通用的模式。例如,如果硬體可以支援 HWTSTAMP_FILTER_PTP_V2_EVENT,則它通常應始終升級 HWTSTAMP_FILTER_PTP_V2_L2_SYNC 等等,因為 HWTSTAMP_FILTER_PTP_V2_EVENT 更通用(並且對應用程式更有用)。

支援硬體時間戳的驅動程式應使用實際的(可能更寬鬆的)配置更新該結構。如果無法對請求的資料包進行時間戳,則不應更改任何內容,並且應返回 ERANGE(與 EINVAL 形成對比,後者表示根本不支援 SIOCSHWTSTAMP)。

只有具有管理員許可權的程序才能更改配置。使用者空間負責確保多個程序不會相互干擾並且設定已重置。

任何程序都可以透過請求 tsconfig netlink 套接字 ETHTOOL_MSG_TSCONFIG_GET 來讀取實際配置。

遺留配置是使用 ioctl(SIOCSHWTSTAMP) 並指向 struct ifreq 的指標,其 ifr_data 指向 struct hwtstamp_config。tx_type 和 rx_filter 提示驅動程式它應該做什麼。如果不支援對傳入資料包的請求的細粒度過濾,則驅動程式可能會對不僅僅是請求的資料包型別進行時間戳。ioctl(SIOCGHWTSTAMP) 的使用方式與 ioctl(SIOCSHWTSTAMP) 相同。但是,並非所有驅動程式都已實施此功能。

/* possible values for hwtstamp_config->tx_type */
enum {
        /*
        * no outgoing packet will need hardware time stamping;
        * should a packet arrive which asks for it, no hardware
        * time stamping will be done
        */
        HWTSTAMP_TX_OFF,

        /*
        * enables hardware time stamping for outgoing packets;
        * the sender of the packet decides which are to be
        * time stamped by setting SOF_TIMESTAMPING_TX_SOFTWARE
        * before sending the packet
        */
        HWTSTAMP_TX_ON,
};

/* possible values for hwtstamp_config->rx_filter */
enum {
        /* time stamp no incoming packet at all */
        HWTSTAMP_FILTER_NONE,

        /* time stamp any incoming packet */
        HWTSTAMP_FILTER_ALL,

        /* return value: time stamp all packets requested plus some others */
        HWTSTAMP_FILTER_SOME,

        /* PTP v1, UDP, any kind of event packet */
        HWTSTAMP_FILTER_PTP_V1_L4_EVENT,

        /* for the complete list of values, please check
        * the include file include/uapi/linux/net_tstamp.h
        */
};

3.1 硬體時間戳實施:裝置驅動程式

支援硬體時間戳的驅動程式必須支援 ndo_hwtstamp_set NDO 或遺留 SIOCSHWTSTAMP ioctl,並按照 SIOCSHWTSTAMP 部分中的描述使用實際值更新提供的 struct hwtstamp_config。它還應支援 ndo_hwtstamp_get 或遺留 SIOCGHWTSTAMP。

必須將接收資料包的時間戳儲存在 skb 中。要獲取指向 skb 的共享時間戳結構的指標,請呼叫 skb_hwtstamps()。然後在結構中設定時間戳

struct skb_shared_hwtstamps {
        /* hardware time stamp transformed into duration
        * since arbitrary point in time
        */
        ktime_t     hwtstamp;
};

傳出資料包的時間戳生成方式如下

  • 在 hard_start_xmit() 中,檢查是否設定了 (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) 非零。如果是,則驅動程式預計會進行硬體時間戳。

  • 如果這對於 skb 是可能的並且被請求,則透過在 skb_shinfo(skb)->tx_flags 中設定標誌 SKBTX_IN_PROGRESS 來宣告驅動程式正在進行時間戳,例如

    skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
    

    您可能想要保留指向關聯 skb 的指標以進行下一步,而不是釋放 skb。不支援硬體時間戳的驅動程式不會這樣做。驅動程式絕不能觸控 sk_buff::tstamp!它用於儲存網路子系統生成的軟體時間戳。

  • 驅動程式應儘可能接近地將 sk_buff 傳遞給硬體時呼叫 skb_tx_timestamp()skb_tx_timestamp() 提供軟體時間戳(如果請求且硬體時間戳不可用(未設定 SKBTX_IN_PROGRESS))。

  • 一旦驅動程式傳送了資料包和/或獲得了資料包的硬體時間戳,它就會透過呼叫 skb_tstamp_tx() 將時間戳傳遞回去,其中包含原始 skb 和原始硬體時間戳。skb_tstamp_tx() 克隆原始 skb 並新增時間戳,因此現在必須釋放原始 skb。如果獲取硬體時間戳以某種方式失敗,則驅動程式不應回退到軟體時間戳。原因是這會在處理管道中的較晚時間發生,因此可能會導致時間戳之間的意外增量。

3.2 堆疊 PTP 硬體時鐘的特殊注意事項

在資料包的資料路徑中可能存在多個 PHC(PTP 硬體時鐘)的情況下。核心沒有顯式機制來允許使用者選擇哪個 PHC 用於時間戳乙太網幀。相反,假設最外面的 PHC 始終是最可取的,並且核心驅動程式協作以實現該目標。目前有 3 種堆疊 PHC 的情況,詳述如下

3.2.1 DSA(分散式交換機架構)交換機

這些是乙太網交換機,其一個埠連線到(否則完全不知情的)主機乙太網介面,並執行具有可選轉發加速功能的埠倍增器的角色。每個 DSA 交換機埠對使用者來說都可見,就像一個獨立的(虛擬)網路介面,並且在底層,它的網路 I/O 是透過主機介面間接執行的(重定向到 TX 上的主機埠,並攔截 RX 上的幀)。

當 DSA 交換機連線到主機埠時,PTP 同步必須受到影響,因為交換機的可變排隊延遲會在主機埠及其 PTP 夥伴之間引入路徑延遲抖動。因此,某些 DSA 交換機包括它們自己的時間戳時鐘,並且能夠在它們自己的 MAC 上執行網路時間戳,這樣路徑延遲僅測量線路和 PHY 傳播延遲。Linux 支援時間戳 DSA 交換機,並公開與其他任何網路介面相同的 ABI(除了 DSA 介面在網路 I/O 方面實際上是虛擬的事實外,它們確實有自己的 PHC)。DSA 交換機的所有介面共享同一個 PHC 是典型的,但不是強制性的。

透過設計,使用 DSA 交換機進行 PTP 時間戳不需要在附加到的主機埠的驅動程式中進行任何特殊處理。但是,當主機埠也支援 PTP 時間戳時,DSA 將負責攔截 .ndo_eth_ioctl 對主機埠的呼叫,並阻止嘗試在其上啟用硬體時間戳。這是因為 SO_TIMESTAMPING API 不允許為同一個資料包傳遞多個硬體時間戳,因此必須防止 DSA 交換機埠以外的任何其他人這樣做。

在通用層中,DSA 為 PTP 時間戳提供以下基礎設施

  • .port_txtstamp():在從使用者空間傳輸帶有硬體 TX 時間戳請求的資料包之前呼叫的掛鉤。這是兩步時間戳所必需的,因為硬體時間戳在實際 MAC 傳輸之後變為可用,因此驅動程式必須準備好將時間戳與原始資料包相關聯,以便它可以將資料包重新排隊到套接字的錯誤佇列中。為了在時間戳可用時儲存資料包,驅動程式可以呼叫 skb_clone_sk,將克隆指標儲存在 skb->cb 中,並將 tx skb 佇列排隊。通常,交換機將具有 PTP TX 時間戳暫存器(或有時是 FIFO),其中時間戳變為可用。對於 FIFO,硬體可能會儲存 PTP 序列 ID/訊息型別/域號的鍵值對和實際時間戳。為了在等待時間戳的資料包佇列和實際時間戳之間正確執行關聯,驅動程式可以使用 BPF 分類器 (ptp_classify_raw) 來識別 PTP 傳輸型別,並使用 ptp_parse_header 來解釋 PTP 標頭欄位。可能有一個 IRQ 在此時間戳可用時引發,或者驅動程式可能需要在呼叫主機介面的 dev_queue_xmit() 之後進行輪詢。一步 TX 時間戳不需要資料包克隆,因為 PTP 協議不需要後續訊息(因為 TX 時間戳是由 MAC 嵌入到資料包中的),因此使用者空間不希望帶有 TX 時間戳註釋的資料包重新排隊到其套接字的錯誤佇列中。

  • .port_rxtstamp():在 RX 上,BPF 分類器由 DSA 執行以識別 PTP 事件訊息(任何其他資料包,包括 PTP 常規訊息,都不會被時間戳)。原始(也是唯一的)可時間戳 skb 提供給驅動程式,以便它可以使用時間戳對其進行註釋(如果該時間戳立即可用),或推遲到以後。在接收時,時間戳可能以帶內方式(透過 DSA 標頭中的元資料或以其他方式附加到資料包)或以帶外方式(透過另一個 RX 時間戳 FIFO)提供。當檢索時間戳需要可睡眠上下文時,通常需要 RX 上的延遲。在這種情況下,DSA 驅動程式有責任在新時間戳的 skb 上呼叫 netif_rx()

3.2.2 乙太網 PHY

這些裝置通常在網路堆疊中扮演第 1 層角色,因此不像 DSA 交換機那樣具有網路介面表示。但是,由於效能原因,PHY 可能能夠檢測和時間戳 PTP 資料包:儘可能靠近線路獲取的時間戳有可能產生更穩定和精確的同步。

支援 PTP 時間戳的 PHY 驅動程式必須建立一個 struct mii_timestamper 並在 phydev->mii_ts 中新增一個指向它的指標。網路堆疊將檢查此指標是否存在。

由於 PHY 沒有網路介面表示,因此它們的時間戳和 ethtool ioctl 操作需要由它們各自的 MAC 驅動程式來協調。因此,與 DSA 交換機相反,需要對每個 MAC 驅動程式進行修改才能支援 PHY 時間戳。這包括

  • .ndo_eth_ioctl 中檢查 phy_has_hwtstamp(netdev->phydev) 是否為真。如果是,則 MAC 驅動程式不應處理此請求,而是應使用 phy_mii_ioctl() 將其傳遞給 PHY。

  • 在 RX 上,可能需要也可能不需要特殊干預,具體取決於用於將 skb 傳遞到網路堆疊的函式。在普通 netif_rx() 和類似函式的情況下,MAC 驅動程式必須檢查 skb_defer_rx_timestamp(skb) 是否是必要的 - 如果是,則根本不呼叫 netif_rx()。如果啟用了 CONFIG_NETWORK_PHY_TIMESTAMPING 並且 skb->dev->phydev->mii_ts 存在,則其 .rxtstamp() hook 將立即被呼叫,以使用與 DSA 非常相似的邏輯來確定 RX 時間戳延遲是否是必要的。同樣像 DSA 一樣,當時間戳可用時,PHY 驅動程式有責任將資料包傳送到堆疊。

    對於其他 skb 接收函式,例如 napi_gro_receivenetif_receive_skb,堆疊會自動檢查 skb_defer_rx_timestamp() 是否必要,因此驅動程式內部不需要此檢查。

  • 在 TX 上,同樣,可能需要也可能不需要特殊干預。呼叫 mii_ts->txtstamp() hook 的函式名為 skb_clone_tx_timestamp()。可以直接呼叫此函式(在這種情況下,確實需要顯式的 MAC 驅動程式支援),但是該函式也從 skb_tx_timestamp() 呼叫中借用,許多 MAC 驅動程式已經出於軟體時間戳目的執行此呼叫。因此,如果 MAC 支援軟體時間戳,則在此階段無需執行任何進一步操作。

3.2.3 MII 匯流排偵聽裝置

這些裝置執行與時間戳 Ethernet PHY 相同的作用,不同之處在於它們是離散裝置,因此可以與任何 PHY 結合使用,即使它不支援時間戳。在 Linux 中,它們可以透過裝置樹發現並附加到 struct phy_device,其餘部分與那些裝置使用相同的 mii_ts 基礎設施。有關更多詳細資訊,請參見 Documentation/devicetree/bindings/ptp/timestamper.txt。

3.2.4 MAC 驅動程式的其他注意事項

堆疊 PHC 的使用可能會暴露在沒有它們的情況下無法觸發的 MAC 驅動程式錯誤。一個例子與這行程式碼有關,已經在前面介紹過

skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;

任何 TX 時間戳邏輯,無論是普通的 MAC 驅動程式、DSA 交換機驅動程式、PHY 驅動程式還是 MII 匯流排偵聽裝置驅動程式,都應設定此標誌。但是,不瞭解 PHC 堆疊的 MAC 驅動程式可能會被其他人而不是自己設定此標誌所絆倒,並傳遞重複的時間戳。例如,TX 時間戳的典型驅動程式設計可能是將傳輸部分分為 2 個部分

  1. “TX”:檢查是否已透過 .ndo_eth_ioctl(“priv->hwtstamp_tx_enabled == true”)先前啟用了 PTP 時間戳,以及當前 skb 是否需要 TX 時間戳(“skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP”)。如果為真,則設定 “skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS” 標誌。注意:如上所述,在堆疊的 PHC 系統的情況下,此條件永遠不應觸發,因為此 MAC 肯定不是最外層的 PHC。但這並不是典型問題所在。傳輸繼續進行此資料包。

  2. “TX 確認”:傳輸已完成。驅動程式檢查是否有必要為其收集任何 TX 時間戳。這是典型問題所在:MAC 驅動程式走捷徑,僅檢查是否設定了 “skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS”。使用堆疊的 PHC 系統,這是不正確的,因為此 MAC 驅動程式並不是 TX 資料路徑中唯一可以首先啟用 SKBTX_IN_PROGRESS 的實體。

此問題的正確解決方案是 MAC 驅動程式在其 “TX 確認” 部分中進行復合檢查,不僅要檢查 “skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS”,還要檢查 “priv->hwtstamp_tx_enabled == true”。因為系統的其餘部分確保 PTP 時間戳僅為最外層的 PHC 啟用,所以此增強的檢查將避免將重複的 TX 時間戳傳遞給使用者空間。