网络驱动移植之解析Linux网络驱动的基本框架

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

 

    概括而言,编写Linux网络驱动其实只要完成两件事即可,一是分配并初始化网络设备,二是注册网络设备。

    1、分配并初始化网络设备

    动态分配网络设备(从C语言角度来看,其实就是定义了一个struct net_device结构体变量,并对这个结构体变量的某些成员进行了初始化而已)及其私有数据的大致过程如下图(以以太网设备为例):

 

    下面将结合linux-2.6.38.8中的代码详细分析网络设备的分配和初始化过程。 

/* linux-2.6.38.8/include/linux/etherdevice.h */
#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
#define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv, count, count)

/* linux-2.6.38.8/net/ethernet/eth.c */
struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs,
				      unsigned int rxqs)
{
	return alloc_netdev_mqs(sizeof_priv, "eth%d", ether_setup, txqs, rxqs);
}

void ether_setup(struct net_device *dev)
{
	dev->header_ops		= &eth_header_ops;
	dev->type		= ARPHRD_ETHER;
	dev->hard_header_len 	= ETH_HLEN;
	dev->mtu		= ETH_DATA_LEN;
	dev->addr_len		= ETH_ALEN;
	dev->tx_queue_len	= 1000;	/* Ethernet wants good queues */
	dev->flags		= IFF_BROADCAST|IFF_MULTICAST;

	memset(dev->broadcast, 0xFF, ETH_ALEN);

}

    以前各类网络设备的分配函数(如以太网设备的alloc_etherdev)都只是alloc_netdev函数的封装而已,但对于linux-2.6.38.8而言已经不是这样了。 

/* linux-2.6.38.8/include/linux/netdevice.h */
#define alloc_netdev(sizeof_priv, name, setup) \
	alloc_netdev_mqs(sizeof_priv, name, setup, 1, 1)

    alloc_netdev_mqs函数的五个参数分别为私有数据大小、设备名称、默认初始化函数、发送队列数目和接收队列数目。

    以太网设备的名称设为eth%d,默认初始化函数设为ether_setup,发送和接收队列数目都设为1。

    函数alloc_netdev_mqs定义在linux-2.6.38.8/net/core/dev.c文件中,大概会完成以下各种操作:

    (1)、为struct net_device和私有数据分配内存空间 

	alloc_size = sizeof(struct net_device);
	if (sizeof_priv) {
		alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);  //#define NETDEV_ALIGN 32
		alloc_size += sizeof_priv;
	}
	alloc_size += NETDEV_ALIGN - 1;

	p = kzalloc(alloc_size, GFP_KERNEL);
	if (!p) {
		printk(KERN_ERR "alloc_netdev: Unable to allocate device.\n");
		return NULL;
	}

	dev = PTR_ALIGN(p, NETDEV_ALIGN);
	dev->padded = (char *)dev - (char *)p;

    对齐操作相关宏: 

