网络驱动移植之例解netdev_priv函数

本文详细介绍了Linux内核如何通过alloc_netdev_mqs函数分配structnet_device结构体及其私有数据,并通过实例展示了整个过程。同时解释了netdev_priv函数的作用以及其原理,提供了深入理解内核结构体分配机制的洞察。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    开发平台:Ubuntu 11.04

    编译器:gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)

    内核源码:linux-2.6.38.8.tar.bz2

 

    1、如何分配struct net_device结构体以及私有数据

    下面将通过实例来讲解Linux内核是如何通过alloc_netdev_mqs函数分配struct net_device结构体以及私有数据的(因为理解了这一点,就能完全理解netdev_priv函数的实现)。

    首先,编写一个模块,代码如下: 

/* tanglinux.c */
#include <linux/module.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/ioctl.h>

#define TANGLINUX _IO('T', 1)

struct net_local {
    int count;
    char ch;
};

static int tanglinux_open(struct inode *inode, struct file *file)
{
    return nonseekable_open(inode, file);
}

static long tanglinux_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct net_device *dev;
    size_t alloc_size;
    size_t sizeof_priv = sizeof(struct net_local);
    struct net_device *p;
    
    switch (cmd) {
	case TANGLINUX:
	    alloc_size = sizeof(struct net_device);
	    printk("first: alloc_size = %d\n", alloc_size);

	    alloc_size += 1; //为验证ALIGN的作用,人为制造net_device结构体的大小不是32位对齐

	    if (sizeof_priv) {
		/* ensure 32-byte alignment of private area */
		alloc_size = ALIGN(alloc_size, NETDEV_ALIGN); //#define	NETDEV_ALIGN	32
		printk("second: alloc_size = %d\n", alloc_size);

		alloc_size += sizeof_priv;
		printk("third: alloc_size = %d\n", alloc_size);
	    }
	    /* ensure 32-byte alignment of whole construct */
	    alloc_size += NETDEV_ALIGN - 1;
	    printk("fourth: alloc_size = %d\n", alloc_size);
    
	    p = kzalloc(alloc_size, GFP_KERNEL);
	    if (!p) {
		printk(KERN_ERR "alloc_netdev: Unable to allocate device.\n");
		return -ENOMEM;
	    }
	    printk("p = %p\n", p);
    
	    dev = PTR_ALIGN(p, NETDEV_ALIGN);
	    printk("dev = %p\n", dev);

	    dev->padded = (char *)dev - (char *)p;
    
	    printk("dev->padded = %d\n", dev->padded);

	    kfree(p);

	    return 0;
	default:
	    return -ENOTTY;
    }
}

static int tanglinux_release(struct inode *inode, struct file *file)
{
    return 0;
}

static const struct file_operations tanglinux_fops = {
    .owner	    = THIS_MODULE,
    .unlocked_ioctl = tanglinux_ioctl,
    .open	    = tanglinux_open,
    .release	    = tanglinux_release,
};

static struct miscdevice tanglinux_miscdev = {
    .minor	= WATCHDOG_MINOR,
    .name	= "tanglinux",
    .fops	= &tanglinux_fops,
};

static int __init tanglinux_init(void)
{
    printk("tanglinux driver\n");

    return misc_register(&tanglinux_miscdev);
}

static void __exit tanglinux_exit(void)
{
    misc_deregister(&tanglinux_miscdev);
}

module_init(tanglinux_init);
module_exit(tanglinux_exit);

MODULE_LICENSE("GPL");

    然后,编译并加载此模块: 

//获得Ubuntu 11.04正在运行的内核版本
$ cat /proc/version
Linux version 2.6.38-13-generic (buildd@roseapple) (gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4) ) #53-Ubuntu SMP Mon Nov 28 19:23:39 UTC 2011

//根据上面获得的信息,在Makefile中指定Ubuntu 11.04的内核源码目录为/usr/src/linux-headers-2.6.38-13-generic/
# Makefile
KERN_DIR = /usr/src/linux-headers-2.6.38-13-generic/

all:
	make -C $(KERN_DIR) M=`pwd` modules

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean

obj-m += tanglinux.o

//编译,并把编译好的模块tanglinux.ko加载到内核中
$ make
$ sudo insmod tanglinux.ko

    最后,通过测试程序获得相关信息: 

