Linux网络设备之注销

Posted smartvxworks

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux网络设备之注销相关的知识,希望对你有一定的参考价值。

在注销网络设备时,会调用pci_driver->remove函数,以e100网卡驱动为例,实际调用e100_remove,该函数调用函数unregister_netdev进行设备注销操作;

函数调用关系图如下,注销分为两步:

(1) 回滚注册操作,进行初步注销,过程中如果设备尚未关闭,则需先调用dev_close_many将设备关闭;

(2) 将设备加入到net_todo_list,然后通过netdev_run_todo进行设备最终的注销,其中包括调用netdev_wait_allrefs来等待所有对设备的引用结束;

/**
 * 设备注销第一步,回滚注销调用关系
 * e100_remove
 *     |-->unregister_netdev
 *          |-->unregister_netdevice
 *                 |-->unregister_netdevice_queue
 *                        |-->rollback_registered
 *                        |       |-->rollback_registered_many
 *                        |              |-->dev_close_many-->__dev_close_many
 *                        |---->net_set_todo
 *
 * 设备注销第二步,todo_list处理调用关系
 * rtnl_unlock
 *     |-->netdev_run_todo
 *             |-->netdev_wait_allrefs
 */

unregister_netdev为注销设备的直接调用函数,其中在锁的包围之下,调用unregister_netdevice进行注销设备,而rtnl_unlock解锁函数调用时,也会触发对net_todo_list的处理;

/**
 *    unregister_netdev - remove device from the kernel
 *    @dev: device
 *
 *    This function shuts down a device interface and removes it
 *    from the kernel tables.
 *
 *    This is just a wrapper for unregister_netdevice that takes
 *    the rtnl semaphore.  In general you want to use this and not
 *    unregister_netdevice.
 */
void unregister_netdev(struct net_device *dev)

    rtnl_lock();
    unregister_netdevice(dev);
    rtnl_unlock();
static inline void unregister_netdevice(struct net_device *dev)

     unregister_netdevice_queue(dev, NULL);

unregister_netdevice_queue函数执行两个操作,rollback_registered对注册过程进行回滚,net_set_todo将设备加入到net_todo_list;

/**
 *    unregister_netdevice_queue - remove device from the kernel
 *    @dev: device
 *    @head: list
 *
 *    This function shuts down a device interface and removes it
 *    from the kernel tables.
 *    If head not NULL, device is queued to be unregistered later.
 *
 *    Callers must hold the rtnl semaphore.  You may want
 *    unregister_netdev() instead of this.
 */

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

    ASSERT_RTNL();

    if (head) 
        list_move_tail(&dev->unreg_list, head);
    
    /*
        可见,下面注销分两个步骤:
        1. rollback_registered
        2. 加入todo_list,后续在rtnl_unlock中调用netdev_run_todo
    */
    else 
        /* 回滚注册 */
        rollback_registered(dev);
        /* Finish processing unregister after unlock */
        /* 设备的todolist加入到todolist中 */
        net_set_todo(dev);
    

rollback_registered将设备加入到新链表中,然后调用回滚多个设备的函数;

static void rollback_registered(struct net_device *dev)

    /* 生成single链表 */
    LIST_HEAD(single);

    /* 加入链表 */
    list_add(&dev->unreg_list, &single);

    /* 注销 */
    rollback_registered_many(&single);

    /* 删除链表 */
    list_del(&single);

rollback_registered_many为核心的回滚注册函数,其会遍历传入设备链表,对每个设备进行注销操作,并且发送相关通知消息;

static void rollback_registered_many(struct list_head *head)

    struct net_device *dev, *tmp;
    LIST_HEAD(close_head);

    BUG_ON(dev_boot_phase);
    ASSERT_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;
        

        //标记设备要被释放
        dev->dismantle = true;
        BUG_ON(dev->reg_state != NETREG_REGISTERED);
    

    /* If device is running, close it first. */
    /* 如果设备正在运行,先将其关闭 */
    list_for_each_entry(dev, head, unreg_list)
        list_add_tail(&dev->close_list, &close_head);

    /* 关闭设备 */
    dev_close_many(&close_head, true);

    /* 从各种链表中移除当前设备 */
    list_for_each_entry(dev, head, unreg_list) 
        /* And unlink it from device chain. */
        unlist_netdevice(dev);

        dev->reg_state = NETREG_UNREGISTERING;
    

    /* 清理工作 */
    flush_all_backlogs();

    /* 同步rcu */
    synchronize_net();

    list_for_each_entry(dev, head, unreg_list) 
        struct sk_buff *skb = NULL;

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

        /* 构建dellink消息 */
        if (!dev->rtnl_link_ops ||
            dev->rtnl_link_state == RTNL_LINK_INITIALIZED)
            skb = rtmsg_ifinfo_build_skb(RTM_DELLINK, dev, ~0U,
                             GFP_KERNEL);

        /*
         *    Flush the unicast and multicast chains
         */
        /* 清理单播和组播链表 */
        dev_uc_flush(dev);
        dev_mc_flush(dev);

        /* 设备的uninit操作 */
        if (dev->netdev_ops->ndo_uninit)
            dev->netdev_ops->ndo_uninit(dev);

        /* 发送netlink消息 */
        if (skb)
            rtmsg_ifinfo_send(skb, dev, GFP_KERNEL);

        /* Notifier chain MUST detach us all upper devices. */
        WARN_ON(netdev_has_any_upper_dev(dev));
        WARN_ON(netdev_has_any_lower_dev(dev));

        /* Remove entries from kobject tree */
        /* 从虚拟文件系统中移除设备 */
        netdev_unregister_kobject(dev);
