由PPPOE看Linux网络协议栈的实现

这个标题起得比较纠结,之前熟知的PPPOE是作为PPP协议的底层载体,而实际上它也是一个完整的协议,不过它的实现比较简单,由它出发,可以很容易理清楚Linux网络栈的实现方式。

1.总述

Linux中用户空间的网络编程,是以socket为接口,一般创建一个sockfd = socket(family,type,protocol),之后以该sockfd为参数,进行各种系统调用来实现网络通信功能。其中family指明使用哪种协议域(如INET、UNIX等),protocol指明该协议域中具体哪种协议(如INET中的TCP、UDP等),type表明该接口的类型(如STREAM、DGRAM等),一般设protocol=0,那么就会用该family中该type类型的默认协议(如INET中的STREAM默认就是TCP协议)。

Linux中利用module机制,层次分明地实现了这套协议体系,并具有很好的扩展性,其基本模块构成如下:

先看右边,顶层的socket模块提供一个sock_register()函数,供各个协议域模块使用,在全局的net_family[]数组中增加一项;各个协议域模块也提供一个类似的register_xx_proto()函数,供各个具体的协议使用,在该协议域私有的xx_proto[]数组中增加一项。这两个数组中的存放的都是指针,指向的数据结构如下图所示:

很明显它们是用来创建不同类型的socket接口的,且是一种分层次的创建过程,可想而知,顶层socket_create()完成一些共有的操作,如分配内存等,然后调用下一层create;协议域内的create()完成一些该协议域内共有的初始化工作;最后具体协议中的create()完成协议特有的初始化。具体的下一节讲。

再来看上图右边的,也是顶层socket模块提供的4个函数,前两个一般由具体协议模块调用,由于协议栈与应用层的交互,具体的后面会讲到。后两个一般有协议域模块调用,用于底层设备与协议栈间的交互。但这也不绝对,如在PPPOE协议中,这4个函数都由具体协议模块调用,这是因为PPPOX协议域内的共有部分不多,各个协议间几乎独立。这4个函数的功能及所用到的数据结构,在后面具体用到时会详细说明。

2.socket插口创建

首先来看一下最终创建好的socket插口由哪些部分组成,该结构是相当庞大的,这里只给出框架:

基本属性有state(listen、accept等),flags标志(blocked等),type类型,这里family和protocol都没有了,因为它们再创建时使用过了,已经被融入到socket结构中。File指针指向一个file结构,在Linux中一个socket也被抽象为一个文件,所以在应用层一般通过标准的文件操作来操作它。Ops指向一个struct proto_ops结构,它是每种协议特有的,应用层的系统调用,最终映射到网络栈中具体协议的操作方法。Sk指向一个struct sock结构,而该结构在分配空间时,多分配了一点以作为该协议的私有部分,这里包含了该协议的具体信息,内容相当多。首先是一个struct sock_common结构,包含了协议的基本信息;然后是一个sk_prot_create指针,指向一个struct proto结构体,该结构体就是第一节中所述的,用proto_regsiter()注册到内核中的,它包含应用层到协议栈的交互操作和信息(也可以说成是App à transport layer的交互信息);然后还有一个sk_backlog_rcv函数指针,所指函数在协议栈处理完接收到的包之后调用,一般仅是把数据包放到该socket的接收队列中,等待APP读取;最后协议的私有部分里存放该协议的私有信息,如pppoe的sessionID、daddr,tcp的连接4元组等,这些信息很重要,利用它们来区分同一个协议中的多个socket。

创建的总体过程,第一节已讲过了,下面以pppoe为例,描述一个socket插口的具体创建过程:

之前所述的关键点这里几乎都涉及到了,要注意的是这里的struct proto结构非常简单,因为PPPOE协议几乎没有传输层,所以不需要有太多的中间操作,仅需要一个obj_size来指明struct sock结构后需分配的私有结构大小,关于私有结构的内容,一般在connect操作时才能初始化。

创建好socket之后,其中的fops,proto_ops,sk_backlog_rcv等操作是如何作用,来实现网络通信的功能?这是后面要讲述的内容。

3.主动过程

主动过程即在应用层中通过系统调用,触发socket完成某种动作,有些系统调用和标准的文件操作类似,因此可以直接用sockfd的fops来描述,如read、write、ioctl等,有些则是socket接口特有的,需重新定义系统调用接口,Linux中用SYSCALL_DEFINEn()宏来定义系统调用接口,如bind、accept等。这些系统调用一般都很简单,最终都会去调用socket内部proto_ops中的接口函数。

如下图所示,在socket层,并不是所有的文件操作都适用于socket,因此其特有的socket_file_ops中只指定了部分函数;另外还封装了几个系统调用,是我们熟悉的bind、listen、connect、accept。这些系统调用接口都是静态的,它们一般经过简单的处理,就调用具体socket中的proto_ops操作。

在协议栈中,主要是socket特有的proto_ops操作,但对于一些复杂的协议,如TCP,还需要其它一些操作来支持,这些接口都放在struct sock中的struct proto中。PPPOE协议比较简单,不需要struct proto的操作来支持,但其中的obj_size仍然重要,如前所述。

如上图所示,PPPOE协议中,并不是所有协议操作都需要,如bind、accept等,下面选几个来详细看一下socket的主动过程的工作。

Ioctl系统调用:ioctl是通过标准的文件操作来调用的,具体如下图所示:

那么威尼斯就是一艘轻盈和流动的舟船,

由PPPOE看Linux网络协议栈的实现

相关文章:

你感兴趣的文章:

标签云: