socket API:
工作在不同层面上的socket
实际上,Linux内核只提供了一个与套接字相关的系统调用,即sys_socketcall,应用程序的所有套接字调用都会映射到这个系统调用上
Linux网络核心数据结构是套接字缓存(socket buffer),简称skb。它代表一个要发送或处理的报文,并贯穿于整个协议栈。
1、 套接字缓存
skb由两部分组成:
(1) 报文数据:它保存了实际在网络中传输的数据;
(2) 管理数据:供内核处理报文的额外数据,这些数据构成了协议之间交换的控制信息。
当应用程序向一个socket传输数据之后,该socket将创建相应的套接字缓存,并将用户数据拷贝到缓存中。当报文在各协议层传达输的过程中,每一导的报文头将插入到用户数据之前。skb为报文头申请了足够的空间,所以避免了由于插入报文头而对报文进行多次拷贝。用户数据只拷贝了两次:一是从用户空间拷贝到内核;二是报文数据从内核传送到网络适配器。
skb结构:
在网络协议栈的实现中,有时需要把许多网络报文放到一个队列中做异步处理。LINUX 为此定义了相关的数据结构 sk_buff_head。这是一个双向链表的头,它把sk_buff链接成一个双向链表。
网络设备(network device)是内核对网络适配器(硬件)的抽象与封装,并为各个协议实例提供统一的接口,它是硬件与内核的接口,它有两个特征:
(1) 作为基于硬件的网络适配器与基于软件的协议之间的接口;
(2) 内核协议栈异步输入输出点。
网络设备与协议和网络适配器的关系如下:
在注册网络设备之前,必须为之分配内存空间,这一任务由net/core/dev.c中定义的alloc_netdev函数来完成。 所有的网络设备组成一个由dev_base开头的链表。
不同类型的网络设备会有不同的函数来封装alloc_netdev:
网络设备注册(a)与注销模型(b):
传输数据
网络子系统中,数据最后由链路层协议调用dev_queue_xmit(),位于net/core/dev.c,完成传输,而dev_queue_xmit 又会调用具体网络适配器的驱动程序方法dev->hard_start_xmit(),从而驱动网络适配器最终完成数据传输
接收数据
当网络适配器接收到数据帧时,就会触发一个中断,中断处理程序执行一些需要及时处理的任务,然后在下半部进行其它可以延迟的处理。中断处理程序主要进行以下一些操作:
(1) 分配sk_buff数据结构,并将接收到的数据帧从网络适配器I/O端口拷贝到sk_buff缓冲区中;
(2) 从数据帧中提取出一些信息,并设置sk_buff相应的参数,这些参数将被上层的网络协议使用,例如skb->protocol;
(3) 通过软中断NET_RX_SOFTIRQ通知内核接收到新的数据帧。
内核2.5中引入一组新的API来处理接收的数据帧,即NAPI。所以,当网络适配器接收到数据帧时,驱动有两种方式通知内核:(1)通过以前的函数netif_rx;(2)通过NAPI机制,但是只有很少的驱动使用它。
net_rx_action为处理接收数据帧的下半部函数,输入的数据帧在两个地方等待net_rx_action来处理:
(1) CPU的输入队列。这是针对NON-NAPI方式的,它调用netif_rx,将数据帧加入到CPU的输入队列softnet_data->input_pkt_queue。
(2) 设备缓存。对于NAPI方式,poll函数从设备缓存读取数据帧。
netif_receive_skb是链路层接收数据报的最后一站。它根据注册在全局数组ptype_all和ptype_base里的网络层数据报类型,把数据报递交给不同的网络层协议的接收函数(INET域中主要是ip_rcv和arp_rcv)。
当协议注册时,内核会调用dev_add_pack添加一个与之对应的packet_type数据结构