閱讀936 返回首頁    go 阿裏雲 go 技術社區[雲棲]


網絡子係統31_網絡設備的注冊與注銷

//	網絡設備注冊
//		內核中,使用兩個全局的hash表,索引net_device,分別為name hash表,index hash表,使用一個鏈表dev_base,鏈接所有net_device

//	包裹函數,獲取rtnl鎖,注冊任務由register_netdev完成
1.1 int register_netdev(struct net_device *dev)
{
	int err;
	//獲取rtnl鎖
 	rtnl_lock();
 	//執行注冊
	err = register_netdevice(dev);
	//開鎖,並繼續完成net_todo_list上掛載的dev的注冊/注銷
	rtnl_unlock();
    return err;
}

//	網絡設備注冊
//	調用路徑:register_netdev->register_netdevice
//	函數主要任務:
//		1.初始化隊列鎖,傳輸鎖
//		2.分配接口索引
//		3.檢查設備名是否唯一
//		4.更新設備SG,TSO特性
//		5.初始化設備的隊列規則
//		6.將設備添加到name,index的兩個hash表中
//		7.通知netdev_chain
//		8.啟動下半部

//	注:net_device->queue_lock用於保護設備的規則隊列,net_device->xmit_lock用於保護驅動程序的hard_start_xmit
//	xmit_lock_owner=獲取xmit_lock的cpu號
1.2 int register_netdevice(struct net_device *dev)
{
	struct hlist_head *head;
	struct hlist_node *p;
	int ret;

	//初始化設備隊列鎖,在net_tx_action中會被獲取
	spin_lock_init(&dev->queue_lock);
	//初始化hard_xmit保護鎖,在qdisc_restart中會被獲取
	spin_lock_init(&dev->xmit_lock);
	//鎖木有沒有擁有者
	dev->xmit_lock_owner = -1;


	//表示設備目前沒有分配接口索引 ifindex字段
	dev->iflink = -1;

	//如果驅動程序提供了init例程,則調用
	if (dev->init) {
		ret = dev->init(dev);
		if (ret) {
			if (ret > 0)
				ret = -EIO;
			goto out_err;
		}
	}
 	//設備名非法
	if (!dev_valid_name(dev->name)) {
		ret = -EINVAL;
		goto out_err;
	}
	//分配唯一的接口索引
	dev->ifindex = dev_new_index();
	if (dev->iflink == -1)
		//iflink等於接口索引
		dev->iflink = dev->ifindex;

	//檢查設備名是否唯一
	head = dev_name_hash(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 out_err;
		}
 	}

	//設備支持分散聚集DMA,不支持校驗和功能,則取消掉SG
	if ((dev->features & NETIF_F_SG) &&
	    !(dev->features & (NETIF_F_IP_CSUM |
			       NETIF_F_NO_CSUM |
			       NETIF_F_HW_CSUM))) {
		printk("%s: Dropping NETIF_F_SG since no checksum feature.\n",
		       dev->name);
		dev->features &= ~NETIF_F_SG;
	}

	//TCP 的TSO功能
	if ((dev->features & NETIF_F_TSO) &&
	    !(dev->features & NETIF_F_SG)) {
		printk("%s: Dropping NETIF_F_TSO since no SG feature.\n",
		       dev->name);
		dev->features &= ~NETIF_F_TSO;
	}

	//以太網默認提供rebuild_header函數,在ether_setup中設置
	if (!dev->rebuild_header)
		dev->rebuild_header = default_rebuild_header;
	//設備存在標誌,多用於熱插拔設備
	set_bit(__LINK_STATE_PRESENT, &dev->state);

	dev->next = NULL;
	//初始化設備的隊列規則
	dev_init_scheduler(dev);
	//獲取保護dev_tail,dev_name_head,dev_index_head鎖
	write_lock_bh(&dev_base_lock);
	*dev_tail = dev;
	dev_tail = &dev->next;
	//將dev添加到hash表中
	hlist_add_head(&dev->name_hlist, head);
	hlist_add_head(&dev->index_hlist, dev_index_hash(dev->ifindex));
	//增加dev的引用計數
	dev_hold(dev);
	//將dev標記為NETREG_REGISTERING狀態,表示正在注冊中
	dev->reg_state = NETREG_REGISTERING;
	write_unlock_bh(&dev_base_lock);
	//通知netdev_chain中的監聽者,有設備注冊
	notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev);
	//由net_set_todo完成後半部的注冊,主要是在sysfs中注冊設備
	net_set_todo(dev);
	ret = 0;

