源码结构
- Documentation:这个目录下面没有内核的代码,有一套有用的内核文档。其中文档质量良势不齐,有很多内核文档的质量很优秀并且相当完整,例如文件系统;但是有的则完全没有文档,例如进程调度。在这个目录里不时可以发现有用的东西。
- arch: 此目录下的所有子目录的东西都是体系结构特有的代码。每个体系结构特有的目录下面至少包含 3 个子目录:kernel-不同体系结构内核特有的实现方式,如信号量、计时器、SMP 等;lib-不同体系结构下的高性能通用代码实现,如 memcpy等;mm-不同体系结构特有的内存管理程序的实现。
- drivers: 内核的驱动程序代码。此部分的代码占内核代码的大部分,包括显卡、网卡、PCI等外围设备的驱动代码。
- fs: 文件系统代码。包含ext2、ext3、ext4等本地文件系统,CD-ROM、isofs等镜像系统,还有NFS等网络文件系统,以及proc等伪文件系统。
- include: 此目录中包含了Linux内核中的大部分头(.h)文件。
- init: 内核初始化过程的代码。
- ipc: 进程间通信代码。
- kernel: 这部分是Linux内核中最重要的,包含了内核中平台无关的基本功能,主要包含进程创建、销毁和调度的代码。
- ib: 此目录中主要包含内核中其他模块使用的通用函数和内核自解压的函数。
- mm: 此目录中的代码实现了平台无关的内存管理代码。
- net: 此目录中包含Linux内核的网络协议栈的代码。在子目录netfilter下为netfilter的实现代码,netfilter构建了框架,允许在不重新编译内核的情况下,编写可加载内核,在指定的地方插入回调函数,以用户自己的方式处理网络数据。子目录ipv4和ipv6为TCP/IP协议栈的IPv4和IPv6的实现,主要包含了TCP、UDP、 IP协议的代码,还有ARP协议、ICMP协议、IGMP协议、netfilter的TCP/IP实现等代码实现,以及如proc、ioctl等控制相关的代码。
- scripts: 此目录下是内核配置时使用的脚本,当使用make menuconfig或者make xconfig命令时,会调用此部分代码。
内核中网络部分流程简介
- 网络协议栈是由若干个层组成的,网络数据的流程主要是指在协议栈的各个层之间的传递。
- 前面介绍TCP网络编程的流程,一个TCP服务器的流程按照建立socket()函数,绑定地址端口bind()函数,侦听端口listen()函数,接收连接accept()函数,发送数据send()函数,接收数据recv()函数,关闭socket()函数的顺序来进行。内核的处理过程也是按照此顺序进行的,网络数据在内核中的处理过程主要是在网卡和协议栈之间进行:从网卡接收数据,交给协议栈处理;协议栈将需要发送的数据通过网络发出去。
-
如下图所示,总结了各层间在网络输入输出时的层间调用关系。看出数据的流向主要有两种。应用层输出数据时,数据按照自上而下的顺序,依次通过插口层、协议层和接口层;当有数据到达的时候,自下而上依次通过接口层、协议层和插口层的方式,在内核层传递。
- 应用层Socket的初始化、绑定(bind)和销毁是通过调用内核层的socket()函数进行资源的申请和销毁的。
- 发送数据的时候,将数据由插口层传递给协议层,协议层在UDP层添加UDP的首部、 TCP层添加TCP的首部、IP层添加IP的首部,接口层的网卡则添加以太网相关的信息后,通过网卡的发送程序发送到网络上。
- 接收数据的过程是一 个相反的过程,当有数据到来的时候,网卡的中断处理程序将数据从以太网网卡的FIFO对列中接收到内核,传递给协议层,协议层在IP层剥离IP的首部、 UDP层剥离UDP的首部、TCP层剥离TCP的首部后传递给插口层,插口层查询socket的标识后,将数据送给用户层匹配的socket。
如下图所示为Linux内核层的网络协议栈的架构视图。最上面是用户空间层,应用层的程序位于此处。最底部是物理设备,例如以太网网卡等,提供网络数据的连接、收发。中间是内核层,即网络协议栈子系统。流经网络栈内部的是socket缓冲区(由结构sk_buffs接连),它负责在源和汇点之间传递报文数据。
顶部是系统调用接口,为用户空间的应用程序提供了一 种访问内核网络子系统的接口。位于其下面的是协议无关层,提供通用方法来使用底层传输层协议。然后是实际协议,在Linux中包括内嵌的协议TCP、UDP, 当然还有IP。然后是另外一个网络设备协议无关层,提供了与各个设备驱动程序通信的通用接口,最下面是设备驱动程序本身。
系统提供修改网络流程点
Linux内核中还提供了一种灵活修改网络数据的机制,用户可以利用这种机制获得和修改内核层的网络数据和属性设置。网络数据检查点如下图所示,白色的框为网络数据的流向,协议栈按照正常的方式进行处理和传递。 Linux内核在网络数据经过的多个地点设置了检查点,当到达检查点的时候,会检查这些点上是否有用户设置的处理方法,按照用户的处理规则对网络数据进行处理后,数据会再次按照正常的网络流程传递。
sk_buff结构
内核层和用户层在网络方面的差别很大,在内核的网络层中sk_buff结构占有重要的地位,几乎所有的处理均与此结构有关系。
网络协议栈是个层次架构的软件结构,层与层之间通过预定的接口传递报文。网络报文中包含了在协议各层使用到的各种信息。由于网络报文之间的大小不是固定的,因此采用合适的数据结构来存储这些网络报文就显得非常重要。
- 结构sk_buff的原型
struct sk_buff
{
struct sk_buff *next;//sk_buff链表中的下一个缓冲区
struct sk_buff *prev;//sk _buff链表中的前一个缓冲区
//构成双向链表
struct sock *sk;//本网络报文所属的sock结构,此值仅在本机发出的报文中有效,从网络收到的报文此值为空。
ktime_t tstamp;//报文收到的时间戳。
struct net_device *dev;//收到此报文的网络设备
union{
struct dst_entry *dst;
struct rtable *rtable;
};
struct sec_path *sp;
char cb[48];//用于控制缓冲区。每个层都可以使用此指针,将私有的数据放置于此。
unsigned int len,//有效数据长度
data_len;//数据长度
_u16 mac_len,//连接层头部长度,对千以太网,指MAC地址所用的长度,为6。
hdr_len;//skb的可写头部长度
union
{
_wsum csum;//校验和(包含开始和偏移)。
struct
{
_u16 csum_start;//当开始计算校验和时从skb->head的偏移。
_u16 csum_offset;// 从csum_start开始的偏移。
};
};
_u32 priority;//包队列的优先级
_u8 local_df:1,//允许本地分片。
ip_summed:2,nohddr:1,nfctinfo:3;
_u8 pkt_type:3,//包的类别
fclone:2,ipvs_property:1,peeked:1,nf_trace:1;
_be16 protocol;
void (*destructor) (struct sk_buff *skb);
struct nf_conntrack *nfct;
struct sk_buff *nfct_reasm;
struct nf_bridge_info *nf_bridge;
int iif;
_u16 queue_mapping;
_u16 tc_index;
_u16 tc_verd;
_u8 ndisc_nodetype:2;
dma_cookie_t dma_cookie;
_u32 secmark;
_u32 mark;
sk_buff_data_t transport_header; //传输层头部。
sk_buff_data_t network_header; //网络层头部。
sk_buff_data_t mac_header; //链接层头部。
sk_buff_data_t tail; //数据的尾指针
sk_buff_data_t end; //报文缓冲区的尾部。
unsigned char *head,//报文缓冲区的头
*data;//数据的头指针
unsigned int truesize;//包文缓冲区的大小
atomic_t users;
}
- sk_buff的含义
下图所示为结构sk_buff的数据结构框图,其中的tail、end、head和data是对网络报文部分的描述。
- 网络报文存储空间是在应用层发送网络数据或者网络设备收到网络数据时动态分配的,分配成功之后,将接收或者发送的网络数据填充到这个存储空间中去。
- 将网络数据填充到存储空间时,在存储空间的头部预留了 一定数量的空隙,然后从此偏移量开始将网络报文复制到存储空间中。
- 结构sk_buff以sk_buff_head构成一个环状的链,如下图所示,next变量指向下一个sk_buff结构,prev变量指向前 一 个sk_buff结构。内核程序通过访问其中的各个单元来遍历整个协议栈中的网络数据。
网络协议数据结构inet_protosw
- 协议TCP、UDP、RAW在文件linux-3.9.5/net/ipv4/af_inet.c中 inet_init()的函数中进行了初始化(因为TCP和UDP都是inet簇协议的一 部分)。inet_init()函数使用proto _register()函数来注册每个内嵌协议。
- 通过linux-3.9.5/net/ipv4/目录中tcp.c和raw.c文件中的proto接口,可以了解各个协议是如何标识自己的。
- 这些协议接口每个都按照类型和协议映射到inetsw _array, 该数组将内嵌协议与操作映射到 一 起。inetsw_array结构及其关系如下所示。
- 最初,会调用inet_ init() 函数中的inet_register protosw()函数将这个数组中的每个协议都初始化为inetsw。函数inet init()也会对各个inet模块进行初始化,如ARP、ICMP和IP模块,以及TCP和UDP模块。
- proto 结构定义了传输特有的方法,而 proto_ops 结构则定义了通用的socket() 方法。
- 可以通过调用 inet_register_protosw() 函数将其他协议加入到 inetsw 协议中。例如,SCTP 就是通过调用 linux-3.9.5/netlsctp/protocol.c 中的sctp_init 加入其中的。
Linux系统收发网络数据包的秘密
Linux 服务器收到网络数据包,需求经过哪些处置,一步步将数据传给应用进程的呢?应用进程发送数据包时,Linux 又是如何操作将数据包发送进来的呢?今天我们就来聊聊这个话题。
在准备好接纳网络数据包之前,Linux需求做很多准备工作,例如:网络子系统的初始化、协议栈的注册、网卡驱动的初始化、启动网卡等等,只要这些都准备好了之后,才敢真正开端接纳网络包。
网络协议栈
在引见Linux收发网络数据包之前,我们先来理解一下Linux网络协议栈。国际规范化组织制定了开放式系统互联通讯参考模型(Open System Interconnection Reference Model),也就是 OSI 网络模型,该模型主要有 7 层,分别是应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层。
由于 OSI 模型太复杂,提出的只是存在于概念和理论上的一种模型,分层太多,增加了网络工作的复杂性,所以没有大范围应用。我们比拟常见是TCP/IP 网络模型,Linux 系统正是依照这套网络模型来完成网络协议栈的。
TCP/IP 网络模型共有 4 层,分别是应用层、传输层、网络层和网络接口层,每一层担任的职能如下:
-
应用层 对应于OSI参考模型的高层,为用户提供所需求的各种效劳,例如:FTP、Telnet、DNS、SMTP等.
- 传输层 对应于OSI参考模型的传输层,为应用层实体提供端到端的通讯功用,保证了数据包的次第传送及数据的完好性。该层定义了两个主要的协议:传输控制协议(TCP)和用户数据报协议(UDP).
-
网络层 对应于OSI参考模型的网络层,主要处理主机到主机的通讯问题。它所包含的协议设计数据包在整个网络上的逻辑传输。注重重新赋予主机一个IP地址来完成对主机的寻址,它还担任数据包在多种网络中的路由。该层有三个主要协议:网际协议(IP)、互联网组管理协议(IGMP)和互联网控制报文协议(ICMP)。
- 网络接口层 与OSI参考模型中的物理层和数据链路层相对应。它担任监视数据在主机和网络之间的交流。事实上,TCP/IP自身并未定义该层的协议,而由参与互连的各网络运用本人的物理层和数据链路层协议,然后与TCP/IP的网络接入层停止衔接。地址解析协议(ARP)工作在此层,即OSI参考模型的数据链路层。
接纳网络数据包
-
网络数据包抵达网卡后,依照FIFO次第被存入网卡的接纳队列,网卡经过 DMA 技术,将网络包写入到指定的内存地址(Ring Buffer)。
-
Ring Buffer是在网卡驱动程序启动时创立和初始化的,存储的是sk_buff缓冲区的描绘符(物理地址和大小等)。
-
当网络包抵达时,从Ring Buffer获取指向的sk_buff描绘符,经过DMA将数据写入该地址。等sk_buff中的数据交由上层协议栈处置后,Ring Buffer中的描绘更新为新分配的sk_buff。
-
接着网卡向 CPU 发起硬件中缀,当 CPU 收到硬件中缀恳求后,依据中缀注册表,找到注册的中缀处置函数。
硬件中缀处置函数会做如下的事情
-
屏蔽网卡的中缀:目的是防止CPU被频繁中缀而无法处置其他任务,屏蔽中缀是通知网卡曾经晓得内存中有数据了,下次再收到数据包直接写内存就能够了,不要再通知 CPU 了。
-
发起软中缀,恢复方才屏蔽的中缀:内核中的 ksoftirqd 线程收到软中缀后,就会调用相应软中缀的处置函数来轮询处置数据,即:从Ring Buffer 中获取一个数据帧,用 sk_buff 表示,作为一个网络包交给网络协议栈从下到上停止逐层处置。
网络协议栈对网络包的处置流程如下
1、网络接口层
首先,网络接口层检查报文的合法性和正确性,假如不合法或报文校验不正确则丢弃,否则找出上层协议的类型(IPv4还是IPv6),去掉帧头、帧尾,然后交给上层即网络层处置。
2、网络层
网络层取出IP头,判别网络包下一步的走向,是转发还是交给上层。当确认网络包是要发送给本机后,就取出上层协议的类型(比方TCP或UDP),去掉IP头,然后交给传输层处置。
3、传输层
传输层取出 TCP 头或者 UDP 头后,依据四元组【 源 IP、源端口、目的 IP、目的端口 】,找出对应的 Socket,并把数据拷贝到 Socket 的接纳缓冲区。
4、应用层
最后,应用层程序调用 Socket 接口,将内核的 Socket 接纳缓冲区的数据拷贝到应用层的缓冲区。到这里,一个网络包的接纳过程就完毕了。
发送网络数据包
我们理解了网络包的接纳流程后,就很容易了解网络包的发送流程了。网络包的发送方向,正好跟接纳方向相反。
- 首先,应用程序调用 Socket 发送网络包的接口。这是一个系统调用,会从用户态堕入到内核态的套接字层中。
- 套接字层会申请一个内核态的 sk_buff 内存,将用户待发送的数据拷贝到 sk_buff 内存,并将其参加到Socket发送缓冲区等候网络协议栈的处置。
- 由于网络数据包从应用程序传到内核时是原始数据,协议栈要在原始数据中参加通讯商定才干保证数据抵达效劳端能被正确辨认。网络协议栈从 Socket 发送缓冲区中,取出数据包,然后依照 TCP/IP 栈的分层(传输层、网络层、网络接口层),从上到下逐层停止处置,各层将协议的头信息不时插入到数据包中。
协议栈对发送数据包的处置流程如下
1、传输层
在传输层,会为器添加TCP头,同时拷贝一个新的 sk_buff 副本 ,这是由于 sk_buff 在抵达网卡发送完成的时分,会被释放掉,而TCP 协议是支持重传的,为确保网络包牢靠传输,在收到对方的 ACK 之前,这个 sk_buff 不能被删除。
2、网络层
在网络层,主要会做这些工作:选取路由(确认下一跳的 IP)、填充 IP 头、netfilter 过滤、对超越 MTU 大小的数据包停止分片。处置完这些工作后会交给网络接口层处置。
3、网络接口层
网络接口层会停止物理地址寻址,以找到下一跳的 MAC 地址,填充帧头和帧尾,将其放到发送队列中。然后触发软中缀通知网卡驱动程序:队列中有新的网络包需求发送。驱动程序收到通知会经过 DMA ,从发送包队列中读出网络帧,并经过DMA将数据写入网卡的FIFO发送队列。
4、网卡设备
网卡设备从FIFO发送队列中取出数据包,将其发送到网络;当发送完成的时分,网卡设备会触发一个硬中缀来释放内存,主要是释放 sk_buff内存和清算 RingBuffer 内存。最后,当收到这个 TCP 报文的 ACK 应对时,传输层就会释放原始的 sk_buff。至此,一个网络包的发送流程就完毕了。