深入理解Linux网络技术内幕-设备注册和初始化(四)


          在内核中,网络设备通过函数register_netdev和unregister_netdev在内核中注册和注销,这两个函数对实际操作函数register_netdevice和unregister_netdevice进行封装,在调用这两个函数之前负责上锁。

           在分析网络设备的注册状态改变时,注销时多了一个NETREG_UNREGISTERING状态,这个状态表示将设备从内核设备链中摘除了,但还有一些操作没有完成,而是将设备放到了net_todo_list链表中,由netdev_run_todo函数来完成所有的注销操作。在net_device结构中有一个const struct net_device_ops *netdev_ops成员,其中有两个特殊的成员函数:

struct net_device_ops { 
    int            (*ndo_init)(struct net_device *dev); 
    void            (*ndo_uninit)(struct net_device *dev);

    ……

}

这两个成员函数分别在注册和注销设备时,对net_device结构的私有数据区进行处理。其调用位置分别为:

static void rollback_registered_many(struct list_head *head)

{

                 ……

if (dev->netdev_ops->ndo_uninit) 
            dev->netdev_ops->ndo_uninit(dev);

                 ……

}

注册时,初始化私有数据结构:

int register_netdevice(struct net_device *dev)

{

             ……

if (dev->netdev_ops->ndo_init) { 
        ret = dev->netdev_ops->ndo_init(dev);

             ……

}

因此,若没有私有数据区,则不需要实现这两个函数,赋值为NULL即可。

image 

在注销了,为了减少占用锁的时间,unregister_netdevice函数将设备从内核的设备链表中移除后,将net_device结构放入net_todo_list链表,由net_device结构的todo_list成员保存链表。因为对设备注销时,需要等待所有和ney_device结构关联的引用全部释放,才能释放这个结构和设备,因此将这个耗费时间的操作放到解锁之后完成。在netdev_run_todo函数中会调用netdev_wait_allrefs函数来等待net_device结构的所有引用全部释放,否则这个函数不会返回。

设备的注销过程,多了todo过程,这里分析一下,首先调用:

void unregister_netdev(struct net_device *dev) 

    rtnl_lock(); 
    unregister_netdevice(dev); 
    rtnl_unlock(); 
}

其中rtnl_lockhe 和rtnl_unlock的实现比较有意思,在rtnl_lock中获取了互斥量rtnl_mutex,而rtnl_unlock中则没有释放这个互斥量,而是rtnl_unlock调用netdev_run_todo函数,在这个函数的开始调用__rtnl_unlock函数释放这个互斥量。

上锁:

void rtnl_lock(void) 

    mutex_lock(&rtnl_mutex); 

EXPORT_SYMBOL(rtnl_lock);

解锁其实是进入netdev_run_todo函数:

void rtnl_unlock(void) 

    /* This fellow will unlock it for us. */ 
    netdev_run_todo(); 
}

这里才是实际解锁操作:

void netdev_run_todo(void) 

    struct list_head list;

    /* Snapshot list, allow later requests */ 
    list_replace_init(&net_todo_list, &list);  //将全局变量net_todo_list的值复制到局部变量list中,然后再释放互斥量,全局变量net_todo_list重新初始化,这里的实现值得借鉴

    __rtnl_unlock(); //释放互斥量

 

  ……

}

设备注销,函数的调用过程:

static inline void unregister_netdevice(struct net_device *dev) 

    unregister_netdevice_queue(dev, NULL); 
}

 

void unregister_netdevice_queue(struct net_device *dev, struct list_head *head) 

    ASSERT_RTNL();

    if (head) { 
        list_move_tail(&dev->unreg_list, head); 
    } else { 
        rollback_registered(dev); 
        /* Finish processing unregister after unlock */ 
        net_set_todo(dev); //将dev加入到全局变量Net_todo_list链表中 
    } 
}

static void rollback_registered(struct net_device *dev) 

    LIST_HEAD(single);

    list_add(&dev->unreg_list, &single);//将设备加入链表,这里实现这么繁琐,应该是为了适应调用rollback_registered_many函数,可以注销多个设备 
    rollback_registered_many(&single); 
    list_del(&single); 
}