out:
	return ret;
out_err:
	free_divert_blk(dev);
	goto out;
}



//	分配接口索引
//		確保唯一性的辦法,檢查新索引是否已經被使用

//	調用路徑:register_netdev->register_netdevice->dev_new_index
1.3 static int dev_new_index(void)
{
	static int ifindex;
	for (;;) {
		if (++ifindex <= 0)
			ifindex = 1;
		//獲取第一個沒有被設備使用的索引
		if (!__dev_get_by_index(ifindex))
			return ifindex;
	}
}

//	掛載設備到後半部操作鏈表
//		將設備注冊,注銷操作,分成兩部分,後一部分在釋放rtnl鎖的情況下進行
2.1 static inline void net_set_todo(struct net_device *dev)
{
	//後半部鏈表由net_todo_list_lock保護
	spin_lock(&net_todo_list_lock);
	//將設備掛在到net_todo_list上,接收之後的注冊
	list_add_tail(&dev->todo_list, &net_todo_list);
	spin_unlock(&net_todo_list_lock);
}

//	注冊、注銷後半部入口點:
//		在釋放rtnl之後,完成剩餘操作

//	注:netlink為核心通知用戶事件的標準內核接口
2.2 void rtnl_unlock(void)
{	
	//釋放rtnl_sem信號量
	rtnl_shunlock();
	//操作net_todo_list
	netdev_run_todo();
}

//	注冊與注銷的切割操作
//	函數主要內容:
//		1.遍曆net_todo_list上的設備
//		2.注冊操作的後半部:
//			2.1 在sysfs中更新相應項
//		3.注銷操作後半部:
//			3.1 刪除sysfs中的相應項
//			3.2 等待net_device的引用計數變為1,
//			3.3 向netdev_chain發送注銷信息,通知持有該設備的模塊釋放對其的引用。
//			3.4 調用驅動提供的destructor

//	注:netdev_run_todo由net_todo_run_mutex信號量保護,全局同時隻有一個實例在運行。
2.3 void netdev_run_todo(void)
{
	struct list_head list = LIST_HEAD_INIT(list);
	int err;

	//保證全局隻有一個netdev_run_todo在執行
	down(&net_todo_run_mutex);

	//檢查net_todo_list上是否有需要執行切割操作的dev
	if (list_empty(&net_todo_list))
		goto out;

	//獲取保護net_todo_list的鎖
	spin_lock(&net_todo_list_lock);
	//將net_todo_list合並到本地list上,然後重新初始化net_todo_list
	list_splice_init(&net_todo_list, &list);
	spin_unlock(&net_todo_list_lock);
		
	while (!list_empty(&list)) {
		struct net_device *dev
			= list_entry(list.next, struct net_device, todo_list);
		//從list上刪除dev
		list_del(&dev->todo_list);

		//判斷dev當前的注冊狀態
		switch(dev->reg_state) {
			//在register_netdevice中被設置
		case NETREG_REGISTERING:
			//在sysfs中注冊設備
			err = netdev_register_sysfs(dev);
			if (err)
				printk(KERN_ERR "%s: failed sysfs registration (%d)\n",
				       dev->name, err);
			dev->reg_state = NETREG_REGISTERED;
			break;
			//在unregister_netdevice中被設置
		case NETREG_UNREGISTERING:
			//刪除sysfs中設備對應的節點
			netdev_unregister_sysfs(dev);
			dev->reg_state = NETREG_UNREGISTERED;
			//等待dev的引用計數為零,周期性打印unregister消息,並向netdev_chain通知此dev的NETDEV_UNREGISTER消息
			netdev_wait_allrefs(dev);
			//如果設備驅動提供了銷毀操作,則調用
			if (dev->destructor)
				dev->destructor(dev);
			break;

		default:
			printk(KERN_ERR "network todo '%s' but state %d\n",
			       dev->name, dev->reg_state);
			break;
		}
	}

out:
	up(&net_todo_run_mutex);
}


