linux内核网络收包过程—UDP协议处理
创建ksoftirqd线程,用来处理软中断协议栈注册,如ip,udp,tcp网卡驱动初始化,将DMA准备好,把NAPI的poll函数地址告诉内核启动网卡,分配RX,TX队列,注册中断对应的函数数据到了后网卡将数据帧通过DMA搬运到内存的RingBuffer中,然后向CPU发出软中断CPU响应中断请求,调用网卡启动时注册的中断处理函数中断函数记录中断数等,基本没有做什么,发起软中断内核线程ksoft
·
目录
网络收包总结
在开始收包前
- 创建ksoftirqd线程,用来处理软中断;
- 协议栈注册,如ip,udp,tcp;
- 网卡驱动初始化,将DMA准备好,把NAPI的poll函数地址告诉内核;
- 启动网卡,分配RX,TX队列,注册中断对应的函数。
在数据到后
- 网卡将数据帧通过DMA搬运到内存的RingBuffer中,然后向CPU发出软中断;
- CPU响应中断请求,调用网卡启动时注册的中断处理函数;
- 中断函数记录中断数等,基本没有做什么,发起软中断;
- 内核线程ksoftirqd线程发现有软中断来,先关闭硬中断;
- ksoftirqd线程开始调用驱动的poll函数收包;
- poll函数将收到的包送到协议栈的ipc_rcv函数;
- ip_rcv函数将数据送到udp包送到udp_rcv,tcp包送到tcp_rcv。
UDP协议处理
udp接数据调用关系如下
int udp_rcv(struct sk_buff *skb)
{
return __udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP);
}
int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
int proto)
{
//根据 skb 来寻找对应的socket
sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);
if (sk)
return udp_unicast_rcv_skb(sk, skb, uh);
...
//如果没有找到,则发送⼀个⽬标不可达的 icmp 包
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
}
//udp_unicast_rcv_skb->udp_queue_rcv_skb
static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
struct sk_buff *next, *segs;
int ret;
if (likely(!udp_unexpected_gso(sk, skb)))
return udp_queue_rcv_one_skb(sk, skb);
BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_SGO_CB_OFFSET);
__skb_push(skb, -skb_mac_offset(skb));
segs = udp_rcv_segment(sk, skb, true);
skb_list_walk_safe(segs, skb, next) {
__skb_pull(skb, skb_transport_offset(skb));
ret = udp_queue_rcv_one_skb(sk, skb);
if (ret > 0)
ip_protocol_deliver_rcu(dev_net(skb->dev), skb, -ret);
}
return 0;
}
重要结构体关系图
socket、sock、proto、proto_ops关系图如下
recvfrom系统调用流程
数据接收与等待
struct sk_buff *__skb_recv_udp(struct sock *sk, unsigned int flags,
int noblock, int *off, int *err)
{
do {
struct sk_buff *skb;
do {
spin_lock_bh(&queue->lock);
skb = __skb_try_recv_from_queue(sk, queue, flags,
udp_skb_destructor,
off, err, &last);
...
sk_busy_loop(sk, flags & MSG_DONTWAIT);
} while (!skb_queue_empty_lockless(sk_queue));
} while (timeo &&
!__skb_wait_for_more_packets(sk, &sk->sk_receive_queue,
&error, &timeo,
(struct sk_buff *)sk_queue));
}
EXPORT_SYMBOL(__skb_recv_udp);
如果没有数据,且⽤户也允许等待,则将调⽤ __skb_wait_for_more_packets() 执⾏等待操作,它加⼊会让⽤户进程进⼊睡眠状态。
等待队列与入队列的关系待理解(Fixme)
参考
GitHub - yanfeizhang/coder-kung-fu: 开发内功修炼
更多推荐
已为社区贡献12条内容
所有评论(0)