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消息。
启用流程如下所示: