码迷,mamicode.com
首页 > 系统相关 > 详细

TCP/IP协议栈在Linux内核中的运行时序分析

时间:2021-01-30 12:08:14      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:检索   work   ssi   routing   监听   node   efi   邻居   取数据   

Linux网络协议栈初始化

sock 初始化

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.sock_init()

 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)。

skb

数据包在应用层称为 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()

  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。

  • sockfs 初始化

  网络通信可以被看作对文件的操作,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 }
  • 协议注册 proto_register()

  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_add_protocol()

  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

 

TCP/IP协议栈在Linux内核中的运行时序分析

标签:检索   work   ssi   routing   监听   node   efi   邻居   取数据   

原文地址:https://www.cnblogs.com/HYRX/p/14347180.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!