ipv6初始化和处理流程分析.docx
《ipv6初始化和处理流程分析.docx》由会员分享,可在线阅读,更多相关《ipv6初始化和处理流程分析.docx(55页珍藏版)》请在冰豆网上搜索。
ipv6初始化和处理流程分析
Ipv6初始化和处理流程分析
一.Ipv6的初始化
1.网络子系统概述
Linux内核中,与网络相关的代码是一个相对独立的子系统,称为网络子系统。
网络子系统是一个层次化的结构,可分为以下几个层次:
1)Socket层
Linux在发展过程中,采用BSDsocketAPIs作为自己的网络相关的API接口。
同时,Linux的目标又要能支持各种不同的协议族,而且这些协议族都可以使用BSDsocketAPIs作为应用层的编程接口。
因此,在socketAPIs与协议族层之间抽象出一个socket层,用于将userspace的socketAPI调用,转给具体的协议族做处理。
2)协议族层(INET协议族、INET6协议族等)
Linux网络子系统功能上相当完备,它不仅支持INET协议族(也就是通常所说的TCP/IPstack),而且还支持其它很多种协议族,如DECnet,ROSE,NETBEUI等。
INET6就是一种新增加的协议族。
对于INET、INET6协议族来说,又进一步划分为传输层和网络层。
3)设备驱动层
设备驱动层则主要将协议族层与物理的网络设备隔离开。
它不在本文的讨论范围之内。
下图是Linux网络系统层次结构图。
2.网络子系统的初始化
1)Socket层的初始化:
Init()->do_basic_setup()->sock_init()
Sock_init():
对sock和skbuff结构进行SLAB内存的初始化工作
2)各种网络协议族的初始化:
Do_initcalls():
对于编译到内核中的功能模块(而不是以模块的形式动态加载),它的初始化函数会在这个地方被调用到。
内核映象中专门有一个初始化段,所有编译到内核中的功能模块的初始化函数都会加入到这个段中;而do_initcalls()就是依次执行初始化段中的这些函数。
INET协议族通常是被编译进内核的;它的模块初始化函数是net/ipv4/af_inet.c中的inet_init()
而INET6是作为一个模块编译的。
它的模块初始化函数是net/ipv6/af_inet6.c中的inet6_init()
3.协议族
Linux网络子系统可以支持不同的协议族,Linux所支持的协议族定义在include/linux/socket.h
1)协议族数据结构
协议族数据结构是structnet_proto_family。
struct net_proto_family {
int family;
int (*create)(struct socket *sock, int protocol);
short authentication;
short encryption;
short encrypt_net;
struct module *owner;
};
这个结构中,最重要的是create函数,一个新的协议族,必须提供此函数的实现。
这是因为:
不同的网络协议族,从userspace的使用方法来说,都是一样的,都是先调用socket()来创建一个socketfd,然后通过这个fd发送/接收数据。
在userspace通过socket()系统调用进入内核后,根据第一个参数协议族类型,来调用相应协议族create()函数。
对INET6来说,这个函数inet6_create()。
因此,要实现一个新的协议族,首先需要提供一个create()的实现。
关于create()里面具体做了什么,后面再叙述。
Linux系统通过这种方式,可以很方便的支持新的网络协议族,而不用修改已有的代码。
这很好的符合了“开-闭原则”,对扩展开放,对修改封闭。
2)协议族注册
Linux维护一个structnet_proto_family 的数组net_families[]
如果要支持一个新的网络协议族,那么需要定义自己的structnet_proto_family,并且通过调用sock_register将它注册到net_families[]中。
4.sock层
socket层又叫“socketaccessprotocollayer”。
它处于BSDsocketAPIs与底层具体的协议族之间。
这是一个抽象层,它起着承上启下的作用。
在这一层的数据结构也有着这种特点
1)数据结构
在userspace,通过socket()创建的socketfd,在内核中对应的就是一个structsocket。
struct socket {
socket_state state;
unsigned long flags;
struct proto_ops *ops;
struct fasync_struct *fasync_list;
struct file *file;
struct sock *sk;
wait_queue_head_t wait;
short type;
};
它定义于include/linux/net.h中。
Structsocket的ops域指向一个structproto_ops结构,structproto_ops定义于include/linux/net.h中,它是socket层提供给上层的接口,这个结构中,都是BSDsocketAPI的具体实现的函数指针。
一个socketAPI通过系统调用进入内核后,首先由socket层处理。
Socket层找到对应的structsocket,通过它找到structproto_ops,然后由它所指向的函数进行进一步处理。
以sendmsg()这个函数为例,从userspace通过系统调用进入kernel后,由sys_sendmsg()、sock_sendmsg()依次处理,然后交给structproto_ops的sendmsg()处理。
2)sock层和传输层的关联
INET和INET6这两种协议族,可以支持多种传输层协议,包括TCP、UDP、RAW,在2.6内核中,又增加了一种新的传输层协议:
SCTP。
从内核角度看,要实现INET6协议族的某种传输层协议,则必须既提供socket层的structproto_ops的实现,也提供structproto的实现。
除此之外,还需要提供一种手段,把这两个结构关联起来,也就是把socket层和传输层关联起来。
Linux提供了一个structinet_protosw的结构,用于socket层与传输层的关联。
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). */
int protocol; /* This is the L4 protocol number. */
struct proto *prot;
struct proto_ops *ops;
int capability; /* Which (if any) capability do
* we need to use this socket
* interface?
*/
char no_check; /* checksum on rcv/xmit/none?
*/
unsigned char flags; /* See INET_PROTOSW_* below. */
};
这个结构定义于 include/net/protocol.h中,从它的命名上可以看到它属于INET和INET6协议族,但是没有查到资料为什么叫做protosw。
这个结构中,ops指向socket层的structproto_ops,prot指向传输层的structproto。
因此,对INET6这种要支持多种传输层协议的协议族,从内核的角度来说,只需要为每一种传输层协议定义相应的structproto_ops、structproto,然后再定义structinet_protosw,并将三者关联起来即可。
以INET6所支持的TCP为例:
static struct proto_ops inet6_sockraw_ops = {
.family = PF_INET6,
.owner = THIS_MODULE,
.release = inet6_release,
.bind = inet6_bind,
.connect = inet_dgram_connect, /* ok */
.socketpair = sock_no_socketpair, /* a do nothing */
.accept = sock_no_accept, /* a do nothing */
.getname = inet6_getname,
.poll = datagram_poll, /* ok */
.ioctl = inet6_ioctl, /* must change */
.listen = sock_no_listen, /* ok */
.shutdown = inet_shutdown, /* ok */
.setsockopt = sock_common_setsockopt, /* ok */
.getsockopt = sock_common_getsockopt, /* ok */
.sendmsg = inet_sendmsg, /* ok */
.recvmsg = sock_common_recvmsg, /* ok */
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
struct proto tcpv6_prot = {
.name = "TCPv6",
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v6_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v6_init_sock,
.destroy = tcp_v6_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.sendmsg = tcp_sendmsg,
.recvmsg = tcp_recvmsg,
.backlog_rcv = tcp_v6_do_rcv,
.hash = tcp_v6_hash,
.unhash = tcp_unhash,
.get_port = tcp_v6_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.sockets_allocated = &tcp_sockets_allocated,
.memory_allocated = &tcp_memory_allocated,
.memory_pressure = &tcp_memory_pressure,
.orphan_count = &tcp_orphan_count,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof(struct tcp6_sock),
.twsk_obj_size = sizeof(struct tcp6_timewait_sock),
.rsk_prot = &tcp6_request_sock_ops,
};
static struct inet_protosw tcpv6_protosw = {
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcpv6_prot,
.ops = &inet6_stream_ops,
.capability = -1,
.no_check = 0,
.flags = INET_PROTOSW_PERMANENT,
};
Linux为INET6协议族定义一个structinet_protosw的链表数组inetsw6[]。
要支持某种传输层协议,首先实现相应的structproto_ops、structproto,然后实现structinet_protosw,将两者关联,最后,通过inet6_register_protosw(),将此structinet_protosw注册到inet6_sw[]中。
注册的时候,根据structinet_protosw的type,将它放到inet6_sw[type]所在的链表中,相同的type,不同的protocol,会在同一个链表上。
3)数据结构之间的联系
从userspace角度看,要使用INET6协议族的某种传输层协议,首先需要通过socket()调用创建一个相应的socketfd,然后再通过这个socketfd,接收和发送数据。
socket()的原型是:
intsocket(intdomain,inttype,intprotocol);
domain指定了协议族.
type表明在网络中通信所遵循的模式。
主要的值有:
SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等。
SOCK_STREAM是面向流的通信方式,而SOCK_DGRAM是面向报文的通信方式。
不同的通信方式,在接收数据和发送数据时,具有不同的处理方式。
Protocol则指明具体的传输层协议。
不同的传输层协议,可能具有相同的type,例如TCP和SCTP都是SOCK_STREAM类型。
以socket(PF_INET6,SOCK_STREAM,0)为例,在进入内核空间后,
根据domain,找到inet6_family_ops。
创建structsocket
调用inet6_family_opsde create(),也就是inet6_create()
inet6_create()根据type和protocol在inet6_sw[]中找到对应的structinet_protosw,也就是tcpv6_protosw
创建structsock,将structsocket和structsock关联起来
将structsocket和tcpv6_protosw的ops,也就是inet6_stream_ops关联起来
将structsock和tcpv6_protosw的prot,也就是tcpv6_prot关联起来。
这样,socket层和传输层的数据结构之间的关系建立起来了,此后,应用层通过socketfd接收或者发送数据的时候,就会先经过socket层inet6_stream_ops处理,然后经过传输层的tcpv6_prot处理。
二.网卡接收数据
这部分是说明数据报文在在链路层的处理,以及如何将报文送交给对应的网络层协议来处理。
这些功能基本都是在驱动中实现的。
1.网络中接收数据报文的两个终端:
硬中断和软中断
(1).硬中断的中断处理函数是在驱动中注册,一般在deviceopen()函数或者deviceinit()函数中注册,使用request_irq()来注册硬中断处理函数。
当网卡接收到数据的时候,就会调用这个终端处理函数来处理。
比如8139too.c函数就用 retval=request_irq(dev->irq,rtl8139_interrupt,SA_SHIRQ,dev->name,dev)来注册硬中断处理函数。
(2).软中断是通过NET_RX_SOFTIRQ信号来触发的,处理函数是net_rx_action。
注册函数是open_softirq(NET_RX_SOFTIRQ,net_rx_action,NULL)。
触发这个中断信号(raiseirq)一般是在硬中断处理流程中,当硬中断处理基本结束的时候,通过调用__raise_softirq_irqoff(NET_RX_SOFTIRQ)来触发这个中断。
2.接收软中断
接收软中断(net_rx_action)主要还是通过调用驱动中的poll的方法进行接收。
在poll方法中,会提取接收包,根据它所在的设备和协议类型传递给各自的包处理器。
以rt