深入理解Linux网络技术内幕——设备的注册与初始化(二)
设备注册于设备除名
设备注册与设备除名一般有 register_netdev和unregister_netdev完成。这两个是包裹函数,负责上锁,真正起作用的是其调用的register_netdevice和unregister_netdevice。参见:net/core/dev.c。
下图描述了设备注册过程中的一些状态变化
状态的改变会用到UNINITIALIZED和REGISTERED之间的状态REGISTERING。这些进程有netdev_run_todo进行。参照“切割操作:netdev_run_todo”
为设备进行注册和除名时,设备驱动程序可以使用net_device的两个虚拟函数init和uninit进行私有数据的初始化和清理工作。
设备除名时,只有对net_device的引用计数为0,netdev_wait_allrefs才会返回,除名工作才会顺利完成。
设备的注册和除名操作都是由netdev_run_todo完成的。
切割操作:netdev_run_todo
register_netdevice会负责一部分的注册工作,然后再让netdev_run_todo予以完成。
net_device的改变会通过rtnl_lock和rtnl_unlock收到Routing
Netlink信号量的保护。这也是为什么register_netdev执行时需要请求锁,并且在返回时要释放锁的原因。一旦register_netdevice完成了它自己的工作,它会通过net_set_todo将net_device结构体添加到net_todo_list中。net_todo_list包含一系列有注册(或除名)操作需要被完成的设备列表。这份列表有register_netdev在释放锁时间接予以处理。
因此rtnl_unlock不仅会释放锁,还会调用 netdev_run_todo。netdev_run_todo会浏览net_todo_list列表,然后完成全部的实例注册。
void rtnl_unlock(void) { /* This fellow will unlock it for us. */ netdev_run_todo(); } EXPORT_SYMBOL(rtnl_unlock);
netdev_run_todo所进行的工作不需要持有锁。
设备注册状态通知
内核组件和应用程序都可能想知道设备的注册、除名、开启、关闭。设备装态信息通过两种方式传送通知。
netdev_chain
Netlink的 RTMGRP_LINK多播组
netdev_chain通知链:
设备的注册和除名阶段各个状态的变化都是通过netdev_chain来进行通告的。对其感兴趣的内核组件可以分别通过register_netdevice_notifier和unregister_netdevice_notifier来针对该链进行注册和除名。
netdev_chain报告的事件定义在:
//格式: #define NETDEV_UP 0x0001 /* For now you can't veto a device up/down */ #define NETDEV_DOWN 0x0002 ...... //含义: NETDEV_UP //该报告表明设备被启用,由dev_open产生报告 NETDEV_DOWN //设备已关闭,由dev_close生成报告 NETDEV_REBOOT //因硬件设备,设备已重启 NETDEV_CHANGE //设备的状态或者配置发生改变 NETDEV_REGISTER //设备已经注册 NETDEV_UNREGISTER //设备已经除名 NETDEV_CHANGEMTU // NETDE // NETDEV_PRE_UP // NETDEV_PRE_TYPE_CHANGE // NETDEV_POST_TYPE_CHANGE // NETDEV_POST_INIT // NETDEV_UNREGISTER_FINAL // NETDEV_RELEASE // NETDEV_NOTIFY_PEERS // NETDEV_JOIN // NETDEV_CHANGEUPPER V_CHANGEADDR //设备硬件地址(或关联的广播地址)发生改变 NETDEV_GOING_DOWN // NETDEV_CHANGENAME //设备名称发生改变 NETDEV_FEAT_CHANGE // NETDEV_BONDING_FAILOVER // NETDEV_RESEND_IGMP //
很多设备都在netdev_chain中注册,如:路由,防火墙
RTnetlink链接通知
当设备发生一些状态改变(或其它事件),会通过rtmsg_ifinfo把通知传给link多播组。
设备注册:
设备注册不仅仅只是把net_device嵌入到全局表dev_base和哈希表dev_name_head、dev_index_head中,它还包括初始化net_device部分参数,发送广播通告(提醒其他模块本设备加入)、以及一些其他的工作。
int register_netdev(struct net_device *dev) { int err; rtnl_lock(); err = register_netdevice(dev); rtnl_unlock(); return err; }
register_netdevice开始设备注册工作,并调用net_set_todo,而net_set_todo最终会调用netdev_run_todo完成注册。
register_netdevice的工作主要包括以下部分:
- 初始化net_device的部分字段
- 如果内核支持Divert功能,则用alloc_divert_blk分配该功能所需的数据空间块,并连接至dev->divert
- 如果设备驱动已经对dev->init进行初始化,则执行此函数。
- 由dev_new_index分配给设备一个识别码。
- 把net_device插入到全局表dev_base,以及两张哈希表dev_name_head,dev_index_head。
- 检查功能标识是否有无效的组合。
- 设置dev->state中的__LINK_STATE_PRESENT标识,使得设备能为内核所用。
- 用dev_init_scheduler初始化设备队列规则,以便流量控制用于实现Qos。
- 通过netdev_chain通知表链通知所有对本设备注册感兴趣的子系统。
当netdev_run_todo被调用完成注册时,它只更新dev->reg_state,并将设备注册进入sysfs。
设备除名:
设备除名需要复原设备注册期间所进行的所有工作和一些其他事项:
- 用dev_close关闭设备
- 释放所有分配的资源(IRQ、I/O端口...)
- 从全局表dev_base和两张哈希表中删除设备
- 当该设备的所有引用都释放后,释放net_device结构的空间、该驱动程序的私有数据结构、以及相链接的内存区域块。
- 删除添加到/proc 和/sys的所有文件
net_device中三个函数指针比较有用:
dev->stop //用以关闭设备,通常包括关闭netif_stop_queue出口,释放硬件资源等 dev->uninit //主要负责引用计数,少用到 dev->destructor //少数虚拟设备使用,通常初始化为: free_netdev 或其包裹函数
设备除名有int unregister_netdevice(struct net_device *dev)进行,其主要工作如下:
- 如果设备没关闭,使用dev_close关闭
- 从全局表dev_base和两张哈希表中删除设备
- 所有与该设备关联的队列规则实例,由dev_shutdown销毁
- 发送NETDEV_UNREGISTER消息到netdev_chain通知链。
- 除名消息也必须通知用户空间。
- 任何链接至net_device的数据块被释放
- register_netdevice中dev_init的所有操作都要有dev_uninit复原。
最后调用net_set_todo,使得net_run_todo能完成除名。
引用计数
只有当设备引用计数都被释放时,net_device结构才能被释放。
引用计数保存在dev->refcnt中。当引用新增或删除时,使用dev_hold和dev_put更新引用计数。
当设备以register_netdevice注册时,dev->refcnt初始化为1, 这第一个引用有负责网络设备数据库的内核代码所持有。因此,只有当设备除名时,该值才有可能降为0.
网络设备的启用和关闭
设备一旦注册即可使用,但是如果没有明确的开启其功能,设备仍然无法接收和传输数据流。
设备的开启请求由dev_open完成:
//net/core/dev.c int dev_open(struct net_device *dev) static int __dev_open(struct net_device *dev)
启用设备包括下列工作:
- 如果 dev->open初始化了,就调用dev->open。
- 设置dev_state中的__LINK_STATE_START标识,以表明设备开启或在运行中
- 设置dev_flags中的IFF_UP,标识设备为开启。
- 调用dev_activate初始化流量控制所需使用到的出口队列规则,然后启用看门狗定时器。
- 向netdev_chain发送NETDEV_UP通知
设备的启用必须显示执行,但设备的关闭则可以通过用户命令显式执行或者通过其他程序隐式执行。
设备关闭有以下任务:
- 向netdev_chain发送 NETDEV_GOING_DOWN通知
- 调用dev_deactivat关闭队列规则
- 清除dev->state的_ _LINK_STATE_START标志,以表明设备禁用了
- 如果正在执行读取入口队列数据包的轮询操作,则等待该操作完成。因为dev->state关系,入口队列不会再有入队操作,所以读取当前已经在队列中的即可
- 如果dev->stop有定义,执行它。
- 清除dev->flags的IFF_UP操作。
- 向netdev_chain发送NEtdEV_DOWN通知。
更新队列规则状态:
以后再补充
从用户空间配置设备相关信息
许多工具可以用来配置设备相关参数:
- ipconfig和mii-tool 来自net-tool套件
- ethtool 来自ethtool套件
- ip link:来自IP ROUTER2 套件