Chinaunix首页 | 论坛 | 博客
  • 博客访问: 637183
  • 博文数量: 51
  • 博客积分: 773
  • 博客等级: 军士长
  • 技术积分: 2392
  • 用 户 组: 普通用户
  • 注册时间: 2012-05-07 21:32
文章分类
文章存档

2018年(1)

2013年(16)

2012年(34)

分类: LINUX

2013-05-21 21:34:59

          在内核中,网络设备通过函数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;
        }
    }
}

阅读(16009) | 评论(2) | 转发(8) |
给主人留下些什么吧!~~