SO_TIMESTAMP - 《Unix网络编程》中未提及的Socket选项
此时内核会在对应的 sock 结构体中标记 netstamp_need 为真,表示后续接收的每个 sk_buff(即网络帧缓冲)都需要打时间戳。
2. netif_receive_skb() 的角色
网络帧从网卡驱动进入内核后,通常在 NAPI(New API)轮询模型的 poll 函数中被处理。poll 完成对接收队列的读取后,会调用 netif_receive_skb() 将 sk_buff 递交给上层协议栈。
原文摘录
netif_receive_skb(),linux内核协议栈中的关键函数,通常在网卡驱动程序 poll 函数(RX 中断处理函数会调度 poll 函数,详情参考最新内核机制 NAPI)的最后一步调用,占们用来处理网络帧,并将网络帧递交至上层协议,而netif_receive_skb函数第一件要做的事就是调用net_timestamp,为当前收到的网络帧打时间戳(net_timestamp函数里会判断是否已经使能了网络时间戳功能,即netstamp_need),并将此时间戳作为一笔SCM_TIMESTAMP类型的附加数据插入sk_buff(即cmsg)。
3. net_timestamp() 打时间戳
net_timestamp() 检查 sock->sk->sk_stamp(或 netstamp_need)是否已启用。如果已启用,它会使用 ktime_get_real_ts64()(或等价的时间获取函数)获取当前系统时间,并把时间戳封装进 skb->tstamp。随后,skb 在向上层递交时会自动把该时间戳包装为 SCM_TIMESTAMP 控制消息。
4. 将时间戳附加到 sk_buff
在 skb 进入套接字接收队列的过程中,内核会调用 sock_cmsg_send()(或类似函数)把 SCM_TIMESTAMP 控制信息写入 sk_buff 的 ancillary data 区域。这样,当用户空间使用 recvmsg() 接收数据时,就可以通过 struct msghdr 中的 msg_control 缓冲区读取到这笔时间戳。
上层代码如何获取时间戳
1. 使用 recvmsg() 接收数据
recvmsg() 是唯一能够同时获取数据和控制信息的系统调用。示例代码如下: