kernel中使用net_device结构来描述网络设备,这个结构是网络驱动及接口层中最重要的结构。该结构不仅描述了接口方面的信息,还包括硬件信息,致使该结构很大很复杂。通过这个结构,内核在底层的网络驱动和网络层之间构建了一个网络接口核心层(这个叫法引自《TCP迁移报告》),这个中间层类似于文件子系统的VFS。这样底层的驱动程序就不需要过多地关注上层的网络协议,只需要通过内核提供的网络接口核心层就可以很方便将和网络层进行数据的交互。而网络层在向下发送数据时,只需要通过内核提供的这个中间层进行交互即可,不需要关心底层究竟是什么类型的网卡。
1、注册网络设备
网络设备通常在内核启动时或者插拔网络设备时注册,由网络设备驱动负责。网络设备驱动会首先根据自己的网卡类型调用相应的分配net_device结构的函数(例如以太网网卡可以调用alloc_etherdev(),当然也可以直接调用alloc_netdev()),然后初始化网卡相关的成员,最后调用register_netdev()来完成网络设备的注册。每个网络设备在系统中都要有一个唯一的名称,通常以网卡类型的一个缩写为前缀,后面跟着数字,例如,eth1就是一个以太网卡。register_netdev()是对register_netdevice()的包装函数。在调用register_netdev()注册设备时,如果指定的名称中包含%d格式串(只支持%d),内核会选择一个适当的数字来替换格式化串,真正的注册工作由register_netdevice()来完成。
在调用register_netdevice()之前,必须要先调用rtnl_lock()获取rtnl_mutex互斥锁,任何网络配置信息改变时都要首先获取rtnl_mutex互斥锁。注册过程如下所示:
net_device结构的netdev_ops由驱动程序初始化,存储的是设备相关的操作。如果设置了初始化函数,则通过ndo_init成员来进行设备相关的初始化操作
dev_valid_name()用来检查设备名是否为空或者包含不合法的字符(例如‘/’、空白字符),并不会检查名称是否冲突。
dev_new_index()为设备分配一个可用的索引号,用来标识设备,索引号由一个32位计数器(dev_new_index()中定义的静态变量)产生,每当一个新的设备添加到系统中时,计数器都会加1,然后检查该索引号是否已经使用,如果可用,则返回,否则继续加1.
在检查完合法性后,会调用dev_name_hash()找到在dev_name_head散列表(网络命名空间net结构中的成员)中的槽位,即冲突链表的头,然后在这个冲突链表中查找是否已存在相同名称的设备。如果找到相同的,则返回错误,终止注册过程。
接下来是是对设备的特性进行检查,看是否冲突,并进行调整。
netdev_register_kobject()用来在sysfs中创建跟设备关联的项,网络设备的索引号以及状态信息可以通过/sys/class/net/eth0(eth0为设备名,不同设备名称不同)目录下的项来查看。
在完成上述的操作后,内核会调用list_netdevice()将设备添加到网络名称空间中的dev_base_head链表、dev_name_head和dev_index_head散列表中,最后发送NETDEV_REGISTER消息到netdev_chain通知链上,通知对设备注册感兴趣的内核组件。
注册到系统中的所有网络设备都会添加到dev_base_head链表、dev_name_head和dev_index_head散列表中,其中dev_list按照FIFO顺序添加,加入name_hlist散列表是根据名称计算出的哈希值添加,加入index_hlist散列表是根据设备索引号计算的哈希值添加。它们的关系如下所示:
有了dev_name_head和dev_index_head散列表,可以分别通过dev_get_by_name()和dev_get_by_index()来根据设备名称或索引来获取网络设备。
2、启用网络设备
设备注册后即可使用,但必须在用户或用户空间应用程序开启后才能收发数据。因为注册到系统中的网络设备,其初始状态是关闭的,此时不能传送数据。用户可以通过"ifconfig 设备名 up"命令来启用,该命令(ioctl()的SIOCSIFFLAGS命令)通过dev_change_flags()调用dev_open()来激活网络设备。
启用网络设备后,会设置IFF_UP标志,如果该标志已经设置,则不用再继续操作,直接返回。如果设备被挂起(休眠状态,电源管理相关)或已经移除,则不能启用,返回ENODEV错误。
设备的启用主要是调用驱动提供的ndo_open接口(存储在net_device结构的netdev_ops成员上)来完成的,除此之外,在开启之前会给netdev_chain通知链上发送NETDEV_PRE_UP消息。开启成功后会给netdev_chain通知链上发送NETDEV_UP消息。
启用流程如下所示:
3、禁用网络设备
网络设备能够被用户命令或其他事件隐含地禁止。用户可以通过"ifconfig 设备名 down"来禁止,最终也是通过ioctl的SIOCSIFFLAGS命令来关闭设备。注意,禁用只是说不能再用这个设备来收发数据了,网络设备依然还是注册的,还可以启用。
禁用设备时,会通过调用dev_chang_flags()调用dev_close()来进行。
dev_close会首先检查设备是否已禁用,即检查是否设置IFF_UP标志,如果已禁用,则直接返回。
在启用设备时,会设置__LINK_STATE_START标志,表示设备可以传递数据,所以在禁用时要清除该标志位。在清除标志位后,会发送NETDEV_GOING_DOWN消息给netdev_chain通知链,通知对禁用设备感兴趣的内核组件。
接着会调用dev_deactivate()禁止出口队列规则,确保该设备不再用于传输,并停止不再需要的监控定时器。
同样,设备的禁用还是要靠驱动提供的接口来完成,这里调用的ndo_stop接口。只要设备是启用的,调用该接口就不能失败,而且内核在这里也没有检查其返回值。所以驱动在提供这个接口时一定要注意这一点。这个接口在DETACH hot-plug事件(应该是热插拔网络设备)后也允许被调用。
调用设备相关的关闭操作后,会清除IFF_UP标志位,然后发送NETDEV_DOWN消息到netdev_chain通知链上。
禁用流程如下所示:
4、注销网络设备
网络设备的注销比较复杂,注销网络设备通过调用unregister_netdev()完成。注销过程主要分为两个阶段,分别由unregister_netdevice()和netdev_run_todo()完成。
unregister_netdevice()负责关闭设备并将其从内核的表中移除,调用设备相关的注销操作,完成后会将待注销的设备放置到net_todo_list队列中。
unregister_netdevice()中通过调用rollback_registered()和net_set_todo()来完成主要的注销操作。
net_set_todo()比较简单,只是将待注销的设备放到net_todo_list队列上。
这里主要看rollback_registered()的操作。在注销之前,要先调用dev_close()来禁用设备,这样之后就不会再使用该设备来收发数据。在注销时会调用list_netdevice()将设备添加到内核的管理结构中,注销时要调用unlist_netdevice()将设备从这些管理结构中移除。在注销的时候可能设备正在接收数据,所以要调用synchronize_net()来等待设备接收完正在接收的数据包。接下来会调用dev_shutdown()来释放所有与设备相关的队列规则实例。在完成这些操作后,会发送NETDEV_UNREGISTER消息给netdev_chain通知链,通知其他使用设备的内核组件进行一些清理,以完成后续的释放操作。在完成内核结构中的清理后,真正的注销操作还是要有网络设备驱动来做,通过调用ndo_uninit接口来进行驱动程序相关的注销操作。最后会释放sysfs中相应的项并释放对网络设备的引用。
这里有一点要注意,注册网络设备时,引用计数被初始化为1,但网络设备不像其他内核对象在引用数为0时由xxx_put()释放,而是直到从内核注销时,即调用unregister_netdevice()时引用计数才减为0,然后释放设备。
netdev_run_todo()会将net_todo_list队列上的设备取下,等待设备的引用计数为0,然后释放网络设备对应的kobject对象(过程中会释放net_device结构占用的内存)。 netdev_run_todo()是通过rtnl_unlock()调用的,也就是说在每次释放rtnl_mutex互斥锁的时候都会继续处理待注销的设备。netdev_run_todo()会调用netdev_wait_allrefs()等待设备的引用计数为0,如果在其他组件仍然使用时直接销毁设备的管理结构会出现问题。在等待的过程中,netdev_wait_allrefs()会每秒发送一个NETDEV_UNREGISTER通知,每10s打印一次警告信息(通过系统日志可以看到)。在引用计数为0后,就可以真正销毁设备的管理结构了。设备相关的销毁操作通过destructor接口(net_device结构的成员)来完成,通过这个接口可以调用到free_netdev()函数。free_netdev()中会完成最后阶段的销毁过程,释放设备的接收队列及对应的device结构实例,并将设备状态设置为NETREG_RELEASED状态,表示即将释放网络设备net_device结构实例。net_device结构实例在netdev_run_todo()调用的kobject_put()中释放,调用的函数是netdev_release(),这个函数才会真正释放net_device结构的实例(如果是未注册的设备,在free_netdev()中释放),至此终于将设备从系统中彻底清除。
注销流程如下所示:
5、网络设备状态迁移通知
在网络设备注册、启用、禁用、注销过程中,总是伴随着状态的改变,每次在发生改变之前或之后,内核总是会发送相应类型的消息到netdev_chain通知链上。内核中的模块如果注册到netdev_chain通知链上,网络设备相关的事件都会通知该模块。如果用户层应用程序要获取这些事件消息,要注册到netlink的RTMGRP_LINK组播组,这样应用程序也可以接收到网络设备相关的事件通知。
在内核中注册netdev_chain通知链时,你的handler中一定不要调用或者隐含调用rtnl_lock()来获取rtnl_mutex互斥锁。因为在事件改变的时候相应的处理函数中都会在获取rtnl_mutex互斥锁的情况下进行,所以在发送消息的时候已经持有了rtnl_mutex互斥锁。如果你在自己的handler中也尝试去获取rtn_mutex互斥锁,则会造成死锁 。
- Android 怎样在linux kernel 中读写文件
前言 欢迎大家我分享和推荐好用的代码段~~ 声明 欢迎转载,但请保留文章原始出处: CSDN:http://www.csdn.net ...
- linux kernel中timer的使用
linux kernel中timer的使用 http://blog.csdn.net/njuitjf/article/details/16888821 在kernel中如果想周期性的干些什么事情,或者 ...
- Linux kernel中常见的宏整理
0x00 宏的基本知识 // object-like #define 宏名 替换列表 换行符 //function-like #define 宏名 ([标识符列表]) 替换列表 换行符 替换列表和标识 ...
- Linux Kernel中所應用的數據結構及演算法
Linux Kernel中所應用的數據結構及演算法 Basic Data Structures and Algorithms in the Linux kernel Links are to the ...
- Linux Kernel 記憶體管理機制之美<转>
转自--http://five.rdaili.com/sohu.com.php?u=Mq3EniVnae0axim7jkGhH0IhA9uho6CQso7R1aYomXWJ9UemfwUQYmKRc8 ...
- Linux Kernel中获取当前目录方法(undone)
目录 . 引言 . 基于进程内存镜像信息struct mm_struct获取struct path调用d_path()获取当前进程的"绝对路径" . 基于文件描述符(fd).tas ...
- 初探Linux内核中的内存管理
Linux内核设计与实现之内存管理的读书笔记 初探Linux内核管理 内核本身不像用户空间那样奢侈的使用内存; 内核不支持简单快捷的内存分配机制, 用户空间支持? 这种简单快捷的内存分配机制是什么呢? ...
- 经典数据结构与算法在经典软件(linux kernel)中的应用
参考文章:Core Alorgithms deployed linux中的priority search tree数据结构研究 虚拟内存: 1.红黑树,管理与进程关联的vm_area_struct实例 ...
- Linux Kernel Development有关内存管理
1 Pages Page的概念来源为处理器Processor的部件MMU(Memory Management Unit),MMU通过设置好的页表(通过设置CR3寄存器,指向页目录所在的物理内存)对内存 ...
随机推荐
- 高性能双端js模板
高性能双端js模板(新增filter)---simplite simplite是一款js实现的模板引擎,它能够完成浏览器端js模版和node服务器端js模板的数据渲染. 渲染性能十分突出. 支持浏览器 ...
- Apache无法启动解决 the requested operation has failed
Apache不能启动解决办法 原因一:80端口占用例如IIS,另外就是迅雷. 原因二:软件冲突装了某些软件会使apache无法启动如Dr.com 你打开网络连接->TcpIp属性->高级- ...
- PHP jpgraph的一点小提示(附安装方法)
PHP中的GD库本身是一套很强大的绘图库了,绘制的图像基本可以满足日常要求,但强大规强大,还是不够方便哈,因为强大方便的基于PHP的GD库的jpgraph也就诞生啦! PHP默认是不启用GD库的,因为 ...
- 浅析 JavaScript 中的 函数 currying 柯里化
原文:浅析 JavaScript 中的 函数 currying 柯里化 何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字 ...
- SQL点滴26—常见T-SQL面试解析
原文:SQL点滴26-常见T-SQL面试解析 它山之石可以攻玉,这一篇是读别人的博客后写下的,不是原原本本的转载,加入了自己的分析过程和演练.sql语句可以解决很多的复杂业务,避免过多的项目代码,下面 ...
- Java 之关键字 null 使用总结
1.null的使用 Java中,null是一个关键字,用来标识一个不确定的对象.因此可以将null赋给引用类型变量,但不可以将null赋给基本类型变量.比如我们在定义一个变量的时候我们通过会这样做:X ...
- Spring IOC之 使用JSR 330标准注解
从Spring 3.0开始,Spring提供了对 JSR 330标准注解的支持.这些注解可以喝Spring注解一样被扫描到.你只需要将相关的Jar包加入到你的classpath中即可. 注意:如果你使 ...
- [翻译]初识SQL Server 2005 Reporting Services Part 4
原文:[翻译]初识SQL Server 2005 Reporting Services Part 4 这一篇是关于SQL Server 2005 Reporting Services四篇文章中最后一篇 ...
- windows系统SVN和apache的下载和安装
原文:windows系统SVN和apache的下载和安装 版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明http://ejb3.blogbus.com/logs/107443052. ...
- 自己写RTPserver——大约RTP协议
自己写RTPserver--大约RTP协议 本文将带领你一步一步地实现一个简单的手RTP变速器server,旨在了解RTP流媒体传输协议以及有关多媒体编解码器的一些知识. RTP协议的必备知识 要动手 ...