Fork Me on GitHub

源码开放学ARM

LASO - Learn ARM with Source Open

首页         目录索引         资料下载         代码下载         联系作者        
下载PDF打印版本

网络设备驱动程序框架

网络设备驱动程序框架

• 上层协议发送数据包时 dev_queue_xmit(struct sk_buf skb)

• 上层协议接受数据包时 int netif_rx(struct sk_buff skb)

• struct sk_buff 套接字缓冲区,用于在linux网络子系统中各层之间传递数据,是linux网络子系统数据传输的中枢神经。

• struct sk_buff(linux/skbuff.h) 中的关键成员:

各层协议头

A 传输层协议头:

union {
	struct tcphdr *th;
	struct udphdr *uh;
	struct icmphdr *icmph;
	struct igmphdr *igmph;
	struct iphdr *ipiph;
	struct ipv6hdr *ipv6h;
	unsigned char *raw;//数据链路层头部
} h;

B 网络层协议头

union {
	struct iphdr *iph;
	struct ipv6hdr      *ipv6h;
	struct arphdr *arph;
	unsigned char     *raw;
} nh;

C 链路层协议头

union {
	unsigned char  *raw;
} mac;

数据缓存区指针:

unsigned char *head:
指向内存中已经分配的用于承载网络数据的缓冲 区起始地址,sk_buff和相关数据块在分配后该指针的值就被固定了。

unsigned char *data:
指向对应当前协议层有效数据的起始地址,每层协议有效数据含义不同。

unsigned char *tail:
指向当前协议层有效数据负载的结尾地址。

unsigned char *end:
指向分配的数据缓冲区的结尾,分配完即固定。

• 注意:网络报文在内存中不一定是联系存储的,同一网络报文有可能分成若干片存储在内存的不同位置:

长度信息

unsigned int data_len: 
记录在frags和frag_list中网络报文的长度。

unsigend int len:
记录网络报文的总长度。

unsigned int truesize:
记录head所指的存储区的大小。

套接字缓冲区操作

1、分配:

struct sk_buff *alloc_skb(unsigned int len,int priority);
分配一个套接字缓冲区和一个数据缓冲区,len为数据缓冲区大小,16字节对齐。

struct sk_buff *dev_alloc_skb(unsigned int)
以GFP_ATOMIC方式调用上面接口,并保留head和data之间的16个字节。

2、释放

void kfree_skb(struct sk_buff *skb)
内核内部使用。

void dev_kfree_skb(struct sk_buff *skb)
驱动中使用,用于非中断上下文。

void dev_kfree_skb_irq(kfree_skb(struct sk_buff *skb)
驱动中使用,用于中断上下文。

void dev_kfree_skb_any(kfree_skb(struct	sk_buff *skb)
驱动中使用,中断和非中断上下文都可以。

指针移动

A、put操作:

将tail指针下移,增加sk_buff的len值,并返回skb->tail的当前值,用于在缓冲区尾部添加数据。
unsigned char *skb_put(struct sk_buff *skb, unsigned int len) 

B、push操作

将data指针上移,增加len的值,在存储空间的头部增加一段可以存储网络数据的空间,主要用于在数据包发送时添加头部。
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)

C、pull操作:

将data指针下移,并减小skb的len的值,一般用于下层协议向上层协议移交数据包,使data指向上一层协议的协议头。
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)

D、reserve操作:

将data指针和tail指针同时下移,主要用于在存储空间的头部预留len长度的空隙。
unsigned char *skb_reserve(struct sk_buff *skb, unsigned int len)

•UDP包接收上传至应用程序实例

数据链路层:dev_alloc_skb(),skb_pull()

网络层: skb_pull()

传输层:

应用层:

调用recv()接收数据时,从skb->data+sizeof(struct udphdr)的位置开始复制数据到应用层缓冲区,由此可见UDP头部始终没有被剥离。

• 驱动需要实现的功能:

1、网卡的初始化
2、数据的发送
3、数据的接收(中断)
4、其它(流量控制,错误统计,超时处理)
  • 模块初始化函数。注册(register_netdev)网络设备的net_device变量。

  • 模块销毁函数。注销(unregister_netdev)网络设备的net_device变量 。

  • 声明net_device变量,并实现如下函数:

    • 实现init函数,完成设备的初始化和net_device结构体变量自身的初始化。
    • 实现打开设备(open)和关闭设备(stop)
    • 实现发送函数(hard_start_transmit) - 实现中断处理函数,处理中断。 - 实现数据接收函数。
    • 实现其他的函数get_stats,do_ioctl

• net_device 结构细节

  • Linux内核源代码include/linux/netdevice.h

* 重要的域

- name:接口名称。
- dev_addr:链路层地址,可以认为是MAC地址。
- priv:私有数据指针。
- init():初始化函数。
- open():接口打开。
- stop():接口关闭。
- hard_start_xmit ():发送数据。

注册驱动程序 register_netdev(struct net_device dev):

内核以dev_base为头指针的设备链表来集体管理所有网络设备,该设备链表中的每个元素代表一个网络设备接口。register_netdev把注册的dev插入到dev_base链表中。

注销驱动程序 unregister_netdev(struct net_device dev):

从dev_base链表移除dev设备

模块初始化:主要完成注册(register_netdev)网络设备的工作。 网络设备初始化:既可以放在init_module函数中完成,也可由net_device数据结构中的init函数指针所指的初始化函数来完成的。 工作包括:

* 首先检测网络物理设备是否存在,这是通过检测物理设备的硬件特征来完成,然后再对设备进行资源配置。

  • 构造并初始化设备的net_device数据结构,并把检测到的数值来对net_device中的变量初始化,比如mac地址等。

功能:

- 初始化硬件,准备发送数据。
- 注册中断处理函数(可以在net_device的init函数中注册)。
- 启动接口的传输队列(允许接口接受传输数据 包)。内核提供的如下函数可启动该队列 netif_start_queue。
- 调用open,通过如下方法:
	ifconfig eth0 up
	ifconfig eth0 192.168.x.x

内核要传输一个数据包,会调用 hard_start_xmit 方法将数据放入发送队列。内核处理后的每个数据包位于一个套接字缓冲区结构(struct sk_buff)里。 这个缓冲区包含了物理数据包(以它在介质上的格式),并拥有完整的传输层数据包头。接口无需修改要传输的数据。

  • hard_start_xmit函数的处理过程:

    • 得到要发送数据的起始地址和长度。skb->data 指向要传输的数据包,而 skb->len 是以字节为单位的长度。
    • 把数据发送到网卡的物理缓冲区中。可以直接把数据写入缓冲区寄存器中;或者通过DMA的方式发送数据。 - 网卡把得到的数据发送到物理媒介中去。这一步一般由网卡自动完成。

* 处理接收数据包的方式:一般通过中断方式

- 中断方式:当有新数据到来时,中断处理器操作,并调用中断处理程序接收新数据。
- 轮询方式:每隔一定间隔访问网卡寄存器,判断是否有新数据到来。如果有则接收数据。

两种方式都可以。但中断方式效率高,占用系统资源少。

* 大多数网卡都可以通过中断处理程序来控制。中断可能产生的原因:

- 新数据包到达
- 外发数据包的传输已经完成
- 数据发送或接收错误。

* 处理流程:

- 读取中断寄存器,判断中断发生的原因。
- 如果中断是新数据到来,进入数据接收函数。
- 如果中断是数据包发送完成,或者产生传输错误,进行数据统计。

上一节 | 目录索引 | 下一节

blog comments powered by Disqus