深入理解Linux内核之TCP/IP协议栈初始化

2023-12-04 11:31:28 浏览数 (2)

大家都知道理解一个程序需要从其初始化看起,内核代码也是一样,不过内核是按模块初始化的,模块初始化这部分今天暂时先不讲,接下来会单独拎出来详细讲解。现在只需要知道TCP/IP协议栈的初始化是通过fs_initcall宏来完成的就行。

TCP/IP协议栈的初始化函数为

代码语言:javascript复制
inet_init
static int __init inet_init(void)
{
 struct inet_protosw *q;
 struct list_head *r;
 int rc = -EINVAL;
 sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
 rc = proto_register(&tcp_prot, 1);
 if (rc)
  goto out;
 rc = proto_register(&udp_prot, 1);
 if (rc)
  goto out_unregister_tcp_proto;
 rc = proto_register(&raw_prot, 1);
 if (rc)
  goto out_unregister_udp_proto;
 rc = proto_register(&ping_prot, 1);
 if (rc)
  goto out_unregister_raw_proto;
 /*
  * Tell SOCKET that we are alive...
  */

 (void)sock_register(&inet_family_ops);
/proc 操作接口

#ifdef CONFIG_SYSCTL
 ip_static_sysctl_init();
#endif
 /*
  * Add all the base protocols.
  */
 if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
  pr_crit("%s: Cannot add ICMP protocoln", __func__);
 if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
  pr_crit("%s: Cannot add UDP protocoln", __func__);
 if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
  pr_crit("%s: Cannot add TCP protocoln", __func__);
#ifdef CONFIG_IP_MULTICAST
 if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
  pr_crit("%s: Cannot add IGMP protocoln", __func__);
#endif
 /* Register the socket-side information for inet_create. */
 for (r = &inetsw[0]; r < &inetsw[SOCK_MAX];   r)
  INIT_LIST_HEAD(r);
 for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN];   q)
  inet_register_protosw(q);
 /*
  * Set the ARP module up
  */
 arp_init();
 /*
  * Set the IP module up
  */
 ip_init();
 tcp_v4_init();
 /* Setup TCP slab cache for open requests. */
 tcp_init();
 /* Setup UDP memory threshold */
 udp_init();
 /* Add UDP-Lite (RFC 3828) */
 udplite4_register();
 ping_init();
 /*
  * Set the ICMP layer up
  */
 if (icmp_init() < 0)
  panic("Failed to create the ICMP control socket.n");
 /*
  * Initialise the multicast router
  */
#if defined(CONFIG_IP_MROUTE)
 if (ip_mr_init())
  pr_crit("%s: Cannot init ipv4 mrouten", __func__);
#endif
 if (init_inet_pernet_ops())
  pr_crit("%s: Cannot init ipv4 inet pernet opsn", __func__);
 /*
  * Initialise per-cpu ipv4 mibs
  */
 if (init_ipv4_mibs())
  pr_crit("%s: Cannot init ipv4 mibsn", __func__);
 ipv4_proc_init();
 ipfrag_init();
 dev_add_pack(&ip_packet_type);
 ip_tunnel_core_init();
 rc = 0;
out:
 return rc;
out_unregister_raw_proto:
 proto_unregister(&raw_prot);
out_unregister_udp_proto:
 proto_unregister(&udp_prot);
out_unregister_tcp_proto:
 proto_unregister(&tcp_prot);
 goto out;
}

接下来我们看一下上述初始化函数用到的一些结构体及函数。

1.

proto_register 注册 struct proto 协议到static LIST_HEAD(proto_list);链。

struct proto 结构体比较庞大,这里不再展开,该结构定义了从 socket层到传输层的接口,开发过用户态网络相关的程序的相比大家对socket的 api应该都了解,close、connect、sendmsg、recvmsg这些接口的实现最终由struct proto中定义的方法完成。

2.

网络协议族接口,定义在

代码语言:javascript复制
include/linux/net.h

struct net_proto_family {
 int  family;
 int  (*create)(struct net *net, struct socket *sock,
      int protocol, int kern);
 .....
};

family:网络协议族,目前支持的有43个,定义在include/linux/socket.h中,常用的有

PF_UNIX Unix domain sockets

PF_INET Internet IP Protocol

PF_NETLINK netlink协议

create: 协议的创建方法

3.

代码语言:javascript复制
/* This is used to register protocols. */
struct net_protocol {
.... 
 int   (*handler)(struct sk_buff *skb);
 void   (*err_handler)(struct sk_buff *skb, u32 info);
....
};
handler 协议的处理函数

err_handler 协议错误处理函数

所有的基础协议icmp_protocol、udp_protocol、tcp_protocol、igmp_protocol通过inet_add_protocol接口注册到该结构体数组中,

const struct net_protocol __rcu *inet_protos[MAX_INET_PROTOS] __read_mostly;

4.

代码语言:javascript复制
/* This is used to register socket interfaces for IP protocols.  */
struct inet_protosw {
 struct list_head list;
        /* These two fields form the lookup key.  */
 unsigned short  type;    /* This is the 2nd argument to socket(2). */
 unsigned short  protocol; /* This is the L4 protocol number.  */
 struct proto  *prot;
 const struct proto_ops *ops;
 ....
};
list:为inetsw表的节点

/* The inetsw table contains everything that inet_create needs to
 * build a new socket.
 */
static struct list_head inetsw[SOCK_MAX]; 
type:为socket的类型:
enum sock_type {
 SOCK_STREAM = 1,
 SOCK_DGRAM = 2,
 SOCK_RAW = 3,
 SOCK_RDM = 4,
 SOCK_SEQPACKET = 5,
 SOCK_DCCP = 6,
 SOCK_PACKET = 10,
};

#define SOCK_MAX (SOCK_PACKET 1)

prot: 即为上面提到到通过 proto_register 的协议接口

ops :为协议的操作接口

所有的协议 inetsw_array 通过 inet_register_protosw 接口注册到 inetsw 表。

通过下图的socket结构与传输控制块之间的关系,我们可以对上面介绍的结构有个全局的把握,具体的这些关系在后续的篇章中会一层层的揭开它们神秘的面纱。

接下来就是各个模块的初始化:

代码语言:javascript复制
arp_init  arp初始化
ip_init ip层初始化 主要是路由模块
tcp_v4_init、tcp_init  tcp相关初始化
udp_init、udplite4_register udp相关初始化
icmp_init icmp初始化
ip_mr_init 多播路由
init_ipv4_mibs mibs初始化
ipv4_proc_init proc初始化
ipfrag_init ip碎片
dev_add_pack 注册 a protocol handler,ip层为 ip_packet_type。
这个结构很关键,涉及到具体协议的处理入口
struct packet_type {
 __be16   type; /* This is really htons(ether_type). */
 int   (*func) (struct sk_buff *,
    struct net_device *,
      struct packet_type *,
      struct net_device *);
 ....
 struct list_head list;
};

type :协议类型

func :协议处理函数

list : ptype_head 的节点,

ptype_head根据协议分区为两个,ptype_all为ETH_P_ALL协议的表,

struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly; struct list_head ptype_all __read_mostly; /* Taps */

ip_tunnel_core_init tunnel模块初始化

上述每一个模块都需要单独一个篇章来写,不在这里展开。

至此,协议栈的初始化就完成了。

0 人点赞