/* linux-2.6.38.8/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))
	
#define PTR_ALIGN(p, a)		((typeof(p))ALIGN((unsigned long)(p), (a)))

    (2)、动态分配per-CPU变量 

	dev->pcpu_refcnt = alloc_percpu(int);
	if (!dev->pcpu_refcnt)
		goto free_p;

    (3)、初始化硬件地址链表dev->dev_addrs,并把首元素赋给dev->dev_addr 

	if (dev_addr_init(dev))
		goto free_pcpu;

    (4)、初始化组播和单播地址链表 

	dev_mc_init(dev);
	dev_uc_init(dev);

    (5)、设置网络命名空间 

	dev_net_set(dev, &init_net);

    (6)、设置GSO最大值 

	dev->gso_max_size = GSO_MAX_SIZE;

    (7)、初始化各种链表 

	INIT_LIST_HEAD(&dev->ethtool_ntuple_list.list);
	dev->ethtool_ntuple_list.count = 0;
	INIT_LIST_HEAD(&dev->napi_list);
	INIT_LIST_HEAD(&dev->unreg_list);
	INIT_LIST_HEAD(&dev->link_watch_list);

    (8)、设置priv_flags值 

	dev->priv_flags = IFF_XMIT_DST_RELEASE;

    (9)、执行默认初始化函数(以太网设备默认为ether_setup) 

	setup(dev);

    (10)、初始化数据包发送队列 

	dev->num_tx_queues = txqs;
	dev->real_num_tx_queues = txqs;
	if (netif_alloc_netdev_queues(dev))
		goto free_all;

    (11)、初始化数据包接收队列 

	dev->num_rx_queues = rxqs;
	dev->real_num_rx_queues = rxqs;
	if (netif_alloc_rx_queues(dev))
		goto free_all;

    (12)、设置网络设备名称 

	strcpy(dev->name, name);

    2、注册网络设备

    通过register_netdev函数把已完成部分初始化的net_device结构体变量(即某个网络设备实例)注册到Linux内核中,大致过程如下图:

 

    下面将结合linux-2.6.38.8中的代码详细分析网络设备的注册过程。

    (1)、获得rtnl信号量 

	rtnl_lock();

    (2)、分配网络设备名(即%d对应的数字) 

	if (strchr(dev->name, '%')) {
		err = dev_alloc_name(dev, dev->name);
		if (err < 0)
			goto out;
	}

    (3)、调用实际注册函数 

	err = register_netdevice(dev);

    3.1、初始化dev->addr_list_lock自旋锁并根据dev->type设置其类别 

	spin_lock_init(&dev->addr_list_lock);
	netdev_set_addr_lockdep_class(dev);

    3.2、调用init函数 

	if (dev->netdev_ops->ndo_init) {
		ret = dev->netdev_ops->ndo_init(dev);
		if (ret) {
			if (ret > 0)
				ret = -EIO;
			goto out;
		}
	}

    3.3、检测网络设备名是否有效 

	ret = dev_get_valid_name(dev, dev->name, 0);
	if (ret)
		goto err_uninit;

    3.4、为网络设备分配唯一的索引号 

	dev->ifindex = dev_new_index(net);
	if (dev->iflink == -1)
		dev->iflink = dev->ifindex;

    3.5、设置网络设备特性(dev->features) 

	if ((dev->features & NETIF_F_HW_CSUM) &&
	    (dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
		printk(KERN_NOTICE "%s: mixed HW and IP checksum settings.\n",
		       dev->name);
		dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM);
	}

	if ((dev->features & NETIF_F_NO_CSUM) &&
	    (dev->features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
		printk(KERN_NOTICE "%s: mixed no checksumming and other settings.\n",
		       dev->name);
		dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM);
	}

	dev->features = netdev_fix_features(dev->features, dev->name);


	if (dev->features & NETIF_F_SG)
		dev->features |= NETIF_F_GSO;


	dev->vlan_features |= (NETIF_F_GRO | NETIF_F_HIGHDMA);

    3.6、通过通知链告知内核其他子系统某种事件的发生(如注册网络设备) 

	ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev);
	ret = notifier_to_errno(ret);
	if (ret)
		goto err_uninit;

	ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
	ret = notifier_to_errno(ret);
	if (ret) {
		rollback_registered(dev);
		dev->reg_state = NETREG_UNREGISTERED;
	}

    3.7、创建网络设备在sysfs文件系统中的入口 

	ret = netdev_register_kobject(dev);
	if (ret)
		goto err_uninit;

    3.8、设置网络设备为已注册状态 

	dev->reg_state = NETREG_REGISTERED;

    3.9、设置网络设备状态为可用 

	set_bit(__LINK_STATE_PRESENT, &dev->state);

    3.10、初始化网络设备的队列规则 

	dev_init_scheduler(dev);

    3.11、增加网络设备的引用计数 

	dev_hold(dev);

    3.12、加入到设备链表(如dev->dev_list、dev->name_hlist、dev->index_hlist) 

	list_netdevice(dev);

    3.13、发送netlink(RFC 3549协议)信息

	if (!dev->rtnl_link_ops ||
	    dev->rtnl_link_state == RTNL_LINK_INITIALIZED)
		rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tanglinux

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

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

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

打赏作者

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

抵扣说明:

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

余额充值