static void rollback_registered_many(struct list_head *head) 

    struct net_device *dev, *tmp;

    BUG_ON(dev_boot_phase); 
    ASSERT_RTNL();  //检查是否获取了rtnl互斥量

    list_for_each_entry_safe(dev, tmp, head, unreg_list) { 
        /* Some devices call without registering 
         * for initialization unwind. Remove those 
         * devices and proceed with the remaining. 
         */ 
        if (dev->reg_state == NETREG_UNINITIALIZED) { //处理在注册过程中失败的设备 
            pr_debug("unregister_netdevice: device %s/%p never " 
                 "was registered\n", dev->name, dev);

            WARN_ON(1); 
            list_del(&dev->unreg_list);//将其从链表删除即可 
            continue; 
        }

        BUG_ON(dev->reg_state != NETREG_REGISTERED); //程序到这里则设备不可能不处于已注册状态 
    }

    /* If device is running, close it first. */ 
    dev_close_many(head); //关闭在运行的设备

    list_for_each_entry(dev, head, unreg_list) { 
        /* And unlink it from device chain. */ 
        unlist_netdevice(dev);

       /*

这里说明,只是将其从系统中的三个链表上移除

static void unlist_netdevice(struct net_device *dev) 

    ASSERT_RTNL();

    /* Unlink dev from the device chain */ 
    write_lock_bh(&dev_base_lock); //dev_base_lock是保证这三个链表互斥的读写锁 
    list_del_rcu(&dev->dev_list); 
    hlist_del_rcu(&dev->name_hlist); 
    hlist_del_rcu(&dev->index_hlist); 
    write_unlock_bh(&dev_base_lock); 
}

       */

        dev->reg_state = NETREG_UNREGISTERING; //然后,更新设备的注册状态 
    }

    synchronize_net();

    list_for_each_entry(dev, head, unreg_list) {

        /* Shutdown queueing discipline. */ 
        dev_shutdown(dev);  //处理设备的接收队列等

        /* Notify protocols, that we are about to destroy 
           this device. They should clean all the things. 
        */ 
        call_netdevice_notifiers(NETDEV_UNREGISTER, dev);  //发出注销通知

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

        /* 
         *    Flush the unicast and multicast chains 
         */ 
        dev_uc_flush(dev); 
        dev_mc_flush(dev);

        if (dev->netdev_ops->ndo_uninit) //处理私有数据区 
            dev->netdev_ops->ndo_uninit(dev);

        /* Notifier chain MUST detach us from master device. */ 
        WARN_ON(dev->master);

        /* Remove entries from kobject tree */ 
        netdev_unregister_kobject(dev); //从内核移除对象,涉及到内核设备管理层的东西 
    }

    /* Process any work delayed until the end of the batch */ 
    dev = list_first_entry(head, struct net_device, unreg_list); 
    call_netdevice_notifiers(NETDEV_UNREGISTER_BATCH, dev);

    rcu_barrier();

    list_for_each_entry(dev, head, unreg_list) 
        dev_put(dev); //释放设备,对其引用计数减一 
}

这里在获取锁的时间范围内的注销操作就完成了,这时设备已经和内核的设备链脱离了关系,也就是内核已经不知道这个设备的存在了。但这个设备可以还被内核中的其他模块,因此,剩余的操作需要在释放了rtnl互斥量后,在net_run_todo函数中处理。

//在这里,释放了互斥量后,可以在等待设备的引用计数归零过程中睡眠

void netdev_run_todo(void) 

    struct list_head list;

    /* Snapshot list, allow later requests */ 
    list_replace_init(&net_todo_list, &list);  //复制全局变量net_todo_list的值,然后初始化

    __rtnl_unlock();  //释放互斥量

    while (!list_empty(&list)) { //对链表中的元素进行处理 
        struct net_device *dev 
            = list_first_entry(&list, struct net_device, todo_list); //处理一个,移除一个 
        list_del(&dev->todo_list);

        if (unlikely(dev->reg_state != NETREG_UNREGISTERING)) { //内核出现重大BUG 
            printk(KERN_ERR "network todo '%s' but state %d\n", 
                   dev->name, dev->reg_state); 
            dump_stack(); 
            continue; 
        }

        dev->reg_state = NETREG_UNREGISTERED;  //更新注册状态

        on_each_cpu(flush_backlog, dev, 1);

        netdev_wait_allrefs(dev);  //等待引用计数归零,可能睡眠

        /* paranoia */ 
        BUG_ON(netdev_refcnt_read(dev)); 
        WARN_ON(rcu_dereference_raw(dev->ip_ptr)); 
        WARN_ON(rcu_dereference_raw(dev->ip6_ptr)); 
        WARN_ON(dev->dn_ptr);

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

        /* Free network device */ 
        kobject_put(&dev->dev.kobj); //释放这个结构,到这里设备的注销完全完成,net_device结构将被释放 
    } 
}

其实查看netdev_wait_allrefs函数,就是定时查看设备的引用计数是否为0,不为0则再次向其他模块发设备注销通知,让它们释放这个设备,然后进入休眠等待的过程。

static void netdev_wait_allrefs(struct net_device *dev) 

    unsigned long rebroadcast_time, warning_time; 
    int refcnt;

    linkwatch_forget_dev(dev);

    rebroadcast_time = warning_time = jiffies; 
    refcnt = netdev_refcnt_read(dev);

    while (refcnt != 0) { 
        if (time_after(jiffies, rebroadcast_time + 1 * HZ)) { //过一秒 
            rtnl_lock(); 

            /* Rebroadcast unregister notification */ 
            call_netdevice_notifiers(NETDEV_UNREGISTER, dev); //发注销广播通知 
            /* don't resend NETDEV_UNREGISTER_BATCH, _BATCH users 
             * should have already handle it the first time */

            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);  //睡眠250毫秒

        refcnt = netdev_refcnt_read(dev);  //夺取引用计数

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

猜你喜欢

转载自blog.csdn.net/wlf_go/article/details/80284454
今日推荐