net_device剖析

       网络设备接口层为了千变万化的设备定义了统一、抽象的数据结构net_device结构体来实现多种硬件在软件层次上的统一。net_device本身是一个巨型结构体,包含网络设备的属性描述和操作接口。

(1)全局信息

char name[IFNAMESIZ];

name是网络设备的名称。

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

init 为设备初始化函数指针,如果这个指针被设置了,则网络设备被注册时调用该函数完成对net_device结构体的初始化。但是,设备驱动程序可以不实现这个函数并将其赋值为NULL。

(2)硬件信息

unsigned long mem_end;
unsigned long mem_start;

mem_start和mem_end 分别定义了设备所使用的共享内存的起始和结束地址


unsigned long base_addr;
unsigned char irq;
unsigned char if_port;
unsigned char dma;

base_addr 为网络设备I/O 基地址。
irq为设备使用的中断号。
if_port指定多端口设备使用哪一个端口,该字段仅针对多端口设备。例如,如果设备同时支持IF_PORT_10BASE2(同轴电缆)和IF_PORT_10BASET(双绞线),则可使用该字段。
dma 指定分配给设备的DMA通道。

(3)接口信息


unsigned short hard_header_len;

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

</pre><pre name="code" class="cpp">unsigned short type;

type是接口的硬件类型。


unsigned mtu;

mtu指最大传输单元(MTU)。


unsigned char dev_addr[MAX_ADDR_LEN];
unsigned char broadcast[MAX_ADDR_LEN];

dev_addr[ ]、broadcast[ ]无符号字符数组,分别用于存放设备的硬件地址和广播地址。对于以太网而言,这两个地址的长度都为6 个字节。以太网设备的广播地址为6个0xFF,而MAC地址需由驱动程序从硬件上读出并填充到dev_addr[ ]中。


unsigned short flags;
flags 指网络接口标志,以IFF_(interface flags)开头,部分标志由内核来管理,其他的在接口初始化时被设置以说明设备接口的能力和特性。接口标志包括IFF_UP(当设备被激活并可以开始发送数据包时,内核设置该标志)、IFF_AUTOMEDIA(设备可在多种媒介间切换)、IFF_BROADCAST(允许广播)、IFF_DEBUG(调试模式,可用于控制printk调用的详细程度)、IFF_LOOPBACK(回环)、IFF_MULTICAST(允许组播)、IFF_NOARP(接口不能执行ARP)、IFF_POINTOPOINT(接口连接到点到点链路)等。

(4)设备操作函数

int (*open)(struct net_device *dev);
int (*stop)(struct net_device *dev);

open()函数的作用是打开网络接口设备,获得设备需要的I/O 地址、IRQ、DMA通道等。stop()函数的作用是停止网络接口设备,与open()函数的作用相反。

int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev);

hard_start_xmit() 函数会启动数据包的发送,当系统调用驱动程序的hard_start_xmit()函数时,需要向其传入一个sk_buff 结构体指针,以使得驱动程序能获取从上层传递下来的数据包。

void (*tx_timeout)(struct net_device *dev);

当数据包的发送超时时,tx_timeout ()函数会被调用,该函数需采取重新启动数据包发送过程或重新启动硬件等策略来恢复网络设备到正常状态。

int (*hard_header) (struct sk_buff *skb,
struct net_device *dev,
unsigned short type,
void *daddr,
 *saddr,
unsigned len);

hard_header()函数完成硬件帧头填充,返回填充的字节数。传入该函数的参数包括sk_buff 指针、设备指针、协议类型、目的地址、源地址以及数据长度。对于以太网设备而言,将内核提供的eth_header()函数赋值给hard_header 指针即可。

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

get_stats()函数用于获得网络设备的状态信息,它返回一个net_device_stats 结构体。net_device_stats结构体保存了网络设备详细的流量统计信息,如发送和接收到的数据包数、字节数等。

int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);
int (*set_config)(struct net_device *dev, struct ifmap *map);
int (*set_mac_address)(struct net_device *dev, void *addr);
do_ioctl()函数用于进行设备特定的I/O 控制。
set_config()函数用于配置接口,可用于改变设备的I/O 地址和中断号。

set_mac_address()函数用于设置设备的MAC地址。

int (*poll)( struct net_device *dev,int quota);
对于NAPI(网络中断缓和)兼容的设备驱动,将以轮询方式操作接口,接收数据包。NAPI 是Linux 系统上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据包,而是采用首先借助中断唤醒数据包接收的服务程序,然后以轮询方式获取数据包。net_device结构体的上述成员需要在设备初始化时被填充。


(5)辅助成员。

unsigned long trans_start;
unsigned long last_rx;

trans_start记录最后的数据包开始发送时的时间戳,last_rx记录最后一次接收到数据包时的时间戳,这两个时间戳记录的都是jiffies,驱动程序应维护这两个成员。

void *priv;

priv 为设备的私有信息指针,与filp->private_data 的地位相当。设备驱动程序中应该以netdev_priv()函数获得该指针。


spinlock_t xmit_lock;
int xmit_lock_owner;
xmit_lock是避免hard_start_xmit()函数被同时多次调用的自旋锁。xmit_lock_owner则指当前拥有xmit_lock自旋锁的CPU的编号。


