前沿
网络协议源码系列
在本系列中,将重点学习linux 1.2.13版本的网络协议源代码,可以同步学习Linux 内核网络协议栈源码剖析
-
为啥还要学习Linux老版本的代码呢?纯粹的心灵,纯粹的代码,才能有更纯粹的理解
-
为啥还要学习Linux老版本的代码呢?有指路仙人,避免迷失在code forest之中
七层模型
下三层是数据通信(通信子网),上三层是数据处理(资源子网);传输层是通信子网、资源子网的接口,承上启下
-
物理层(device):利用传输介质为数据链路层提供物理连接,实现比特流的透明传输
-
数据链路层(packet_type):建立和管理节点间的链路;通过差错控制/流量控制,使有差错的物理线路变为无差错的数据链路;分为介质控制访问(MAC)/逻辑链路控制(LLC)
-
MAC(medium access control)子层(IEEE802.3):解决共享型网络中多用户对信道竞争问题,完成网络介质的访问控制
媒介访问控制:确定链路两端是否使用同一种链路层协议;
MAC地址:二层硬件地址
-
LLC(Logical Link Control)子层(IEEE802.2):建立和维护网络连接,执行差错校验,流量控制,链路控制
LLC1:默认用来封装广播型链路;服务访问协议(SAP), 子网访问协议(SNAP–>arp)
LLC2: 默认封装点对点链路
-
-
网络层:通过路由算法,为报文或分组通过通信子网选择最适当的路径;控制数据链路层与传输层之间的信息转发、建立、维持和终止网络的连接,IP、ICMP、IGMP(internel组管理协议)、RIP,该层要解决如下问题:
-
寻址:数据链路层使用物理地址(MAC地址)仅解决网络内部的寻址问题,不同子网间通信使用逻辑地址(IP地址) IP协议
-
交换:规定不同信息交换方式;线路交换、存储转发交换(报文交换,分组交换)
-
路由算法:源地址与目的地址间存在多条路径,由路由算法决定最佳路径 RIP(Routing information protocal)
-
连接服务:数据链路层控制网络相邻节点间流量;本层控制源节点到目的节点的流量,并防阻塞、差错检测 ICMP(Internet control message protocal)
-
-
传输层:向用户提供可靠的端到端差错、流量控制,保证报文传输正确,TCP、UDP、spx、NetBIOS/NetBEUI
-
从会话层中获取数据,必要是会对数据进行分割
-
确保将数据正确无误的传送到网络层
-
如果是”面向连接”服务,则在此层如果指定时间内未收到确认信息,数据将被重发
-
-
会话层:用户应用程序、网络之间的接口,向两个实体表示层提供建立和使用连接的方法;INET socket层(af_inet.c)
- 建立连接、保持连接、断开连接
-
表示层:对来自应用层的命令和数据进行解释,并按照一定的格式传送给会话层;BSD socket层(socket.c)
-
数据格式处理:协商和建立数据交换的格式,解决各应用程序之间在数据格式表示上的差异
-
数据的编码:处理字符集和数字的转换
-
压缩与解压缩;加密与解密
-
-
应用层:为用户提供服务、协议;ftp,telnet,email,dns,whois,smtp,http…..
本篇主要内容
- 网络协议栈初始化及报文收发流程
代码分析
内核网络栈初始化
start_kernel
/* 内核入口 */
asmlinkage void start_kernel(void)
{
char * command_line;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
/* 平台初始化 */
setup_arch(&command_line, &memory_start, &memory_end);
/* 内存初始化 */
memory_start = paging_init(memory_start,memory_end);
trap_init();/* 陷阱初始化 */
init_IRQ();/* 中断初始化 */
sched_init();/* 进程调度初始化 */
parse_options(command_line);/* uboot参数解析 */
init_modules();
#ifdef CONFIG_PROFILE
prof_buffer = (unsigned long *) memory_start;
/* only text is profiled */
prof_len = (unsigned long) &etext;
prof_len >>= CONFIG_PROFILE_SHIFT;
memory_start += prof_len * sizeof(unsigned long);
#endif
memory_start = console_init(memory_start,memory_end);
memory_start = bios32_init(memory_start,memory_end);
memory_start = kmalloc_init(memory_start,memory_end);
sti();
calibrate_delay();
memory_start = chr_dev_init(memory_start,memory_end);
memory_start = blk_dev_init(memory_start,memory_end);
sti();
#ifdef CONFIG_SCSI
memory_start = scsi_dev_init(memory_start,memory_end);
#endif
#ifdef CONFIG_INET
memory_start = net_dev_init(memory_start,memory_end);
#endif
memory_start = inode_init(memory_start,memory_end);
memory_start = file_table_init(memory_start,memory_end);
memory_start = name_cache_init(memory_start,memory_end);
mem_init(memory_start,memory_end);
buffer_init();/* 缓冲区初始化 */
time_init();/* 时间初始化 */
sock_init();/* 网络初始化 */
#ifdef CONFIG_SYSVIPC
ipc_init();
#endif
sti();
check_bugs();
printk(linux_banner);
if (!fork()) /* we count on this going ok */
init();
/*
* task[0] is meant to be used as an "idle" task: it may not sleep, but
* it might do some general things like count free pages or it could be
* used to implement a reasonable LRU algorithm for the paging routines:
* anything that can be useful, but shouldn't take time from the real
* processes.
*
* Right now task[0] just does a infinite idle loop.
*/
for(;;)
idle();
}
sock_init
/* 网络初始化 */
void sock_init(void)
{
int i;
printk("Swansea University Computer Society NET3.019\n");
/*
* 初始化协议族,全局结构体 pops ;默认支持配置16个协议族;
* 主要为协议族编号、协议操作方法
*/
for (i = 0; i < NPROTO; ++i) pops[i] = NULL;
/*
* 初始化kernel默认支持的协议,调用各个协议默认的初始化函数
* SNAP(子网络访问协议): IEEE802.2;数据链路层
* 802.2:高层协议与MAC子层的接口;数据链路层
* AX.25:?;数据链路层协议
* INET:
* IPX: 分组交换协议,提供分组寻址、选择路由的功能;网络层协议
* DDP:
*/
proto_init();
#ifdef CONFIG_NET
/*
* 往dev_base设备链表中加入配置的设备;所有配置的设备在space.c文件中;
* 初始化该链表中只有loopback_dev设备;通过next,一个接一个加入到dev_base中
* 对于NE设备,初始化函数均为ethif_probe->ne_probe->ne_probe1->init_etherdev->ether_setup
* 初始化过程中会注册网卡内存地址到 iolist中,并注册open、发送、接受、关闭、查询状态等函数
* 中断处理函数在open中会通过 request_irq 进行注册
* 有个中断号和device的映射表;中断产生后,根据该表格找到对应的device
*/
dev_init();
/*
* linux将完整中断切成2部分
* top half(硬中断): 在呼叫 request_irq()时指定的interrupt handler函数
1. 真正接受中断请求的task,避免执行过久,主要任务是根据硬件中断排程bottom half
2. 具有原子性:top half期间,中断关闭,CPU无法接收新的中断;
* bottom half(软中断):由top half排程,负责真正响应中断的task
1. 执行执行中断的task,是硬中断服务程序对内核的中断
2. 不具原子性,而且可能将多个中断的bottom half合并一起做
* 注册NET bottom Half handle函数;
*/
bh_base[NET_BH].routine= net_bh;
/* 在中断向量中使能NET中断 */
enable_bh(NET_BH);
#endif
}
inet_proto_init
/* 初始化INET协议族 */
void inet_proto_init(struct net_proto *pro)
{
struct inet_protocol *p;
int i;
printk("Swansea University Computer Society TCP/IP for NET3.019\n");
/* 在协议族全局结构体在中寻找空闲位置;并注册INET协议族方法结构体到其中*/
(void) sock_register(inet_proto_ops.family, &inet_proto_ops);
seq_offset = CURRENT_TIME*250;
for(i = 0; i < SOCK_ARRAY_SIZE; i++)
{
tcp_prot.sock_array[i] = NULL;
udp_prot.sock_array[i] = NULL;
raw_prot.sock_array[i] = NULL;
}
tcp_prot.inuse = 0;
tcp_prot.highestinuse = 0;
udp_prot.inuse = 0;
udp_prot.highestinuse = 0;
raw_prot.inuse = 0;
raw_prot.highestinuse = 0;
printk("IP Protocols: ");
/* 初始化INET协议族中的子协议,ICMP,IGMP,TCP,UDP;并加入INET协议族结构体中 */
for(p = inet_protocol_base; p != NULL;)
{
struct inet_protocol *tmp = (struct inet_protocol *) p->next;
inet_add_protocol(p);/* 将各个子协议加入到inet_protos中 */
printk("%s%s",p->name,tmp?", ":"\n");
p = tmp;
}
/* 初始化arp模块 */
arp_init();
/* 初始化IP模块;类似arp,不再详细分析 */
ip_init();
}
总结
linux网络栈初始化
-
内核完成自解压过程后进入内核启动;heads.S: 负责BBS,中断描述表、段描述表(GDT)、页表、寄存器初始化后进入kernel_entry()
-
kernel_entry: 体系结构相关汇编,初始化内核堆栈段,将内核映像区未初始化的数据段清零–>start_kernel
-
start_kernel->sock_init->proto_init(->inet_proto_init)->dev_init->bottom_half_init
-
proto_init: 初始化目前linux支持的域,目前支持UNIX、802.2、SNAP、AX.25、INET、IPX、DDP;并将域操作函数结构体通过
sock_register
注册到pops
中 -
inet_proto_init:初始化INET域,将INET协议族的操作函数注册到全局协议族数组 pops(BSD层到inet_sock层接口) 中;该协议族包含 inet_create,inet_bind,inet_connect,inet_socketpair…属于会话层接口函数
-
通过sock_register()将会话层的接口注册到表示层
-
初始化传输层协议操作函数结构体
tcp_prot, udp_prot, raw_prot
;同时所有的会话层sock资源都注册在对应的传输层协议结构体中proto->sock_array
-
inet_protocol_add:将传输层收报结构体注册到
inet_protos
中 -
arp_init,ip_init
-
-
arp_init:arp(网络层协议)协议初始化函数
-
为了从链路层接受数据包,其必须定义一个packet_type结构,调用dev_add_pack函数向链路层
struct packet_type *ptype_base
注册arp协议收报函数 -
注册arp缓存刷新任务到timer
-
注册网络设备状态变更handler导致内核通知链netdev_chain(维护arp表项有效性)
-
-
ip_init:IP协议初始化
-
调用dev_add_pack函数向链路层
struct packet_type *ptype_base
注册IP协议收报函数 -
注册网络设备状态变更handler到内核通知链netdev_chain(维护路由表项有效性)
-
IP、ARP协议
都为网络层协议,初始化函数功能基本相同
-
向链路层注册数据包接受函数,完成网络层和链路层之间的衔接
-
由于arp表项以及路由表项都与某个网络设备绑定,故为了保证表项有效性,二者都必须对网络设备状态变化事件进行监听
网络栈初始化
经过inet_proto_init, arp_init, ip_init函数后,系统完成了由下而上的各层接口之间的衔接
-
inet_proto_init:
-
会话层向表示层注册接口(下层到上层的注册),
struct proto_ops *pops[NPROTO]
中每个结构体代表一个域(会话层协议) -
传输层向网络层注册接口(上层到下层注册),每个元素代表一个传输层协议(tcp, udp, icmp, igmp);其中icpm/igmp介于网络层和传输层之间,比较特殊
-
-
arp_init, ip_init:
- 网络层往链路层注册接口(上层到下层注册),每个packet_type代表一种网络层协议
数据包接收通道解析
当我们open一个网卡,会触发内核将网卡的接收驱动函数(struct irqaction)通过request_irq注册到内核的中
-
物理层–>驱动(3c501.c):硬件监听物理传输介质,进行数据接受,当完全接受一个数据包后,产生中断,调用对应中断的irqaction->handler(xx_interrupt)函数
-
驱动–>链路层(dev.c):xx_interrupt判断是rx报文中断后,会调用对应的xx_receive()函数,在其中完成sk_buff结构对报文的封装,并通过insb完成从硬件缓冲区到内核缓冲区的复制,最终调用netif_rx,将数据包由驱动层传递给链路层
2.1. 链路层:在netif_rx中,为了减少中断执行时间,会将数据包丢到sk_buff链表(struct sk_buff_head backlog)中,等待软中断从队列中取包处理;并在软中断向量表bh_active
中注册,do_bottom_half()
会检测该向量,并调用对应的软中断函数
-
链路层–>网络层(ip.c,arp.c):在网络软中断
net_bh()
函数中,首先会尝试对所有dev_base链表中硬件设备发包;再遍历backlog队列,则找到该包对应的struct packet_type
,调用packet_type->func()将包发给网络层 -
网络层–>传输层(protocal.c):在ip_rcv()中,根据ipheader->protocal(传输层协议号),找到对应的inet_protocal结构,调用结构中的接收函数,将报文送到传输层,例如 tcp_rcv()
-
传输层–>会话层(tcp.c,udp.c):根据tcp_rcv()中,根据tcp->dest(目标端口号)从tcp_prot->sock_array数组中找到本会话的套接字
struct sock
;通过tcp_data()将数据包挂入该sock结构的缓存队列中sock->receive_queue,完成数据的读取 -
会话层–>用户态(数据拷贝):根据收件描述符(sockfd)得到对应的节点(inode)结构,由节点得到对应的socket结构(作为inode结构中union类型字段存在),进而得到对应的sock结构
数据包发送通道解析
-
物理层:提供硬件发送函数注册到对应device->hard_start_xmit(ei_start_xmit函数)中,该函数会将内核缓冲区发送数据拷贝至NE网卡设备硬件缓冲区
-
链路层:提供dev_queue_xmit()发送函数供网络层使用,调用device->hard_start_xmit指针指向的具体硬件发送函数
-
网络层:提供ip_queue_xmit()发送函数供传输层使用,将数据包缓存域sock->send_head队列
-
传输层:在不同协议的 inet_prot 中提供了不同的发包封装函数(例如 tcp_write()),在本层完成数据封装到sk_buff结构中
-
会话层/表示层:sock_write()
-
应用层:write()
附录
socket
/* 表示层(BSD层)描述结构体 */
struct socket {
...
/* inet会话层协议向BSD层注册的结构,不同协议对应不同操作函数
* 在proto_init中通过 sock_register()注册,且有个全局结构体数组struct proto_ops *pops[NPROTO]
* 例如 INET域的 inet_proto_ops 操作函数集, inet_proto_init->inet_proto_ops
*/
struct proto_ops *ops;
...
}
sock
/* 会话层描述结构体,linux 1.2.13版本该结构体杂糅了太多东西,我们只看关心部分 */
struct sock {
...
/* 传输层向会话层注册的接口;不同的传输层协议,接口指针不同 */
struct proto *prot
...
/* 指向所属的BSD层 */
struct socket *socket;
...
}
proto
/* 相当于一个传输层协议描述结构体,每个传输层协议都对应一个proto
* tcp->tcp_prot; udp->udp_prot
*/
struct proto {
...
unsigned short max_header;
unsigned long retransmits;
/* 所有会话层sock在对应的传输层注册结构体
* 例如所有tcp协议的套接字对应sock结构都被划入tcp_prot全局变量表示的proto结构
*/
struct sock * sock_array[SOCK_ARRAY_SIZE];
char name[80];
int inuse, highestinuse;
};
inet_protocol
/* 传输层向网络层注册的收包接口结构体,在protocol.c中定义, tcp,udp
* 所有协议初始化后在全局结构体inet_protos中
*/
struct inet_protocol {
/* 传输层协议报文处理函数;在此会将报文传给上层协议
* 一般通过get_sock*方法,获取上层协议封装结构体
*/
int (*handler)(struct sk_buff *skb, struct device *dev,
struct options *opt, unsigned long daddr,
unsigned short len, unsigned long saddr,
int redo, struct inet_protocol *protocol);
int (*frag_handler)(struct sk_buff *skb, struct device *dev,
struct options *opt, unsigned long daddr,
unsigned short len, unsigned long saddr,
int redo, struct inet_protocol *protocol);
/* 传输层协议异常报文处理函数 */
void (*err_handler)(int err, unsigned char *buff,
unsigned long daddr,
unsigned long saddr,
struct inet_protocol *protocol);
/* 所有传输层协议作为一个单向链表存在 */
struct inet_protocol *next;
/* 协议号 */
unsigned char protocol;
unsigned char copy:1;
void *data;
char *name;
};
static struct inet_protocol udp_protocol = {
udp_rcv, /* UDP handler */
NULL, /* Will be UDP fraglist handler */
udp_err, /* UDP error control */
&tcp_protocol, /* next */
IPPROTO_UDP, /* protocol ID */
0, /* copy */
NULL, /* data */
"UDP" /* name */
}
packet_type
/* 网络层描述结构体,在netdevice.h中定义, IP,ICMP、IGMP
* 所有协议初始化后在全局结构体inet_protos中
*/
struct packet_type {
unsigned short type; /* This is really htons(ether_type). */
struct device * dev;
int (*func) (struct sk_buff *, struct device *,
struct packet_type *);
void *data;
struct packet_type *next;
};
static struct packet_type arp_packet_type =
{
0, /* Should be: __constant_htons(ETH_P_ARP) - but this _doesn't_ come out constant! */
NULL, /* All devices */
arp_rcv,
NULL,
NULL
}
arp_table
/* ARP缓存中的每一个由arp_table结构表示,将这些表项串联起来构成链表,就构成了ARP缓存 */
struct arp_table
{
struct arp_table *next; /* Linked entry list */
unsigned long last_used; /* For expiry */
unsigned int flags; /* ATF_PERM 永不过期 */
unsigned long ip; /* ip address of entry */
unsigned long mask; /* netmask - used for generalised proxy arps (tridge) */
unsigned char ha[MAX_ADDR_LEN]; /* Hardware address */
unsigned char hlen; /* Length of hardware address */
unsigned short htype; /* Type of hardware in use */
struct device *dev; /* Device the entry is tied to */
/*
* The following entries are only used for unresolved hw addresses.
*/
struct timer_list timer; /* expire timer */
int retries; /* remaining retries */
struct sk_buff_head skb; /* list of queued packets */
};