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 data tail end 。指向数据缓冲区。

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->head sdk_data 之间的 16 个字节。

 

分配完成后 , skb_buff data tail 指针都指向存储空间的起始地址 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 中的数据缓冲区指针操作有 : put push pull reserve

 

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 指针下移,并减小 skb len 值。

 

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 从下到上处理的流程: 1 2 3 4

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 头得以幸存,没有被剥掉 .

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值