Chinaunix首页 | 论坛 | 博客
  • 博客访问: 174112
  • 博文数量: 44
  • 博客积分: 627
  • 博客等级: 中士
  • 技术积分: 345
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-20 21:55
文章分类

全部博文(44)

文章存档

2012年(44)

分类:

2012-02-22 23:07:33

Linux网络设备驱动程序体系结构

从上到下:网络协议接口层-->网络设备结构层-->设备驱动实现层-->网络设备与媒介层

 

记忆方法:

分三层,1、最上面理解为我们用的网络传输方法,就是网络协议,2、最下面就是物理硬件,即网络设备层,3、中间是一层,设备驱动,然后拆成2部分,上部分是结构(层),下部分是结构中函数的实现(层)。

 

功能描述:

网络协议接口层

dev_queue_xmit() 发送数据, netif_rx() 接收数据

网络设备结构层

有一个结构net_device

设备驱动实现层

net_device里的函数实现, 通过hard_start_xmit()启动发送操作,通过中断触发接收操作。

网络设备与媒介层

哪里管的了那么多,不理它硬件怎么实现的。

 

网络协议接口层

有一个NB的结构体:sk_buff叫做:套接字缓冲区,各层之间数据传输都靠他。

dev_queue_xmit()netif_rx()的参数都是只是sk_buff

函数原型:

dev_queue_xmit(struct sk_buff  *sb );          //sb实际是 skb,少写一个k助记

netif_rx(struct sk_buff sk_buff  *sb);             //同上

 

sk_buff 内容详解

 

1 协议头 ,有好多好多协议要使用,所以协议头是必要滴,当然不能同时使用TCP/IP UDP或者其他什么协议,所以把头结构定义成联合体。

2 数据缓冲区:要搞个地方放数据,要功能强大必须能找到各需要的位置比如:头、尾所以在sk_buff中定义了4个指针:head datatailend。指向数据缓冲区。

head:缓冲区起始地址,sk_buff 一旦创建,head数据就固定了。

data:当前层的有效数据起始地址

tail 有效数据的结尾地址,和data对应

end:缓冲区的结尾地址,sk_buff 一旦创建,end数据就固定了。

3 长度信息

len:数据包有效数据长度,包括协议头和负载(Payload?)

data_len:记录分片的数据长度,数据包的有效数据是分成几片存在不同的内存空间中,每片空间最大是一页。

truesize:缓冲区的整体长度,即:sizeof(struct sk_buff)+(传入alloc_sdb()dev_alloc_skb()函数的长度)--说实话不理解传入函数的长度是什么.

 

NB的结构体:sk_buff的操作

各层之间就靠他,当然需要对他进行操作。

Ø  分配:

struct sk_buff  *alloc_skb(unsigned int len,int priority);

分配一个套接字缓冲区(sk_buff)和一个数据缓冲区,参数len为数据缓冲区的空间大小。16字节对齐, priority是内存分配的优先级。

 

struct sk_buff  *dev_alloc_skb(unsigned int len);

用这个函数优先级就确定了--FGP_ATOMIC:代表分配过程中不能被中断。

会调用alloc_skb()函数,并保存skb->headsdk_data 之间的16个字节。

 

分配完成后, skb_buff datatail指针都指向存储空间的起始地址head,len的大小是0

 

Ø  释放

就是释放alloc_skb()分配的套接字缓冲区,和数据缓冲区。

 

linux专用:

void kree_skb(struct sk_buff  *skb)

网络设备驱动程序用:

非中断上下文专用:void dev_kree_skb(struct  sk_buff  *skb);

中断上下文转用:void dev_kree_skb_irq(strcut sk_buff   *skb);

中断非中断上下文都可用:void dev_kree_skb_any(struct sk_buff  *skb);

Ø  指针移动

sk_buff中的数据缓冲区指针操作有: putpush pullreserve

 

put操作:

往数据缓冲区尾部添加可以存储网络数据包的空间。

unsigned char *skb_put(struct sk_buff  *skb , unsigned int len);          // 会检测放入的数据

unsigned char *__skb_put(同上)                                                                 //不检查

上述函数使tail指针下移,增加sk_buff中的len值,并返回skb_tail的值。

 

push操作:

往数据缓冲区头部增加一段可以存储网络数据包的空间。主要用于在数据包发送时添加头部。

unsigned char *skb_push(struct sk_buff  *skb , unsigned int len);       // 会检测放入的数据

unsigned char *__skb_push(同上)                                                              //不检查

会使data指针上移,也增加len的值。

 

pull操作:

用于下层协议向上层协议移交数据包,使data指针指向上一层协议的协议头。

unsigned char *skb_pull(struct sk_buff  *skb , unsigned int len);

会将data指针下移,并减小skblen值。

 

reserve操作:

主要用于在存储空间的头部预留len长度的空隙。

void skb_reserve(struct sk_buff  *skb , unsigned int len);

会使data指针和tail指针同时下移。

 

 

skb_buff的操作过程绝大部分由linux内核完成,驱动工程师只需要完成数据链路层部分工作。下面搞个例子加深理解!

补充协议头设定:

sk_buff中定义了3个协议头用于网络协议的不同层次,传输层TCP/IP协议头:h,网络层协议头:nh,链路层协议头mac,前面说了,这三个头都各定义成联合体。

 

网卡接收到一个UDP数据包,Linux从下到上处理的流程:1234

skb->mac.raw在步骤1到位,指向的位置就不变了,其他头指针也是这样。skb->nh.raw在步骤2到位,skb->h.raw在步骤3到位。每次pull到上一层,data指向就移到上面一层数据开始的地方,然后len减掉previous(避免中文歧义)的那层的头长度。

 

 

1、创建一个sk_buff结构体和数据缓冲区,将收到的数据复制到data指向的空间,skb->mac.raw指向数据,  有效数据的开始位子是一个以太网头,skb->mac.raw指向链路层的以太网头部。

2、用pull传到网络层之后,以太网协议头被剥掉了,skb->data指向下移到IP头了,len也减掉链路层头部那个长度skb_nh.raw指向data,即IP头部。

3、用pull传到传输层,剥掉IP头,data指针继续向下移,len长度再减掉ip头长度,skb_h.raw指向UDP头部。

4、应用程序调用recv()接收数据时,从skb->data+sizeof(struct udphdr)的位置开始复制到应用层缓冲区,所以,UDP头得以幸存,没有被剥掉.

 

阅读(1004) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~