内核版本:2.6.30
CPU:S3C2440
根据<<深入理解Linux网络技术内幕>>一书,本文将对网络子系统用到的关键函数进行一一分析。
下文将简称该书为<<内幕>>。
本文还将提及另一本书:<<TCP/IP详解 卷1:协议>>,下文将简称<<卷1>>。
函数列表及添加日期:
alloc_netdev_mq 2013.1.29
ether_setup 2013.1.30
在<<内幕>>一书中提到struct net_device结构体使用alloc_netdev函数进行分配。
在浏览2.6.30内核的代码后发现,发现该函数已被alloc_netdev_mq所替代。
下面,我们以以太网(ether)为例,看看alloc_netdev_mq是如何被调用的。
以太网设备用于分配net_device结构体的函数为alloc_etherdev。我们来看下:
下来代码位于include/linux/etherdevice.h。
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
首先,可以看到,调用alloc_etherdev等价于调用alloc_etherdev_mq函数。
alloc_etherdev_mq实现如下:
下来代码位于net/ethernet/eth.c。
/** * alloc_etherdev_mq - Allocates and sets up an Ethernet device * @sizeof_priv: Size of additional driver-private structure to be allocated * for this Ethernet device * @queue_count: The number of queues this device has. * * Fill in the fields of the device structure with Ethernet-generic * values. Basically does everything except registering the device. * * Constructs a new net device, complete with a private data area of * size (sizeof_priv). A 32-byte (not bit) alignment is enforced for * this private data area. */ struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count) { return alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count); }该函数只是alloc_netdev_mq的包裹(wrapper)函数,实际调用的就是 alloc_netdev_mq函数。
从这里我们可以看到以太网设备的名字eth%d。
下列函数位于net/core/dev.c。
/** * alloc_netdev_mq - allocate network device * @sizeof_priv: size of private data to allocate space for * @name: device name format string * @setup: callback to initialize device * @queue_count: the number of subqueues to allocate * * Allocates a struct net_device with private data area for driver use * and performs basic initialization. Also allocates subquue structs * for each queue on the device at the end of the netdevice. */ struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name, void (*setup)(struct net_device *), unsigned int queue_count) { struct netdev_queue *tx; struct net_device *dev; size_t alloc_size; void *p; BUG_ON(strlen(name) >= sizeof(dev->name)); /*检查name字段长度*/ alloc_size = sizeof(struct net_device); /*获取结构体大小*/ /*是否分配私有数据区*/ if (sizeof_priv) { /* ensure 32-byte alignment of private area */ /*确保私有数据区是32位对齐的*/ alloc_size = (alloc_size + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST;/*NETDEV_ALIGN_CONST = 31*/ alloc_size += sizeof_priv; } /* ensure 32-byte alignment of whole construct */ /*确保整个结构是字节对齐的*/ alloc_size += NETDEV_ALIGN_CONST; /*分配内存空间*/ p = kzalloc(alloc_size, GFP_KERNEL); if (!p) { printk(KERN_ERR "alloc_netdev: Unable to allocate device.\n"); return NULL; } /*根据queue_count的个数分配内存空间*/ tx = kcalloc(queue_count, sizeof(struct netdev_queue), GFP_KERNEL); if (!tx) { printk(KERN_ERR "alloc_netdev: Unable to allocate " "tx qdiscs.\n"); kfree(p); return NULL; } /*dev指针是32位对齐的!!!*/ dev = (struct net_device *) (((long)p + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST); dev->padded = (char *)dev - (char *)p; /*计算填充区域的大小*/ dev_net_set(dev, &init_net); /*该函数需要内核配置namespace,先无视*/ dev->_tx = tx; /*保存发送队列*/ dev->num_tx_queues = queue_count; /*保存发送队列数*/ dev->real_num_tx_queues = queue_count; /*保存以激活发送队列数*/ dev->gso_max_size = GSO_MAX_SIZE; /*65536*/ netdev_init_queues(dev); /*初始化队列*/ INIT_LIST_HEAD(&dev->napi_list); /*初始化链表头*/ setup(dev); /*调用函数指针setup函数*/ strcpy(dev->name, name); /*拷贝name*/ return dev; } EXPORT_SYMBOL(alloc_netdev_mq); #define NETDEV_ALIGN 32 #define NETDEV_ALIGN_CONST (NETDEV_ALIGN - 1)
注意该函数的第四个参数queue_count,该函数用来指定发送队列的个数。以太网设备的发送队列个数为1。
函数中,一个比较关键的地方是计算执行kzalloc时的空间大小,计算过程中保证了私有数据区32位对齐的,同时在最后也保证了net_device是32位对齐的。
当然alloc_size肯定要满足所需的空间大小。在这里,给出如何获取priv指针的函数,根据这个函数,强烈读者仔细思考下计算过程。
下来代码位于include/linux/etherdevice.h。
/** * netdev_priv - access network device private data * @dev: network device * * Get network device private data */ static inline void *netdev_priv(const struct net_device *dev) { return (char *)dev + ((sizeof(struct net_device) + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST); }
下列函数位于net/core/dev.c。
static void netdev_init_queues(struct net_device *dev) { /*为接受队列保存对应的net_device*/ netdev_init_one_queue(dev, &dev->rx_queue, NULL); /*为每个发送列保存对应的net_device*/ netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL); spin_lock_init(&dev->tx_global_lock); /*初始化自旋锁*/ } static void netdev_init_one_queue(struct net_device *dev, struct netdev_queue *queue, void *_unused) { queue->dev = dev; /*保存队列所对应的net_device*/ } static inline void netdev_for_each_tx_queue(struct net_device *dev, void (*f)(struct net_device *, struct netdev_queue *, void *), void *arg) { unsigned int i; for (i = 0; i < dev->num_tx_queues; i++) f(dev, &dev->_tx[i], arg); }
最后,给出alloc_netdev_mq执行过后的内存布局图,该图出自<<内幕>>一书。
在<<内幕>>一书的第八章提及XXX_setup函数用于对同一设备类型系列的所有设备所通用字段的初始化。
在上面的alloc_etherdev_mq函数的分析中中,其第三个参数的实参即为ether_setup,该函数用于对以太网设备字段的初始化。
在alloc_etherdev_mq的最后阶段,通过函数指针调用该函数。我们来看下该函数所做的初始化工作。
下列函数位于net/ethernet/eth.c。
/** * ether_setup - setup Ethernet network device * @dev: network device * Fill in the fields of the device structure with Ethernet-generic values. */ void ether_setup(struct net_device *dev) { dev->header_ops = ð_header_ops; /*封装头操作方法*/ #ifdef CONFIG_COMPAT_NET_DEV_OPS /*如果使用net_device_ops结构体*/ dev->change_mtu = eth_change_mtu; dev->set_mac_address = eth_mac_addr; dev->validate_addr = eth_validate_addr; #endif dev->type = ARPHRD_ETHER; /*以太网*/ dev->hard_header_len = ETH_HLEN; /*14 Bytes*/ dev->mtu = ETH_DATA_LEN; /*1500*/ dev->addr_len = ETH_ALEN; /*6 Bytes*/ dev->tx_queue_len = 1000; /* Ethernet wants good queues */ dev->flags = IFF_BROADCAST|IFF_MULTICAST; /*允许广播和组播*/ memset(dev->broadcast, 0xFF, ETH_ALEN); /*设置广播地址*/ } EXPORT_SYMBOL(ether_setup); const struct header_ops eth_header_ops ____cacheline_aligned = { .create = eth_header, .parse = eth_header_parse, .rebuild = eth_rebuild_header, .cache = eth_header_cache, .cache_update = eth_header_cache_update, }
header_ops设置成了以太网封装的头操作方法,该方法的具体实现在此就不分析了。
其次就是设置了发送队列的长度为1000。
未完待续。。。。