道系统协议栈

socket 注册

创建sock和关联协议,之后recv 和send都用的这个sock。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sock = pal_sock (NSM_ZG, AF_INET, SOCK_RAW, IPPROTO_VRRP);
|
V
inet_create
case SOCK_RAW: /* init will come here */
if (!capable(CAP_NET_RAW))
goto free_and_badperm;
if (!protocol)
goto free_and_noproto;
...
//例如 raw_sendmsg
prot = &raw_prot;
sk->reuse = 1;
sk->ip_pmtudisc = IP_PMTUDISC_DONT;
sk->num = protocol;
...
//例如 inet_sendmsg
sock->ops = &inet_dgram_ops;
if (protocol == IPPROTO_RAW)
sk->ip_hdrincl = 1;

说明: socket的创建初始化了以下模块

例:收包inet_recvmsg 发包inet_sendmsg

收包流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
inet_recvmsg(af_inet.c)
|
V
raw_recvmsg(raw.c)
|
V
skb_recv_datagram(sk->receive_queue)queue


===========sk_recv_queue=========================


skb_queue_tail 存入queue
^
|
sock_queue_rcv_skb(sk->receive_queue)
^
|
raw_rcv_skb
^
|
raw_rcv
^
|
ip_local_deliver
^
|
skb->dst->input(skb)
^
|
ip_route_input -> ip_route_input_slow
^
|
ip_rcv <= ip_init_ict{dev_add_pack(&ip_packet_type)}
^
|
net_bh

===========msg(net_mq)=========================

mark_bh
^
|
netif_rx
^
|
hal_packet_rx

发包流程

传输层到网络层发送图

image-20220429092941156

邻居层到物理层

UDP之数据报发送过程 https://blog.csdn.net/wangquan1992/article/details/109164638

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
inet_sockraw_ops
|
V

raw_prot/udp_prot
{
道: ->ip_build_xmit->ip_output_ict
linux: raw_sendmsg ->ip_append_data
/*
1. 送的数据按照MTU大小分割成若干个方便IP处理的片段,每个片段一个skb;并且这些skb会放入到套接字的发送缓冲区中;
2. 该函数只是组织数据包,并不执行发送动作,如果要发送的报文中没有MORE,则需要由调用者主动调用ip_push_frames()
err = ip_append_data(sk, &fl4, raw_getfrag,
&rfv, len, 0,
&ipc, &rt, msg->msg_flags);
if (err)
ip_flush_pending_frames(sk);
else if (!(msg->msg_flags & MSG_MORE)) {
err = ip_push_pending_frames(sk, &fl4);
if (err == -ENOBUFS && !inet->recverr)
err = 0;
}
*/
ip_push_pending_frames
|
V
ip_send_skb -> ip_local_out -> dst_output -> {skb_dst(skb)->output(net, sk, skb);}
/*
如果是单播数据包,设置的是ip_output(),
如果是组播数据包,设置的是ip_mc_output().dev_queue_xmit

道这个指针是在raw_sendmsg->ip_route_output->ip_route_output_slow中指定的:
linux这个指针是在ip_route_output_flow->__ip_route_output_key->ip_route_output_slow中指定的:
rth->u.dst.output=ip_output;
ip_route_output的作用 便是查找路由,并将结果记录到skb->dst中。
同时通过rt_intern_hash->arp_bind_neighbours设置了dst->neighbout
*/
}

tcp_prot
{
tcp_sendmsg->tcp_sendmsg_locked -> tcp_push_one -> tcp_write_xmit -> tcp_transmit_skb
err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);
|
V
ip_queue_xmit
}
==============ip skb->sk_write_queue 道和新版linux有点区别=========================

//道
ip_queue_xmit
|
V
skb->dst->output = Ip_output_ict
|
V
ip_finish_output
|
V
dst->neighbout->output/hh->hh_output = dev_queue_xmit
|
V
dev->hard_start_xmit
|
V
ethVlanTx
|
V
serialDevHardXmit
|
V
hal_packet_tx



linux

ip_queue_xmit
|
V
ip_local_out
|
V
dst_output
|
V
ip_output
|
V
ip_finish_output
|
V
dst->neighbout->output/hh->hh_output
|
V
dev_queue_xmit -> dev_hard_start_xmit //没有队列
|
V
__dev_xmit_skb
|
V
看下面:软中断发送过程



dev_hard_start_xmit
|
V
xmit_one
|
V
netdev_start_xmit
|
V
ops->ndo_start_xmit (dm9000)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1. ip_route_output_slow 
设置路由缓存 后面用到的dst都是sk->dst_cache复制的

