用xterm.js结合websocket, 做了一个路由器远程终端。最近发现这个远程终端体验很不好,有明显的卡顿和延时。
在web终端执行:watch -n1 ls
可以明显的感觉到卡顿,输出没有按每s输出。
起初以为是socket没有关闭nagle导致的,关闭nagle后,尝试关闭:
ret = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &ret, sizeof(ret));
关闭后,体验好了一些,但是体验还是不好,一个明显的例子是输入一个字符a后,很久都没有回显。于是尝试在路由器和pc上面抓包。
这是路由器上使用tcpdump抓包:
21:41:08.347888 IP 192.168.3.119.43760 > 192.168.3.1.80: Flags [P.], seq 7:14, ack 4, win 501, options [nop,n
op,TS val 3043609973 ecr 2095739433], length 7: HTTP
21:41:08.348160 IP 192.168.3.1.80 > 192.168.3.119.43760: Flags [.], ack 14, win 2019, options [nop,nop,TS val
2095782819 ecr 3043609973], length 0
21:41:08.348775 IP 192.168.3.1.80 > 192.168.3.119.43760: Flags [P.], seq 4:7, ack 14, win 2019, options [nop,
nop,TS val 2095782820 ecr 3043609973], length 3: HTTP
21:41:16.942421 IP 192.168.3.119.43760 > 192.168.3.1.80: Flags [.], ack 7, win 501, options [nop,nop,TS val 3
043618567 ecr 2095782820], length 0
可以看到08.347的时候,收到了pc发过来的字符。08.348回了一个ack. 紧跟着08.348775的时候回复了回显字符。
但是直到16s的时候,才收到pc的ack。
在看pc上的抓包,可以看到第3个包是路由器发送的回显,迟到了8分钟。收到回显后,立即回复了ack.
那这个回显包到底在哪里了呢。是路由器没发出去,还是pc缓存了。我尝试杀掉路由器的websocket进程,回显立即发送了。
这也不好断定是pc和路由器的问题。尝试换一个pc有线网卡,还是有问题。
我开始怀疑是路由器的有线驱动的问题,因为我改过有线驱动的代码。于是pc无线接入路由器,再次测试发现无比的丝滑。
这样我就断定是路由器有线驱动的问题了。那个回显包应该是在有线驱动中,没有丢,但是也没有发出去,当有后续包的时候
这个包就被驱动发出去了。
这让我有联想到之前,路由器上使用wget下载固件,也概率性失败,那应该就是同一个问题了。
阅读有线驱动代码,在tx和释放的时候加上打印,尝试复现:
[597886.153959] put skb 654588a9 to index 119 desc 2
。。。
[597898.404994] put skb d75d9860 to index 121 desc 1
[597898.405076] free skb 654588a9 index 119 desc 2
很快就复现了,可以看到 654588a9 这个skb tx之后,迟迟没有释放,直到2s后,有另外一个包tx,才释放。
这个是带碎片的tx,需要多个desc。阅读代码很快发现了问题。
@@ -200,6 +203,8 @@ int syn_dp_tx_nr_frags(struct syn_dp_info_tx *tx_info, struct sk_buff *skb)
*/
first_desc->status |= (DESC_OWN_BY_DMA | ((skb->ip_summed == CHECKSUM_PARTIAL) ? DESC_TX_CIS_TCP_PSEUDO_CS : 0));
+ wmb();
+
syn_resume_dma_tx(tx_info->mac_base);
原来的代码,修改desc的status,唤醒dma。缺少write memory barrier。因为我们要保证唤醒之前,status已经生效了。
修改之后,编译有线驱动,上传重新安装,发现xterm变的异常丝滑,快速刷新页面,也没有任何卡顿。