一、Linux网络体系架构
需要掌握的知识点:
1、Linux的协议栈层次
2、Linux的网络子系统架构
协议栈:Linux的优点之一在于它丰富而稳定的网络协议栈。其范围从协议无关层(例如通用socket层接口或设备层)到各种具体的网络协议实现。
协议简介:对于网络的理论介绍一般都采用OSI(open systems interconnection)模型,但是Linux中网络栈的介绍一般分为四层的Internet模型。
网络接口层
网络接口层把数据链路层和物理层合并在了一起,提供访问物理设备的驱动程序,对应的网络协议主要是以太网协议。
网际层
网络层协议管理离散的计算机间的数据传输,如IP协议为用户和远程计算机提供了信息包的传输方法,确保信息包能正确地到达目的机器。重要的网络层协议包括ARP(地址解析协议)、ICMP(Internet控制消息协议)和IP协议(网际协议)等。
传输层
传输层的功能包括:格式化信息流、提供可靠传输。传输层包括TCP(Transmission Control Protocol,传输控制协议)和UDP(User Datagram Protocol,用户数据报协议),它们是传输层中最主要的协议。
应用层
应用层位于协议栈的顶端,它的主要任务是服务于应用,如利用FTP(文件传输协议)传输一个文件。常见的应用层协议有HTTP,FTP,Telnet等。应用层是Linux网络设定很关键的一层,Linux服务器的配置文档主要针对应用层中的协议。
Linux网络子系统
Linux网络子系统的顶部是系统调用接口层。它为用户空间的应用程序提供了一种访问内核网络子系统的方法。位于其下面的是一个协议无关层,它提供了一种通用方法来使用传输层协议。然后是具体协议的实现,在Linux中包括内嵌的协议TCP、UDP,当然还有IP。然后是设备无关层,它提供了协议与设备驱动通信的通用接口,最下面是设备驱动程序。
系统调用接口:为应用程序提供访问内核网络子系统的方法:socket系统调用
协议无关接口:实现一组通用函数来访问各种不同的协议:通过socket实现。Linux中的socket使用struct sock来描述,这个结构包含了特定socket所需要的所有状态信息,还包括socket所使用的特定协议和在socket上可以执行的一些操作。
网络协议层用于实现各种具体的网络协议,如:TCP、UDP等。
设备无关接口将协议与各种网络设备驱动连接在一起。这一层提供一组通用函数供底层网络设备驱动程序使用,让它们可以对高层协议栈进行操作。首先设备驱动程序可能会通过调用register_netdevice或unregister_netdevice在内核中进行注册或注销。调用者首先填写net_device结构,然后传递这个结构进行注册。内核调用它的init函数(如果定义了这种函数),然后执行一组健全性检查,并将新设备添加到设备列表中(内核中的活动设备链表)。
设备无关接口:要从协议层向设备发送数据,需要使用dev_queue_xmit函数,这个函数对数据进行排队,并交由底层设备驱动程序进行最终传输报文的接收通常是使用netif_rx执行的。当底层设备驱动程序接收到一个报文(包含在所分配的sk_buff中)时,就会通过调用netif_rx将数据上传至设备无关层,然后,这个函数通过netif_rx_schedule将sk_buff在上层协议队列中进行排队,供以后进行处理。
驱动程序:网络体系结构的最底部是负责管理物理网络设备的设备驱动程序层
二、网卡驱动设计
设备描述:
每个网络接口都由一个net_device结构来描述,该结构可使用如下内核函数动态分配:
1、struct net_device *alloc_netdev(int sizeof_priv, const char *mask, void (*setup)(struct net_device *)) //sizeof_priv私有数据区大小;mask:设备名;setup 初始化函数
2、struct net_device *alloc_etherdev(int sizeof_priv)
两个函数的关系:可以查看内核
结构net_device的主要成员包括:
# char name[IFNAMSIZ]
设备名,如:eth%d //%d代表数字是由内核来分配的
# unsigned long state
设备状态
# unsigned long base_addr
I/O基地址
# unsigned int irq
中断号
int (*init)(struct net_device *dev) //初始化函数。该函数在register_netdev时被调用来完成对net_device结构的初始化
和字符设备驱动一样,网络设备也要声明能操作它的函数。有些操作可以保留为NULL,有的可以通过ether_setup来使用默认设置。网络接口的设备方法可分为两组:基本的和可选的,基本方法包括那些使用接口所必需的;可选的方法实现更多高级的功能。
基本方法:
int (*open)(struct net_device *dev) //打开接口。ifconfig激活时,接口将被打开。
int (*stop)(struct net_device *dev) //停止接口,该什么时候调用呢?自己看内核
int (*hard_start_xmit)(struct sk_buff *skb, struct net_device *dev) //数据发送函数
可选操作:
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd) //处理特定于接口的ioctl命令
int (*set_mac_address)(struct net_device *dev, void *addr) //改变Mac地址的函数,需要硬件支持该功能
设备注册
网络接口驱动的注册方式与字符驱动不同之处在于它没有主次设备号,并使用如下函数注册。
int register_netdev(struct net_device *dev)
sk_buff
Linux内核中的每个网络数据包都由一个套接字缓冲区结构struct sk_buff描述,即一个sk_buff结构就是一个包,指向sk_buff的指针通常被称做skb。
该结构包含如下重要成员:
struct device *dev; //处理该包的设备
_u32 saddr; //IP源地址
_u32 daddr; //IP目的地址
_u32 raddr; //IP路由器地址
unsigned char *head; //分配空间的开始
unsigned char *data; //有效数据的开始
unsigned char *tail; //有效数据的结束
unsigned char *end; //分配空间的结束
unsigned long len; //有效数据的长度
操作sk_buff的内核函数如下:
struct sk_buff *alloc_skb(unsigned int len, int priority) //分配一个sk_buff结构,供协议栈代码使用
struct sk_buff *dev_alloc_skb(unsigned int len) //分配一个sk_buff结构,供驱动代码使用
unsigned char *skb_push(struct sk_buff *skb, int len) //向后移动skb的tail指针,并返回tail移动之前的值。函数常用来:看内核
undigned char *skb_put(struct sk_buff *skb, int len) //向前移动skb的head指针,并返回head移动之后的值。函数常用来:看内核
kfree_skb(struct sk_buff *skb) //释放一个sk_buff结构,供协议栈代码使用
dev_kfree_skb(struct sk_buff *skb) //释放一个sk_buff结构,供驱动代码使用
网卡的主要功能就是发送和接收数据
设备打开:
open请求任何它需要的系统资源并且启动接口:
1、注册中断,DMA等
2、设置寄存器,启动设备
3、启动发送队列
设备打开的例子:
int net_open(struct net_device *dev)
{
//申请中断
request_irq(dev->irq, &net_interrupt, SA_SHIRQ, "dm9000", dev);
//设置寄存器,启动设备
....... ...... ...... ......
//启动发送队列
netif_start_queue(dev);
}
数据发送
当核心需要发送一个数据包时,它调用hard_start_transmit函数,该函数将最终调用到net_device结构中的hard_start_xmit函数指针。
数据接收
网络接口驱动可以实现两种方式的报文接收:中断和查询,Linux中驱动多采用中断方式。
1、分配skb,skb = dev_alloc_skb(pkt->datalen + 2)
2、从硬件中读取数据到skb
3、调用netif_rx将数据交给协议栈netif_rx(skb)(由于设备无关接口的存在,所以这里不区分到底是哪种协议)
中断处理:
网络接口通常支持3种类型的中断:新报文到达中断、报文发送完成中断和出错中断。中断处理程序可通过查看网卡中的中断状态寄存器,来分辨出中断类型。
三、Dm9000网卡驱动分析
芯片介绍
DM9000是开发板经常采用的网络芯片,是一种高度集成而且功耗很低的高速网络控制器,可以和CPU直连,支持10/100M以太网连接,芯片内部自带16k的SRAM(3KB用来发送,13KB用来接收)。
DM9000在收到由上层发来的以太网帧后,开始侦听网络线路,如果线路忙,就等到线路空闲为止,否则立即发送该数据帧。接收时,它将从以太网收到的数据包在经过解码、去掉帧头和地址检验等步骤后缓存在片内。在CRC校验通过后,它会通知CPU收到了数据帧。
具体可查看DM9000手册
9.1、host interface
9.3、packet transmission
9.4、packet reception
以太网帧:
网卡驱动分析见:http://blog.chinaunix.net/uid-29585232-id-4194386.html
http://blog.chinaunix.net/uid-24231261-id-4196018.html
cs8900a.c(cs8900a网卡程序设计)
/*
* cs8900a.c: A Crystal Semiconductor (Now Cirrus Logic) CS8900A
driver for SMDK-s3c2410 (based on cs89x0.c)
*
* Author: Yong-iL Joh <tolkien@mizi.com>
* Date : $Date: 2002/10/16 09:08:07 $
*
* $Revision: 1.1.2.5 $
Wed Aug 14 2002 Yong-iL Joh <tolkien@mizi.com>
- initial, based on cs89x0.c
Wed Aug 16 2002 Yong-iL Joh <tolkien@mizi.com>
- working!
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive
* for more details.
*/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <linux/ioport.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/hardware.h>
#define IRQ_LAN IRQ_CS8900
#include "cs89x0.h"
/*
* Set this to zero to remove all the debug statements via
* dead code elimination
*/
#undef DEBUGGING 4
#if DEBUGGING
#define DPRINTK(n, args...) \
if (n <= DEBUGGING) { \
printk(args); \
}
#else
#define DPRINTK(n, args...)
#endif
#if DEBUGGING
static char version[] __initdata =
"cs89x0.c: v2.4.3-pre1 Russell Nelson <nelson@crynwr.com>, Andrew Morton <andrewm@uow.edu.au>\n";
#endif
/* First, a few definitions that the brave might change.
A zero-terminated list of I/O addresses to be probed. Some special flags..
Addr & 1 = Read back the address port, look for signature and reset
the page window before probing
Addr & 3 = Reset the page window and probe
The CLPS eval board has the Cirrus chip at 0x80090300, in ARM IO space,
but it is possible that a Cirrus board could be plugged into the ISA
slots. */
static unsigned int netcard_portlist[] __initdata =
{ vCS8900_BASE + 0x300, 0};
/* The number of low I/O ports used by the ethercard. */
#define NETCARD_IO_EXTENT 0xfff
/* we allow the user to override various values normally set in the EEPROM */
#define FORCE_RJ45 0x0001 /* pick one of these three */
#define FORCE_AUI 0x0002
#define FORCE_BNC 0x0004
#define FORCE_AUTO 0x0010 /* pick one of these three */
#define FORCE_HALF 0x0020
#define FORCE_FULL 0x0030
/* Information that need to be kept for each board. */
struct net_local {
struct net_device_stats stats;
int chip_type; /* one of: CS8900, CS8920, CS8920M */
char chip_revision; /* revision letter of the chip ('A'...) */
int send_cmd; /* the proper send command:
TX_NOW, TX_AFTER_381, or TX_AFTER_ALL */
int auto_neg_cnf; /* auto-negotiation word from EEPROM */
int adapter_cnf; /* adapter configurat