标签:检索 work ssi routing 监听 node efi 邻居 取数据
socket 和文件系统都位于 VFS 下一层,对 socket 的操作都要经过VFS,下图为super_blocks和file_systems的链表结构图。
Linux 里面每个文件都有唯一的 inode ,inode 会大量使用,为了提高效率会对 inode 进行缓存。
VFS要调用具体的文件系统就需要知道每个文件系统的信息,这些信息都放在各自的超级块(super_block) 里,需要文件系统注册(register_filesystem)把自己挂到 VFS 的 file_systems 全局链表上,然后通过挂载(kern_mount)自己、将超级块告知 VFS。
内核使用 init.h 中定义的初始化宏来进行,即将初始化函数放入特定的代码段去执行:
core_initcall(sock_init);
相关的宏和初始化函数还包括:
1 core_initcall: sock_init
2 fs_initcall: inet_init
3 subsys_initcall: net_dev_init
4 device_initcall: 设备驱动初始化
上面四种宏声明的函数是按顺序执行的。
1 static int __init sock_init(void)
2 {
3 int err;
4 /*
5 * Initialize the network sysctl infrastructure.
6 */
7 err = net_sysctl_init();
8 if (err)
9 goto out;
10
11 /*
12 * Initialize skbuff SLAB cache
13 */
14 skb_init();
15
16 /*
17 * Initialize the protocols module.
18 */
19
20 init_inodecache();
21
22 err = register_filesystem(&sock_fs_type);
23 if (err)
24 goto out_fs;
25 sock_mnt = kern_mount(&sock_fs_type);
26 if (IS_ERR(sock_mnt)) {
27 err = PTR_ERR(sock_mnt);
28 goto out_mount;
29 }
30
31 /* The real protocol initialization is performed in later initcalls.
32 */
33
34 #ifdef CONFIG_NETFILTER
35 err = netfilter_init();
36 if (err)
37 goto out;
38 #endif
39
40 #ifdef CONFIG_NETWORK_PHY_TIMESTAMPING
41 skb_timestamping_init();
42 #endif
43
44 out:
45 return err;
46
47 out_mount:
48 unregister_filesystem(&sock_fs_type);
49 out_fs:
50 goto out;
51 }
sock_init() 可以分为 4 部分 : 初始化网络的系统调用(net_sysctl_init)、初始化 skb 缓存(skb_init)、初始化VFS相关(init_inodecache、 register_filesystem 、 kern_mount)、初始化网络过滤模块(netfilter_init)。
数据包在应用层称为 data,在 TCP 层称为 segment,在 IP 层称为 packet,在数据链路层称为 frame。 Linux 内核中 sk_buff 结构来存放数据。
sk_buff 结构体
1 struct sk_buff {
2 /* These two members must be first. */
3 struct sk_buff *next;
4 struct sk_buff *prev;
5 //首要的这两个字段是为了实现链表操作
6 ktime_t tstamp;
7 //记录时间戳,计算这个字段代价很大,所以必要的时候才设置
8
9 struct sock *sk;
10 //记录这个SKB关联的套接字,当在某一个套接字上收发一个packet时,与之相关的内存会得到分配
11 struct net_device *dev;
12 //具体的网络设备
13 /*
14 * This is the control buffer. It is free to use for every
15 * layer. Please put your private variables there. If you
16 * want to keep them across layers you have to do a skb_clone()
17 * first. This is owned by whoever has the skb queued ATM.
18 */
19 #ifdef CONFIG_AS_FASTPATH
20 char cb[96] __aligned(8);
21 //SKB控制块,该透明存储区用来存每个packet的私有信息,如TCP可以用来放序列号和帧的重传状态
22 #else
23 char cb[48] __aligned(8);
24 #endif
25 unsigned long _skb_refdst;
26 #ifdef CONFIG_XFRM
27 struct sec_path *sp;
28 #endif
29 unsigned int len,
30 data_len;
31 //SKB是由一个线性缓冲区和可选的页缓存构成,len是packet的总长度,data_len是页缓存中字节长度
32 //所以线性缓存中数据长度: skb->len - skb->data_len ,有函数 skb_headlen(skb)实现
33
34
35 __u16 mac_len,
36 hdr_len;
37 //mac_len通常没有必要维护,除非为了实现IP tunnel的IPSEC解分装过程
38 //‘netif_receive_skb()‘中通过 skb->nh.raw - skb->mac.raw 得到的
39 union {
40 __wsum csum;
41 struct {
42 __u16 csum_start;
43 __u16 csum_offset;
44 };
45 };
46 //校验和字段,如果网络设备具备计算csum功能的话,我们可以忽略
47 __u32 priority; //QoS优先级;
48 kmemcheck_bitfield_begin(flags1);
49 __u8 local_df:1,
50 cloned:1,
51 ip_summed:2,
52 nohdr:1,
53 nfctinfo:3;
54 __u8 pkt_type:3,
55 fclone:2,
56 ipvs_property:1,
57 peeked:1,
58 nf_trace:1;
59 kmemcheck_bitfield_end(flags1);
60 __be16 protocol;
61 //pkt_type包类型表示这个包发给谁,有PACKET_HOST,PACKET_BROADCAST,PACKET_MULTICAST,PACKET_OTHERHOST
62
63 void (*destructor)(struct sk_buff *skb);
64 #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
65 struct nf_conntrack *nfct;
66 #endif
67 #ifdef CONFIG_BRIDGE_NETFILTER
68 struct nf_bridge_info *nf_bridge;
69 #endif
70
71 int skb_iif;
72
73 __u32 rxhash;
74
75 __be16 vlan_proto;
76 __u16 vlan_tci;
77
78 #ifdef CONFIG_NET_SCHED
79 __u16 tc_index; /* traffic control index */
80 #ifdef CONFIG_NET_CLS_ACT
81 __u16 tc_verd; /* traffic control verdict */
82 #endif
83 #endif
84
85 __u16 queue_mapping;
86 kmemcheck_bitfield_begin(flags2);
87 #ifdef CONFIG_IPV6_NDISC_NODETYPE
88 __u8 ndisc_nodetype:2;
89 #endif
90 __u8 pfmemalloc:1;
91 __u8 ooo_okay:1;
92 __u8 l4_rxhash:1;
93 __u8 wifi_acked_valid:1;
94 __u8 wifi_acked:1;
95 __u8 no_fcs:1;
96 __u8 head_frag:1;
97 /* Encapsulation protocol and NIC drivers should use
98 * this flag to indicate to each other if the skb contains
99 * encapsulated packet or not and maybe use the inner packet
100 * headers if needed
101 */
102 __u8 encapsulation:1;
103 /* 6/8 bit hole (depending on ndisc_nodetype presence) */
104 kmemcheck_bitfield_end(flags2);
105
106 #if defined CONFIG_NET_DMA || defined CONFIG_NET_RX_BUSY_POLL
107 union {
108 unsigned int napi_id;
109 dma_cookie_t dma_cookie;
110 };
111 #endif
112 #ifdef CONFIG_NETWORK_SECMARK
113 __u32 secmark;
114 #endif
115 union {
116 __u32 mark;
117 __u32 dropcount;
118 __u32 reserved_tailroom;
119 };
120
121 __be16 inner_protocol;
122 __u16 inner_transport_header;
123 __u16 inner_network_header;
124 #if defined(CONFIG_GIANFAR) && defined(CONFIG_AS_FASTPATH)
125 __u8 owner;
126 struct sk_buff *new_skb;
127 #endif
128 __u16 inner_mac_header;
129 __u16 transport_header;
130 __u16 network_header;
131 __u16 mac_header;
132 //传输层,网络层,链路层协议头,这三个字段在逐层解析packet的时候会设置; sk_buff_data_t依赖具体硬件
133 /* These elements must be at the end, see alloc_skb() for details. */
134 sk_buff_data_t tail;
135 sk_buff_data_t end;
136 unsigned char *head,
137 *data;
138 unsigned int truesize;
139 atomic_t users;
140 };
其中几个主要的成员是 :
truct sk_buff *next; //sk_buff 是以链表组织起来的,需要知道前后两个 sk_buff 的位置
struct sk_buff *prev;
struct net_device *dev; //数据报所属的网络设备
unsigned int len, //全部数据的长度
data_len; //当前 sk_buff 的分片数据长度
__be16 protocol; //所属报文的协议类型
__u8 pkt_type:3; //该数据包的类型
unsigned char *data; //保存的数据
atomic_t users; //每引用或“克隆”一次 sk_buff 的时候,都自加 1
skb_init() 创建了两个缓存 skbuff_head_cache 和 skbuff_fclone_cache ,协议栈中所使用到的所有的 sk_buff 结构都是从这两个后备高速缓存中分配出来的,两者的区别在于前者是以sizeof(struct sk_buff)为单位创建的,是用来存放单纯的 sk_buff ,后者是以2 * sizeof(struct sk_buff)+sizeof(atomic_t)为单位创建的,这一对sk_buff是克隆的,即它们指向同一个数据缓冲区,引用计数值是 0,1 或 2, 表示这一对 中有几个sk_buff已被使用。
sk_buff 的使用,通过 alloc_skb()分配sk_buff,kfree_skb()销毁sk_buff。
网络通信可以被看作对文件的操作,socket 也是一种文件。网络初始化首先就要初始化 网络文件系统(sockfs)。
第一步是初始化 inode 缓冲(init_inodecache),为 sockfs 的 inode 分配一片高速缓存 :
1 static int init_inodecache(void)
2 {
3 sock_inode_cachep = kmem_cache_create("sock_inode_cache",
4 sizeof(struct socket_alloc),
5 0,
6 (SLAB_HWCACHE_ALIGN |
7 SLAB_RECLAIM_ACCOUNT |
8 SLAB_MEM_SPREAD),
9 init_once);
10 if (sock_inode_cachep == NULL)
11 return -ENOMEM;
12 return 0;
13 }
接着注册 sockfs 这种文件系统类型到 VFS 并将 sockfs 注册到 super_blocks :
1 ... 2 init_inodecache(); 3 4 err = register_filesystem(&sock_fs_type); 5 6 sock_mnt = kern_mount(&sock_fs_type); 7 ...
这样以后创建 socket 就是在 sockfs 文件系统里创建一个特殊的文件,而文件系统的 super_block 里面有一个成员变量 struct super_operations *s_op 记录了文件系统支持的操作函数,而这些操作函数都是让 VFS 来调用的,这样一来 socket 的表现就更像一个普通文件,支持大部分操作接口比如 write、read、close 等。
网络过滤模块初始化函数 netfilter_init() 主要做了两件事:注册网 netfilter 络模块到每个网络 namespace 和初始化日志(本质也是注册日志模块到每个网络 namespace) 。
1 int __init netfilter_init(void)
2 {
3 ...
4 ret = register_pernet_subsys(&netfilter_net_ops);
5 ...
6 ret = netfilter_log_init();
7 ...
8 }
按照上文所述的协议栈初始化顺序, 网络文件系统初始化(sock_init) 结束之后就开始进入网络协议栈初始化(由宏fs_initcall 修饰的inet_init),这才开始真正的网络协议的初始化。
注意,网络协议的初始化是在网络设备的初始化之前完成的,在Linux系统中并不是说网络设备不 存在就不需要网络协议了,而是在没有网络设备存在的时候,照样可以完成网络的工作,只不过网络系 统物理上只存在于本机一台机器中而已。
1 static int __init inet_init(void)
2 {
3 ...
4 sysctl_local_reserved_ports = kzalloc(65536 / 8, GFP_KERNEL);
5 if (!sysctl_local_reserved_ports)
6 goto out;
7
8 rc = proto_register(&tcp_prot, 1);
9 if (rc)
10 goto out_free_reserved_ports;
11
12 rc = proto_register(&udp_prot, 1);
13 if (rc)
14 goto out_unregister_tcp_proto;
15
16 rc = proto_register(&raw_prot, 1);
17 if (rc)
18 goto out_unregister_udp_proto;
19
20 rc = proto_register(&ping_prot, 1);
21 if (rc)
22 goto out_unregister_raw_proto;
23
24 (void)sock_register(&inet_family_ops);
25 ...
26 /*
27 * Add all the base protocols.
28 */
29
30 if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
31 pr_crit("%s: Cannot add ICMP protocol\n", __func__);
32 if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
33 pr_crit("%s: Cannot add UDP protocol\n", __func__);
34 if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
35 pr_crit("%s: Cannot add TCP protocol\n", __func__);
36 #ifdef CONFIG_IP_MULTICAST
37 if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
38 pr_crit("%s: Cannot add IGMP protocol\n", __func__);
39 #endif
40
41 /* Register the socket-side information for inet_create. */
42 for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
43 INIT_LIST_HEAD(r);
44
45 for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
46 inet_register_protosw(q);
47
48 /*
49 * Set the ARP module up
50 */
51
52 arp_init();
53
54 /*
55 * Set the IP module up
56 */
57
58 ip_init();
59
60 tcp_v4_init();
61
62 /* Setup TCP slab cache for open requests. */
63 tcp_init();
64
65 /* Setup UDP memory threshold */
66 udp_init();
67
68 /* Add UDP-Lite (RFC 3828) */
69 udplite4_register();
70
71 ping_init();
72
73 /*
74 * Set the ICMP layer up
75 */
76
77 if (icmp_init() < 0)
78 panic("Failed to create the ICMP control socket.\n");
79
80 /*
81 * Initialise the multicast router
82 */
83 #if defined(CONFIG_IP_MROUTE)
84 if (ip_mr_init())
85 pr_crit("%s: Cannot init ipv4 mroute\n", __func__);
86 #endif
87 /*
88 * Initialise per-cpu ipv4 mibs
89 */
90
91 if (init_ipv4_mibs())
92 pr_crit("%s: Cannot init ipv4 mibs\n", __func__);
93
94 ipv4_proc_init();
95
96 ipfrag_init();
97
98 dev_add_pack(&ip_packet_type);
99
100 rc = 0;
101 ...
102 }
kernel 定义了一个链表proto_list,所有的网络协议都要 挂到这个链表上,而proto_register() 就是干这件事的。每个网络协议在kernel内都是通过结构体struct proto(net/sock.h)表示的:
1 struct proto {
2 void (*close)(struct sock *sk,
3 long timeout);
4 int (*connect)(struct sock *sk,
5 struct sockaddr *uaddr,
6 int addr_len);
7 int (*disconnect)(struct sock sock*sk, int flags);
8
9 struct sock * (*accept)(struct sock *sk, int flags, int *err);
10
11 int (*ioctl)(struct sock *sk, int cmd,
12 unsigned long arg);
13 int (*init)(struct sock *sk);
14 void (*destroy)(struct sock *sk);
15 void (*shutdown)(struct sock *sk, int how);
16 int (*setsockopt)(struct sock *sk, int level,
17 int optname, char __user *optval,
18 unsigned int optlen);
19 int (*getsockopt)(struct sock *sk, int level,
20 int optname, char __user *optval,
21 int __user *option);
22 #ifdef CONFIG_COMPAT
23 int (*compat_setsockopt)(struct sock *sk,
24 int level,
25 int optname, char __user *optval,
26 unsigned int optlen);
27 int (*compat_getsockopt)(struct sock *sk,
28 int level,
29 int optname, char __user *optval,
30 int __user *option);
31 int (*compat_ioctl)(struct sock *sk,
32 unsigned int cmd, unsigned long arg);
33 #endif
34 int (*sendmsg)(struct sock *sk, struct msghdr *msg,
35 size_t len);
36 int (*recvmsg)(struct sock *sk, struct msghdr *msg,
37 size_t len, int noblock, int flags,
38 int *addr_len);
39 int (*sendpage)(struct sock *sk, struct page *page,
40 int offset, size_t size, int flags);
41 int (*bind)(struct sock *sk,
42 struct sockaddr *uaddr, int addr_len);
43
44 int (*backlog_rcv) (struct sock *sk,
45 struct sk_buff *skb);
46
47 void (*release_cb)(struct sock *sk);
48
49 /* Keeping track of sk‘s, looking them up, and port selection methods. */
50 void (*hash)(struct sock *sk);
51 void (*unhash)(struct sock *sk);
52 void (*rehash)(struct sock *sk);
53 int (*get_port)(struct sock *sk, unsigned short snum);
54 void (*clear_sk)(struct sock *sk, int size);
55
56 /* Keeping track of sockets in use */
57 #ifdef CONFIG_PROC_FS
58 unsigned int inuse_idx;
59 #endif
60
61 bool (*stream_memory_free)(const struct sock *sk);
62 /* Memory pressure */
63 void (*enter_memory_pressure)(struct sock *sk);
64 atomic_long_t *memory_allocated; /* Current allocated memory. */
65 struct percpu_counter *sockets_allocated; /* Current number of sockets. */
66 /*
67 * Pressure flag: try to collapse.
68 * Technical note: it is used by multiple contexts non atomically.
69 * All the __sk_mem_schedule() is of this nature: accounting
70 * is strict, actions are advisory and have some latency.
71 */
72 int *memory_pressure;
73 long *sysctl_mem;
74 int *sysctl_wmem;
75 int *sysctl_rmem;
76 int max_header;
77 bool no_autobind;
78
79 struct kmem_cache *slab;
80 unsigned int obj_size;
81 int slab_flags;
82
83 struct percpu_counter *orphan_count;
84
85 struct request_sock_ops *rsk_prot;
86 struct timewait_sock_ops *twsk_prot;
87
88 union {
89 struct inet_hashinfo *hashinfo;
90 struct udp_table *udp_table;
91 struct raw_hashinfo *raw_hash;
92 } h;
93
94 struct module *owner;
95
96 char name[32];
97
98 struct list_head node;
99 #ifdef SOCK_REFCNT_DEBUG
100 atomic_t socks;
101 #endif
102 #ifdef CONFIG_MEMCG_KMEM
103 /*
104 * cgroup specific init/deinit functions. Called once for all
105 * protocols that implement it, from cgroups populate function.
106 * This function has to setup any files the protocol want to
107 * appear in the kmem cgroup filesystem.
108 */
109 int (*init_cgroup)(struct mem_cgroup *memcg,
110 struct cgroup_subsys *ss);
111 void (*destroy_cgroup)(struct mem_cgroup *memcg);
112 struct cg_proto *(*proto_cgroup)(struct mem_cgroup *memcg);
113 #endif
114 };
接着调用 sock_register 添加一个socket的handler,而 inet_family_ops的定义如下:
1 static const struct net_proto_family inet_family_ops = {
2 .family = PF_INET,
3 .create = inet_create,
4 .owner = THIS_MODULE,
5 };
表明自己的协议族(family)为PF_INET,而成员函数 .create 是用来创建套接字的接口, 此处指定创建PF_NET 套接字的接口函数就是inet_create。协议栈就定义了几个这样的结构体变量:tcp_prot、udp_prot、raw_prot、ping_prot:
1 struct proto udp_prot = {
2 .name = "UDP",
3 .owner = THIS_MODULE,
4 .close = udp_lib_close,
5 .connect = ip4_datagram_connect,
6 .disconnect = udp_disconnect,
7 .ioctl = udp_ioctl,
8 .destroy = udp_destroy_sock,
9 .setsockopt = udp_setsockopt,
10 .getsockopt = udp_getsockopt,
11 .sendmsg = udp_sendmsg,
12 .recvmsg = udp_recvmsg,
13 .sendpage = udp_sendpage,
14 .backlog_rcv = __udp_queue_rcv_skb,
15 .release_cb = ip4_datagram_release_cb,
16 .hash = udp_lib_hash,
17 .unhash = udp_lib_unhash,
18 .rehash = udp_v4_rehash,
19 .get_port = udp_v4_get_port,
20 .memory_allocated = &udp_memory_allocated,
21 .sysctl_mem = sysctl_udp_mem,
22 .sysctl_wmem = &sysctl_udp_wmem_min,
23 .sysctl_rmem = &sysctl_udp_rmem_min,
24 .obj_size = sizeof(struct udp_sock),
25 .slab_flags = SLAB_DESTROY_BY_RCU,
26 .h.udp_table = &udp_table,
27 #ifdef CONFIG_COMPAT
28 .compat_setsockopt = compat_udp_setsockopt,
29 .compat_getsockopt = compat_udp_getsockopt,
30 #endif
31 .clear_sk = sk_prot_clear_portaddr_nulls,
32 };
33
34 struct proto tcp_prot = {
35 .name = "TCP",
36 .owner = THIS_MODULE,
37 .close = tcp_close,
38 .connect = tcp_v4_connect,
39 .disconnect = tcp_disconnect,
40 .accept = inet_csk_accept,
41 .ioctl = tcp_ioctl,
42 .init = tcp_v4_init_sock,
43 .destroy = tcp_v4_destroy_sock,
44 .shutdown = tcp_shutdown,
45 .setsockopt = tcp_setsockopt,
46 .getsockopt = tcp_getsockopt,
47 .recvmsg = tcp_recvmsg,
48 .sendmsg = tcp_sendmsg,
49 .sendpage = tcp_sendpage,
50 .backlog_rcv = tcp_v4_do_rcv,
51 .release_cb = tcp_release_cb,
52 .hash = inet_hash,
53 .unhash = inet_unhash,
54 .get_port = inet_csk_get_port,
55 .enter_memory_pressure = tcp_enter_memory_pressure,
56 .stream_memory_free = tcp_stream_memory_free,
57 .sockets_allocated = &tcp_sockets_allocated,
58 .orphan_count = &tcp_orphan_count,
59 .memory_allocated = &tcp_memory_allocated,
60 .memory_pressure = &tcp_memory_pressure,
61 .sysctl_mem = sysctl_tcp_mem,
62 .sysctl_wmem = sysctl_tcp_wmem,
63 .sysctl_rmem = sysctl_tcp_rmem,
64 .max_header = MAX_TCP_HEADER,
65 .obj_size = sizeof(struct tcp_sock),
66 .slab_flags = SLAB_DESTROY_BY_RCU,
67 .twsk_prot = &tcp_timewait_sock_ops,
68 .rsk_prot = &tcp_request_sock_ops,
69 .h.hashinfo = &tcp_hashinfo,
70 .no_autobind = true,
71 #ifdef CONFIG_COMPAT
72 .compat_setsockopt = compat_tcp_setsockopt,
73 .compat_getsockopt = compat_tcp_getsockopt,
74 #endif
75 #ifdef CONFIG_MEMCG_KMEM
76 .init_cgroup = tcp_init_cgroup,
77 .destroy_cgroup = tcp_destroy_cgroup,
78 .proto_cgroup = tcp_proto_cgroup,
79 #endif
80 };
81
82 struct proto raw_prot = {
83 .name = "RAW",
84 .owner = THIS_MODULE,
85 .close = raw_close,
86 .destroy = raw_destroy,
87 .connect = ip4_datagram_connect,
88 .disconnect = udp_disconnect,
89 .ioctl = raw_ioctl,
90 .init = raw_init,
91 .setsockopt = raw_setsockopt,
92 .getsockopt = raw_getsockopt,
93 .sendmsg = raw_sendmsg,
94 .recvmsg = raw_recvmsg,
95 .bind = raw_bind,
96 .backlog_rcv = raw_rcv_skb,
97 .release_cb = ip4_datagram_release_cb,
98 .hash = raw_hash_sk,
99 .unhash = raw_unhash_sk,
100 .obj_size = sizeof(struct raw_sock),
101 .h.raw_hash = &raw_v4_hashinfo,
102 #ifdef CONFIG_COMPAT
103 .compat_setsockopt = compat_raw_setsockopt,
104 .compat_getsockopt = compat_raw_getsockopt,
105 .compat_ioctl = compat_raw_ioctl,
106 #endif
107 };
这几个对应的就是应用层的stream、datagram和raw等Linux 的网络功能就要靠这几个协议支撑起来了。从结构体的成员变量可以看到我们平时使用 socket 要用到很多接口: getsockopt 、 connect 、 bind 等,当然这并不是socket直接使用的接口函数,肯定还要经过封装之后才能暴露给用户空间的。proto_register() (net/core/sock.c)的实现大致如下,就是讲协议变量挂到proto_list链表上,是连接传输层和网络层的纽带。
1 int proto_register(struct proto *prot, int alloc_slab)
2 {
3 ..
4 mutex_lock(&proto_list_mutex);
5 list_add(&prot->node, &proto_list);
6 assign_proto_idx(prot);
7 mutex_unlock(&proto_list_mutex);
8 ...
9 }
INET是一种适合Linux 的TCP/IP 协议实现,它和用户层通信使用了 BSD Socket 接口。inet_add_protocol() 的核心实现就一句话:
1 int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol)
2 {
3 ...
4 return !cmpxchg((const struct net_protocol **)&inet_protos[protocol],
5 NULL, prot) ? 0 : -1;
6 }
其中cmpxchg就是一个比较替换函数:比较第一个变量的值是否和第二个变量相等,相等的话则将第三个变量写入第一个变量。这里实际上就是检查inet_protos[protocol] 所指向的内容是否为空,为空则表示该协议还没有添加,那么就可以把新协议添加到inet_protos,完成添加协议,以后要用到某个协议就直接检索这个数组就行了,数组的定义如下:
1 const struct net_protocol __rcu *inet_protos[MAX_INET_PROTOS] __read_mostly;
其中struct net_protocol是专门给注册网络协议定义的结构体:
1 /* This is used to register protocols. */
2 struct net_protocol {
3 void (*early_demux)(struct sk_buff *skb);
4 int (*handler)(struct sk_buff *skb);
5 void (*err_handler)(struct sk_buff *skb, u32 info);
6 unsigned int no_policy:1,
7 netns_ok:1,
8 /* does the protocol do more stringent
9 * icmp tag validation than simple
10 * socket lookup?
11 */
12 icmp_strict_tag_validation:1;
13 };
初始化协议栈时要添加的几种协议(igmp 、 tcp 、 udp 、 icmp)的net_protocol 变量,其中成员变量handler 负责处理收到的数据包,err_handler 负责错误处理,early_demux 待了解 :
1 #ifdef CONFIG_IP_MULTICAST
2 static const struct net_protocol igmp_protocol = {
3 .handler = igmp_rcv,
4 .netns_ok = 1,
5 };
6 #endif
7
8 static const struct net_protocol tcp_protocol = {
9 .early_demux = tcp_v4_early_demux,
10 .handler = tcp_v4_rcv,
11 .err_handler = tcp_v4_err,
12 .no_policy = 1,
13 .netns_ok = 1,
14 .icmp_strict_tag_validation = 1,
15 };
16
17 static const struct net_protocol udp_protocol = {
18 .early_demux = udp_v4_early_demux,
19 .handler = udp_rcv,
20 .err_handler = udp_err,
21 .no_policy = 1,
22 .netns_ok = 1,
23 };
24
25 static const struct net_protocol icmp_protocol = {
26 .handler = icmp_rcv,
27 .err_handler = icmp_err,
28 .no_policy = 1,
29 .netns_ok = 1,
30 };
Linux 区分永久和非永久协议。永久协议包括象UDP 和TCP,这是TCP/IP 协议实现的基本部分,去 掉一个永久协议是不允许的。所以,UDP和TCP 是不能unregistered。此机制由 2 个函数和一个维护注册协议的数据结构组成。一个负责注册协议,另一个负责注销。每一个注册的协议都放在一个表里,叫协议切换表。表中的每一个入口是一个inet_protosw的实例。
1 static struct inet_protosw inetsw_array[] =
2 {
3 {
4 .type = SOCK_STREAM,
5 .protocol = IPPROTO_TCP,
6 .prot = &tcp_prot,
7 .ops = &inet_stream_ops,
8 .flags = INET_PROTOSW_PERMANENT |
9 INET_PROTOSW_ICSK,
10 },
11
12 {
13 .type = SOCK_DGRAM,
14 .protocol = IPPROTO_UDP,
15 .prot = &udp_prot,
16 .ops = &inet_dgram_ops,
17 .flags = INET_PROTOSW_PERMANENT,
18 },
19
20 {
21 .type = SOCK_DGRAM,
22 .protocol = IPPROTO_ICMP,
23 .prot = &ping_prot,
24 .ops = &inet_dgram_ops,
25 .flags = INET_PROTOSW_REUSE,
26 },
27
28 {
29 .type = SOCK_RAW,
30 .protocol = IPPROTO_IP, /* wild card */
31 .prot = &raw_prot,
32 .ops = &inet_sockraw_ops,
33 .flags = INET_PROTOSW_REUSE,
34 }
35 };
在inet_init()中调用了多次inet_register_protosw()将inetsw_array数组注册到inetsw ,分别对应tcp、udp、icmp、raw,inetsw会在以后创建socket 时用到。
注意到结构体inet_protosw里面有一个成员变量ops,顾名思义指向的是和对应协议关联的操作函数,以udp的ops为例:
1 const struct proto_ops inet_dgram_ops = {
2 .family = PF_INET,
3 .owner = THIS_MODULE,
4 .release = inet_release,
5 .bind = inet_bind,
6 .connect = inet_dgram_connect,
7 .socketpair = sock_no_socketpair,
8 .accept = sock_no_accept,
9 .getname = inet_getname,
10 .poll = udp_poll,
11 .ioctl = inet_ioctl,
12 .listen = sock_no_listen,
13 .shutdown = inet_shutdown,
14 .setsockopt = sock_common_setsockopt,
15 .getsockopt = sock_common_getsockopt,
16 .sendmsg = inet_sendmsg,
17 .recvmsg = inet_recvmsg,
18 .mmap = sock_no_mmap,
19 .sendpage = inet_sendpage,
20 #ifdef CONFIG_COMPAT
21 .compat_setsockopt = compat_sock_common_setsockopt,
22 .compat_getsockopt = compat_sock_common_getsockopt,
23 .compat_ioctl = inet_compat_ioctl,
24 #endif
25 };
而inet_dgram_ops实际上和udp_prot是有关联的,最终inet_dgram_ops调用的函数都是udp_prot提供的。而inet_dgram_ops的成员函数是供socket使用的。
如代码所述,网络模块初始化过程分为:ARP (arp_init())、IP (ip_init(),这个函数又会调用ip_rt_init 初始化路由表)、TCP Slab 缓存(tcp_init())、UDP存储(udp_init())、UDP-lite (udplite4_register())、ICMP 层(icmp_init())、广播路由(ip_mr_init())、IPV4 mibs(Management Information Bases,管理信息库)(init_ipv4_mibs())、网络的proc文件系统(ipv4_proc_init())以及IP分包(ipfrag_init()),最后一个函数 dev_add_pack()。
dev_add_pack() 的作用就是添加packet 处理器,协议栈底层网络层有两种数据包:arp 和ip,协议栈要区分处理。数据包类型抽象为结构体packet_type:
1 struct packet_type {
2 __be16 type; /* This is really htons(ether_type). */
3 struct net_device *dev; /* NULL is wildcarded here */
4 int (*func) (struct sk_buff *,
5 struct net_device *,
6 struct packet_type *,
7 struct net_device *);
8 bool (*id_match)(struct packet_type *ptype,
9 struct sock *sk);
10 void *af_packet_priv;
11 struct list_head list;
12 };
而 ip 和 arp 都有自己的类型:ip_packet_type 、arp_packet_type,前者在inet_init 里直接通过通过dev_add_pack() 注册,后者在arp_init里面使用dev_add_pack()注册,这样以后网络层收到数据包之后就先区分(id_match)再处理(func)。
core_initcall阶段执行的初始化函数:
1.netpoll_init
2.net_inuse_init
fs_initcall阶段执行的初始化函数:
1.init_sunrpc sun开发的一种远程服务(RPC)服务
2.eth_offload_init
3.ipv6_offload_init
4.ipv4_offload_init
5.af_unix_init本地socket初始化
sysctl_core_init初始化sysctl
1 static __init int sysctl_core_init(void)
2 {
3 register_net_sysctl(&init_net, "net/core", net_core_table);
4 return register_pernet_subsys(&sysctl_core_ops);
5 }
注册sysctl表(net_core_table),net_core_table保存了系统运行过程中配置网络的所要使用到的 /proc/net目录的参数和对应的实现函数等:
1 static struct ctl_table net_core_table[] = {
2 #ifdef CONFIG_NET
3 {
4 .procname = "wmem_max",
5 .data = &sysctl_wmem_max,
6 .maxlen = sizeof(int),
7 .mode = 0644,
8 .proc_handler = proc_dointvec_minmax,
9 .extra1 = &min_sndbuf,
10 },
11 {
12 .procname = "rmem_max",
13 .data = &sysctl_rmem_max,
14 .maxlen = sizeof(int),
15 .mode = 0644,
16 .proc_handler = proc_dointvec_minmax,
17 .extra1 = &min_rcvbuf,
18 },
19 {
20 .procname = "wmem_default",
21 .data = &sysctl_wmem_default,
22 .maxlen = sizeof(int),
23 .mode = 0644,
24 .proc_handler = proc_dointvec_minmax,
25 .extra1 = &min_sndbuf,
26 },
27 {
28 .procname = "rmem_default",
29 .data = &sysctl_rmem_default,
30 .maxlen = sizeof(int),
31 .mode = 0644,
32 .proc_handler = proc_dointvec_minmax,
33 .extra1 = &min_rcvbuf,
34 },
35 ...
注册成功之后,系统运行过程中就可以通过修改/proc/net下的文件来调整网络参数。注册控制模块到每个网络namespace。
Linux网络协议栈可以表示如下 :
Linux的TCP/IP层次结构和实现方式如下所示:
BSD socket层:这一部分处理BSD socket相关操作,每个socket在内核中以struct socket结构。这一部分的文件主要有:/net/socket.c /net/protocols.c。
INET socket层:BSD socket是个可以用于各种网络协议的接口,而当用于tcp/ip,即建立了AF_INET形式的socket时,还需要保留些额外的参数,于是就有了struct sock结构。文件主要有:/net/ipv4/protocol.c /net/ipv4/af_inet.c /net/core/sock.c。
TCP/UDP层:处理传输层的操作,传输层用struct inet_protocol和struct proto两个结构表示。文件主要有:/net/ipv4/udp.c /net/ipv4/datagram.c /net/ipv4/tcp.c /net/ipv4/tcp_input.c/net/ipv4//tcp_output.c/net/ipv4/tcp_minisocks.c/net/ipv4/tcp_output.c /net/ipv4/tcp_timer.c。
IP层:处理网络层的操作,网络层用struct packet_type结构表示。文件主要有:/net/ipv4/ip_forward.c ip_fragment.c ip_input.c ip_output.c。
1)sock_write:初始化msghdr{}的结构类型变量msg,并且将需要接收的数据存放的地址传给msg.msg_iov->iov_base。msg_iov向量指向了多个数据区,msg_iovlen标识了数据区个数;在通过系统调用进入内核后,该结构中的信息会拷贝给内核的msghdr结构。net/socket.c
2)sock_sendmsg做一些错误检查,然后调用__sock_sendmsg;后者做一些自己的错误检查 ,然后调用__sock_sendmsg_nosec。__sock_sendmsg_nosec 将数据传递到 socket 子系统的更深处。net/socket.c
3)inet_sendmsg这是 AF_INET 协议族提供的通用函数。此函数首先调用 sock_rps_record_flow 来记录最后一个处理该(数据所属的)flow 的 CPU; Receive Packet Steering 会用到这个信息。接下来,调用 socket 的协议类型对应的 sendmsg 方法,sk->sk_prot->sendmsg 指向的udp_sendmsg 函数。net/ipv4/af_net.c
4)tcp_sendmsg该函数完成以下任务:从用户空间读取数据,拷贝到内核skb,将skb加入到发送队列的任务,调用发送函数;函数在执行过程中会锁定控制块,避免软中断在tcp层的影响;函数核心流程为,在发送数据时,查看是否能够将数据合并到发送队列中最后一个skb中,如果不能合并,则新申请一个skb,把msghdr{}结构中的数据填入sk_buff空间;拷贝过程中,如果skb的线性区域有空间,则优先使用线性区域,线性区域空间不足,则使用分页区域;拷贝完成后,调用发送函数发送数据。net/ipv4/tcp.c
5)tcp_send_skb功能是将数据发送出去,接着要启动一个超时计时器,以便在超时时重发此数据包。net/ipv4/tcp_output.c
6)tcp_transmit_skb的作用是复制或者拷贝skb,构造skb中的tcp首部,并将调用网络层的发送函数发送skb;在发送前,首先需要克隆或者复制skb,因为在成功发送到网络设备之后,skb会释放,而tcp层不能真正的释放,是需要等到对该数据段的ack才可以释放;然后构造tcp首部和选项;最后调用网络层提供的发送回调函数发送skb,ip层的回调函数为ip_queue_xmit。net/ipv4/tcp_output.c
7)ip_queue_xmit是ip层提供给tcp层发送回调,大多数tcp发送都会使用这个回调,tcp层使用tcp_transmit_skb封装了tcp头之后,调用该函数,该函数提供了路由查找校验、封装ip头和ip选项的功能,封装完成之后调用ip_local_out发送数据包。net/ipv4/ip_output.c
8)ip_queue_xmit2:net/ipv4/ip_output.c
9)ip_output设置输出设备和协议,然后经过POST_ROUTING钩子点,最后调用ip_finish_output。net/ipv4/ip_output.c
10)ip_finish_output-对skb进行分片判断,需要分片,则分片后输出,不需要分片则知直接输出。net/ipv4/ip_output.c
11)ip_finish_output2对skb的头部空间进行检查,看是否能够容纳下二层头部,若空间不足,则需要重新申请skb;然后,获取邻居子系统,并通过邻居子系统输出。net/ipv4/ip_output.c
1)sock_read:初始化msghdr{}的结构类型变量msg,并且将需要接收的数据存放的地址传给msg.msg_iov->iov_base。net/socket.c
2)sock_recvmsg: 调用函数指针sock->ops->recvmsg()完成在INET Socket层的数据接收过程。其中sock->ops被初始化为inet_stream_ops,其成员recvmsg对应的函数实现为inet_recvmsg()函数。net/socket.c
3)sys_recv()/sys_recvfrom():分别对应着面向连接和面向无连接的协议两种情况。 net/socket.c
4)inet_recvmsg:调用sk->prot->recvmsg函数完成数据接收,这个函数对于tcp协议便是tcp_recvmsg。net/ipv4/af_net.c
5)tcp_recvmsg:从网络协议栈接收数据的动作,自上而下的触发动作一直到这个函数为止,出现了一次等待的过程。函数tcp_recvmsg可能会被动地等待在sk的接收数据队列上,也就是说,系统中肯定有其他地方会去修改这个队列使得tcp_recvmsg可以进行下去.入口参数sk是这个网络连接对应的sock{}指针,msg用于存放接收到的数据。接收数据的时候会去遍历接收队列中的数据,找到序列号合适的。
但读取队列为空时tcp_recvmsg就会调用tcp_v4_do_rcv使用backlog队列填充接收队列。
6)tcp_v4_rcv:tcp_v4_rcv被ip_local_deliver函数调用,是从IP层协议向INET Socket层提交的"数据到"请求,入口参数skb存放接收到的数据,len是接收的数据的长度,这个函数首先移动skb->data指针,让它指向tcp头,然后更新tcp层的一些数据统计,然后进行tcp的一些值的校验.再从INET Socket层中已经建立的sock{}结构变量中查找正在等待当前到达数据的哪一项。可能这个sock{}结构已经建立,或者还处于监听端口、等待数据连接的状态。返回的sock结构指针存放在sk中。然后根据其他进程对sk的操作情况,将skb发送到合适的位置。调用如下:
TCP包接收器(tcp_v4_rcv)将TCP包投递到目的套接字进行接收处理. 当套接字正被用户锁定,TCP包将暂时排入该套接字的后备队列(sk_add_backlog).这时如果某一用户线程企图锁定该套接字(lock_sock),该线程被排入套接字的后备处理等待队列(sk->lock.wq)。当用户释放上锁的套接字时(release_sock,在tcp_recvmsg中调用),后备队列中的TCP包被立即注入TCP包处理器(tcp_v4_do_rcv)进行处理,然后唤醒等待队列中最先的一个用户来获得其锁定权. 如果套接字未被上锁,当用户正在读取该套接字时, TCP包将被排入套接字的预备队列(tcp_prequeue),将其传递到该用户线程上下文中进行处理.如果添加到sk->prequeue不成功,便可以添加到 sk->receive_queue队列中(用户线程可以登记到预备队列,当预备队列中出现第一个包时就唤醒等待线程.) /net/tcp_ipv4.c
7)ip_rcv、ip_rcv_finish:从以太网接收数据,放到skb里,作ip层的一些数据及选项检查,调用ip_route_input()做路由处理,判断是进行ip转发还是将数据传递到高一层的协议.调用skb->dst->input函数指针,这个指针的实现可能有多种情况,如果路由得到的结果说明这个数据包应该转发到其他主机,这里的input便是ip_forward;如果数据包是给本机的,那么input指针初始化为ip_local_deliver函数。/net/ipv4/ip_input.c
8)ip_local_deliver、ip_local_deliver_finish:入口参数skb存放需要传送到上层协议的数据,从ip头中获取是否已经分拆的信息,如果已经分拆,则调用函数ip_defrag将数据包重组。然后通过调用ip_prot->handler指针调用tcp_v4_rcv(tcp)。ip_prot是inet_protocol结构指针,是用来ip层登记协议的,比如由udp,tcp,icmp等协议。 /net/ipv4/ip_input.c
标签:检索 work ssi routing 监听 node efi 邻居 取数据
原文地址:https://www.cnblogs.com/HYRX/p/14347180.html