之前看libpcap/tcpdump源码的时候就看过部分kernel/net部分代码。上周日偶然间看到刚出版不久的《深入理解Linux网络》这本书,而作者又恰好是我当时阅读libpcap参考文章的公众号维护者,简单看了一下目录果断准备下单(这次第一次尝试学校图书馆的借购功能,别说还真方便,直接平台下单收货看了还给图书馆就行)。
四五天时间看完了,也把kernel/net部分代码再更深入地看了一下,也看到书上的一点问题,已经通过微信告知作者。总体上来说是本不错的书籍,我也收获良多。每章前面都会提出一些问题,通过一章的源码及原理解读,然后在章节末尾给出答案,大部分问题是随着看书获得解答的,但是有一些问题有些宽泛或者吸引眼球之嫌。大部分章节是值得看的,但是也有部分章节存在水篇幅之嫌,假如想要快速浏览,建议看书本插图以及每章总结即可。无论怎么说这是一本优质的书(否则也不可能短短几月售出上万册),插图质量也甚是不错。
那我也以书籍章节的逻辑进行本文布局吧。
内核收包
|
|
注册per-CPU queues softnet_data
注册软中断处理函数到softirq_vec 后续根据软中断类型进行不同处理
|
|
注册协议
inet_add_protocol将协议注册到inet_protos数组
dev_add_pack将ip_packet_type添加到ptype_base哈希表
其中添加逻辑是packet_type==ETH_P_ALL久添加到ptype_all 这也是libpcap抓包原理
|
|
注册网卡驱动igb_probe使网卡ready
注册igb_netdev_ops到netdev_ops,napi添加igb_poll
igb_netdev_ops中包含igb_open 其初始化资源如创建ringbuffer,注册中断处理函数igb_msix_ring 为每个队列设置单独MSI-X中断,启用napi等
|
|
当数据到时
igb_msix_ring
__raise_softirq_irqoff(NET_RX_SOFTIRQ)
__do_softirq
net_rx_action
napi_poll
igb_poll
igb_clean_rx_irq
napi_gro_receive->napi_skb_finish->gro_normal_one->gro_normal_list->netif_receive_skb_list_internal->__netif_receive_skb_list->__netif_receive_skb_list_core->__netif_receive_skb_core
deliver_skb
ip_rcv
ip_rcv_finish->dst_input->ip_local_deliver->ip_local_deliver_finish->ip_protocol_deliver_rcu->INDIRECT_CALL_2(ipprot->handler, tcp_v4_rcv, udp_rcv, skb);
tcp_v4_rcv
|
|
BIO/NIO
BIO
recvfrom有数据从sk_receive_queue接受数据 没数据就放入sk_wq
recvfrom->inet_recvmsg->tcp_recvmsg->sk_wait_data->sk_sleep(sk)阻塞
|
|
tcp_v4_rcv时tcp_rcv_established一边保存数据到sk_receive_queue一边sk_data_ready唤醒sk_wq的等待进程
|
|
NIO
本质上epoll和阻塞io不同在于一个wake epoll wq中进程一个wake socket wq中进程
epoll内部维持rb_tree维持众多socket ,rdllist 和 wq是就绪队列和等待队列,每个项有func进行wake private指向的进程,rb_entry中的socket收到数据func是ep_call_back不需要唤醒socket对应进程所以private指向null,epoll wq需要唤醒epoll的进程所以其是default_wake_func 唤醒private指向current本身进程。其本质是将多个socket进行封装交由一个进程管理从而减少上下文切换造成的性能损耗。
|
|
epoll_ctl Add添加socket并将其func设置成ep_poll_callback,因为在default_wake_function中是根据entry的private唤醒对应进程而ep_poll_callback只是通知epoll所以不需要设置这个entry的private
|
|
epoll_wait从epoll->rdllist中取就绪socket,如果没有就创建等待事件添加到epoll->wq,然后epoll_wait进程阻塞
|
|
内核发包
inet_sendmsg
sk->sock_prot->sendmsg
tcp_sendmsg
icsk->icsk_af_ops->queue_xmit(xkb)
ip_queue_xmit
ip_local_out
ip_finish_output2
dst_neigh_output
neigh_hh_output
dev_queue_xmit
dev_hard_start_xmit
igb_xmit_frame
igb_xmit_frame_ring
net_rx_action
igb_poll
igb_clean_tx_irq
|
|
lo
lo也是正常path走一遍网络栈
与正常dev相比唯一不同的就是收到数据不再硬中断而是直接软中断收包
正常设备MTU1500 lo是虚拟设备MTU65536 可以减少分包减少资源消耗
|
|
路由表其实本质就是找到网络设备
fib_lookup查找路由表设置dev 输入到lo
|
|
tcp
连接建立
listen初始化全连接队列和半连接队列(两者长度有限)
accept从全连接队列上摘下
connect发送syn包 server假如半连接队列已满则drop 否则发送synack并加入半连接队列
收到youngack,加入全连接已满则drop
都是先改变状态再发包的
|
|
内存消耗
ESTABLISHED 3.3k
TIME_WAIT 0.3k
4G 100w
最大tcp连接数
|
|
容器网络
进程nsproxy
CLONE_NEWNET
bridge 走 iptables 135找nat表从一个端口送到另一个端口
|
|