一、Linux网络设备驱动整体架构

网络设备是完成用户数据包在网络媒介上发送和接收的设备,它将上层协议传递下来的数据包,以特定的媒介访问控制方式进行发送,并将接收到的数据包传递给上层协议。

Linux系统对网络设备驱动定义了4个层次,如下图:

i.MX6ULL驱动开发 | 31 - Linux内核网络设备驱动框架_网络

1. 网络协议接口层

网络协议接口层最主要的功能是给上层协议提供透明的数据包发送和接收接口,使得上层协议独立于具体的设备。

无论是ARP协议还是IP协议,都通过统一的​​dev_queue_xmit()​​​ 函数发送数据,通过统一的​​netif_rx()​​ 函数接收数据。

2. 网络设备接口层

网络设备接口层的主要功能是为千变万化的网络设备定义统一、抽象的数据结构​net_device​​ 结构体,以不变以万变,实现多种硬件在软件层次上的统一。

3. 设备驱动功能层

设备驱动功能层的各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通​​过hard_start_xmit()​​函数启动发送操作,并通过网络设备上的中断触发接收操作。

4. 网络设备与媒介层

网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介。网络适配器被设备驱动功能层的函数在物理上驱动。

综上所述,在编写具体的网络设备驱动程序时,我们需要完成的主要工作是编写设备驱动功能层的相关函数,以填充net_device数据结构的内容,并将net_device注册入内核。

二、sk_buff结构体(了解)

1. 调用关系

在网络协议接口层中,发上层发送数据包时调用函数的原型如下,定义在文件​​include/linux/netdevice.h​​中:

static inline int dev_queue_xmit(struct sk_buff *skb)
{
return dev_queue_xmit_sk(skb->sk, skb);
}

可以看到,上层需要传递给该函数一个指向sk_buff结构体的指针。

同样的,上层对数据包的接收也通过向netif_rx函数传递一个 sk_buff 结构体的指针来完成,原型如下:

int netif_rx(struct sk_buff *skb);

2. sk_buff结构体

sk_buff结构体非常重要,定义在​​include/linux/skbuff.h​​中,作用为socket缓冲区用于在Linux网络子系统中的各层之间传递数据

当发送数据包时,Linux内核的网络处理模块首先建立一个包含要传输数据的sk_buff,然后将sk_buff递交给下层,各层在sk_buff中添加不同的协议头直至交给网络设备发送。

同样,当网络设备从网络媒介上接收到数据包后,它必须将接收到的数据转换为sk_buff数据结构,并传递给上层,各层剥离相应的协议头,直至交给用户。

三、net_device结构体(重点)

1. 结构体定义

net_device结构体在内核中指代一个网络设备,定义在​​include/linux/netdevice.h​​,是一个非常大的结构体,包含网络设备的属性描述和操作接口,其中一些关键的成员如下。

1.1. 全局信息

网络设备名称:

char      name[IFNAMSIZ];

1.2. 硬件信息

设备使用的共享内存起始地址和结束地址:

unsigned long    mem_end;
unsigned long mem_start;

网络设备I/O基地址:

unsigned long    base_addr;

设备使用的中断号:

int      irq;

指定多端口设备使用哪一个端口(该字段仅针对多端口设备):

unsigned char    if_port;

指定分配给设备的DMA通道:

unsigned char    dma;

1.3. 接口信息

网络设备的硬件头长度,在以太网设备的初始化函数中,该成员被赋为ETH_HLEN,即14。

unsigned short    hard_header_len;

接口的硬件类型:

unsigned short    type;

mtu最大传输单元:

unsigned int    mtu;

用于存放设备的硬件地址:

/* Interface address info used in eth_type_trans() */
unsigned char *dev_addr;

flag指网络接口标志:

unsigned int    flags;

标志类型定义在 ​​include/uapi/linux/if.h​​ 文件中,为一个枚举类型:

enum net_device_flags {
IFF_UP = 1<<0, /* sysfs */
IFF_BROADCAST = 1<<1, /* volatile */
IFF_DEBUG = 1<<2, /* sysfs */
IFF_LOOPBACK = 1<<3, /* volatile */
IFF_POINTOPOINT = 1<<4, /* volatile */
IFF_NOTRAILERS = 1<<5, /* sysfs */
IFF_RUNNING = 1<<6, /* volatile */
IFF_NOARP = 1<<7, /* sysfs */
IFF_PROMISC = 1<<8, /* sysfs */
IFF_ALLMULTI = 1<<9, /* sysfs */
IFF_MASTER = 1<<10, /* volatile */
IFF_SLAVE = 1<<11, /* volatile */
IFF_MULTICAST = 1<<12, /* sysfs */
IFF_PORTSEL = 1<<13, /* sysfs */
IFF_AUTOMEDIA = 1<<14, /* sysfs */
IFF_DYNAMIC = 1<<15, /* sysfs */
IFF_LOWER_UP = 1<<16, /* volatile */
IFF_DORMANT = 1<<17, /* volatile */
IFF_ECHO = 1<<18, /* volatile */
};

