網絡子係統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