#ifdef CONFIG_XPS
        /* Remove XPS queueing entries */
        netif_reset_xps_queues_gt(dev, 0);
#endif
    

    /* 同步rcu */
    synchronize_net();

    /* 减少设备引用 */
    list_for_each_entry(dev, head, unreg_list)
        dev_put(dev);

注销过程中,如果发现有设备尚未关闭,则需要调用dev_close_many将设备进行关闭;

/* 关闭设备 */
int dev_close_many(struct list_head *head, bool unlink)

    struct net_device *dev, *tmp;

    /* Remove the devices that don't need to be closed */
    /* 移除未运行的设备 */
    list_for_each_entry_safe(dev, tmp, head, close_list)
        if (!(dev->flags & IFF_UP))
            list_del_init(&dev->close_list);

    /* 关闭设备 */
    __dev_close_many(head);

    list_for_each_entry_safe(dev, tmp, head, close_list) 
        /* netlink消息 */
        rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP|IFF_RUNNING, GFP_KERNEL);

        /* 通知设备关闭 */
        call_netdevice_notifiers(NETDEV_DOWN, dev);

        /* 移除设备节点 */
        if (unlink)
            list_del_init(&dev->close_list);
    

    return 0;
static int __dev_close_many(struct list_head *head)

    struct net_device *dev;

    ASSERT_RTNL();
    might_sleep();

    list_for_each_entry(dev, head, close_list) 
        /* Temporarily disable netpoll until the interface is down */
        /* 禁用netpoll */
        netpoll_poll_disable(dev);

        /* 通知设备正在关闭 */
        call_netdevice_notifiers(NETDEV_GOING_DOWN, dev);

        /* 清除start标志位 */
        clear_bit(__LINK_STATE_START, &dev->state);

        /* Synchronize to scheduled poll. We cannot touch poll list, it
         * can be even on different cpu. So just clear netif_running().
         *
         * dev->stop() will invoke napi_disable() on all of it's
         * napi_struct instances on this device.
         */
        smp_mb__after_atomic(); /* Commit netif_running(). */
    

    /* 未发送完的数据发送完 */
    dev_deactivate_many(head);

    list_for_each_entry(dev, head, close_list) 
        const struct net_device_ops *ops = dev->netdev_ops;

        /*
         *    Call the device specific close. This cannot fail.
         *    Only if device is UP
         *
         *    We allow it to be called even after a DETACH hot-plug
         *    event.
         */
        /* 调用设备关闭操作 */
        if (ops->ndo_stop)
            ops->ndo_stop(dev);

        /* 标记设备关闭 */
        dev->flags &= ~IFF_UP;
        /* 启用netpoll */
        netpoll_poll_enable(dev);
    

    return 0;
static int __dev_close_many(struct list_head *head)

    struct net_device *dev;

    ASSERT_RTNL();
    might_sleep();

    list_for_each_entry(dev, head, close_list) 
        /* Temporarily disable netpoll until the interface is down */
        /* 禁用netpoll */
        netpoll_poll_disable(dev);

        /* 通知设备正在关闭 */
        call_netdevice_notifiers(NETDEV_GOING_DOWN, dev);

        /* 清除start标志位 */
        clear_bit(__LINK_STATE_START, &dev->state);

        /* Synchronize to scheduled poll. We cannot touch poll list, it
         * can be even on different cpu. So just clear netif_running().
         *
         * dev->stop() will invoke napi_disable() on all of it's
         * napi_struct instances on this device.
         */
        smp_mb__after_atomic(); /* Commit netif_running(). */
    

    /* 未发送完的数据发送完 */
    dev_deactivate_many(head);

    list_for_each_entry(dev, head, close_list) 
        const struct net_device_ops *ops = dev->netdev_ops;

        /*
         *    Call the device specific close. This cannot fail.
         *    Only if device is UP
         *
         *    We allow it to be called even after a DETACH hot-plug
         *    event.
         */
        /* 调用设备关闭操作 */
        if (ops->ndo_stop)
            ops->ndo_stop(dev);

        /* 标记设备关闭 */
        dev->flags &= ~IFF_UP;
        /* 启用netpoll */
        netpoll_poll_enable(dev);
    

    return 0;

对于net_todo_list的处理,则是在解锁的时候进行,它将调用netdev_run_todo来处理todo_list;

void rtnl_unlock(void)

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

netdev_run_todo需要在等待所有对设备的引用结束之后,再对设备进行销毁; 

