网络子系统关键函数分析(未完待续)

内核版本: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执行过后的内存布局图,该图出自<<内幕>>一书。

网络子系统关键函数分析(未完待续)_第1张图片

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        = &eth_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。


未完待续。。。。

你可能感兴趣的:(网络子系统关键函数分析(未完待续))