1.4. 设备操作函数

包括三种:

  • net_device_ops:网络设备硬件操作函数合集。
  • ethtool_ops:与用户空间ethtool工具的各个命令选项相对应,ethtool提供了网卡及网卡驱动管理能力,能够为Linux网络开发人员和管理人员提供对网卡硬件、驱动程序和网络协议栈的设置、查看、以及调试等功能。
  • header_ops:对应于硬件头部操作,主要是完成创建硬件头部和从给定的sk_buff分析出硬件头部等操作。

1.5. 辅助成员

(1)记录最后的数据包开始发送时的时间戳

/*
* trans_start here is expensive for high speed devices on SMP,
* please use netdev_queue->trans_start instead.
*/
unsigned long trans_start;

(2)记录最后一次接收到数据包时的时间戳

/*
* Cache lines mostly used on receive path (including eth_type_trans())
*/
unsigned long last_rx;

2. net_device_ops操作函数

const struct net_device_ops *netdev_ops;

该结构体同样定义在​​include/linux/netdevice.h​​中,是网络设备的一系列硬件操作函数的集合,也是一个非常大的结构体,这里截选基础部分如下。

(1)打开网络接口设备,获得设备需要的I/O地址、IRQ、DMA通道等

int      (*ndo_open)(struct net_device *dev);

(2)停止网络接口设备

int      (*ndo_stop)(struct net_device *dev);

(3)启动数据包的发送

netdev_tx_t    (*ndo_start_xmit) (struct sk_buff *skb,
struct net_device *dev);

(4)获取网络设备的状态信息

struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);

(5)用于进行设备特定的I/O控制

int      (*ndo_do_ioctl)(struct net_device *dev,
struct ifreq *ifr, int cmd);

(6)配置接口

int      (*ndo_set_config)(struct net_device *dev,
struct ifmap *map);

(7)设置设备的MAC地址

int      (*ndo_set_mac_address)(struct net_device *dev,
void *addr);

四、NAPI处理机制

1. 机制概述

Linux在轮询处理机制和中断处理机制的基础上,提出了一种高效的网络处理技术:NAPI。

NAPI的核心思想是不全部采用中断来读取网络数据,而是采用中断来唤醒数据接收服务程序,在接收服务程序中采用poll的方法来轮询处理数据。

这种机制的优点是:提高短数据包的接收效率,减少中断处理的时间

2. 如何在驱动中使用NAPI机制

Linux内核使用结构体napi_struct表示NAPI,定义在文件​​include/linux/netdevice.h​​中。

2.1. 初始化NAPI

/**
* netif_napi_add - initialize a napi context
* @dev: network device
* @napi: napi context
* @poll: polling function
* @weight: default weight
*
* netif_napi_add() must be used to initialize a napi context prior to calling
* *any* of the other napi related functions.
*/
void netif_napi_add(struct net_device *dev, struct napi_struct *napi,
int (*poll)(struct napi_struct *, int), int weight);

2.2. 移除NAPI

/**
* netif_napi_del - remove a napi context
* @napi: napi context
*
* netif_napi_del() removes a napi context from the network device napi list
*/
void netif_napi_del(struct napi_struct *napi);

2.3. 使能和禁止NAPI调度

/**
* napi_disable - prevent NAPI from scheduling
* @n: napi context
*
* Stop NAPI from being scheduled on this context.
* Waits till any outstanding processing completes.
*/
void napi_disable(struct napi_struct *n);

/**
* napi_enable - enable NAPI scheduling
* @n: napi context
*
* Resume NAPI from being scheduled on this context.
* Must be paired with napi_disable.
*/
static inline void napi_enable(struct napi_struct *n)
{
BUG_ON(!test_bit(NAPI_STATE_SCHED, &n->state));
smp_mb__before_atomic();
clear_bit(NAPI_STATE_SCHED, &n->state);
}

2.4. 调度运行

检查NAPI是否可调度:

/**
* napi_schedule_prep - check if napi can be scheduled
* @n: napi context
*
* Test if NAPI routine is already running, and if not mark
* it as running. This is used as a condition variable
* insure only one NAPI poll instance runs. We also make
* sure there is no pending NAPI disable.
*/
static inline bool napi_schedule_prep(struct napi_struct *n)
{
return !napi_disable_pending(n) &&
!test_and_set_bit(NAPI_STATE_SCHED, &n->state);
}

调度轮询实例的运行:

/**
* napi_schedule - schedule NAPI poll
* @n: napi context
*
* Schedule NAPI poll routine to be called if it is not already
* running.
*/
static inline void napi_schedule(struct napi_struct *n)
{
if (napi_schedule_prep(n))
__napi_schedule(n);
}

2.5. NAPI处理完成

/**
* napi_complete - NAPI processing complete
* @n: napi context
*
* Mark NAPI processing as complete.
* Consider using napi_complete_done() instead.
*/
static inline void napi_complete(struct napi_struct *n)
{
return napi_complete_done(n, 0);
}

参考资料