//	使用切割操作的優勢主要體現在注銷設備時:
//		當驅動需要同時注銷多個設備時,unregister_netdev會造成嚴重的鎖開銷,因為一次上鎖解鎖隻能注銷一個設備
//		通過使用unregister_netdevice,手工對rtnl加鎖,可以通過一次加鎖注銷多個net_device,從而降低鎖開銷。

//	
//	參考 深入理解linux網絡技術內幕 提供的一個例子
//1.rtnl_lock( );
// loop for each device driven by this driver {
//     ... ... ...
//     unregister_netdevice(dev);
//     ... ... ...
// }
// rtnl_unlock( );
//
//2.loop for each device driven by this driver {
//     ... ... ...
//     unregister_netdev(dev);
//     ... ... ...
// } 

//unregister_netdev()
//{
//	rtnl_lock();
//	unregister_netdevice();
//	rtnl_unlock();
//}



//	注銷網絡設備

//	函數主要任務:
//		1.確保設備已經被關閉
//		2.從name 哈希表,index哈希表刪除
//		3.通知netdev_chain設備正在注銷
//		4.釋放多播列表
//		5.啟動後半部操作
3.2 int unregister_netdevice(struct net_device *dev)
{
	struct net_device *d, **dp;


	if (dev->reg_state == NETREG_UNINITIALIZED) {//注銷的設備必須是已經注冊過的設備
		printk(KERN_DEBUG "unregister_netdevice: device %s/%p never "
				  "was registered\n", dev->name, dev);
		return -ENODEV;
	}


	if (dev->flags & IFF_UP)//設備處於開啟狀態,需要先關閉設備
		dev_close(dev);//修改設備狀態,以及修改設備隊列規則

	for (dp = &dev_base; (d = *dp) != NULL; dp = &d->next) {//遍曆網卡設備列表
		if (d == dev) {
			write_lock_bh(&dev_base_lock);//操作dev_base需要獲取dev_base_lock鎖
			hlist_del(&dev->name_hlist);//從index,name鏈表刪除設備
			hlist_del(&dev->index_hlist);
			if (dev_tail == &dev->next)
				dev_tail = dp;
			*dp = d->next;
			write_unlock_bh(&dev_base_lock);
			break;
		}
	}
	if (!d) {
		printk(KERN_ERR "unregister net_device: '%s' not found\n",
		       dev->name);
		return -ENODEV;
	}

	dev->reg_state = NETREG_UNREGISTERING;//設置設備為正在注銷狀態,稍有由後半部完成剩餘的注銷操作

	dev_shutdown(dev);//刪除設備隊列規則

	notifier_call_chain(&netdev_chain, NETDEV_UNREGISTER, dev);//通知netdev_chain,設備要注銷
	
	dev_mc_discard(dev);//刪除多播列表

	if (dev->uninit)
		dev->uninit(dev);//如果設備提供了注銷操作,則調用

	free_divert_blk(dev);//釋放分流器

	net_set_todo(dev);//將設備鏈接到todo鏈表上

	dev_put(dev);//遞減引用計數
	return 0;
}

//	刪除設備多播列表
void dev_mc_discard(struct net_device *dev)
{
	spin_lock_bh(&dev->xmit_lock);
	
	while (dev->mc_list != NULL) {//鏈表非空
		struct dev_mc_list *tmp = dev->mc_list;
		dev->mc_list = tmp->next;//從鏈表取下多播控製項
		if (tmp->dmi_users > tmp->dmi_gusers)//dmi_users = number of users;dmi_guesers = number of groups
			printk("dev_mc_discard: multicast leakage! dmi_users=%d\n", tmp->dmi_users);
		kfree(tmp);//直接kfree
	}
	dev->mc_count = 0;

	spin_unlock_bh(&dev->xmit_lock);
}

最後更新:2017-04-03 15:22:11

  上一篇:go matlab濾波器設計
  下一篇:go 網絡子係統23_skb常用函數