
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内核中最重要的,包含了内核中平台无关的基本功能,主要包含进程创建、销毁和调度的代码。
lib: 此目录中主要包含内核中其他模块使用的通用函数和内核自解压的函数。
mm: 此目录中的代码实现了平台无关的内存管理代码。
scripts: 此目录下是内核配置时使用的脚本,当使用make menuconfig或者make xconfig命令时,会调用此部分代码。
net: 此目录中包含Linux内核的网络协议栈的代码。在子目录netfilter下为netfilter的实现代码,netfilter构建了框架,允许在不重新编译内核的情况下,编写可加载内核,在指定的地方插入回调函数,以用户自己的方式处理网络数据。子目录ipv4和ipv6为TCP/IP协议栈的IPv4和IPv6的实现,主要包含了TCP、UDP、 IP协议的代码,还有ARP协议、ICMP协议、IGMP协议、netfilter的TCP/IP实现等代码实现,以及如proc、ioctl等控制相关的代码。
源代码另一种表现方式,映射到Linux代码的3个内核层。

网络协议栈是由若干个层组成的,网络数据的流程主要是指在协议栈的各个层之间的传递。
前面介绍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的原型
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结构。内核程序通过访问其中的各个单元来遍历整个协议栈中的网络数据。

1.协议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内核采用软中断的方式实现的,实现原理如下:

软中断机制的构成核心元素包括软中断状态、软中断向量表和软中断守护内核线程。
软中断状态:即是否有触发的软中断未处理。
软中断向量表:包含两个成员向量,一个是处理软中断的回调函数,另一个处理时所需的参数。
软中断守护内核线程:内核建立一个内核线程ksoftirqd来轮询软中断状态,调用软中断向量表中的软中断回调函数处理中断。
Linux内核中的软中断的工作框架模拟了实际的硬中断处理过程。
当某软中断事件发生后,首先调用 raise_softirq() 函数设置对应的中断标记位,触发中断事务。
然后会检测中断状态寄存器的状态,如果 ksoftiqd 通过查询发现某一 软中断事务发生之后,那么通过软中断向量表调用软中断服务程序 action。
项软中断的过程与硬中断是十分类似的,二者的唯一不同之处是从中断向量到中断服务程序的映射过程。
在CPU的硬件中断发生之后,CPU的具体的服务程序通过中断向量值进行映射,这个过程是硬件自动完成的。
但是软中断的中断映射不是自动完成的,需要中断服务守护线程去实现这 一 过程,这也就是软件模拟的中断。
②.Linux内核软中断的使用方法
Linux系统中最多可以同时注册32个软中断,目前系统使用了6个软中断,它们是定时器处理、 SCSI处理、网络收发处理,以及 tasklet 机制,这里的tasklet 机制就是用来实现下半部的,描述软中断的核心数据结构为中断向量表,其定义如下:
struct softirq_action
{
void (*action)(struct softirq_action *);//action为软中断服务程序
void *data;//data服务程序输入参数
}
软中断守护程序是软中断机制实现的核心,它的实现过程比较简单。
通过查询软中断的状态来判断是否发生事件,当发生事件就会映射软中断向量表,调用执行注册的action()函数就可以了。
从这 一 点分析可以看出,软中断的服务程序是daemon。在 Linux 中软中断daemon线程函数为do_ softirq()。
发软中断事务通过raise_ softirq()函数来实现,该函数就是在中断关闭的情况下设置软中断状态位,然后判断如果不再中断上下文,那么直接唤醒守护daemon。
.
常用的软中断函数:
open_ softirq()函数:它注册 一 个软中断,将软中断的服务程序注册到系统的软中断向量表。raise_softirq()函数:设置软中断状态映射表,触发软中断事务响应。Linux软中断的处理框架也采用了上半部和下半部的处理方式。
软中断的上半部处理紧急的、需要立即处理的、关键性的处理动作,例如网卡驱动的接收动作,当有中断到达的时候,先查询网卡的中断寄存器,判断为何种方式的中断,清空中断寄存器后,复制数据,然后设置软中断的状态,触发软中断。
网络收发的处理通过软中断进行处理,考虑到优先级问题,分别占用了向量表中的2号和3号软中断来分别处理接收和发送。网络协议栈的软中断机制的实现原理如图所示。

