大家好,今天我们用一张图总结下内核是如何发生网络包的,并且给大家解释下几个经常遇到的问题。话不多说,直接开干!
了解了整个发送过程以后,我们回头再来看几个问题。
1.
我们在监控内核发送数据消耗的
CPU
时,是应该看
sy
还是
si
?
在⽹络包的发送过程中,⽤户进程(在内核态)完成了绝⼤部分的⼯作,甚⾄连调⽤驱动的
事情都⼲了。 只有当内核态进程被切⾛前才会发起软中断。 发送过程中,绝⼤部分(
90%
)
以上的开销都是在⽤户进程内核态消耗掉的。
只有⼀少部分情况下才会触发软中断(
NET_TX
类型),由软中断
ksoftirqd
内核进程来发
送。
所以,在监控⽹络
IO
对服务器造成的
CPU
开销的时候,不能仅仅只看
si
,⽽是应该把
si
、
sy
都考虑进来。
2.
在服务器上查看
/proc/softirqs
,为什么
NET_RX
要⽐
NET_TX
⼤的多的多?
之前我认为
NET_RX
是读取,
NET_TX
是传输。对于⼀个既收取⽤户请求,⼜给⽤户返回的
Server
来说。 这两块的数字应该差不多才对,⾄少不会有数量级的差异。但事实上,我⼿
头的⼀台服务器是这样的:

经过今天的分析,发现这个问题的原因有两个。
第⼀个原因是当数据发送完成以后,通过硬中断的⽅式来通知驱动发送完毕。但是硬中断⽆
论是有数据接收,还是对于发送完毕,触发的软中断都是
NET_RX_SOFTIRQ
,⽽并不是
NET_TX_SOFTIRQ
。
第⼆个原因是对于读来说,都是要经过
NET_RX
软中断的,都⾛
ksoftirqd
内核进程。⽽对于
发送来说,绝⼤部分⼯作都是在⽤户进程内核态处理了,只有系统态配额⽤尽才会发出
NET_TX
,让软中断上。
综上两个原因,那么在机器上查看
NET_RX
⽐
NET_TX
⼤的多就不难理解了。
3.
发送⽹络数据的时候都涉及到哪些内存拷⻉操作?
这⾥的内存拷⻉,我们只特指待发送数据的内存拷⻉。
第⼀次拷⻉操作是内核申请完
skb
之后,这时候会将⽤户传递进来的
buffer
⾥的数据内容都
拷⻉到
skb
中。如果要发送的数据量⽐较⼤的话,这个拷⻉操作开销还是不⼩的。
第⼆次拷⻉操作是从传输层进⼊⽹络层的时候,每⼀个
skb
都会被克隆⼀个新的副本出来。
⽹络层以及下⾯的驱动、软中断等组件在发送完成的时候会将这个副本删除。传输层保存着
原始的
skb
,在当⽹络对⽅没有
ack
的时候,还可以重新发送,以实现
TCP
中要求的可靠传
输。
第三次拷⻉不是必须的,只有当
IP
层发现
skb
⼤于
MTU
时才需要进⾏。会再申请额外的
skb
,并将原来的
skb
拷⻉为多个⼩的
skb。
看到这⾥,相信内核发送数据包对于你来说,已经不再是⼀个完全不懂的⿊盒了。本⽂哪怕
你只看懂⼗分之⼀,你也已经掌握了这个⿊盒的打开⽅式。这在你将来优化⽹络性能时你就
会知道从哪⼉下⼿了。
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习: