轉:http://www.shangshuwu.cn/index.php/Linux%E5%86%85%E6%A0%B8%E7%BD%91%E5%8D%A1%E9%A9%B1%E5%8A%A8%E7%A8%8B%E5%BA%8F
Linux具有作为网络操作系统尤其是服务器端操作系统的优势。网络部分代码量很大,TCP/IP协议本身就很复杂,因而,本章就主要介绍数据包的传递过程、与应用层的接口、与底层的接口及网络驱动程序的编写。
目录 |
1 Linux网络系统分层结构
Linux网络系统遵守一个四层的模型概念:应用层、传输层、互联层和网络接口层。模型的基层是网络接口层。负责数据帧的发送和接收,帧是独立的网 络信息传输单元。网络接口层将帧放在网上,或从网上把帧取下来。互联协议将数据包封装成internet数据报,并运行必要的路由算法。 这里有四个互联协议:网际协议IP:负责在主机和网络之间寻址和路由数据包。
地址解析协议ARP:获得同一物理网络中的硬件主机地址。
网际控制消息协议ICMP:发送消息,并报告有关数据包的传送错误。
互联组管理协议IGMP:被IP主机拿来向本地多路广播路由器报告主机组成员。
传输协议在计算机之间提供通信会话。传输协议的选择根据数据传输方式而定。
两个传输协议:
传输控制协议TCP:为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求得到响应的应用程序。
用户数据报协议UDP:提供了无连接通信,且不对传送包进行可靠的保证。适合于一次传输小量数据,可靠性则由应用层来负责。
应用层 应用程序通过这一层访问网络。
网络接口技术 IP使用网络设备接口规范NDIS向网络接口层提交帧。IP支持广域网和本地网接口技术。
串行线路协议 TCP/IPG一般通过internet串行线路协议SLIP或点对点协议PPP在串行线上进行数据传送。
BSD Socket 接口是个通用的接口,它支持各种网络工作形式。一个套接字描述一个通讯连接的一端,两个通讯程序中各自有一个套接字来描述它们自己那一端。Linux支持 多种类型的套接字。每一类型的套接字有它自己的通信寻址方法。Linux支持下列套接字地址族或域:
UNIX Unix 域套接字
INET Internet地址族支持通过TCP/IP协议的通信
AX25 Amateur radio X25
IPX Novell IPX
APPLETALK Appletalk DDP
X25 X25
INET socket层支持包括TCP/IP协议在内的internet地址族。这些协议是分层的,一个协议使用另一个协议的服务。它与BSD socket层的接口要通过一系列Internet地址族socket操作,这一操作是在网络初始化时就已经注册到BSD socket层的。为了不把BSD socket 与TCP/IP的特定信息搞混,INET socket层使用它自己的数据结构sock ,它与BSD socket 结构相连。
2 数据包结构
结构sock_iocb是socket的I/O控制块,用来进行socket的I/O异步处理,它是网络传输的一个总的控制结构,结构sock_iocb列出如下(在include/net/sock.h中):struct sock_iocb {
struct list_head list;
int flags;
int size;
struct socket *sock;
struct sock *sk;
struct scm_cookie *scm;
struct msghdr *msg, async_msg;
struct iovec async_iov;
struct kiocb *kiocb;
};
在内核中的BSD Socket层使用msghdr结构来存储数据包,使用socket结构来字处理控制socket。在INET Socket以下层中使用sk_buff结构来存储数据包。使用sock结构来管理数据包存放和调度。下面这两个结构分别进行讨论。
msghdr结构
linux/socket.hstruct msghdr {
void * msg_name; // Socket名字
int msg_namelen; //名字长度
struct iovec * msg_iov; //数据块
__kernel_size_t msg_iovlen; //数据块个数
void * msg_control; //各个协议的 magic (如BSD文件描述子
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags; //保存接收数据的时候使用的控制标志
};
linux/uio.h
struct iovec
{
void __user *iov_base; //数据缓存区地址
__kernel_size_t iov_len; //数据长度
};
socket结构
Linux应用层的程序使用的是标准的BSD Socket接口,BSD Socket层管理多种地址簇。socket结构是BSD Socket层的socket控制结构,在应用程序中通过使用一个socket文件描述符与一个socket相对应。include/linux/net.h
struct socket {
socket_state state; //socket状态
unsigned long flags;//如SOCK_ASYNC_NOSPACE的标识
struct proto_ops *ops;//协议特定的socket操作
struct fasync_struct *fasync_list;//异步唤醒队列
struct file *file;//回指向file的指针
struct sock *sk;//指向下一层协议中的sock结构
wait_queue_head_t wait;//等待在这个socket上的任务列表
short type; //数据包的类型
unsigned char passcred;// credentials (used only in Unix Sockets (aka PF_LOCAL�
};
socket结构中的state状态值如下枚举结构,主要的有SS_UNCONNECTED和SS_CONNECTED,这个结构列出如下
typedef enum {
SS_FREE = 0, //不允许的状态
SS_UNCONNECTED, //没连接到任何socket
SS_CONNECTING, //在连接中
SS_CONNECTED, //已连接到socket
SS_DISCONNECTING //连接正在断开中
} socket_state;
Linux系统调用建立新的套接字时,需要传递套接字的地址族标识符、套接字类型以及协议。内核套接字分配新的socket数据结构。Socket 数据结构实际是VFS索引节点数据结构的一部分,分配新的socket数据结构实际就是分配新的VFS索引节点。socket在文件系统位置如下图所示:
图 socket在文件系统inode中的位置
sk_buff结构及管理
sk_buff结构
Linux网络层采用统一的缓冲区结构skbuff,一个个单独的skbuff被组织成双向链表的形式。网卡接收到数据帧后,系统内核为接收到的数据帧放在skbuff结构管理的缓冲区内。在网络协议处理的时候, 数据均以skbuff的形式在各层之间传递、处理。sk_buff提供了众多指针,可以快速的定位协议头位置,它同时保留了许多数据包信息(如使用的网络设备等),以便协议层根据需要灵活应用。
下面套接字缓冲区流程图说明了sk_buff流动的过程。在源主机上,INET socket层创建了sk_buff缓冲区,它沿网络自上而下传递,它先在协议层流动,最后在物理层消失。同时把它所带的数据传递给目标主机的物理层的套 接字缓冲区,该缓冲区自下而上传递到目标主机的套接字,并把数据传递给用户进程,目标主机的套接字缓冲区也同时消失。当套接字缓冲区在协议层流动过程中, 每个协议都在发送数据区添加自己的协议头和协议尾,而在接收数据时去掉这些协议头和协议尾。设置sk_buff数据结构的主要目的就是为网络提供一种统一 有效的缓冲区操作方法。
struct sk_buff_head {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
__u32 qlen; //链表节点的个数
spinlock_t lock;
};
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
struct sk_buff_head *list;//表示在哪个链表上
struct sock *sk; //被哪个socket拥有
struct timeval stamp;//数据包到达的时间
struct net_device *dev; //数据包经过的网络设备
struct net_device *input_dev;//数据包到达的设备
struct net_device *real_dev; //数据包正在使用的设备
union {
struct tcphdr *th; //TCP传输层头
struct udphdr *uh; //UDP头
struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct ipv6hdr *ipv6h;
unsigned char *raw; //
} h;
union {
struct iphdr *iph; //IP层头
struct ipv6hdr *ipv6h;
struct arphdr *arph;
unsigned char *raw;
} nh;
union {
unsigned char *raw;
} mac;//MAC层即数据链路层头
struct dst_entry *dst;//发送到的目标地址描述
struct sec_path *sp;
char cb[40];//控制buffer,每层自由使用,存放个人参数,
//如果你想在层间保持这些数据,你得先做skb_clone()操作。
unsigned int len,//实际的数据长度
data_len,//数据长度
mac_len,//链路层头的长度
csum;//校验结构
unsigned char local_df,//
cloned,//指这个sk_buff结构是否克隆过
pkt_type,
ip_summed;
__u32 priority;//数据包排列优先级
unsigned short protocol,//ethernet协议的类型
security;//数据包的安全保密级别
void (*destructor)(struct sk_buff *skb);//释放函数
#ifdef CONFIG_NETFILTER //与包过滤相关的成员
unsigned long nfmark;//
__u32 nfcache;
__u32 nfctinfo;
struct nf_conntrack *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge;
#endif
#endif /* CONFIG_NETFILTER */
#if defined(CONFIG_HIPPI)
union {
__u32 ifield;
} private;
#endif
#ifdef CONFIG_NET_SCHED
__u32 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u32 tc_verd; /* traffic control verdict */
__u32 tc_classid; /* traffic control classid */
#endif
#endif
/* These elements must be at the end, see alloc_skb() for details. */
unsigned int truesize;//buffer大小
atomic_t users;
unsigned char *head,//buffer头指针
*data,//数据头指针
*tail,//数据尾指针
*end;//结束指针
};
sk_buff结构与它管理的数据区的关系如下图所示,数据区是由sk_buff结构中的几个指针及长度来定义的。
图 :sk_buff结构
sk_buff有两个非常重要长度字段,len和truesize,分别描述当前协议数据包的长度和数据缓冲区的实际长度。
skb_init函数创建了sk_buff的对象缓存,由全局变量skbuff_head_cache指向这个缓存,函数列出如下(在net/core/skbuff.c):
static kmem_cache_t *skbuff_head_cache;
void __init skb_init(void)
{
skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
sizeof(struct sk_buff),
0,
SLAB_HWCACHE_ALIGN,
NULL, NULL);
if (!skbuff_head_cache)
panic("cannot create skbuff cache");
}
sk_buff及其链表的操作
在传输过程中,存在着多个套接缓冲区,这些缓冲区组成一个链表。每个链表都有一个链表头sk_buff_head,链表中每个节点分别对应内存中的一块的数据区。对它的操作有两种基本方式:第一种是对缓冲区链表进行操作,第二种是对缓冲区对应的数据区进行控制。(1)对sk_buff对应的数据区进行控制
操作sk_buff对应的数据区函数的作用说明如下图:- 创建或取消一个缓冲区结构函数
struct sk_buff *alloc_skb(int size, int priority)分配sk_buff对象缓存,分配size大小的数据区,将sk_buff结构的指针head、data等指向数据区。
void kfree_skb(struct sk_buff *skb, int rw) 释放一个skb_buff。
stuct sk_buff *skb_clone(struct sk_buff *old, int priority)复制一个sk_buff, 但不复制数据部分。
struct sk_buff *skb_copy(struct sk_buff *skb) 完全复制一个sk_buff。
- 对sk_buff结构数据区的操作函数
unsigned char *skb_headroom(struct sk_buff *skb)返回缓冲区头部为skb_push预留的空间的字节数大小。
unsigned char *skb_pull(struct sk_buff *skb, int len ) 该函数将data指针向数据区的末尾移动,减少了len字段的长度。该函数可用于从接收到的数据头上移动数据或协议头。
unsigned char *skb_push(struct sk_buff *skb, int len)该函数将data指针向数据区的前端移动,增加了len字段的长度。在发送数据的过程中,利用该函数可在数据的前端添加数据或协议头。
unsigned char *skb_put(struct sk_buff *skb, int len)该函数将tail指针向数据区的末尾移动,增加了len字段的长度。在发送数据的过程中,利用该函数可在数据的末端添加数据或协议尾。
unsigned char *skb_reserve(struct sk_buff *skb, int len) 该函数在缓冲区头部创建一块额外的空间,这块空间在skb_push添加数据时使用。因为套接字建立时并没有为skb_push预留空间。它也可以用于在 缓冲区的头部增加一块空白区域,从而调整缓冲区的大小,使缓冲区的长度统一。这个函数对一个空的缓冲区才能使用。
unsigned char *skb_tailroom(struct sk_buff *skb)返回缓冲区尾部为skb_put预留的空间的字节数大小。
unsigned char *skb_trim(struct sk_buff *skb, int len) 该函数和put函数的功能相反,它将tail指针向数据区的前端移动,减小了len字段的长度。该函数可用于从接收到的数据尾上移去数据或协议尾。如果缓 冲区的长度比"len"还长,那么它就通过移去缓冲区尾部若干字节,把缓冲区的大小缩减到"len"长度。
还有一些其它的对sk_buff结构及数据区的操作函数,另外还有各种对sk_buff结构双向链表的操作函数,这里不作说明了。
(2)对缓冲区链表进行操作
sk_buff链表是一个双向链表,它包括一个链表头而且每一个缓冲区都有一个prev和next指针,指向链表中前一个和后一个缓冲区结点。对链表的操作函数说明如下(在net/core/skbuff.c中):struct sk_buff *skb_dequeue(struct skb_buff_head *list )
这个函数作用是把第一个缓冲区从链表中移走。返回取出的sk_buff,如果队列为空,就返回空指针。添加缓冲区用到skb_queue_head和skb_queue_tail两个例程。
int skb_peek(struct sk_buff_head *list)返回指向缓冲区链表第一个节点的指针。
int skb_queue_empty(struct sk_buff_head *list) 如果链表为空,返回true。
void skb_queue_head(struct sk_buff_head *skb) 在链表头部添加一个缓冲区。
void skb_queue_head_init(struct sk_buff_head *list) 初始化sk_buff_head结构。
_u32 skb_queue_len(struct sk_buff_head *list) 返回队列中排队的缓冲区的数目。
void skb_queue_tqil(struct sk_buff *skb) 在链表的尾部添加一个缓冲区。
void skb_unlink(struct sk_buff *skb) 这个函数从链表中移去一个缓冲区,它只是将缓冲区从链表中移去,但并不释放它。
void skb_append(struct sk_buff *entry, struct sk_buff *new_entry)
void skb_insert(struct sk_buff *entry, struct sk_buff *new_entry)
它们可以使用户把一个缓冲区放在链表中任何一个位置。
sock结构
INET 是Linux的TCP/IP协议簇的应用,在INET Socket层中管理数据包存放和调度的数据结构是sock,sock是socket的网络层代表。include/net/sock.h
这是socket在小型网络层的代表,是sock结构及tcp_tw_bucket结构的头。
struct sock_common {
unsigned short skc_family;//网络地址簇
volatile unsigned char skc_state;//连接状态
unsigned char skc_reuse;// %SO_REUSEADDR设置
int skc_bound_dev_if;//绑定设备的序号
struct hlist_node skc_node;//各种协议查找表的主hash链接
struct hlist_node skc_bind_node;// 各种协议查找表的bind hash连接
atomic_t skc_refcnt; //引用计数
};
socket的网络层代表
struct sock {
/*sock_common结构是tcp_tw_bucket结构的共享设计,现在tcp_tw_bucket
*结构也用到sock_common结构,因而sock_common结构必须作为第一个成员
*/
struct sock_common __sk_common;
#define sk_family __sk_common.skc_family
#define sk_state __sk_common.skc_state
#define sk_reuse __sk_common.skc_reuse
#define sk_bound_dev_if __sk_common.skc_bound_dev_if
#define sk_node __sk_common.skc_node
#define sk_bind_node __sk_common.skc_bind_node
#define sk_refcnt __sk_common.skc_refcnt
volatile unsigned char sk_zapped;// ax25 & ipx means !linked
// mask of %SEND_SHUTDOWN and/or %RCV_SHUTDOWN
unsigned char sk_shutdown;
unsigned char sk_use_write_queue;
unsigned char sk_userlocks; //%SO_SNDBUF和%SO_RCVBUF设置
socket_lock_t sk_lock;//同步锁
int sk_rcvbuf;//接收buffer的大小(字节为单位)
wait_queue_head_t *sk_sleep;//sock等待队列
struct dst_entry *sk_dst_cache;//目的地cache
rwlock_t sk_dst_lock;// 目的地cache读写锁
struct xfrm_policy *sk_policy[2];// flow policy
atomic_t sk_rmem_alloc;//提交接收队列字节数
struct sk_buff_head sk_receive_queue;//正进来的包
atomic_t sk_wmem_alloc;//提交传输队列字节数
struct sk_buff_head sk_write_queue;//包正送向队列
atomic_t sk_omem_alloc;// "o" is "option" or "other"
int sk_wmem_queued;// persistent queue size
int sk_forward_alloc;//向前分配的空间
unsigned int sk_allocation;//分配的模式
int sk_sndbuf; //发送buffer大小,以字节为单位
unsigned long sk_flags;// %SO_LINGER (l_onoff), %SO_BROADCAST,
//%SO_KEEPALIVE, %SO_OOBINLINE
char sk_no_check;// %SO_NO_CHECK设置
unsigned char sk_debug;// %SO_DEBUG设置
unsigned char sk_rcvtstamp;// %SO_TIMESTAMP设置
unsigned char sk_no_largesend;//是否送大片段
int sk_route_caps;//路由能力如%NETIF_F_TSO等
unsigned long sk_lingertime;// %SO_LINGER设置
int sk_hashent;//几个表的hash entry如tcp_ehash
//backlog队列是特殊的,
struct {
struct sk_buff *head;
struct sk_buff *tail;
} sk_backlog;//与每socket持有的自旋锁一起使用,要求较小的访问延迟
rwlock_t sk_callback_lock;//这个结构尾部回调用函数使用
struct sk_buff_head sk_error_queue;//很少用的错误队列
struct proto *sk_prot;//网络簇内的协议句柄
int sk_err,//最后的错误
sk_err_soft;//不引起失败的错误但它是永久失败的原因
unsigned short sk_ack_backlog;//当前listen backlog
unsigned short sk_max_ack_backlog;
__u32 sk_priority//%SO_PRIORITY设置
unsigned short sk_type;//socket类型如%SOCK_STREAM等
unsigned char sk_localroute;//仅当地路由,%SO_DONTROUTE设置
unsigned char sk_protocol;//socket协议属于这个网络簇
struct ucred sk_peercred;// %SO_PEERCRED设置
int sk_rcvlowat;// %SO_RCVLOWAT设置
long sk_rcvtimeo;// %SO_RCVTIMEO设置
long sk_sndtimeo;// %SO_SNDTIMEO设置
struct sk_filter *sk_filter;//socket过滤操作
void *sk_protinfo;//私有区域,当不用slab时的特定net簇
kmem_cache_t *sk_slab;//分配这个sock实例的slabcache
struct timer_list sk_timer;//sock清除定时器
struct timeval sk_stamp;//最后包接收的时间戳
struct socket *sk_socket;// Identd and reporting IO signals
void *sk_user_data;//RPC层私有数据
struct module *sk_owner;//拥有这个socker的模块
struct page *sk_sndmsg_page;//发送消息的缓存页
__u32 sk_sndmsg_off;//发送消息的缓存偏移
struct sk_buff *sk_send_head;// front of stuff to transmit
int sk_write_pending;//写到流中的socker等待开始
void *sk_security;//
__u8 sk_queue_shrunk;//写队列最近被缩小
/* three bytes hole, try to pack */
//指示sock状态变化的回调函数
void (*sk_state_change)(struct sock *sk);
//数据准备好将要处理的回调函数
void (*sk_data_ready)(struct sock *sk, int bytes);
//指示有可用的缓存发送空间回调函数
void (*sk_write_space)(struct sock *sk);
//指示如%MSG_ERRQUEUE的错误发生的回调函数
void (*sk_error_report)(struct sock *sk);
//处理backlog的回调函数
int (*sk_backlog_rcv)(struct sock *sk,
struct sk_buff *skb);
//当所有的refcnt == 0,sock释放的时刻调用的函数
void (*sk_destruct)(struct sock *sk);
};
结构proto是INET Socket层的控制函数,它定义不同的协议簇,如对于TCP,则为tcp_prot;对于UDP协议,则为udp_prot。
sockfs文件系统
通过sockfs文件系统的操作也可实现对socket的操作,sockfs文件系统通过操作函数指针转接,最终调用了socket的操作函数集来完成对sockfs文件系统中的文件操作。sock_init函数在init进程中的main函数被调用,它初始化协议簇、建立socket缓存、建立socketfs文件系统和建立用于包过滤的nf_hooks链表数组。函数列出如下(在在net/socket.c中):
void __init sock_init(void)
{
int i;
//初始化地址(协议)簇
for (i = 0; i < NPROTO; i++)
net_families[i] = NULL;
//创建名为sock的对象缓存
sk_init();
#ifdef SLAB_SKB
//创建名为skbuff_head_cache的对象缓存
skb_init();
#endif
//创建名为smb_inode_cache的对象缓存
init_inodecache();
register_filesystem(&sock_fs_type);
sock_mnt = kern_mount(&sock_fs_type);
#ifdef CONFIG_NETFILTER
//建立nf_hooks链表数组
netfilter_init();
#endif
}
static struct vfsmount *sock_mnt;
下面是文件系统类型定义:
static struct file_system_type sock_fs_type = {
.name = "sockfs",
.get_sb = sockfs_get_sb,
.kill_sb = kill_anon_super,
};
函数sockfs_get_sb得到文件系统超级块,函数列出如下:
static struct super_block *sockfs_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
//伪文件系统的通用处理函数,初始化根节点、根目录及超级块
return get_sb_pseudo(fs_type, "socket:", &sockfs_ops, SOCKFS_MAGIC);
}
下面是文件系统超级块的操作函数集结构:
static struct super_operations sockfs_ops = {
.alloc_inode = sock_alloc_inode,
.destroy_inode =sock_destroy_inode,
.statfs = simple_statfs,
};
下面结构socket socket_file_ops是文件操作函数集,这些操作不出现在操作结构里而是直接通过socketcall()复用器完成。
static struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.aio_read = sock_aio_read,
.aio_write = sock_aio_write,
.poll = sock_poll,
.ioctl = sock_ioctl,
.mmap = sock_mmap,
.open = sock_no_open, //不允许在通过/proc打开
.release = sock_close,
.fasync = sock_fasync,
.readv = sock_readv,
.writev = sock_writev,
.sendpage = sock_sendpage
};
下面只对文件的底层操作接口函数sock_aio_write进行分析,函数sock_aio_write调用层次图如下:
static ssize_t sock_aio_write(struct kiocb *iocb, const char __user *ubuf,
size_t size, loff_t pos)
{
struct sock_iocb *x, siocb;
struct socket *sock;
if (pos != 0)
return -ESPIPE;
if(size==0) /* Match SYS5 behaviour */
return 0;
if (is_sync_kiocb(iocb)) //同步操作
x = &siocb;
else {//异步操作
x = kmalloc(sizeof(struct sock_iocb), GFP_KERNEL);
if (!x)
return -ENOMEM;
// sock_aio_dtor只调用kfree函数
iocb->ki_dtor = sock_aio_dtor;
}
iocb->private = x;
x->kiocb = iocb;
//得到socket结构
sock = SOCKET_I(iocb->ki_filp->f_dentry->d_inode);
//初始化x
x->async_msg.msg_name = NULL;
x->async_msg.msg_namelen = 0;
x->async_msg.msg_iov = &x->async_iov;
x->async_msg.msg_iovlen = 1;
x->async_msg.msg_control = NULL;
x->async_msg.msg_controllen = 0;
x->async_msg.msg_flags = !(iocb->ki_filp->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT;
if (sock->type == SOCK_SEQPACKET)
x->async_msg.msg_flags |= MSG_EOR;
x->async_iov.iov_base = (void __user *)ubuf;
x->async_iov.iov_len = size;
return __sock_sendmsg(iocb, sock, &x->async_msg, size);
}
static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size)
{
struct sock_iocb *si = kiocb_to_siocb(iocb);
int err;
si->sock = sock;
si->scm = NULL;
si->msg = msg;
si->size = size;
//进行安全检查
err = security_socket_sendmsg(sock, msg, size);
if (err)
return err;
//调用socket的操作函数发送消息
return sock->ops->sendmsg(iocb, sock, msg, size);
}
c
利用socket通信
socket对连接控制及数据信息进行管理,从用户的角度看,网络通信就是创建socket和使用socket的过程。一个socket映射到一个文件,对这个文件的读写,就相当于对socket进行操作,也就相当于接收和发送网络数据。Linux网络收发数据有两种发送数据的方式:一是通过文件系统中的write系统调用,将需要发送的数据写入socket文件描述符;另一种通过sys_send系统调用完成。
同样,接收数据可以通过文件系统中的read系统调用或通过sys_recv系统调用来接收数据,这两种发送和接收数据方式,经过形式处理后统一到函数sock_readv_writev函数上。
整个网络层的流程如下(以两个进程通过TCP/IP进行通信为例):
在IP协议层有三个关键函数:ip_rcv( )、ip_forward( )、ip_output( ),分别处理IP层的接收、转发和发送工作。防火墙的功能函数将在此三个函数中调用。
socket层
sys_socketcall函数是网络通信的系统调用,应用程序通过对这个函数的调用建立socket,并利用socket进行网络通信。这个函 数把用户空间的参数args分解成地址簇、协议类型等,根据参数call选择不同的函数进行网络操作。如侦听、传送、接收等。这个函数在下面只列出部分操 作函数,其它参看源代码。这个函数如下(在net/socket.c中)://协议列表,每个协议注册在这里,NPROTO 缺省定义为
static struct net_proto_family *net_families[NPROTO];
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
unsigned long a[6];
unsigned long a0,a1;
int err;
if(call<1||call>SYS_RECVMSG)
return -EINVAL;
/* copy_from_user should be SMP safe. */
if (copy_from_user(a, args, nargs[call]))
return -EFAULT;
a0=a[0];
a1=a[1];
switch(call)
{
case SYS_SOCKET:
err = sys_socket(a0,a1,a[2]);
break;
case SYS_BIND:
err = sys_bind(a0,(struct sockaddr __user *)a1, a[2]);
break;
case SYS_CONNECT:
err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
break;
case SYS_LISTEN:
err = sys_listen(a0,a1);
break;
case SYS_ACCEPT:
err = sys_accept(a0,(struct sockaddr __user *)a1, (int __user *)a[2]);
break;
……
}
return err;
}
下面对函数sys_socket进行分析,函数sys_socket调用层次图如下:
asmlinkage long sys_socket(int family, int type, int protocol)
{
int retval;
struct socket *sock;
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
retval = sock_map_fd(sock);
if (retval < 0)
goto out_release;
out:
/* It may be already another descriptor 8) Not kernel problem. */
return retval;
out_release:
sock_release(sock);
return retval;
}
int sock_create(int family, int type, int protocol, struct socket **res)
{
return __sock_create(family, type, protocol, res, 0);
}
static int __sock_create(int family, int type, int protocol, struct socket **res, int kern)
{
int i;
int err;
struct socket *sock;
检查协议范围
……
//权限检查
err = security_socket_create(family, type, protocol, kern);
if (err)
return err;
……
//确认协议模块是否存在,若存在,增加引用计数
i = -EAFNOSUPPORT;
if (!try_module_get(net_families[family]->owner))
goto out_release;
if ((i = net_families[family]->create(sock, protocol)) < 0)
goto out_module_put;
……
//从参数传回调用函数
*res = sock;
security_socket_post_create(sock, family, type, protocol, kern);
……
}
下面是全局的协议链表,每个协议注册在这里。
static struct net_proto_family *net_families[NPROTO];
对INET类型的socket来说,如果上面的socket结构的地址簇是AF_INET,对应的net_proto_family结构的变量存放 在net_families[]数组中(在sock_register()函数中完成注册)的定义如下(在net/ipv6/af_inet6.c中):
static struct net_proto_family inet6_family_ops = {
.family = PF_INET6,
.create = inet6_create,
.owner = THIS_MODULE,
};
函数inet6_create实现了IPV6的socket的创建,函数列出如下(在net/ipv6/af_inet6.c中):
static int inet6_create(struct socket *sock, int protocol)
{
struct inet_opt *inet;
struct ipv6_pinfo *np;
struct sock *sk;
struct tcp6_sock* tcp6sk;
struct list_head *p;
struct inet_protosw *answer;
struct proto *answer_prot;
unsigned char answer_flags;
char answer_no_check;
int rc;
//查找要求的type/protocol 对
answer = NULL;
rcu_read_lock();
list_for_each_rcu(p, &inetsw6[sock->type]) {
answer = list_entry(p, struct inet_protosw, list);
//检查协议是否匹配及是否为IPPROTO_IP
if (protocol == answer->protocol) {
if (protocol != IPPROTO_IP)
break;
} else {
/* Check for the two wild cases. */
if (IPPROTO_IP == protocol) {
protocol = answer->protocol;
break;
}
if (IPPROTO_IP == answer->protocol)
break;
}
answer = NULL;
}
……
//得到协议对应的操作函数集结构
sock->ops = answer->ops;
answer_prot = answer->prot;
answer_no_check = answer->no_check;
answer_flags = answer->flags;
rcu_read_unlock();
BUG_TRAP(answer_prot->slab != NULL);
rc = -ENOBUFS;
//分配新的sock结构
sk = sk_alloc(PF_INET6, GFP_KERNEL,
answer_prot->slab_obj_size,
answer_prot->slab);
if (sk == NULL)
goto out;
//将sock与sk用指针关联起来,初始化sk
sock_init_data(sock, sk);
sk_set_owner(sk, THIS_MODULE);
//sk设置
rc = 0;
//sk设置协议的控制操作函数集
sk->sk_prot = answer_prot;
sk->sk_no_check = answer_no_check;
if (INET_PROTOSW_REUSE & answer_flags)
sk->sk_reuse = 1;
//得到对应协议的inet_opt结构,
//inet_opt结构包含了对收到的包进行socket解复用比较的数据
inet = inet_sk(sk);
if (SOCK_RAW == sock->type) {
inet->num = protocol;
if (IPPROTO_RAW == protocol)
inet->hdrincl = 1;
}
sk->sk_destruct = inet6_sock_destruct;
sk->sk_family = PF_INET6;
sk->sk_protocol = protocol;
sk->sk_backlog_rcv = answer->prot->backlog_rcv;
tcp6sk = (struct tcp6_sock *)sk;
tcp6sk->pinet6 = np = inet6_sk_generic(sk);
//设置IPV6私有信息
np->hop_limit = -1;
np->mcast_hops = -1;
np->mc_loop = 1;
np->pmtudisc = IPV6_PMTUDISC_WANT;
np->ipv6only = sysctl_ipv6_bindv6only;
//初始化在socket中的ipv4部分,因为有socket因IPV4而使用IPV6的API
inet->uc_ttl = -1;
inet->mc_loop = 1;
inet->mc_ttl = 1;
inet->mc_index = 0;
inet->mc_list = NULL;
if (ipv4_config.no_pmtu_disc)
inet->pmtudisc = IP_PMTUDISC_DONT;
else
inet->pmtudisc = IP_PMTUDISC_WANT;
#ifdef INET_REFCNT_DEBUG
atomic_inc(&inet6_sock_nr);
atomic_inc(&inet_sock_nr);
#endif
if (inet->num) {
//假定允许用户在socket创建时指定一个数的任何协议共享
inet->sport = ntohs(inet->num);
sk->sk_prot->hash(sk);
}
if (sk->sk_prot->init) {
rc = sk->sk_prot->init(sk);
if (rc) {
sk_common_release(sk);
goto out;
}
}
out:
return rc;
out_rcu_unlock:
rcu_read_unlock();
goto out;
}
函数sock_alloc 分配一个新inode和socket对象,两者绑在一起并初始化。接着这个socket被返回。如果超出了inodes个数,返回NULL。
struct socket *sock_alloc(void)
{
struct inode * inode;
struct socket * sock;
inode = new_inode(sock_mnt->mnt_sb);
if (!inode)
return NULL;
//得到socket结构
sock = SOCKET_I(inode);
inode->i_mode = S_IFSOCK|S_IRWXUGO;
inode->i_sock = 1;
inode->i_uid = current->fsuid;
inode->i_gid = current->fsgid;
get_cpu_var(sockets_in_use)++;
put_cpu_var(sockets_in_use);
return sock;
}
函数sock_map_fd获得第一个可用文件描述子,并建立成可用的。这个函数创建文件结构并把它映射到当前进程的fd空间。如果成功返回文件描述子,并且把文件结构存在sock->file中。
int sock_map_fd(struct socket *sock)
{
int fd;
struct qstr this;
char name[32];
//分配一个空闲的fd
fd = get_unused_fd();
if (fd >= 0) {
//分配对象缓存并初始化file
struct file *file = get_empty_filp();
if (!file) {
put_unused_fd(fd);
fd = -ENFILE;
goto out;
}
sprintf(name, "[%lu]", SOCK_INODE(sock)->i_ino);
this.name = name;
this.len = strlen(name);
this.hash = SOCK_INODE(sock)->i_ino;
//分配dentry,将其挂接在文件系统sockfs的根目录下。
file->f_dentry = d_alloc(sock_mnt->mnt_sb->s_root, &this);
if (!file->f_dentry) {
put_filp(file);
put_unused_fd(fd);
fd = -ENOMEM;
goto out;
}
//赋上目录操作函数集
file->f_dentry->d_op = &sockfs_dentry_operations;
//加上sock的节点
d_add(file->f_dentry, SOCK_INODE(sock));
file->f_vfsmnt = mntget(sock_mnt);
file->f_mapping = file->f_dentry->d_inode->i_mapping;
sock->file = file;
//加上文件操作函数集
file->f_op = SOCK_INODE(sock)->i_fop = &socket_file_ops;
file->f_mode = FMODE_READ | FMODE_WRITE;
file->f_flags = O_RDWR;
file->f_pos = 0;
//将fd与file连接起来
fd_install(fd, file);
}
out:
return fd;
}
系统调用connect将一个和socket结构关联的文件描述符和一个sockaddr结构的地址对应的远程机器相关联,并且调用各个协议自己对 应的connect连接函数。入口参数sockfd是通过socket系统调用创建的socket文件描述符,serv_addr包括了需要连接的远程主 机的IP地址,addrlen表示了serv_addr的长度。
函数sys_connect 列出如下(在net/socket.c中):
asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err;
//查找socket
sock = sockfd_lookup(fd, &err);
if (!sock)
goto out;
//调用copy_from_user函数将用户空间的uservaddr拷贝到内核空间address中
err = move_addr_to_kernel(uservaddr, addrlen, address);
if (err < 0)
goto out_put;
//检查安全连接许可
err = security_socket_connect(sock, (struct sockaddr *)address, addrlen);
if (err)
goto out_put;
//调用各个协议自己对应的connect函数,
err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen,
sock->file->f_flags);
out_put:
sockfd_put(sock);
out:
return err;
}
IP层收发数据包函数
在数据包进入IP层时,有个数据包队列sk_backlog,在数据包从IP层进入TCP层时有包队列sk_receive_queue,它们都是结构sock的成员变量。它们可用软中断来实现接收。接收例程
接收操作从底层发起,因而,接收例程从底层向上层进行分析。下面分析接收例程的函数。函数ip_rcv()调用层次图如上图,函数ip_rcv()分析如下:
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
struct iphdr *iph;
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop; //丢掉发往其它机器的包
//置接收状态
IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);
// skb_share_check()检查包是否共享的,若是克隆skb
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);//置丢弃状态
goto out;
}
//在skb中拉出IP头大小的位置
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error;
iph = skb->nh.iph;
//IP头长度小于5或IP头版本号不是4
if (iph->ihl < 5 || iph->version != 4)
goto inhdr_error;
//在skb中拉出空间
if (!pskb_may_pull(skb, iph->ihl*4))
goto inhdr_error;
iph = skb->nh.iph;
//检查校验和是否为0
if (ip_fast_csum((u8 *)iph, iph->ihl) != 0)
goto inhdr_error;
{
__u32 len = ntohs(iph->tot_len);
if (skb->len < len || len < (iph->ihl<<2))
goto inhdr_error;
//传送的媒体可能已超出buffer,裁剪到帧的真实长度。
if (skb->len > len) {
__pskb_trim(skb, len); //裁剪skb到合适的长度len
if (skb->ip_summed == CHECKSUM_HW)
skb->ip_summed = CHECKSUM_NONE;
}
}
//调用netfilter的hook:NF_IP_PRE_ROUTING
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
inhdr_error:
IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
out:
return NET_RX_DROP;
}
函数ip_rcv_finish在完成包接收后进行路由处理,函数列出如下:
static inline int ip_rcv_finish(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct iphdr *iph = skb->nh.iph;
//给包初始化虚拟路径缓存,它描述了包在linux网络里怎样旅行
if (skb->dst == NULL) {
//获得路由信息,判断包是进行IP转发还是发向高一层协议
if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))
goto drop;
}
#ifdef CONFIG_NET_CLS_ROUTE
if (skb->dst->tclassid) {
struct ip_rt_acct *st = ip_rt_acct + 256*smp_processor_id();
u32 idx = skb->dst->tclassid;
st[idx&0xFF].o_packets++;
st[idx&0xFF].o_bytes+=skb->len;
st[(idx>>16)&0xFF].i_packets++;
st[(idx>>16)&0xFF].i_bytes+=skb->len;
}
#endif
if (iph->ihl > 5) {
struct ip_options *opt;
if (skb_cow(skb, skb_headroom(skb))) {//规范IP头的大小
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto drop;
}
iph = skb->nh.iph;
//骓options并填充指针在options结构里
if (ip_options_compile(NULL, skb))
goto inhdr_error;
opt = &(IPCB(skb)->opt);
if (opt->srr) {
struct in_device *in_dev = in_dev_get(dev);
if (in_dev) {
if (!IN_DEV_SOURCE_ROUTE(in_dev)) {
…
in_dev_put(in_dev);
goto drop;
}
in_dev_put(in_dev);
}
if (ip_options_rcv_srr(skb))
goto drop;
}
}
return dst_input(skb);//即skb->dst->input(skb),如:ip_local_delever
inhdr_error:
IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
函数ip_route_input给进来的包skb查找路由,先在缓冲区的路由hash表查找,如果没找到,再调用ip_route_input_slow函数到路由表中查找。函数ip_route_input分析如下(在net/ipv4/route.c中):
int ip_route_input(struct sk_buff *skb, u32 daddr, u32 saddr,
u8 tos, struct net_device *dev)
{
struct rtable * rth;
unsigned hash;
int iif = dev->ifindex;
tos &= IPTOS_RT_MASK;
//得到hash表
hash = rt_hash_code(daddr, saddr ^ (iif << 5), tos);
rcu_read_lock();
//从入口开始循环查找匹配的路由
for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;
rth = rcu_dereference(rth->u.rt_next)) {
if (rth->fl.fl4_dst == daddr &&
rth->fl.fl4_src == saddr &&
rth->fl.iif == iif &&
rth->fl.oif == 0 &&
#ifdef CONFIG_IP_ROUTE_FWMARK
rth->fl.fl4_fwmark == skb->nfmark &&
#endif
rth->fl.fl4_tos == tos) {
rth->u.dst.lastuse = jiffies;
dst_hold(&rth->u.dst);
rth->u.dst.__use++;
RT_CACHE_STAT_INC(in_hit);
rcu_read_unlock();
//查找到目的地址,并赋上
skb->dst = (struct dst_entry*)rth;
return 0;
}
RT_CACHE_STAT_INC(in_hlist_search);
}
rcu_read_unlock();
if (MULTICAST(daddr)) {//如果是多播地址
struct in_device *in_dev;
rcu_read_lock();
if ((in_dev = __in_dev_get(dev)) != NULL) {
int our = ip_check_mc(in_dev, daddr, saddr,
skb->nh.iph->protocol);
if (our
#ifdef CONFIG_IP_MROUTE
|| (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev))
#endif
) {//如果是多播路由器
rcu_read_unlock();
return ip_route_input_mc(skb, daddr, saddr,
tos, dev, our);
}
}
rcu_read_unlock();
return -EINVAL;
}
//接收路由包,分析路由表,设备路由表处理函数
return ip_route_input_slow(skb, daddr, saddr, tos, dev);
}
函数ip_local_deliver 分发IP包给更高的协议层。在ip_route_input_slow函数里若是本地路由就指定作dst->input例程。函数ip_local_deliver分析如下(在net/ipv4/ip_input.c中):
int ip_local_deliver(struct sk_buff *skb)
{
//如果是可重装配的IP碎片包
if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
skb = ip_defrag(skb);//重组装碎片包
if (!skb)
return 0;
}
//如果hook没有呑没包,就调用ip_local_deliver_finish函数完成分发
return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
函数ip_local_deliver_finish找到负载类型相关的协议,函数分析如下(在net/ipv4/ip_input.c中):
static inline int ip_local_deliver_finish(struct sk_buff *skb)
{
int ihl = skb->nh.iph->ihl*4;
#ifdef CONFIG_NETFILTER_DEBUG
nf_debug_ip_local_deliver(skb);
#endif /*CONFIG_NETFILTER_DEBUG*/
//从skb中拉出IP头空间
__skb_pull(skb, ihl);
//从netfilter connection-tracking模块中早释放引用,
//因为不再需要,它可能无限期地占有装载的ip_conntrack模块。
nf_reset(skb);
//指进IP数据报,仅传递头部分
skb->h.raw = skb->data;
rcu_read_lock();
{
/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
int protocol = skb->nh.iph->protocol;
int hash;
struct sock *raw_sk;
struct net_protocol *ipprot;
resubmit:
hash = protocol & (MAX_INET_PROTOS - 1);
raw_sk = sk_head(&raw_v4_htable[hash]);
//如果有raw socket需要检查
if (raw_sk)
// raw socket的IP输入处理
raw_v4_input(skb, skb->nh.iph, hash);
//如果IP协议存在
if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
int ret;
if (!ipprot->no_policy &&
!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
goto out;
}
ret = ipprot->handler(skb);//找到包负载类型的处理协议
if (ret < 0) {
protocol = -ret;
goto resubmit;
}
IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);//devliver状态
} else {
if (!raw_sk) {
if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
//标识不知道协议的状态
IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
//送出ICMP消息作为回应
icmp_send(skb, ICMP_DEST_UNREACH,
ICMP_PROT_UNREACH, 0);
}
} else
//标识为devliver状态
IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
kfree_skb(skb);
}
}
out:
rcu_read_unlock();
return 0;
}
在函数ip_route_input_slow中,如果不是本地路由时就把函数ip_forward指定给dst->input例程。函数ip_forward分析如下(在net/ipv4/ip_forward.c中):
int ip_forward(struct sk_buff *skb)
{
struct iphdr *iph; /* Our header */
struct rtable *rt; /* Route we use */
struct ip_options * opt = &(IPCB(skb)->opt);
if (!xfrm4_policy_check(NULL, XFRM_POLICY_FWD, skb))
goto drop;
//如果是路由器提醒信息
if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
return NET_RX_SUCCESS;
//如果不是给主机的包,就丢弃
if (skb->pkt_type != PACKET_HOST)
goto drop;
skb->ip_summed = CHECKSUM_NONE;
//按照RFC,我们必须首先减少TTL域,如果达到0,
//我们必须回答一个ICMP控制信息告诉包的生命期超期。
iph = skb->nh.iph;
if (iph->ttl <= 1)
goto too_many_hops; //太多的hops(路由器间传递一次为一跳)
if (!xfrm4_route_forward(skb))
goto drop;
iph = skb->nh.iph;
rt = (struct rtable*)skb->dst;
//如果严格的路由指示并且下一跳不合适,
if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
goto sr_failed;//丢弃包送出路由主机不可达的ICMP信息
//克隆skb头,扩展skb的headroom
if (skb_cow(skb, LL_RESERVED_SPACE(rt->u.dst.dev)+rt->u.dst.header_len))
goto drop;
iph = skb->nh.iph;
//减少ttl
ip_decrease_ttl(iph);
//现在产生ICMP主机重定向路由并送出
if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr)
ip_rt_send_redirect(skb);
skb->priority = rt_tos2priority(iph->tos);
return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, rt->u.dst.dev,
ip_forward_finish);
sr_failed:
/*
* Strict routing permits no gatewaying
*/
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
goto drop;
too_many_hops:
//告诉发送者它的包已死亡
icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
发送例程
数据的发送从上层发起的,因而,发送例程的函数分析从上层开始。net/ipv4/ip_output.c
函数ip_build_and_send_pkt加IP头到一个skbuff并把它发送出去。
int ip_build_and_send_pkt(struct sk_buff *skb, struct sock *sk,
u32 saddr, u32 daddr, struct ip_options *opt)
{
struct inet_opt *inet = inet_sk(sk);
struct rtable *rt = (struct rtable *)skb->dst;
struct iphdr *iph;
//建立IP头
if (opt)
iph=(struct iphdr *)skb_push(skb,sizeof(struct iphdr)
+ opt->optlen);
else
iph=(struct iphdr *)skb_push(skb,sizeof(struct iphdr));
iph->version = 4; //版本号IPV4
iph->ihl = 5; //IP头的长度
iph->tos = inet->tos;
if (ip_dont_fragment(sk, &rt->u.dst))
iph->frag_off = htons(IP_DF);
else
iph->frag_off = 0;
iph->ttl = ip_select_ttl(inet, &rt->u.dst);
iph->daddr = rt->rt_dst;
iph->saddr = rt->rt_src;
iph->protocol = sk->sk_protocol;
iph->tot_len = htons(skb->len);
ip_select_ident(iph, &rt->u.dst, sk);
skb->nh.iph = iph;
if (opt && opt->optlen) {
iph->ihl += opt->optlen>>2;
//写options到IP头,记录到源路由选项的目的地址及本地地址
ip_options_build(skb, opt, daddr, rt, 0);
}
//产生IP数据报的校验和
ip_send_check(iph);
skb->priority = sk->sk_priority;
//hook并在dst_output函数中调用skb->dst->output把包发送出去
return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
dst_output);
}
网络核心层
网络核心层是协议无关的层,它提供对网络设备统一的管理。net_device结构
结构net_device是对网络设备的描述结构,它描述了网络设备的特征、控制函数及一些控制相关的变量。这个结构列出如下(在include/linux/netdevice.h中):struct net_device
{
char name[IFNAMSIZ];//接口的名字
//I/O 特定成员
unsigned long mem_end; //共享内存结束端
unsigned long mem_start; //共享内存开始端
unsigned long base_addr; //设备 I/O地址
unsigned int irq; //设备 IRQ号
//某些硬件需要这些成员,但它们不是定义在Space.c中常用的部分
unsigned char if_port; //可选的AUI, TP,
unsigned char dma; // DMA通道
unsigned long state;
struct net_device *next;//下一个网络设备结构
//设备初始化函数,仅调用一次
int (*init)(struct net_device *dev);
/* ------- Fields preinitialized in Space.c finish here ------- */
struct net_device *next_sched;
//接口序号,唯一的设备ID
int ifindex;
int iflink;
struct net_device_stats* (*get_stats)(struct net_device *dev);
struct iw_statistics* (*get_wireless_stats)(struct net_device *dev);
//函数链表用来处理无线扩展(代表ioctl)细节在<net/iw_handler.h>
struct iw_handler_def * wireless_handlers;
struct ethtool_ops *ethtool_ops;
//标识这个结构可见部分的尾端,这个成员后面成员的对系统来说是内部的,并且可任意改变
//这些可能是将来必须的,用来作网络power-down代码
unsigned long trans_start; //上一次Tx时间(jiffies)
unsigned long last_rx; //上一次 Rx时间
unsigned short flags; //接口标识(BSD用的)
unsigned short gflags;
unsigned short priv_flags; //类似'flags'但用户空间可见
unsigned short unused_alignment_fixer; //是32-bit 对齐的priv_flags
unsigned mtu; //接口的MTU值
unsigned short type; //接口硬件类型
unsigned short hard_header_len; /* hardware hdr length */
void *priv; //私有数据
struct net_device *master; //指向组的主设备,这个设备是组的成员
//接口地址信息
unsigned char broadcast[MAX_ADDR_LEN]; //硬件广播地址
unsigned char dev_addr[MAX_ADDR_LEN]; //硬件地址
unsigned char addr_len; //硬件地址长度
struct dev_mc_list *mc_list; //多播mac地址
int mc_count; //安装的多播地址的数量
int promiscuity;
int allmulti;
int watchdog_timeo;
struct timer_list watchdog_timer;
//协议特定的指针
void *atalk_ptr; // AppleTalk link
void *ip_ptr; //IPv4特定数据
void *dn_ptr; //DECnet特定数据
void *ip6_ptr; //IPv6特定数据
void *ec_ptr; //Econet特定数据
void *ax25_ptr; // AX.25 特定数据
struct list_head poll_list; /* Link to poll list */
int quota;
int weight;
struct Qdisc *qdisc;
struct Qdisc *qdisc_sleeping;
struct Qdisc *qdisc_ingress;
struct list_head qdisc_list;
unsigned long tx_queue_len; //每队列允许的最大帧数
/* ingress path synchronizer */
spinlock_t ingress_lock;
/* hard_start_xmit synchronizer */
spinlock_t xmit_lock;
//进入hard_start_xmit的处理器的cpu ID,若没有,则为-1
int xmit_lock_owner;
/* device queue lock */
spinlock_t queue_lock;
//这个设备的引用数
atomic_t refcnt;
//延迟的register/unregister
struct list_head todo_list;
//hash链中的设备名字
struct hlist_node name_hlist;
//hash链中的设备序号
struct hlist_node index_hlist;
// register/unregister 状态机
enum { NETREG_UNINITIALIZED=0,
NETREG_REGISTERING, //调用 register_netdevice
NETREG_REGISTERED, //完成了 register操作
NETREG_UNREGISTERING, //调用unregister_netdevice
NETREG_UNREGISTERED, //完成了unregister操作
NETREG_RELEASED, //调用free_netdev
} reg_state;
//网络设备相关特征
int features;
#define NETIF_F_SG 1 /* Scatter/gather IO. */
#define NETIF_F_IP_CSUM 2 //仅仅在IPv4上的TCP/UDP 能有校验
#define NETIF_F_NO_CSUM 4 //不需要校验结果,如loopack
#define NETIF_F_HW_CSUM 8 //能校验所有的包
#define NETIF_F_HIGHDMA 32 //能在high memory 进行DMA
#define NETIF_F_FRAGLIST 64 /* Scatter/gather IO. */
#define NETIF_F_HW_VLAN_TX 128 //传送VLAN硬件加速
#define NETIF_F_HW_VLAN_RX 256 //接收VLAN硬件加速
#define NETIF_F_HW_VLAN_FILTER 512 //VLAN上接收过滤
#define NETIF_F_VLAN_CHALLENGED 1024 //设备不能处理VLAN包
#define NETIF_F_TSO 2048 /* Can offload TCP/IP segmentation */
#define NETIF_F_LLTX 4096 /* LockLess TX */
//在设备从网络分离后调用
void (*uninit)(struct net_device *dev);
//在是最后一个用户引用消失后调用
void (*destructor)(struct net_device *dev);
//接口服务例程的指针
int (*open)(struct net_device *dev);
int (*stop)(struct net_device *dev);
int (*hard_start_xmit) (struct sk_buff *skb,
struct net_device *dev);
……
/* bridge stuff */
struct net_bridge_port *br_port;
#ifdef CONFIG_NET_DIVERT
//在每个接口类型初始化例程,这个将得到初始化
struct divert_blk *divert;
#endif /* CONFIG_NET_DIVERT */
/* class/net/name entry */
struct class_device class_dev;
//已经被alloc_netdev()加进的填充字节数
int padded;
};
网络设备初始化
在init/main.c中的函数do_basic_setup调用do_init_call函数,然后do_init_call函数调用了fs /partitions/check.c中的partition_setup函数,接着函数partition_setup调用了drivers /block/genhd.c中的device_init函数,最后,函数device_init才调用函数net_dev_init。函数net_dev_init调用了全局链表*base中各个成员的init函数(即各个具体网络设备的初始化函数)来检测和初始化网络接口设备。在系统启动时系统就遍历设备链表,去除不能正常初始化的设备(一般是硬件不存在),链表中剩下的都是正常的设备。
函数net_dev_init分析如下(在net/cort/dev.c中):
static int __init net_dev_init(void)
{
int i, rc = -ENOMEM;
BUG_ON(!dev_boot_phase);
net_random_init();
//在proc文件系统里net下加入文件及操作
if (dev_proc_init())
goto out;
//在sysfs文件系统中加入网络设备
if (netdev_sysfs_init())
goto out;
//生成ptype_all链表
INIT_LIST_HEAD(&ptype_all);
//生成包类型链表,共有16种类型包
for (i = 0; i < 16; i++)
INIT_LIST_HEAD(&ptype_base[i]);
for (i = 0; i < ARRAY_SIZE(dev_name_head); i++)
INIT_HLIST_HEAD(&dev_name_head[i]);
for (i = 0; i < ARRAY_SIZE(dev_index_head); i++)
INIT_HLIST_HEAD(&dev_index_head[i]);
//初始化包接收队列
for (i = 0; i < NR_CPUS; i++) {
struct softnet_data *queue;
//得到每CPU的softnet_data结构并初始化
queue = &per_cpu(softnet_data, i);
skb_queue_head_init(&queue->input_pkt_queue);
queue->throttle = 0;
queue->cng_level = 0;
queue->avg_blog = 10; /* arbitrary non-zero */
queue->completion_queue = NULL;
INIT_LIST_HEAD(&queue->poll_list);
set_bit(__LINK_STATE_START, &queue->backlog_dev.state);
queue->backlog_dev.weight = weight_p;
//注册设备结构中的poll函数
queue->backlog_dev.poll = process_backlog;
atomic_set(&queue->backlog_dev.refcnt, 1);
}
#ifdef OFFLINE_SAMPLE
samp_timer.expires = jiffies + (10 * HZ);
add_timer(&samp_timer);
#endif
dev_boot_phase = 0;
//打开软中断,设置软中断号及运行函数
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
//设置CPU提示函数
hotcpu_notifier(dev_cpu_callback, 0);
//注册设备提示函数
dst_init();
//在proc文件系统注册dev_mcast节点及操作函数
dev_mcast_init();
rc = 0;
out:
return rc;
}
结构netdev_state_t是网络设备运行状态的标识,这些bit位标识对通用网络排队是私有的,不能被其它代码引用。结构netdev_state_t列出如下(在include/linux/netdevice.h中):
enum netdev_state_t
{
__LINK_STATE_XOFF=0,
__LINK_STATE_START,
__LINK_STATE_PRESENT,
__LINK_STATE_SCHED,
__LINK_STATE_NOCARRIER,
__LINK_STATE_RX_SCHED,
__LINK_STATE_LINKWATCH_PENDING
};
接收数据包
NET_RX_SUCCESS (没有拥塞)
NET_RX_CN_LOW (低程度的拥塞)
NET_RX_CN_MOD (中程度的拥塞)
NET_RX_CN_HIGH (高程度的拥塞)
NET_RX_DROP (包被丢弃)
函数netif_rx调用层次图如上图,函数netif_rx分析如下(在net/core/dev.c中):
int netif_rx(struct sk_buff *skb)
{
int this_cpu;
struct softnet_data *queue;
unsigned long flags;
#ifdef CONFIG_NETPOLL //使用了POLL方法
if (skb->dev->netpoll_rx && netpoll_rx(skb)) {
kfree_skb(skb);
return NET_RX_DROP;
}
#endif
if (!skb->stamp.tv_sec)//打上时间戳
net_timestamp(&skb->stamp);
//当CPU拥塞但还在运行时,这个代码被再安排以致径是最短的。
/*
* The code is rearranged so that the path is the most
* short when CPU is congested, but is still operating.
*/
local_irq_save(flags);
this_cpu = smp_processor_id(); //当前进程的CPU
//得到处理器的softnet_data中输入包队列,
queue = &__get_cpu_var(softnet_data);
__get_cpu_var(netdev_rx_stat).total++;
{//队列长度 <= 最大长度
if (queue->input_pkt_queue.qlen <= netdev_max_backlog)
if (queue->input_pkt_queue.qlen) {
if (queue->throttle) //流量限制
goto drop; //丢弃包
enqueue:
dev_hold(skb->dev);//拥有设备,设备引用读数加1
//将skb排到当前CPU的输入包队列未尾
__skb_queue_tail(&queue->input_pkt_queue, skb);
#ifndef OFFLINE_SAMPLE
get_sample_stats(this_cpu);
#endif
local_irq_restore(flags);
return queue->cng_level;
}
if (queue->throttle) {
queue->throttle = 0;
#ifdef CONFIG_NET_HW_FLOWCONTROL
if (atomic_dec_and_test(&netdev_dropping))
netdev_wakeup(); //唤醒流量控制设备
#endif
}
netif_rx_schedule(&queue->backlog_dev);
goto enqueue;
}
if (!queue->throttle) {
queue->throttle = 1;
__get_cpu_var(netdev_rx_stat).throttled++;
#ifdef CONFIG_NET_HW_FLOWCONTROL
atomic_inc(&netdev_dropping);
#endif
}
drop:
__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
}
函数netif_rx_schedule_prep 确定设备处于运行,且还没加到网络层的POLL处理队列中去。函数列出如下(在include/linux/netdevice.h中):
static inline int netif_rx_schedule_prep(struct net_device *dev)
{
//设备运行状态且没有被调度(这里调度指加到POLL队列)
return netif_running(dev) &&
!test_and_set_bit(__LINK_STATE_RX_SCHED, &dev->state);
}
函数netif_rx_schedule将设备接口添加到收发处理的 POLL 队列的尾部,排队并且准备接收数据包。触发一个 NET_RX_SOFTIRQ 的软中断通知网络层接收数据包。函数列出如下:
static inline void netif_rx_schedule(struct net_device *dev)
{
//设备运行状态且没有被调度(这里调度指加到POLL队列)
if (netif_rx_schedule_prep(dev))
__netif_rx_schedule(dev);
}
static inline void __netif_rx_schedule(struct net_device *dev)
{
unsigned long flags;
local_irq_save(flags);
dev_hold(dev);
//将新的设备&dev->poll_list加到合适的CPU的队列中
list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
//设置接收限额
if (dev->quota < 0)
dev->quota += dev->weight;
else
dev->quota = dev->weight;
//启动网络接收软中断,
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
local_irq_restore(flags);
}
函数netif_rx_complete在中断服务程序中使用,它从poll链表中移除网络设备接口,此时这个网络设备接口必须在当前CPU的POLL链表中。当完成工作时,在dev->poll()中调用这个函数。函数列出如下:
static inline void netif_rx_complete(struct net_device *dev)
{
unsigned long flags;
local_irq_save(flags);
BUG_ON(!test_bit(__LINK_STATE_RX_SCHED, &dev->state));
list_del(&dev->poll_list);
smp_mb__before_clear_bit();
clear_bit(__LINK_STATE_RX_SCHED, &dev->state);
local_irq_restore(flags);
}
函数net_rx_action 是软中断接收函数的句柄,是软中断中执行的接收函数,它是给NET_RX_SOFTIRQ注册的,通过当前处理器的接收POLL队列运行。接收POLL队 列是与处理器的包输入队列相关的"backlog device"链表。接收POLL队列调用每个"backlog device"的POLL方法。每个"backlog device"的POLL方法是函数process_backlog(),它在netdev_init()系统初始化时被指定。这个例程从包队列中取下 包,并为每个包调用netif_receive_skb函数。
函数net_rx_action 分析如下(在net/core/dev.c中):
static void net_rx_action(struct softirq_action *h)
{
//得到每CPU的softnet_data结构
struct softnet_data *queue = &__get_cpu_var(softnet_data);
unsigned long start_time = jiffies;
//budget 表示一个时间片内最大数据传输的块数,为300
//块的意思为每个 POLL 所完成 sk_buff数量,
//每块中间的 sk_buff 数量为 dev->quota 决定,
int budget = netdev_max_backlog;
local_irq_disable();
while (!list_empty(&queue->poll_list)) {//队列不为空
struct net_device *dev;
//当前的POLL过程的时间不超过一个时间片,软中断不能占用太多的时间
if (budget <= 0 || jiffies - start_time > 1)
goto softnet_break;
local_irq_enable();
//从链表成员中得到对应的设备结构
dev = list_entry(queue->poll_list.next,
struct net_device, poll_list);
//dev->poll是调用process_backlog函数
if (dev->quota <= 0 || dev->poll(dev, &budget)) {
//完成一次POLL过程的数据的接收
local_irq_disable();
list_del(&dev->poll_list);
list_add_tail(&dev->poll_list, &queue->poll_list);
//重新定义设备接收数据的"配额",即sk_buff缓冲区的数量
if (dev->quota < 0)
dev->quota += dev->weight;
else
dev->quota = dev->weight;
} else {
dev_put(dev);
local_irq_disable();
}
}
out:
local_irq_enable();
return;
softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
}
函数process_backlog是设备结构中的poll函数,用来处理轮询的方法,从网络设备上的Ring Buffer中读入数据,向上层提交数据。它在函数net_rx_action中被调用。它把包传递给函数netif_receive_skb()做更进 一步的处理。
函数process_backlog分析如下(在net/core/dev.c中):
static int process_backlog(struct net_device *backlog_dev, int *budget)
{
int work = 0;
int quota = min(backlog_dev->quota, *budget);
struct softnet_data *queue = &__get_cpu_var(softnet_data);
unsigned long start_time = jiffies;
for (;;) {
struct sk_buff *skb;
struct net_device *dev;
local_irq_disable();
//从输入包队列中取下skb
skb = __skb_dequeue(&queue->input_pkt_queue);
if (!skb)
goto job_done;
local_irq_enable();
dev = skb->dev;
//接收数据包
netif_receive_skb(skb);
dev_put(dev);
work++;
if (work >= quota || jiffies - start_time > 1)
break;
#ifdef CONFIG_NET_HW_FLOWCONTROL
if (queue->throttle &&
queue->input_pkt_queue.qlen < no_cong_thresh ) {
queue->throttle = 0;
if (atomic_dec_and_test(&netdev_dropping)) {
netdev_wakeup();//唤醒流量控制设备
break;
}
}
#endif
}
backlog_dev->quota -= work;
*budget -= work;
return -1;
job_done:
backlog_dev->quota -= work;
*budget -= work;
list_del(&backlog_dev->poll_list);
smp_mb__before_clear_bit();
netif_poll_enable(backlog_dev);
if (queue->throttle) {
queue->throttle = 0;
#ifdef CONFIG_NET_HW_FLOWCONTROL
if (atomic_dec_and_test(&netdev_dropping))
netdev_wakeup();
#endif
}
local_irq_enable();
return 0;
}
检查负载类型,调用根据包类型注册的函数句柄,如对于IP流来说,注册的函数是ip_rcv()。
int netif_receive_skb(struct sk_buff *skb)
{
struct packet_type *ptype, *pt_prev;
int ret = NET_RX_DROP;
unsigned short type;
#ifdef CONFIG_NETPOLL
if (skb->dev->netpoll_rx && skb->dev->poll && netpoll_rx(skb)) {
kfree_skb(skb);
return NET_RX_DROP;
}
#endif
if (!skb->stamp.tv_sec)
net_timestamp(&skb->stamp);//设置时间戳
skb_bond(skb);
__get_cpu_var(netdev_rx_stat).total++;
skb->h.raw = skb->nh.raw = skb->data;
skb->mac_len = skb->nh.raw - skb->mac.raw;
pt_prev = NULL;
rcu_read_lock();
#ifdef CONFIG_NET_CLS_ACT
if (skb->tc_verd & TC_NCLS) {
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
}
#endif
list_for_each_entry_rcu(ptype, &ptype_all, list) {
if (!ptype->dev || ptype->dev == skb->dev) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev);//分发包
pt_prev = ptype;
}
}
#ifdef CONFIG_NET_CLS_ACT
if (pt_prev) {
ret = deliver_skb(skb, pt_prev);
pt_prev = NULL; /* noone else should process this after*/
} else {
skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
}
ret = ing_filter(skb);//入口处包过滤
if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN)) {
kfree_skb(skb);
goto out;
}
skb->tc_verd = 0;
ncls:
#endif
handle_diverter(skb);//处理重定向,赋上包类型PACKET_HOST及设备MAC地址
if (handle_bridge(&skb, &pt_prev, &ret))//网桥处理
goto out;
type = skb->protocol;
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
if (ptype->type == type &&
(!ptype->dev || ptype->dev == skb->dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev);
pt_prev = ptype;
}
}
if (pt_prev) {
ret = pt_prev->func(skb, skb->dev, pt_prev);
} else {
kfree_skb(skb);
ret = NET_RX_DROP;
}
out:
rcu_read_unlock();
return ret;
}
函数deliver_skb调用调用包类型对应的接收处理例程来处理包,函数deliver_skb列出如下(在net/core/dev.c中):
static __inline__ int deliver_skb(struct sk_buff *skb,
struct packet_type *pt_prev)
{
atomic_inc(&skb->users);
//调用包类型对应的接收处理例程来处理包,如:ip_rcv()。
return pt_prev->func(skb, skb->dev, pt_prev);
}
包接收
函数dev_queue_xmit 的功能是发送一个skb。其参数skb是发送的buffer。函数dev_queue_xmit把发送的包排队到一个网络设备上。在调用这个函数前调用者必须设备了设备、优先级和建立了buffer。这个函数能从一个中断调用。如果错误函数返回一个为负值的errno。函数执行成功不能保证帧被传输,因为可能由于网络拥塞造成包被丢弃。
函数dev_queue_xmit分析如下(在net/core/dev.c中):
int dev_queue_xmit(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct Qdisc *q;
int rc = -ENOMEM;
if (skb_shinfo(skb)->frag_list &&
!(dev->features & NETIF_F_FRAGLIST) &&
__skb_linearize(skb, GFP_ATOMIC))
goto out_kfree_skb;
//如果设备不支持SG,即Scatter/gather IO,碎片的skb被线性化,
//或者如果至少碎片中的一个在highmem高端内存里且设备不支持从它开始的DMA
if (skb_shinfo(skb)->nr_frags &&
(!(dev->features & NETIF_F_SG) || illegal_highdma(dev, skb)) &&
__skb_linearize(skb, GFP_ATOMIC))
goto out_kfree_skb;
//如果包没有校验和并且设备不支持这种协议校验,在这儿完成校验
if (skb->ip_summed == CHECKSUM_HW &&
(!(dev->features & (NETIF_F_HW_CSUM | NETIF_F_NO_CSUM)) &&
(!(dev->features & NETIF_F_IP_CSUM) ||
skb->protocol != htons(ETH_P_IP))))
if (skb_checksum_help(&skb, 0))
goto out_kfree_skb;
//为下面各种锁使软中断失效,为RCU停止预占方式
local_bh_disable();
//如果qdisc有一个enqueue函数,就还需要在调用它之前持有queue_lock,
//因为queue_lock被串行化来访问设备队列。
q = rcu_dereference(dev->qdisc);
#ifdef CONFIG_NET_CLS_ACT
skb->tc_verd = SET_TC_AT(skb->tc_verd,AT_EGRESS);//交通控制结论为外出包
#endif
if (q->enqueue) {
/* Grab device queue */
spin_lock(&dev->queue_lock);
rc = q->enqueue(skb, q);//排队包
qdisc_run(dev);//如果设备没停止,再开始设备
spin_unlock(&dev->queue_lock);
rc = rc == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : rc;
goto out;
}
if (dev->flags & IFF_UP) {//是上行,即输出包
int cpu = smp_processor_id(); //得到当前CPU
if (dev->xmit_lock_owner != cpu) {
HARD_TX_LOCK(dev, cpu);//加锁,设置到当前CPU
//如果不是__LINK_STATE_XOFF状态,即网络连接正常
if (!netif_queue_stopped(dev)) {
if (netdev_nit)//网络接口类型计数 > 0
//调用包类型处理函数ptype->func()送输出的包到
//当前在用的任何网络设备
dev_queue_xmit_nit(skb, dev);
rc = 0;
if (!dev->hard_start_xmit(skb, dev)) {//发送包
HARD_TX_UNLOCK(dev);
goto out;
}
}
HARD_TX_UNLOCK(dev);
//printk打印速度限制,网络子系统要求每printk前必须有的。
//这个强制的速度限制是:不超过一个内核消息每printk_ratelimit_jiffies时间内,//这使一个denial-of-service的攻击不可能。
if (net_ratelimit())
printk(KERN_CRIT "Virtual device %s asks to "
"queue packet!\n", dev->name);
goto out_enetdown;
} else {
//递归被检测到,它是可能存在的虚拟设备上的死循环
if (net_ratelimit())
printk(KERN_CRIT "Dead loop on virtual device "
"%s, fix it urgently!\n", dev->name);
}
}
out_enetdown:
rc = -ENETDOWN;
out_kfree_skb:
kfree_skb(skb);
out:
local_bh_enable();
return rc;
}
网卡驱动程序
在Linux中系统的设备分为字符设备、块设备和网络设备。网络设备驱动程序在linux里是单独的一类。在Linux中为网络设备提供了一个接 口,由数据结构 struct device来描述这个网络设备接口,它包括纯软件网络设备接口,如环路(Loopback),也包括硬件网络设备接口,如以太网卡。在include/linux/netdevice.h中定义了全局变量*dev_base,形成链表管理所有网络设备。全局网络设备结构定义如下:
extern struct net_device loopback_dev; /* The loopback */
extern struct net_device *dev_base; /* All devices */
extern rwlock_t dev_base_lock; /* Device list lock */
NAPI
NAPI(new application program interface)是提高网络处理速度的方法,它是一种中断机制和轮询机制的混合体。它首先采用中断唤醒数据接收的服务程序,然后采用POLL的方法来 轮询数据。NAPI 技术适用于对高速率的短长度数据包的处理。在网络重负载时能有效地降低了中断数量。在低负载下,当未到达MLFFR(最大无丢包发送速率)时,使用中断时,数据包、中断速率较低并且延时也较小。在高负载下,系统会轮询注册设备。一旦系统能处理他们就进入中断:数据包、中断速率较高且延时较大。
NAPI的实现方法是:当一批数据包中的第一个数据包到达时,以中断的方式通知系统,在硬中断中,系统将该设备注册到一个设备轮询队列中,并关闭中断。同时,激活一个软中断,对采用POLL的方法轮询队列中的注册的网络设备,从中读取数据包。这个操作流程见下图。
struct softnet_data
{
int throttle;// 为 1 表示当前队列的数据包被禁止
int cng_level;// 表示当前处理器的数据包处理拥塞程度
int avg_blog; //某个处理器的平均拥塞度
struct sk_buff_head input_pkt_queue;// 接收缓冲区的sk_buff队列
struct list_head poll_list; // POLL设备队列头
struct net_device *output_queue;// 网络设备发送队列的队列头
struct sk_buff *completion_queue;// 完成发送的数据包等待释放的队列
struct net_device backlog_dev; //表示当前参与POLL处理的网络设备
}
8139CP网卡驱动程序
8139CP网卡是比较流行的网卡,下面对它的驱动程序进行分析。这个网卡的驱动程序源代码在driver/net/8139cp.c中。1)模块加载和卸载
模块加载和卸载函数分别是cp_init和cp_exit,函数cp_init列出如下:
static int __init cp_init (void)
{
return pci_module_init (&cp_driver);
}
static struct pci_driver cp_driver = {
.name = DRV_NAME,
.id_table = cp_pci_tbl,
.probe = cp_init_one,//检测网卡并初始化
.remove = cp_remove_one,//删除网卡
#ifdef CONFIG_PM //电源管理
.resume = cp_resume, //网卡恢复
.suspend = cp_suspend, //网卡挂起,即进入省电模式
#endif
};
2)网络接口初始化
函数cp_init_one完成对网卡进行检测,并且初始化系统中网络设备信息,用于后面的网络数据的发送和接收。
static int cp_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct net_device *dev;
struct cp_private *cp;
int rc;
void *regs;
long pciaddr;
unsigned int addr_len, i, pci_using_dac;
u8 pci_rev;
…
//读网卡版本号到pci_rev
pci_read_config_byte(pdev, PCI_REVISION_ID, &pci_rev);
……
//给结构分配空间
dev = alloc_etherdev(sizeof(struct cp_private));
if (!dev)
return -ENOMEM;
SET_MODULE_OWNER(dev);
SET_NETDEV_DEV(dev, &pdev->dev);
//初始化私有数据结构cp
cp = netdev_priv(dev);
cp->pdev = pdev;
cp->dev = dev;
cp->msg_enable = (debug < 0 ? CP_DEF_MSG_ENABLE : debug);
spin_lock_init (&cp->lock);
cp->mii_if.dev = dev;
cp->mii_if.mdio_read = mdio_read;
cp->mii_if.mdio_write = mdio_write;
cp->mii_if.phy_id = CP_INTERNAL_PHY;
cp->mii_if.phy_id_mask = 0x1f;
cp->mii_if.reg_num_mask = 0x1f;
cp_set_rxbufsize(cp);
//在它被一个驱动程序使用之前激活设备,请求底层代码使能I/O和内存,
//如果设备处于挂起状态就唤醒它。
rc = pci_enable_device(pdev);
…
//在pdev上用%PCI_COMMAND使能memory-write-invalidate PCI事务处理,
//接着调用pcibios_set_mwi函数或通用的mwi-prep操作函数
rc = pci_set_mwi(pdev);
…
//标识所有与设备pdev相关PCI区域,保留给DRV_NAME,
//在PCI区域内的地址不能被访问,除非调用函数后成功返回。
rc = pci_request_regions(pdev, DRV_NAME);
…
//得到第1个pdev的资源地址
pciaddr = pci_resource_start(pdev, 1);
……
//配置DMA属性
if ((sizeof(dma_addr_t) > 4) &&
!pci_set_consistent_dma_mask(pdev, 0xffffffffffffffffULL) &&
!pci_set_dma_mask(pdev, 0xffffffffffffffffULL)) {
pci_using_dac = 1;
} else {
pci_using_dac = 0;
rc = pci_set_dma_mask(pdev, 0xffffffffULL);
…
}
rc = pci_set_consistent_dma_mask(pdev, 0xffffffffULL);
…
}
cp->cpcmd = (pci_using_dac ? PCIDAC : 0) |
PCIMulRW | RxChkSum | CpRxOn | CpTxOn;
//映射总线到CPU的物理内存空间即pciaddr物理地址处
regs = ioremap(pciaddr, CP_REGS_SIZE);
…
dev->base_addr = (unsigned long) regs;
cp->regs = regs;
cp_stop_hw(cp);
//从EEPROM中读出MAC地址
addr_len = read_eeprom (regs, 0, 8) == 0x8129 ? 8 : 6;
for (i = 0; i < 3; i++)
((u16 *) (dev->dev_addr))[i] =
le16_to_cpu (read_eeprom (regs, i + 7, addr_len));
dev->open = cp_open;
dev->stop = cp_close;
dev->set_multicast_list = cp_set_rx_mode;
dev->hard_start_xmit = cp_start_xmit;
dev->get_stats = cp_get_stats;
dev->do_ioctl = cp_ioctl;
dev->poll = cp_rx_poll; //设备POLL函数
dev->weight = 16; /* arbitrary? from NAPI_HOWTO.txt. */
#ifdef BROKEN
dev->change_mtu = cp_change_mtu;
#endif
dev->ethtool_ops = &cp_ethtool_ops;
…
if (pci_using_dac)
dev->features |= NETIF_F_HIGHDMA;
dev->irq = pdev->irq;
//注册设备结构
rc = register_netdev(dev);
……
//把dev放入到pdev中的结构成员pdev->dev->driver_data上
pci_set_drvdata(pdev, dev);
/* enable busmastering and memory-write-invalidate */
pci_set_master(pdev);
/* Put the board into D3cold state and wait for WakeUp signal */
if (cp->wol_enabled) cp_set_d3_state (cp);
return 0;
…
}
3)网络接口设备打开和关闭
对于网络接口自己的dev->open函数一般包括以下几方面的内容:
(1)若没有在初始化函数中注册中断号和I/O地址,则在设备打时要进行注册,分别用requext_irq、request_region这两个函数进行注册。
(2)若要分配DMA通道,则用request_dma进行分配注册。
(3)将该设备挂到irq2dev_map中。若使用基于中断的数据接收方式,以后就可以通过中断号直接索引相应的设备了。
(4)初始化物理设备的寄存器的状态。
(5)设置接口相应的dev的私有数据结构(dev->priv)中的一些字段。
(6)设置dev中的tbusy、interrupt和start等字段。
(7)在返回之前嵌入宏MOD_INC_USE_COUNT。
网络接口设备打开就是激活网络接口,使它能接收来自网络的数据并且传递到网络协议栈的上面,也可以将数据发送到网络上。设备关闭就是停止操作。
函数cp_open和cp_close分别完成打开和关闭操作。函数cp_close用来停止设备,释放中断,释放环形缓冲区。
static int cp_open (struct net_device *dev)
{
struct cp_private *cp = netdev_priv(dev);
int rc;
if (netif_msg_ifup(cp))
printk(KERN_DEBUG "%s: enabling interface\n", dev->name);
//分配收发包的环形缓冲区并初始化
rc = cp_alloc_rings(cp);
if (rc)
return rc;
//初始化硬件,设置相关的寄存器值
cp_init_hw(cp);
//分配一个中断线,是共享中断
rc = request_irq(dev->irq, cp_interrupt, SA_SHIRQ, dev->name, dev);
if (rc)
goto err_out_hw;
netif_carrier_off(dev);//设置__LINK_STATE_NOCARRIER状态
mii_check_media(&cp->mii_if, netif_msg_link(cp), TRUE);
netif_start_queue(dev); //清除__LINK_STATE_XOFF状态
return 0;
err_out_hw:
cp_stop_hw(cp);
cp_free_rings(cp);
return rc;
}
4)中断处理
在驱动程序层次上的发送和接收数据都是通过低层对硬件的读写来完成的。当网络上的数据到来时,将触发硬件中断,根据注册的中断向量表确定处理函数,进入中断向量处理程序,将数据送到上层协议进行处理。
数据发送是由dev_dev_start_xmit函数指针对应的函数为cp_start_xmit函数,由它来完成数据包的发送。在函数ethdev_init()把net_device结构的hard_start_xmit指针初始化为cp_start_xmit。
网卡的中断收发数据流程如下:
(1)当网卡接收到数据帧或发送完数据帧时, 就会产生一个中断。
(2) 当网卡成功接收到数据帧时, 驱动程序根据帧长度分配包缓冲区。将接收到的数据帧放入到缓冲区中,然后再插入到接收软中断的接收包队列中, 并激活接收软中断。当硬件中断返回时, 接收软中断将执行。
在缺省配置下, 每个CPU最多可缓冲300个接收包。当网卡接收包的速度太快, 接收软中断中无法及时处理,接收包队列长度达到300时, 系统进入"节流(throttle)"状态, 所有后继的接收包被丢弃,直到接收包队列重新变为空。
(3)数据包的发送可以设计成直接发送或环形发送方式。在直接发送方式下,数据帧直接写入网卡发送, 网卡的发送中断被忽略。
在环形发送方式下, 发送包首先加入驱动程序的环形发送队列,再通过发送中断来从发送环中取包发送。已发送完成的包通过dev_kfree_skb_irq(skb)加入发送软中断的完成队列,再激活发送软中断对其进行释放。
设备状态的__LINK_STATE_XOFF标志可用来控制包的发送。netif_stop_queue(dev)使系统暂停向驱动程序 请求发送包, netif_wake_queue(dev)可激活包的发送。netif_schedule(dev)将设备dev加入到发送软中断的发送设备队列,并激 活发送软中断。发送软中断执行qdisc_run(dev)来清空设备dev的发送包队列。
static irqreturn_t cp_interrupt (int irq, void *dev_instance,
struct pt_regs *regs)
{
struct net_device *dev = dev_instance;
struct cp_private *cp = dev->priv;
u16 status;
//检查rx-ring中是否有中断到达
status = cpr16(IntrStatus);
if (!status || (status == 0xFFFF))
return IRQ_NONE; //没有中断
if (netif_msg_intr(cp))//若使能中断消息,就打印
printk(KERN_DEBUG "%s: intr, status %04x cmd %02x cpcmd %04x\n",
dev->name, status, cpr8(Cmd), cpr16(CpCmd));
//清除网卡的中断控制器的内容
cpw16(IntrStatus, status & ~cp_rx_intr_mask);
spin_lock(&cp->lock);
//接收状态寄存器表示有数据包到达
if (status & (RxOK | RxErr | RxEmpty | RxFIFOOvr)) {
//把当前的产生中断的网卡设备挂在softnet_data中的POLL队列上,
//激活软中断,等待调度
if (netif_rx_schedule_prep(dev)) {
//关闭接收中断
cpw16_f(IntrMask, cp_norx_intr_mask);
__netif_rx_schedule(dev);//加dev到POLL末尾,激活软中断
}
}
//发送中断的处理过程
if (status & (TxOK | TxErr | TxEmpty | SWInt))
//环形发送方式,发送完后,通过软中断清除发送包
cp_tx(cp);
//如果发生链路变化,
if (status & LinkChg)
//检查介质无关接口(MII)的载波状态是否也随着变化,
//否则就要准备重新启动MII接口.
mii_check_media(&cp->mii_if, netif_msg_link(cp), FALSE);
//如果PCI总线发生错误,对8139CP重新复位
if (status & PciErr) {
u16 pci_status;
pci_read_config_word(cp->pdev, PCI_STATUS, &pci_status);
pci_write_config_word(cp->pdev, PCI_STATUS, pci_status);
printk(KERN_ERR "%s: PCI bus error, status=%04x,
PCI status=%04x\n",
dev->name, status, pci_status);
/* TODO: reset hardware */
}
spin_unlock(&cp->lock);
return IRQ_HANDLED;
}
5)数据包的接收
一般设备收到数据后都会产生一个中断,在中断处理程序中通过POLL方法,在软中断中调用设备的poll接收函数,申请一块 sk_buff(skb),从硬件读出数据放置到申请好的缓冲区里。接下来填充sk_buff中的一些信息。skb->dev = dev,判断收到帧的协议类型,填入skb->protocol(多协议的支持)。把指针skb->mac.raw指向硬件数据,然后丢弃硬 件帧头(skb_pull)。还要设置skb->pkt_type,标明第二层(链路层)数据类型。可以是以下类型:
PACKET_BROADCAST : 链路层广播
PACKET_MULTICAST : 链路层组播
PACKET_SELF : 发给自己的帧
PACKET_OTHERHOST : 发给别人的帧(监听模式时会有这种帧)
最后调用netif_rx()将数据放入处理队列传送给协议层。netif_rx()把数据放入处理队列然后返回,这样减少软中断时间。
poll接收函数的功能概述如下:
(1)申请skb缓冲区存储新的数据包;
(2)从硬件中读取新到达的数据;
(3)调用函数netif_rx(),将新的数据包向网络协议的上一层传送;
(4)修改接口的统计数据。
在8139CP网卡中,cp_init_one设备探测函数注册了poll接收函数cp_rx_poll,即dev->poll = cp_rx_poll。dev->poll方法被网络层在向驱动层的接收循环队列获取新的数据包时调用,而驱动层的接收循环队列中可以向网络层交付 的包数量则在dev->quota中表示。
函数cp_rx_poll分析如下:
static int cp_rx_poll (struct net_device *dev, int *budget)
{
struct cp_private *cp = netdev_priv(dev);
unsigned rx_tail = cp->rx_tail;
//设置从设备发送到网络层次最大的数据包
unsigned rx_work = dev->quota;
unsigned rx;
rx_status_loop:
rx = 0;
//中断可以打开,准备接收新的数据包*/
cpw16(IntrStatus, cp_rx_intr_mask);
while (1) {/*POLL循环的开始*/
u32 status, len;
dma_addr_t mapping;
struct sk_buff *skb, *new_skb;
struct cp_desc *desc;
unsigned buflen;
//从环行接收队列rx_skb上提到套接字缓冲区
skb = cp->rx_skb[rx_tail].skb;
if (!skb)
BUG();
desc = &cp->rx_ring[rx_tail];
//得到环形队列(rx_ring)上的最后的数据接收状态
status = le32_to_cpu(desc->opts1);
if (status & DescOwn)
break;
len = (status & 0x1fff) - 4;
mapping = cp->rx_skb[rx_tail].mapping;
if ((status & (FirstFrag | LastFrag)) != (FirstFrag | LastFrag)) {
//不支持输入的碎片帧,假定确认了预分配的
//RX套接字缓冲区有合适的大小以致于从不会遇到RX碎片
cp_rx_err_acct(cp, rx_tail, status, len);
cp->net_stats.rx_dropped++;
cp->cp_stats.rx_frags++;
goto rx_next;
}
//有出现接收或者 FIFO 的错误
if (status & (RxError | RxErrFIFO)) {
cp_rx_err_acct(cp, rx_tail, status, len);
goto rx_next;
}
…
buflen = cp->rx_buf_sz + RX_OFFSET;
//创建新的套接字缓冲区
new_skb = dev_alloc_skb (buflen);
if (!new_skb) {
cp->net_stats.rx_dropped++;
goto rx_next;
}
skb_reserve(new_skb, RX_OFFSET);
new_skb->dev = cp->dev;
//解除原先映射的环行队列上的映射区域
pci_unmap_single(cp->pdev, mapping,
buflen, PCI_DMA_FROMDEVICE);
//检查进来的包数据校验和
if (cp_rx_csum_ok(status))
skb->ip_summed = CHECKSUM_UNNECESSARY;
else
skb->ip_summed = CHECKSUM_NONE;
skb_put(skb, len); //skb增加len大小,
mapping =
cp->rx_skb[rx_tail].mapping =
//DMA映射新创建的skb虚拟地址new_buf->tail到实际的物理地址上,
//并且把这个物理地址挂在接收缓冲区的队列中
pci_map_single(cp->pdev, new_skb->tail,
buflen, PCI_DMA_FROMDEVICE);
//把new_skb挂在接收缓冲区的队列中,
//POLL方法会从这里读出接收到的数据包
cp->rx_skb[rx_tail].skb = new_skb;
//调用netif_rx_skb,填充接收数据包队列,
//等待网络层在Bottom half队列中调用ip_rcv接收网络数据,
cp_rx_skb(cp, skb, desc);
rx++;
rx_next:
cp->rx_ring[rx_tail].opts2 = 0;
//把映射的物理地址mapping挂在网卡的环行队列上
cp->rx_ring[rx_tail].addr = cpu_to_le64(mapping);
//设置操作字符串,给寄存器写入状态描述控制字,
if (rx_tail == (CP_RX_RING_SIZE - 1))
desc->opts1 = cpu_to_le32(DescOwn | RingEnd |
cp->rx_buf_sz);
else
desc->opts1 = cpu_to_le32(DescOwn | cp->rx_buf_sz);
//步进到下一个接收缓冲队列的下一个单元
rx_tail = NEXT_RX(rx_tail);
if (!rx_work--)
break;
}
cp->rx_tail = rx_tail;
//递减配额值quota, quota为0表示这次的POLL传输完成,
//就等待有数据到来的时候再次唤醒软中断执行POLL方法
dev->quota -= rx;
*budget -= rx;
//如果还没达到工作限制,接着进行poll方法循环
if (rx_work) {
/*如果仍然有数据达到,那么返回POLL方法循环的开始,继续接收数据*/
if (cpr16(IntrStatus) & cp_rx_intr_mask)
goto rx_status_loop;
//到这里表明已经接收完毕
local_irq_disable();
cpw16_f(IntrMask, cp_intr_mask);//设置接收准备好等中断掩码
//把完成POLL操作的设备从poll_list上删除
__netif_rx_complete(dev);
local_irq_enable();
return 0; //完成
}
return 1; //没有完成
}
6)数据包的发送
数据包通过dev_queue_xmit函数传送给网络设备。在device结构中有一个dev->hard_start_xmit的硬件传输函数指针,指向具体的设备驱动程序中的包发送函数。由这个包发送函数来完成数据包的发送工作。
硬件传输函数hard_start_xmit的一般流程如下:
(1)通过标志位tbusy判断上次数据包的传输是否完成。若tbusy=0就跳转到下一步;否则,看上次传输是否已超时,若未超时,就以不成功返回,若已超时,则初始化芯片寄存器,置tbusy=0,然后继续下一步;
(2)将tbusy标志位打开;
(3)将数据包传给硬件让它发送;
(4)释放缓冲区skb;
(5)修改接口的一些统计信息。
在8139CP网卡中的硬件包发送函数是cp_start_xmit,它以环形发送的方式将数据包发送出去。