当网络的软中断事件发生之后,执行net_ rx _ action()函数或者net_tx _action()函数的软中断服务程序,该服务程序会扫描一个网络中断状态的值,查找中断源,执行具体服务程序。
例子说明:当网络上有数据时,发生了硬件中断,硬件中断服务程序会接收网络数据,设置中断状态,并将网络数据挂接到链表上,进行中断调度,这可以通过net_ schedule()函数完成。硬件中断服务程序最后退出并且CPU开始调度软中断,软中断daemon会发现网络软中断发生了事件,其会执行网络中断对应的服务程序,即进入网络协议栈处理程序。
socket数据在内核中的流程主要包含初始化、销毁、接收和发送网络数据。其过程涉
及网卡驱动、网络协议栈和应用层的接口函数。
创建socket()函数需传递family、type、protocol3个参数,创建socket()函数就是创建一 个socket实例,然后创建一个文件描述符结构。
创建套接字文件描述符会互相建立一些关联,即建立互相连接的指针,并且初始化这些对文件的写读操作映射到 socket的read()、write()函数上来。
在初始化套接字的时候,同时初始化socket的操作函数(proto_ops结构)。如果传入的type参数是STREAM类型,那么就初始化SOCKET->ops为inet_ stream_ops。
数inet_stream_ ops是 一 个结构体,包含了stream 类型的socket操作的一 些入口函数。在这些函数里主要做的是对socket进行相关的操作。
创建socket的同时还创建 一个sock 结构的数据空间。
初始化sock,初始化过程主要做的事情是初始化3个队列: receive_ queue (接收到的数据包sk_buff链表队列),send_queue (需要发送数据包的sk_buff链表队列),backlog_queue (主要用于tcp中三次握手成功的那些数据包)。
根据family、type参数,初始化sock的操作,例如对于family为inet类型的, type为stream类型的,sock->proto初始化为tcp_prot, 其中包括stream类型的协议sock操作对应的入口函数。
应用层关闭socket时,内核需释放所关闭socket申请的资源。
网络数据接收依次经过网卡驱动和协议栈程序(DM9000A网卡)。
上半部实现过程:
网卡在一个数据包到来时,会产生一个硬中断,网络驱动程序会执行中断处理过程:
首先申请一个skb 结构及pkt_len+5大小的内存用于保存数据,然后将接收到的数据从网卡复制到这个skb的数据部分中。
当数据从网卡中成功接收后,调用netif_rx(skb)进 一 步处理数据,将skb加入到相应的input_pkt _ queue队列中,并调用netif_ rx _ schedule()函数,会产生 一 个软中断来执行网络协议栈的例程。
下半部来实现:

下半部的内核守护线程do_ softirq()函数,将执行net_ rx _ action()函数,对数据进行处理。
IP层输入处理程序轮询处理输入队列中的每个IP数据,在整个队列处理完毕后返回。
IP层验证IP首部的校验和,处理IP选项,验证IP主机地址和正确性等,并调用相应协议(TCP或者UDP等)处理程序。
接收的进程在网络协议栈处理完毕后会收到唤醒的信号,并收到发送来的网络数据。
Linux对网络数据的发送过程的处理与接收过程相反。
在 一 端对 socket进行 write()函数的过程中,首先会把要write的字符串缓冲区整理成msghdr的数据结构形式,然后调用sock_sendmsg()函数把msghdr的数据传送至inet层。
对于msghdr结构中数据区中的每个数据包,创建sk_buff结构,填充数据,挂至发送队列。
一 层层往下层协议传递,下图所示,以下每层协议不再对数据进行复制,而是对sk_buff结构进行操作。

最后调用网络驱动,发送数据,在网络发送成功后要产生中断,将发送结果反馈回应用层,此过程与接收网络数据的过程类似。