内核版本:2.6.30
CPU:S3C2440
根据<<深入理解Linux网络技术内幕>>一书,本文将对网络子系统用到的关键函数进行一一分析。
下文将简称该书为<<内幕>>。
本文还将提及另一本书:<<TCP/IP详解 卷1:协议>>,下文将简称<<卷1>>。
函数列表及添加日期:
alloc_netdev_mq 2013.1.29
ether_setup 2013.1.30
1.alloc_netdev_mq(alloc_netdev)
在<<内幕>>一书中提到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);
}
在alloc_netdev_mq执行的最后调用了netdev_init_queues(dev)用来初始化队列,我们来看下:
下列函数位于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);
}
实质上,该函数为接受队列和发送队列保存了相应的net_device实体,就是队列对应的是哪个net_device。
最后,给出alloc_netdev_mq执行过后的内存布局图,该图出自<<内幕>>一书。
2. ether_setup
在<<内幕>>一书的第八章提及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,
}
该函数完成了对net_device中某些字段的初始化,其中大部分的值是根据以太网协议设置的,具体可参考<<卷一>>中的第二章。
header_ops设置成了以太网封装的头操作方法,该方法的具体实现在此就不分析了。
其次就是设置了发送队列的长度为1000。
未完待续。。。。