/* The sequence is:
 *
 *    rtnl_lock();
 *    ...
 *    register_netdevice(x1);
 *    register_netdevice(x2);
 *    ...
 *    unregister_netdevice(y1);
 *    unregister_netdevice(y2);
 *      ...
 *    rtnl_unlock();
 *    free_netdev(y1);
 *    free_netdev(y2);
 *
 * We are invoked by rtnl_unlock().
 * This allows us to deal with problems:
 * 1) We can delete sysfs objects which invoke hotplug
 *    without deadlocking with linkwatch via keventd.
 * 2) Since we run with the RTNL semaphore not held, we can sleep
 *    safely in order to wait for the netdev refcnt to drop to zero.
 *
 * We must not return until all unregister events added during
 * the interval the lock was held have been completed.
 */
void netdev_run_todo(void)

    struct list_head list;

    /* Snapshot list, allow later requests */
    list_replace_init(&net_todo_list, &list);

    __rtnl_unlock();


    /* Wait for rcu callbacks to finish before next phase */
    if (!list_empty(&list))
        rcu_barrier();

    /* 遍历链表 */
    while (!list_empty(&list)) 
        struct net_device *dev
            = list_first_entry(&list, struct net_device, todo_list);

        /* 移除设备 */
        list_del(&dev->todo_list);

        /* 通知设备进行最后的注销 */
        rtnl_lock();
        call_netdevice_notifiers(NETDEV_UNREGISTER_FINAL, dev);
        __rtnl_unlock();

        /* 检查设备状态 */
        if (unlikely(dev->reg_state != NETREG_UNREGISTERING)) 
            pr_err("network todo '%s' but state %d\\n",
                   dev->name, dev->reg_state);
            dump_stack();
            continue;
        

        /* 设置设备状态为注销 */
        dev->reg_state = NETREG_UNREGISTERED;

        /* 等待所有引用结束 */
        netdev_wait_allrefs(dev);

        /* paranoia */
        BUG_ON(netdev_refcnt_read(dev));
        BUG_ON(!list_empty(&dev->ptype_all));
        BUG_ON(!list_empty(&dev->ptype_specific));
        WARN_ON(rcu_access_pointer(dev->ip_ptr));
        WARN_ON(rcu_access_pointer(dev->ip6_ptr));
        WARN_ON(dev->dn_ptr);

        /* 调用设备销毁 */
        if (dev->priv_destructor)
            dev->priv_destructor(dev);

        /* 需要释放设备,则释放 */
        if (dev->needs_free_netdev)
            free_netdev(dev);

        /* Report a network device has been unregistered */
        /* 报告设备被注销 */
        rtnl_lock();
        dev_net(dev)->dev_unreg_count--;
        __rtnl_unlock();
        wake_up(&netdev_unregistering_wq);

        /* Free network device */
        /* 减少设备对象引用 */
        kobject_put(&dev->dev.kobj);
    

netdev_wait_allrefs会等待设备引用计数不为0的设备引用结束; 

/**
 * netdev_wait_allrefs - wait until all references are gone.
 * @dev: target net_device
 *
 * This is called when unregistering network devices.
 *
 * Any protocol or device that holds a reference should register
 * for netdevice notification, and cleanup and put back the
 * reference if they receive an UNREGISTER event.
 * We can get stuck here if buggy protocols don't correctly
 * call dev_put.
 */
/* 等待所有的引用结束 */
static void netdev_wait_allrefs(struct net_device *dev)

    unsigned long rebroadcast_time, warning_time;
    int refcnt;

    /* 从linkwatch中删除设备 */
    linkwatch_forget_dev(dev);

    rebroadcast_time = warning_time = jiffies;

    /* 统计所有引用数 */
    refcnt = netdev_refcnt_read(dev);

    /* 引用数不为0 */
    while (refcnt != 0) 
        /* 每1s中进行一次通知 */
        /* 到达通知广播时间窗口 */
        if (time_after(jiffies, rebroadcast_time + 1 * HZ)) 
            rtnl_lock();

            /* Rebroadcast unregister notification */
            /* 通知设备注销 */
            call_netdevice_notifiers(NETDEV_UNREGISTER, dev);

            __rtnl_unlock();
            rcu_barrier();
            rtnl_lock();

            /* 通知设备最终注销 */
            call_netdevice_notifiers(NETDEV_UNREGISTER_FINAL, 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);

        /* 重新读取引用数 */
        refcnt = netdev_refcnt_read(dev);

        /* 10s钟仍未结束,则打印告警 */
        if (time_after(jiffies, warning_time + 10 * HZ)) 
            pr_emerg("unregister_netdevice: waiting for %s to become free. Usage count = %d\\n",
                 dev->name, refcnt);
            warning_time = jiffies;
        
    

以上是关于Linux网络设备之注销的主要内容,如果未能解决你的问题,请参考以下文章

Linux基础命令之关机,重启,注销

Linux基础命令之关机、重启、注销命令

LINUX驱动之IIC驱动

Linux驱动之I2C驱动架构

GB28181控制传输流程和协议接口之注册|注销和技术实现

logstash之filter处理中括号包围的内容