sk->dst_cache = &rt->u.dst
tcp_v4_connect -> ip_route_connect -> ip_route_output ->
ip_route_output_slow //创建rth->u.dst
{
rth->u.dst.output = ip_output_ict;
rth->u.dst.input = ip_local_deliver;
rt_intern_hash(vrfIndex, hash, rth, rp);
//arp_bind_neighbour dst->neighbout = dst->neighbout(&rt->u.dst);
}

/* 设置路由缓存 */
tcp_make_synack
{
/*我们的旧版本实现*/
skb->dst = dst_clone(dst);
/*新版本实现*/
skb_dst_set(skb, dst);
}


2. neighbout设置

dst->neighbout->output = dev_queue_xmit
arp_constructor(struct neighbour *neigh)
{
neigh->ops = &arp_direct_ops; //output = dev_queue_xmit
neigh->ops = &arp_hh_ops; //output = neigh_resolve_output
neigh->ops = &arp_generic_ops; //output = dev_queue_xmitt;
}

「干货」25 张图一万字,拆解 Linux 网络包发送过程(下)

https://baijiahao.baidu.com/s?id=1707608648230579710&wfr=spider&for=pc

软中断发送过程

image-20220408171722214

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//file: net/sched/sch_generic.c
void __qdisc_run(struct Qdisc *q)
{
int quota = weight_p;

//循环从队列取出一个 skb 并发送
while (qdisc_restart(q)) {

// 如果发生下面情况之一,则延后处理:
// 1. quota 用尽
// 2. 其他进程需要 CPU
if (--quota <= 0 || need_resched()) {
//将触发一次 NET_TX_SOFTIRQ 类型 softirq
__netif_schedule(q);
break;
}
}
}

while 循环不断地从队列中取出 skb 并进行发送。注意,这个时候其实都占用的是用户进程的系统态时间(sy)。只有当 quota 用尽或者其它进程需要 CPU 的时候才触发软中断进行发送。

image-20220408171722214

该函数会进入到 __netif_reschedule,由它来实际发出 NET_TX_SOFTIRQ 类型软中断。是通过软中断

open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

软中断

软中断的实现难机制很多,比如bottom half(下半部)、task queue,不过这两者已经被废弃,下面三种实现机制是现在kernel还支持的:

软中断(softirq):内核2.3引入,是最基本、最优先的软中断处理形式,为了避免名字冲突,本文中将这种子类型的软中断叫softirq。

tasklet:其底层使用softirq机制实现,提供了一种用户方便使用的软中方式,为软中断提供了很好的扩展性。

work queue:前两种软中断执行时是禁止抢占的(softirq的ksoftirq除外),对于用户进程不友好。如果在softirq执行时间过长,会继续推后到work queue中执行,work queue执行处于进程上下文,其可被抢占,也可以被调度,如果软中断需要执行睡眠、阻塞,直接选择work queue。

Linux的中断处理机制 : https://zhuanlan.zhihu.com/p/80371745

Linux内核浅析-软中断 :https://zhuanlan.zhihu.com/p/81742153

linux 中断子系统-softirq 的实现原理: https://zhuanlan.zhihu.com/p/363225717

s3c2440 linux中断过程:https://blog.csdn.net/oqqYuJi12345678/article/details/99726927

软中断的触发时机

1、irq_exit:在硬中断退出时,会检查local_softirq_pending和preemt_count,如果都符合条件,则执行软中断。

1
2
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();

2、local_bh_enable:使用此函数开启软中断时,会检查local_softirq_pending,如果都符合条件,则执行软中断。调用链为local_bh_enable()->__local_bh_enable()->do_softirq()。

3、raise_softirq:主动唤起一个软中断,会首先设置__softirq_pending对应的软中断位为挂起,然后检查in_interrupt,如果不在中断中,则唤起ksoftirq线程执行软中断(ksoftirq是softirq的一种执行机制,在软中的运行流程中会提到)。

软中断线程是在系统初始化时创建的,cpu几个核心就创建几个线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
smpboot_register_percpu_thread

smpboot_thread_fn

{

if (!ht->thread_should_run(td->cpu)) {

}else{

ht->thread_fn(td->cpu);

}

}

image-20220429092941156


道系统协议栈
https://dnsnat.gitee.io/NETWORK/道系统协议栈.html
作者
dnsnat
发布于
2022年5月12日
许可协议