/* test.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define TANGLINUX _IO('T', 1)

int main(void)
{
    int fd;

    fd = open("/dev/tanglinux", O_RDWR);
    if (fd < 0)
    {
	printf("can't open /dev/tanglinux\n");
	return -1;
    }

    ioctl(fd, TANGLINUX);
    
    return 0;
}
//编译、执行测试程序,然后通过dmesg命令获得模块输出的信息
$ make test
$ sudo ./test
$ dmesg | tail -7
[19853.353282] first: alloc_size = 1088
[19853.353296] second: alloc_size = 1120
[19853.353306] third: alloc_size = 1128
[19853.353316] fourth: alloc_size = 1159
[19853.353348] p = cddf6000
[19853.353358] dev = cddf6000
[19853.353369] dev->padded = 0

    根据Ubuntu 11.04(基于X86硬件平台)中的配置,struct net_device结构体的大小为1088字节,刚好32位对齐,为了验证对齐函数ALIGN的作用,在例子中故意把struct net_device结构体的大小增加了1,所以第二次输出的alloc_size大小为1120个字节,也就是在1089个字节的基础上为了对齐增加了31个字节。

    PTR_ALIGN函数的作用是为了使struct net_device *dev最终得到的内存地址也是32位对齐的。

    上面所讨论的问题都可以通过下面的图示体现出来:

 

    2、如何通过netdev_priv访问到其私有数据

    netdev_priv函数的源代码如下: 

static inline void *netdev_priv(const struct net_device *dev)
{
	return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
}

    即通过struct net_device *dev首地址加对齐后的偏移量就得到了私有数据的首地址,如上图。

<think>我们正在讨论Linux内核网络设备驱动中的netdev_priv()函数。 根据提供的引用,特别是引用[1]和引用[3],我们可以得到以下信息: 1. netdev_priv()函数的作用:用于访问网络设备的私有数据。每个网络设备驱动都可以有自己的私有数据结构,这个结构存储了设备特定的信息。 2. 实现原理:该函数是一个内联函数,它通过计算网络设备结构体(struct net_device)之后的地址来获取私有数据的指针。具体实现是: ```c static inline void *netdev_priv(const struct net_device *dev) { return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN); } ``` 这里,`ALIGN`宏用于确保私有数据在内存中对齐(通常是对齐到NETDEV_ALIGN指定的边界)。因此,私有数据区域实际上紧跟在`struct net_device`之后。 使用方法: - 在分配网络设备时,需要为`struct net_device`和私有数据一起分配内存。通常使用`alloc_netdev`或`alloc_netdev_mqs`等函数,这些函数会分配一个连续的内存块,其中包含`struct net_device`和私有数据。 - 然后,在驱动中,通过`netdev_priv(dev)`来获取指向私有数据的指针。 如,假设我们有一个私有数据结构: ```c struct my_priv { int some_field; // ... 其他字段 }; ``` 在分配网络设备时,我们这样分配: ```c struct net_device *dev; dev = alloc_netdev(sizeof(struct my_priv), "eth%d", NET_NAME_UNKNOWN, setup); if (!dev) { // 处理错误 } ``` 然后,在驱动中,我们可以这样使用: ```c struct my_priv *priv = netdev_priv(dev); priv->some_field = ...; ``` 注意:私有数据的大小在分配网络设备时指定(如上中的`sizeof(struct my_priv)`),这样分配的内存块就足够容纳`struct net_device`和私有数据。 引用[2]提到了另一个函数`__netdev_start_xmit`,但它与`netdev_priv`没有直接关系,所以我们可以忽略。 因此,总结如下: - `netdev_priv()`用于获取网络设备私有数据的指针。 - 私有数据的内存位置紧跟在`struct net_device`之后,并且通过内存偏移计算得到。 - 分配网络设备时必须指定私有数据的大小,以确保分配足够的内存。 现在,根据系统级指令,我们需要生成相关问题: §§相关问题§§ 1. 在Linux内核中,除了`netdev_priv()`,还有哪些访问私有数据的方法? 2. 为什么网络设备驱动需要私有数据?它通常包含哪些内容? 3. 如何确保`netdev_priv()`返回的指针正确指向私有数据区域? 4. 在分配网络设备时,`alloc_netdev`函数的具体参数和用法是什么?</think>### `netdev_priv()` 函数 #### 一、作用 `netdev_priv()` 是 Linux 内核网络设备驱动中的核心工具函数,主要用于**安全访问网络设备的私有数据区**。其核心作用包括: 1. **私有数据访问** 每个网络设备(`struct net_device`)都需要存储驱动特定的配置信息(如硬件寄存器地址、统计信息、状态标志等)。该函数通过指针计算返回紧跟在`net_device`结构体之后的私有数据区首地址[^1]。 2. **内存布局优化** 私有数据区与`net_device`结构体在内存中连续存储,通过单次内存分配完成,提升缓存命中率[^3]。 #### 二、实现原理 ```c static inline void *netdev_priv(const struct net_device *dev) { return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN); } ``` - **指针偏移计算** 将`net_device`指针转换为字节指针(`(char *)dev`),加上`struct net_device`的对齐后尺寸(`ALIGN(sizeof(...), NETDEV_ALIGN)`),得到私有数据区起始地址[^3]。 - **内存对齐保证** `ALIGN`宏确保私有数据区起始地址满足硬件对齐要求(通常为`NETDEV_ALIGN=32`),避免未对齐内存访问导致的性能损失或错误[^3]。 #### 三、使用方法 ##### 1. 定义私有数据结构 ```c struct mynet_priv { void __iomem *reg_base; // 硬件寄存器基地址 spinlock_t tx_lock; // 发送锁 u32 packet_count; // 包计数器 }; ``` ##### 2. 设备分配时指定私有数据大小 使用内核提供的分配函数: ```c struct net_device *dev; dev = alloc_netdev(sizeof(struct mynet_priv), "eth%d", NET_NAME_UNKNOWN, setup_function); ``` - `sizeof(struct mynet_priv)` 明确私有数据区大小 - `setup_function` 是设备初始化回调[^1] ##### 3. 访问私有数据 在驱动代码中通过类型转换访问: ```c struct mynet_priv *priv = netdev_priv(dev); spin_lock(&priv->tx_lock); priv->packet_count++; iowrite32(value, priv->reg_base + REG_OFFSET); ``` #### 四、关键注意事项 1. **内存连续性** 私有数据区必须与`net_device`连续存储,不可单独分配。 2. **类型安全** 强制类型转换需与分配时定义的结构体严格匹配。 3. **零初始化** 私有数据区不会自动清零,需在`setup_function`中手动初始化。 4. **引用场景** 常用于数据包发送(如`ndo_start_xmit`)、接收中断处理、设备配置回调等[^2]。 --- ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tanglinux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值