<think>我们分析给定的`netdev_priv`函数实现: ```c static inline void *netdev_priv(const struct net_device *dev) { return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN); } ``` ### 实现机制分析 1. **函数目的**: - 该函数用于获取与`net_device`结构体关联的**私有数据区**的起始地址。 - 在Linux网络驱动中,每个网络设备除了通用的`net_device`结构外,通常还需要存储设备特定的数据(如硬件寄存器地址、状态变量等)。这些数据存储在紧随`net_device`之后的内存区域。 2. **内存布局**: - 当分配一个网络设备实例时,内存布局如下: ``` [struct net_device][设备私有数据][可能的填充] ``` - 分配方式通常为: ```c struct net_device *dev; dev = alloc_netdev(sizeof(struct my_private_data), "eth%d", setup); ``` 其中`alloc_netdev`会分配一块连续内存,大小为`sizeof(struct net_device) + 私有数据大小 + 对齐填充`。 3. **计算原理**: - `(char *)dev`:将`dev`指针转换为字节指针(便于按字节计算偏移)。 - `sizeof(struct net_device)`:获取`net_device`结构体的大小。 - `ALIGN(sizeof(struct net_device), NETDEV_ALIGN)`:将`net_device`的大小按`NETDEV_ALIGN`对齐。对齐的原因是为了确保私有数据区的起始地址满足特定架构的对齐要求(如内存访问效率或硬件要求)。 - 最终返回的地址是`dev`地址加上对齐后的`net_device`大小,即私有数据区的起始地址。 4. **对齐宏`ALIGN`**: - `ALIGN`宏的典型实现(如Linux内核中的`include/linux/kernel.h`): ```c #define ALIGN(x, a) __ALIGN_KERNEL((x), (a)) #define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a)-1) #define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask)) ``` - 作用:将`x`向上对齐到`a`的倍数。例如,若`NETDEV_ALIGN=8`,则对齐后的大小为8的倍数。 5. **使用示例**: - 驱动中定义私有数据结构: ```c struct my_private { int reg_base; // ...其他字段 }; ``` - 获取私有数据: ```c struct my_private *priv = netdev_priv(dev); ``` ### 关键点总结 - **连续性**:`net_device`和私有数据位于同一块连续内存中。 - **对齐要求**:确保私有数据起始地址满足硬件或架构的对齐约束,避免未对齐访问错误。 - **访问安全**:只要分配时预留了足够的空间,通过`netdev_priv`访问私有数据就是安全的。 §§相关问题§§ 1. 在Linux网络驱动中,使用`alloc_netdev`分配网络设备内存时,如何确定私有数据区的大小? 2. 为什么在计算私有数据区地址时需要对齐?如果不对齐可能会导致什么问题? 3. 除了`netdev_priv`函数,Linux内核中还有哪些类似机制用于访问设备私有数据(例如在字符设备驱动中)? 4. 当网络设备被注销时,与`net_device`关联的私有数据内存是如何释放的? 5. 在多核CPU环境下,访问通过`netdev_priv`获取的私有数据时需要注意哪些同步问题?</think>### `netdev_priv`函数实现机制分析 #### 1. 函数作用 `netdev_priv`是Linux内核网络子系统的核心工具函数,用于**获取网络设备私有数据区的指针**。其设计原理基于Linux内核的设备驱动内存布局模型: ```c static inline void *netdev_priv(const struct net_device *dev) { return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN); } ``` #### 2. 关键实现机制解析 1. **内存布局原理**: ```c +----------------------------+ | struct net_device | // 基础设备结构(公共部分) +----------------------------+ | Driver Private Data | // 驱动私有数据区(通过netdev_priv访问) +----------------------------+ ``` 当驱动程序通过`alloc_netdev()`分配设备时,实际分配的内存包含: - `net_device`结构体(固定部分) - 驱动自定义的私有数据区(可变部分) 2. **指针运算步骤**: - `(char *)dev`:将`net_device`指针转换为字节指针(便于按字节计算偏移) - `sizeof(struct net_device)`:获取公共结构体的大小 - `ALIGN(..., NETDEV_ALIGN)`:按架构要求对齐(通常8/16字节对齐) - `(char *)dev + ALIGN(...)`:计算出私有数据区的起始地址 3. **对齐宏`ALIGN`的作用**: ```c // 典型对齐实现(include/linux/kernel.h) #define ALIGN(x, a) __ALIGN_KERNEL((x), (a)) #define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a)-1) #define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask)) ``` - 确保私有数据区起始地址满足CPU架构的内存对齐要求 - 避免未对齐访问导致的性能损失或硬件异常 #### 3. 驱动使用示例 ```c // 定义私有数据结构 struct my_private { u32 reg_base; spinlock_t lock; // ...其他驱动特定字段 }; // 在驱动中获取私有数据 static int my_driver_xmit(struct sk_buff *skb, struct net_device *dev) { struct my_private *priv = netdev_priv(dev); // 关键调用 spin_lock(&priv->lock); // 访问硬件寄存器 iowrite32(skb->data, priv->reg_base); spin_unlock(&priv->lock); return NETDEV_TX_OK; } ``` #### 4. 内存分配验证 驱动分配设备时的对应操作: ```c // 分配时指定私有数据大小 struct net_device *dev = alloc_netdev(sizeof(struct my_private), "eth%d", NET_NAME_UNKNOWN, setup_function); // 内核实际分配大小 size_t total_size = ALIGN(sizeof(struct net_device), NETDEV_ALIGN) + sizeof(struct my_private); ``` ### 设计优势与注意事项 1. **性能优势**: - 零成本抽象:编译时完成地址计算(`static inline`) - 避免额外的指针解引用 2. **安全边界**: - 私有数据区紧跟`net_device`,内存连续 - 无动态内存分配开销 3. **架构兼容性**: - `NETDEV_ALIGN`根据不同架构定义(通常为L1_CACHE_BYTES) - 确保SMP环境下缓存行对齐
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值