当linux系统启动后,会加载image文件到内存,然后解压、安装文件系统、内存管理以及其他关键系统。当完成初始任务后,开始执行init程序。
网络的初始化是通过函数inet_init来实现的。
先来看下内核网络代码实现层和理论模型的关系,
1.1.1 套接字层
地址族其实就是套接字接口的种类, 每种套接字种类有自己的通信寻址方法。 Linux 将不同的地址族抽象统一为 BSD 套接字接口,应用程序关心的是 BSD 套接字接口。
为 了 支 持 多 个 地 址 族 , 定 义 了 变 量 : static struct net_proto_family *net_families[NPROTO], NPROTO 根据版本不一样定义不一样。其中NPROTO可以是PF_UNIX( 1)、 PF_INET( 2)、 PF_NETLINK( 16)。
在初始化inet_init()中,会调用 (void)sock_register(&inet_family_ops); 其中inet_family_ops是struct net_proto_family的结构体对象如下,指定了PF_INET地址族的.create函数:
static const struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
那么调用socket()创建PF_INET地址族的套接字时,内核使用的创建函数就是inet_create。确定了socket的创建函数。
1.1.2 套接字与inet层
我们先来看下数组inetsw,该数组是在系统初始化时候初始化的,通过套接字类型(例如SOCK_STREAM等)索引。
而inet_register_protosw()函数位于net/ipv4/af_inet.c文件中, 注册inet套接字的回调函数。
inetsw_array是一个全局静态数组,对象是结构体inet_protosw,结构体static struct inet_protosw inetsw_array[]已经定义如下,系统在初始化的时候会读取inetsw_array来填写inetsw数组(在网络初始化inet_init函数中,调用inet_register_protosw函数来完成),因此系统中所有inet套接字都在inetsw数组中。
当socket调用的时候,将使用第二个参数type到这个数组中查找对应的inet_protosw对象。将套接字和相关操作进行了关联。
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
.flags = INET_PROTOSW_PERMANENT,
},
其中.type是套接字类型,而.protocol是协议,分别对应socket函数的第二和第三个参数。ops是该类型套接字的操作函数。
proto结构体里面存放的是不同协议(IPPROTO_TCP, IPPROTO_UDP)的操作函数集,也就是具体协议的实现。而struct proto_ops是根据套接字的类型(SOCK_STREAM, SOCK_DGRAM...)不同而组成不同的套接字管理函数集,面向的套接字。
将proto对象赋值给inet_protosw结构体中的prot指针,从而建立两者的联系
1.1.3 协议链表
此外,结构体proto对象维护了一个链表,串连在proto_list全局链表。
inet_init函数,定义在net/ipv4/af_inet.c文件中,
先调用proto_register函数,来注册内嵌协议。函数是在 net/core/sock.c 中定义的,注册struct proto对象,第一个参数传的是协议,第二个传的是内存分配方式,如果是1表示在高速缓存中分配空间,0则在内存中分配。因为tcp这些协议经常使用,所以分配在高速缓存里面。将tcp_proto对象添加到全局链表proto_list里面,其他的 udp_proto、raw_proto、ping_proto都会在初始化的时候添加到proto_list链表里面。
使用频繁,将其放在slab中便于快速分配使用。
1.1.4 inet层与ip层
结构体net_protocol是inet层和网络层(IP层)之间的连接,结构体定义在include/net/protocol.h文件中:
struct net_protocol {
int (*early_demux)(struct sk_buff *skb);
int (*early_demux_handler)(struct sk_buff *skb);
int (*handler)(struct sk_buff *skb);
void (*err_handler)(struct sk_buff *skb, u32 info);
unsigned int no_policy:1,
netns_ok:1,
/* does the protocol do more stringent
* icmp tag validation than simple
* socket lookup?
*/
icmp_strict_tag_validation:1;
};
相关协议(TCP)如下:
/* thinking of making this const? Don't.
* early_demux can change based on sysctl.
*/
static struct net_protocol tcp_protocol = {
.early_demux = tcp_v4_early_demux,
.early_demux_handler = tcp_v4_early_demux,
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
.no_policy = 1,
.netns_ok = 1,
.icmp_strict_tag_validation = 1,
};
其中handler是回调函数。在inet_init函数中,通过函数inet_add_protocol(&tcp_protocol, IPPROTO_TCP),将协议接收函数添加到inet_protos[protocol] 全局链表中,从而完成IP层和传输层的链接。
1.1.5 ip层
报文从设备层送到上层之前,要区分是 IP 报文还是 ARP 报文。该过程由一个数据结构来抽象,叫 packet_type{},定义在linux/netdevice.h。
函数 dev_add_pack() 用于将指定的协议 (packet_type) 注册到系统中。
该函数定义在:net/core/dev.c文件中,将协议句柄增加到网络栈中。
Packet_type结构体定义在include/linux/netdevice.h文件中
struct packet_type {
__be16 type; /* This is really htons(ether_type). */
struct net_device *dev; /* NULL is wildcarded here */
int (*func) (struct sk_buff *,
struct net_device *,
struct packet_type *,
struct net_device *);
bool (*id_match)(struct packet_type *ptype,
struct sock *sk);
void *af_packet_priv;
struct list_head list;
};
其中type是协议类型,定义在include/uapi/linux/if_ether.h文件中如下,dev表示什么设备可以使用,func表示回调函数,list是链表头:
#define ETH_P_LOOP 0x0060 /* Ethernet Loopback packet */
#define ETH_P_PUP 0x0200 /* Xerox PUP packet */
#define ETH_P_PUPAT 0x0201 /* Xerox PUP Addr Trans packet */
#define ETH_P_TSN 0x22F0 /* TSN (IEEE 1722) packet */
#define ETH_P_ERSPAN2 0x22EB /* ERSPAN version 2 (type III) */
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
#define ETH_P_X25 0x0805 /* CCITT X.25 */
#define ETH_P_ARP 0x0806 /* Address Resolution packet */
#define ETH_P_BPQ 0x08FF /* G8BPQ AX.25 Ethernet Packet [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_IEEEPUP 0x0a00 /* Xerox IEEE802.3 PUP packet */
#define ETH_P_IEEEPUPAT 0x0a01 /* Xerox IEEE802.3 PUP Addr Trans packet */
#define ETH_P_BATMAN 0x4305 /* B.A.T.M.A.N.-Advanced packet [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_DEC 0x6000 /* DEC Assigned proto */
#define ETH_P_DNA_DL 0x6001 /* DEC DNA Dump/Load */
#define ETH_P_DNA_RC 0x6002 /* DEC DNA Remote Console */
#define ETH_P_DNA_RT 0x6003 /* DEC DNA Routing */
#define ETH_P_LAT 0x6004 /* DEC LAT */
#define ETH_P_DIAG 0x6005 /* DEC Diagnostics */
#define ETH_P_CUST 0x6006 /* DEC Customer use */
#define ETH_P_SCA 0x6007 /* DEC Systems Comms Arch */
#define ETH_P_TEB 0x6558 /* Trans Ether Bridging */
#define ETH_P_RARP 0x8035 /* Reverse Addr Res packet */
#define ETH_P_ATALK 0x809B /* Appletalk DDP */
#define ETH_P_AARP 0x80F3 /* Appletalk AARP */
#define ETH_P_8021Q 0x8100 /* 802.1Q VLAN Extended Header */
#define ETH_P_ERSPAN 0x88BE /* ERSPAN type II */
#define ETH_P_IPX 0x8137 /* IPX over DIX */
#define ETH_P_IPV6 0x86DD /* IPv6 over bluebook */
#define ETH_P_PAUSE 0x8808 /* IEEE Pause frames. See 802.3 31B */
#define ETH_P_SLOW 0x8809 /* Slow Protocol. See 802.3ad 43B */
#define ETH_P_WCCP 0x883E /* Web-cache coordination protocol
* defined in draft-wilson-wrec-wccp-v2-00.txt */
每种协议通过向ptype_base[]数组张注册进行登记,维护了一张全局的ptype_base[]数组,每一个包网络层使用的是哪种协议都可以来这里来查,查到匹配的协议以后,就调用对应的处理函数。inet_init函数通过调用dev_add_pack(&ip_packet_type)将IP协议添加到ptype_base[]数组中而成为一员,而且对应的处理函数是ip_rcv.
static struct packet_type ip_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
};
最后以网上一张很不错的图来结尾: