linux网络设备理解

来源:互联网 发布:股票数据分析 编辑:程序博客网 时间:2024/05/16 11:31

网络层次

linux网络设备驱动与字符设备和块设备有很大的不同。
1. 字符设备和块设备对应/dev下的一个设备文件。而网络设备不存在这样的设备文件。网络设备使用套接字socket访问,虽然也使用read,write系统调用,但这些调用只作用于软件对象。
2. 块设备只响应来自内核的请求,而网络驱动程序异步接收来自外部世界的数据包,并向内核请求发送到内核。

linux内核中网络子系统的设计基于设备无关及协议无关思想。即无论什么网卡驱动、网络协议,都对应统一的驱动程序。

inux网络协议栈层次有四层:
网络协议接口层
网络设备接口层
设备驱动功能层
网络设备媒介层

这里写图片描述

下面只分析下linux是如何实现网络设备的设备无关性及协议无关性。

网络协议接口层: 给上层协议提供透明的数据包发送和接收接口。与协议无关
当要发送数据包时,ARP协议或者IP协议,都将调用这一层中的dev_queue_xmit()函数发送该数据包。
上层对数据包的接收时,通过netif_rx()接收。具体协议处理在上级网络协议处理。
这其中需要一个很重要的结构体struct sk_buff.

网络设备接口层:为千变万化的网络设备定义统一、抽象的数据结构net_device结构体,实现多种硬件在软件层次上的统一。与设备无关。
net_device结构体在内核中指代一个网络设备,网络设备驱动程序只需通过填充net_device中的具体成员并注册net_device即可实现硬件操作函数与内核的挂接。使用统一的注册、注销及初始化等一系列函数。

设备驱动功能层:填充net_device中的成员。管理物理网络设备。
包括设备打开、停止、传输等操作。由于网络包得接收可以由中断产生,所以设备驱动功能层中另一个主题部分是中断处理函数,负责读取硬件上接收的数据包并传送给上层协议。

网络设备与媒介层:对应于实际的物理设备
包括设备寄存器的描述,寄存器读写函数等。

网络协议

这里主要进行数据包的收发,使用函数原型为:
dev_queue_xmit(struct sk_buff *skb);int netif_rx(struct sk_buff *skb);
这里使用了一个skb_buff结构体,定义于include/linux/skbuff.h中,它的含义为“套接字缓冲区”,用于在Linux网络子系统各层间传输数据

sk_buff中重要的数据成员

struct device *dev;正在处理该包的设备__u32 sadd;r//IP元地址__u32 daddr;//IP目的地址__u32 raddr;//IP路由器地址unsigned char *head;//分配空间的开始unsigned char *data;//有效数据的开始unsigned char *tail;//有效数据的结束unsigned char *end;//分配空间的结束unsigned long len;//有效数据的长度

sk_buff操作
1) 分配:分配一个sk_buff结构,供协议栈代码使用

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

分配一个缓冲区。alloc_skb函数分配一个缓冲区并初始化skb->data和skb->tail为skb->head。参数len为数据缓冲区的空间大小,通常以L1_CACHE_BYTES字节(对ARM为32)对齐,参数priority为内存分配的优先级。dev_alloc_skb()函数以GFP_ATOMIC优先级进行skb的分配。
2) 释放:

void kfree_skb(struct sk_buff *skb);void dev_kfree_skb(struct sk_buff *skb); 

Linux内核内部使用kfree_skb()函数,而网络设备驱动程序中则最好使用dev_kfree_skb()。
sk_buff中比较重要的成员是指向数据包中数据的指针
用于寻址数据包中数据的指针,head指向已分配空间开头,data指向有效的octet开头,tail指向有效的octet结尾,而end指向tail可以到达的最大地址。如果不这样做而分配一个大小固定的缓冲区,如果buffer不够用,则要申请一个更大的buffer,拷贝进去再增加,这样降低了性能。
3)变更
unsigned char *skb_put(struct sk_buff *skb, int len);
将taill指针向后移动len长度,并返回tail移动之前的值。用于向skb有效数据区域末尾添加数据。
unsigned char *skb_push(struct sk_buff *skb, int len);
将data指针向前移动len长度。并返回移动之后的值。用于向skb有效数据区域前端添加数据(包头)。
unsigned char *skb_pull(struct sk_buff *skb, int len);
void skb_reserve(struct sk_buff ×skb, int len);

网络设备

系统检测到了PCI设备,并启用它,但它只是一支硬件设备(网卡设备),而Linux的网络协议栈只认得[网络设备]。[网络设备]是一支逻辑设备,由结构net_device表征。也就是说,网络协议栈向[网络设备]发出命令,而[网络设备]的驱动将这些命令传递到PCI[网卡设备]。表3列出了结构net_device的一些重要数据域,

struct net_device  {      char *name;      unsigned long base_addr;      unsigned char addr_len;      unsigned char dev_addr[MAX_ADDR_LEN];      unsigned char broadcast[MAX_ADDR_LEN];      unsigned short hard_header_len;      unsigned char irq;      int (*open) (struct net_device *dev);      int (*stop) (struct net_device *dev);      int (*hard_start_xmit) (struct sk_buff *skb,  struct net_device *dev);      struct net_device_stats* (*get_stats)(struct net_device *dev);      void *priv;  };  

以上是结构net_device部分重要成员,以下简介这些成员的用途:

  • name – 设备的名称。如果名称的第一个字符是null,那么register_netdev分配给它取名为“ethN”,其中N是合适的数字。例如,如果您的系统已经有eth0和eth1,您的设备将被命名的eth2。
  • base_addr – I/O基地址。
  • addr_len – 硬件地址(MAC地址)的长度。以太网接口地址长度为6字节。
  • dev_addr – 硬件地址(以太网地址或MAC地址)
  • broadcast – 设备的广播地址。以太网接口的广播地址是FF:FF:FF:FF:FF:FF
  • hard_header_len – “硬件头的长度”是数据包硬件头的八位位组(octets)的数量。 以太网接口的hard_header_len的值是14
  • IRQ – 分配的中断号
  • open – 这是打开设备函数的指针。这个函数在用ifconfig命令激活设备时被调用,例如“ifconfig eth0 up”。 open函数负责向系统申请所需的系统资源需求(I/O端口,IRQ,DMA等),启用硬件和递增模块的使用计数。
  • stop – 这是停止设备函数的指针。这个函数在用ifconfig命令停用设备时被调用,例如“ifconfig eth0 down”。 stop函数释放所有open函数获得的资源。
  • hard_start_xmit – 此函数在传输线路上发送一个给定的数据包。该函数的第一个参数是指向结构sk_buff指针。结构sk_buff的是通过Linux网络协议栈的数据包。本文并不需要详细了解有关的sk_buff的结构的细节,你可以在下网址获得更多的结构sk_buff的信息:http://www.tldp.org/LDP/khg/HyperNews/get/net/net-intro.html。
  • get_stats – 此函数提供了接口统计信息。命令“ifconfig eth0”的很多输出内容来自get_stats。
  • priv – 驱动程序的私有数据域。驱动程序拥有这一数据域,并可以使用它。

特别注意,net_device没有接收数据包的成员函数,这是因为接收数据包是由设备的[中断处理程序]负责的

驱动的实现

1).初始化(init)
设备探测工作在init方法中进行,一般调用一个称之为probe方法的函数
初始化的主要工作时检测设备,配置和初始化硬件,最后向系统申请这些资源。此外填充该设备的dev结构,我们调用内核提供的ether_setup方法来设置一些以太网默认的设置。

2)打开(open)
open这个方法在网络设备驱动程序里是网络设备被激活时被调用(即设备状态由down变成up)
实际上很多在初始化的工作可以放到这里来做。比如说资源的申请,硬件的激活。如果dev->open返回非0,则硬件状态还是down,
注册中断、DMA等;设置寄存器,启动设备;启动发送队列
一般注册中断都在init中做,但在网卡驱动程序中,注册中断大部分都是放在open中注册,因为要经常关闭和重启网卡

3)关闭(stop)
stop方法做和open相反的工作
可以释放某些资源以减少系统负担
stop是在设备状态由up转为down时被调用

4)发送(hard_start_xmit)
在系统调用的驱动程序的hard_start_xmit时,发送的数据放在一个sk_buff结构中。一般的驱动程序传给硬件发出去。也有一些特殊的设备比如说loopback把数据组成一个接收数据在传送给系统或者dummy设备直接丢弃数据。
如果发送成功,hard_start_xmit方法释放sk_buff。如果设备暂时无法处理,比如硬件忙,则返回1。

5) 接收
驱动程序并存在一个接受方法。当有数据收到时驱动程序调用netif_rx函数将skb交交给设备无关层。
一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff(skb)从硬件中读取数据位置到申请号的缓冲区里。
接下来填充sk_buff中的一些信息。
中断有可能是收到数据产生也可能是发送完成产生,中断处理程序要对中断类型进行判断,如果是收到数据中断则开始接收数据,如果是发送完成中断,则处理发送完成后的一些操作,比如说重启发送队列。
接收流程:
1、分配skb=dev_alloc_skb(pkt->datalen+2)
2、从硬件中读取数据到skb
3、调用netif_rx将数据交给协议栈

0 0
原创粉丝点击