标签:检索 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