`
simohayha
  • 浏览: 1407391 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

内核网络设备的注册与初始化

阅读更多
首先来看如何分配内存给一个网络设备。

内核通过alloc_netdev来分配内存给一个指定的网络设备:

#define alloc_netdev(sizeof_priv, name, setup) \
	alloc_netdev_mq(sizeof_priv, name, setup, 1)

struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
		void (*setup)(struct net_device *), unsigned int queue_count)


其中alloc_netdev_mq中的第一个元素是每个网络设备的私有数据(主要是包含一些硬件参数,比如中断之类的)的大小,也就是net_device结构中的priv的大小。第二个参数是设备名,我们传递进来一般都是一个待format的字符串,比如"eth%d",到时多个相同类型网卡设备就会依次为eth0,1(内核会通过dev_alloc_name来进行设置)... 第三个参数setup是一个初始化net_device结构的回调函数。

可是一般我们不需要直接调用alloc_netdev的,内核提供了一些包装好的函数:

这里我们只看alloc_etherdev:

#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
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);
}


这里实际是根据网卡的类型进行包装,也就类似于oo中的基类,ether_setup初始化一些所有相同类型的网络设备的一些相同配置的域:

void ether_setup(struct net_device *dev)
{
	dev->header_ops		= &eth_header_ops;

	dev->change_mtu		= eth_change_mtu;
	dev->set_mac_address 	= eth_mac_addr;
	dev->validate_addr	= eth_validate_addr;

	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);

}


接下来我们来看注册网络设备的一些细节。

int register_netdev(struct net_device *dev)
{
	int err;

	rtnl_lock();

	/*
	 * If the name is a format string the caller wants us to do a
	 * name allocation.
	 */
	if (strchr(dev->name, '%')) {
///这里通过dev_alloc_name函数来对设备名进行设置。
		err = dev_alloc_name(dev, dev->name);
		if (err < 0)
			goto out;
	}
///注册当前的网络设备到全局的网络设备链表中.下面会详细看这个函数.
	err = register_netdevice(dev);
out:
	rtnl_unlock();
	return err;
}


整个网络设备就是一个链表,他需要很方便的遍历所有设备,以及很快的定位某个指定的设备。为此net_device包含了下面3个链表(有关内核中数据结构的介绍,可以去自己google下):

///可以根据index来定位设备
struct hlist_node	index_hlist;
///可以根据name来定位设备
struct hlist_node	name_hlist;
///通过dev_list,将此设备插入到全局的dev_base_head中,我们下面会介绍这个。
struct list_head	dev_list;


当设备注册成功后,还需要通知内核的其他组件,这里通过netdev_chain类型的notifier chain来通知其他组件。事件是NETDEV_REGISTER..其他设备通过register_netdevice_notifier来注册自己感兴趣的事件到此notifier chain上。

网络设备(比如打开或关闭一个设备),与用户空间的通信通过rtmsg_ifinfo函数,也就是RTMGRP_LINK的netlink。

每个设备还包含两个状态,一个是state字段,表示排队策略状态(用位图表示),一个是注册状态。

包的排队策略也就是qos了。。
int register_netdevice(struct net_device *dev)
{
	struct hlist_head *head;
	struct hlist_node *p;
	int ret;
	struct net *net;

	BUG_ON(dev_boot_phase);
	ASSERT_RTNL();

	might_sleep();

	/* When net_device's are persistent, this will be fatal. */
	BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
	BUG_ON(!dev_net(dev));
	net = dev_net(dev);

///初始化相关的锁
	spin_lock_init(&dev->addr_list_lock);
	netdev_set_addr_lockdep_class(dev);
	netdev_init_queue_locks(dev);

	dev->iflink = -1;

	/* Init, if this function is available */
	if (dev->init) {
		ret = dev->init(dev);
		if (ret) {
			if (ret > 0)
				ret = -EIO;
			goto out;
		}
	}

	if (!dev_valid_name(dev->name)) {
		ret = -EINVAL;
		goto err_uninit;
	}
///给设备分配一个唯一的identifier.
	dev->ifindex = dev_new_index(net);
	if (dev->iflink == -1)
		dev->iflink = dev->ifindex;

///在全局的链表中检测是否有重复的名字
	head = dev_name_hash(net, dev->name);
	hlist_for_each(p, head) {
		struct net_device *d
			= hlist_entry(p, struct net_device, name_hlist);
		if (!strncmp(d->name, dev->name, IFNAMSIZ)) {
			ret = -EEXIST;
			goto err_uninit;
		}
	}
///下面是检测一些特性的组合是否合法。
	/* Fix illegal checksum combinations */
	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);
	}


	/* Fix illegal SG+CSUM combinations. */
	if ((dev->features & NETIF_F_SG) &&
	    !(dev->features & NETIF_F_ALL_CSUM)) {
		printk(KERN_NOTICE "%s: Dropping NETIF_F_SG since no checksum feature.\n",
		       dev->name);
		dev->features &= ~NETIF_F_SG;
	}

	/* TSO requires that SG is present as well. */
	if ((dev->features & NETIF_F_TSO) &&
	    !(dev->features & NETIF_F_SG)) {
		printk(KERN_NOTICE "%s: Dropping NETIF_F_TSO since no SG feature.\n",
		       dev->name);
		dev->features &= ~NETIF_F_TSO;
	}
	if (dev->features & NETIF_F_UFO) {
		if (!(dev->features & NETIF_F_HW_CSUM)) {
			printk(KERN_ERR "%s: Dropping NETIF_F_UFO since no "
					"NETIF_F_HW_CSUM feature.\n",
							dev->name);
			dev->features &= ~NETIF_F_UFO;
		}
		if (!(dev->features & NETIF_F_SG)) {
			printk(KERN_ERR "%s: Dropping NETIF_F_UFO since no "
					"NETIF_F_SG feature.\n",
					dev->name);
			dev->features &= ~NETIF_F_UFO;
		}
	}

	/* Enable software GSO if SG is supported. */
	if (dev->features & NETIF_F_SG)
		dev->features |= NETIF_F_GSO;

///初始化设备驱动的kobject并创建相关的sysfs
	netdev_initialize_kobject(dev);
	ret = netdev_register_kobject(dev);
	if (ret)
		goto err_uninit;
///设置注册状态。
	dev->reg_state = NETREG_REGISTERED;

	/*
	 *	Default initial state at registry is that the
	 *	device is present.
	 */

///设置排队策略状态。
	set_bit(__LINK_STATE_PRESENT, &dev->state);
///初始化排队规则
	dev_init_scheduler(dev);
	dev_hold(dev);
///将相应的链表插入到全局的链表中。紧接着会介绍这个函数
	list_netdevice(dev);

	/* Notify protocols, that a new device appeared. */
///调用netdev_chain通知内核其他子系统。
	ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
	ret = notifier_to_errno(ret);
	if (ret) {
		rollback_registered(dev);
		dev->reg_state = NETREG_UNREGISTERED;
	}

out:
	return ret;

err_uninit:
	if (dev->uninit)
		dev->uninit(dev);
	goto out;
}


这里要注意有一个全局的struct net init_net;变量,这个变量保存了全局的name,index  hlist以及全局的网络设备链表。

net结构我们这里所需要的也就三个链表:

///设备链表
struct list_head 	dev_base_head;
///名字为索引的hlist
	struct hlist_head 	*dev_name_head;
///index为索引的hlist
	struct hlist_head	*dev_index_head;


static int list_netdevice(struct net_device *dev)
{
	struct net *net = dev_net(dev);

	ASSERT_RTNL();

	write_lock_bh(&dev_base_lock);
///插入全局的list
	list_add_tail(&dev->dev_list, &net->dev_base_head);
插入全局的name_list以及index_hlist
	hlist_add_head(&dev->name_hlist, dev_name_hash(net, dev->name));
	hlist_add_head(&dev->index_hlist, dev_index_hash(net, dev->ifindex));
	write_unlock_bh(&dev_base_lock);
	return 0;
}


最终执行完之后,注册函数将会执行rtnl_unlock函数,而此函数则会执行netdev_run_todo方法。也就是完成最终的注册。(要注意,当取消注册这个设备时也会调用这个函数来完成最终的取消注册)

这里有一个全局的net_todo_list的链表:
static LIST_HEAD(net_todo_list);


而在取消注册的函数中会调用这个函数:

static void net_set_todo(struct net_device *dev)
{
	list_add_tail(&dev->todo_list, &net_todo_list);
}


也就是把当前将要取消注册的函数加入到todo_list链表中。

void netdev_run_todo(void)
{
	struct list_head list;

	/* Snapshot list, allow later requests */
///replace掉net_todo_list用list代替。
	list_replace_init(&net_todo_list, &list);

	__rtnl_unlock();
///当注册设备时没有调用net_set_todo函数来设置net_todo_list,因此list为空,所以就会直接跳过。
	while (!list_empty(&list)) {
///通过todo_list得到当前的device对象。
		struct net_device *dev
			= list_entry(list.next, struct net_device, todo_list);
///删除此todo_list;
		list_del(&dev->todo_list);


		if (unlikely(dev->reg_state != NETREG_UNREGISTERING)) {
			printk(KERN_ERR "network todo '%s' but state %d\n",
			       dev->name, dev->reg_state);
			dump_stack();
			continue;
		}
///设置注册状态为NETREG_UNREGISTERED.
		dev->reg_state = NETREG_UNREGISTERED;
///在每个cpu上调用刷新函数。
		on_each_cpu(flush_backlog, dev, 1);

///等待引用此设备的所有系统释放资源,也就是引用计数清0.
		netdev_wait_allrefs(dev);

		/* paranoia */
		BUG_ON(atomic_read(&dev->refcnt));
		WARN_ON(dev->ip_ptr);
		WARN_ON(dev->ip6_ptr);
		WARN_ON(dev->dn_ptr);

		if (dev->destructor)
			dev->destructor(dev);

		/* Free network device */
		kobject_put(&dev->dev.kobj);
	}
}


下面来看netdev_wait_allrefs函数,我们先看它的调用流程:




static void netdev_wait_allrefs(struct net_device *dev)
{
	unsigned long rebroadcast_time, warning_time;

	rebroadcast_time = warning_time = jiffies;
	while (atomic_read(&dev->refcnt) != 0) {
		if (time_after(jiffies, rebroadcast_time + 1 * HZ)) {
			rtnl_lock();

///给netdev_chain发送NETDEV_UNREGISTER事件,通知各个子模块释放资源
			/* Rebroadcast unregister notification */
			call_netdevice_notifiers(NETDEV_UNREGISTER, dev);

			if (test_bit(__LINK_STATE_LINKWATCH_PENDING,
				     &dev->state)) {
				/* We must not have linkwatch events
				 * pending on unregister. If this
				 * happens, we simply run the queue
				 * unscheduled, resulting in a noop
				 * for this device.
				 */
				linkwatch_run_queue();
			}

			__rtnl_unlock();

			rebroadcast_time = jiffies;
		}

		msleep(250);

		if (time_after(jiffies, warning_time + 10 * HZ)) {
			printk(KERN_EMERG "unregister_netdevice: "
			       "waiting for %s to become free. Usage "
			       "count = %d\n",
			       dev->name, atomic_read(&dev->refcnt));
			warning_time = jiffies;
		}
	}
}

  • 大小: 42.6 KB
1
0
分享到:
评论

相关推荐

    疯狂内核之——内核初始化

    ### 知识点总结 #### 1. 引子 - **上电**: 当电源接入计算机时,系统开始上电自检(POST),检查硬件...- **子系统的初始化**: 初始化各种子系统,如网络、设备驱动等。 - **启动shell环境**: 提供交互式命令行界面。

    内核初始化

    ### 内核初始化知识点详解 #### 一、内核初始化概述 **内核初始化**是操作系统启动过程中的关键环节之一,主要涉及系统硬件资源的初始化、内核数据结构的建立、核心服务的启动等步骤。良好的初始化过程可以确保...

    网络设备内核初始化过程

    Linux操作系统中的网络设备初始化过程是系统启动的关键环节,它确保了网络接口的正常运作。网络设备驱动程序在Linux中通常以模块的形式存在,可以是built-in内核的一部分,也可以是动态加载的模块。无论是哪种方式,...

    Linux内核1.2.13网络协议栈初始化及源码剖析

    主要涵盖了Socket编程基础知识和Linux内核网络协议栈的工作方式,其中包括协议实现模块和网络设备驱动的初始化过程;探讨了数据链路层的功能,以及客户端、服务器的简单Socket程序和多线程并发模型的设计与注意事项...

    linux 内核 socket相关的协议栈初始化

    2. **subsys_initcall(net_dev_init)**: 初始化网络设备。这里主要是设置各种网络接口设备,准备接收和发送数据包的硬件环境。 3. **fs_initcall(inet_init)**: 初始化Internet协议栈。这一步骤主要负责TCP/IP协议...

    VxWorks内核、设备驱动与BSP开发详解

    《VxWorks内核、设备驱动与BSP开发详解》第二版是一本深入探讨VxWorks实时操作系统核心、设备驱动程序开发以及板级支持包(BSP)构建的专业书籍。VxWorks是由Wind River Systems开发的一款广泛应用在嵌入式系统的实时...

    Vxworks内核、设备驱动与BSP开发详解

    ### VxWorks内核、设备驱动与BSP开发详解 #### 一、VxWorks概述与应用场景 VxWorks是一款著名的嵌入式实时操作系统(RTOS),由Wind River Systems开发。由于其优秀的实时性能和高度可靠性,在航空、航天、通信、...

    <VxWorks内核、设备驱动与BSP开发详解>内附代码

    书中详细阐述了设备驱动开发的基本原理和步骤,包括字符设备、块设备、网络设备等各类驱动的编写方法。开发者需要理解硬件的工作模式,然后根据VxWorks驱动程序接口来实现相应的函数,以实现设备的操作和管理。 BSP...

    linux内核初始化及启动之用户模式开始.pdf

    以上内容总结了Linux内核在启动进入用户模式后的一些关键步骤和技术细节,包括PCI设备初始化、网络协议栈初始化、交换守护进程kswapd的工作原理、日志块设备驱动的加载以及devfs和pty的配置。这些过程对于理解Linux...

    Armlinux内核移植及系统初始化过程分析.pdf

    在这个阶段,根文件系统会被挂载,设备驱动继续初始化,网络接口、设备树等高级功能得到配置。 了解这些过程对内核移植至关重要,因为每个阶段都可能需要针对新平台进行定制。例如,可能需要修改`mach-*`目录下的...

    android初始化流程

    在初始化过程中,init会启动一系列系统服务,如adbd(Android Debug Bridge Daemon),它提供与设备交互的能力;servicemanager,负责管理Android服务的生命周期;mountd,用于挂载文件系统;以及debuggerd,提供...

    Linux网络设备驱动编程.pdf

    网络设备驱动程序可以被分为多个部分,包括驱动模块的加载与卸载、设备的探测与初始化、数据包的发送与接收处理等。 在本文件中,提到了Linux网络设备驱动编程中几个核心的编程概念,以下是根据文档内容提炼出的...

    嵌入式系统的以太网接口设计及linux内核网络设备驱动宣贯.pdf

    《嵌入式系统的以太网...总的来说,嵌入式系统中的以太网接口设计与Linux内核网络设备驱动涉及硬件接口、协议栈、数据传输和驱动程序的编写等多个层面。理解这些概念和技术对于开发高效稳定的嵌入式网络系统至关重要。

    嵌入式系统的以太网接口设计及linux内核网络设备驱动[归纳].pdf

    "嵌入式系统的以太网接口设计及Linux内核网络设备驱动" 以太网概述: 以太网(Ethernet)是当今局域网采用的最通用的通信协议标准。在以太网中,所有计算机被连接在一条电缆上,采用带冲突检测的载波侦听多路访问...

    linux内核初始化及启动之用户模式开始文.pdf

    #### PCI设备初始化 在内核初始化早期阶段,会有一系列针对硬件设备的检测与配置工作。其中,PCI(Peripheral Component Interconnect)总线设备的初始化尤为关键。文档中的信息显示了内核初始化PCI设备的一部分...

    嵌入式系统的以太网接口设计及linux内核网络设备驱动汇编.pdf

    嵌入式系统的以太网接口设计及 Linux 内核网络设备驱动汇编 以太网概述 以太网是当今局域网采用的最通用的通信协议标准。在以太网中,所有计算机被连接在一条电缆上,采用带冲突检测的载波侦听多路访问(CSMA/CD)...

    嵌入式系统的以太网接口设计及linux内核网络设备驱动.pdf

    【嵌入式系统的以太网接口设计】 以太网(Ethernet)是局域网通信的主要标准...同时,讲解了Linux内核网络驱动程序的体系结构和初始化过程,阐述了设备驱动层的关键功能,为理解嵌入式系统中的以太网通信提供了基础。

Global site tag (gtag.js) - Google Analytics