在Linux下对网络驱动程序分析

2022-09-11

1 Linux网络驱动程序

在Linux中, 为了简化对设备的管理, 将所有的外围设备都归结为三类:字符设备 (如键盘, 鼠标等) 、块设备 (如硬盘、软驱等) 和网络设备 (如网卡、串口等) 。为了将网络环境中的物理网络设备的多样性屏蔽, Linux对所有的网络物理设备抽象并定义了一个统一的概念:接口 (Interface) 。对于所有的网络硬件都是通过接口进行访问的, 接口实际上提供了一个对于所有类型的网络硬件的一致化的操作集合, 用于处理对数据的发送和接收。对于每一个已经驱动了的网络设备, 都用一个struct device的数据结构表示。在内核启动或者驱动模块插入时, 通过网络驱动程序, 向系统注册检测到的网络设备。在进行网络数据传输的时候, 网络驱动程序应通过标准的接口将数据发送到相应的网络层, 或者向网络发送数据包。

网络接口并不存在于Linux的文件系统中, 而是定义在内核中的struct device的一个数据结构中。每一个device的数据结构都是在驱动的时候创建的。而不像字符设备或者块设备那样, 即使不存在物理设备也存在有这个设备文件。这是在linux2.2内核版式本之后做的改动, 在这个版本之前, 一旦网络驱动程序被激活, 在/dev目录下就会出现eth0的文件, 对这个设备文件的读写就完成了网络传输, 这种做法现在还是用在字符设备和块设备上。现在的版本是数据包的发送和接收都通过直接对接口进行方问来完成。

2 网络驱动程序中用到的重要数据结构

Struct device这个数据结构是在系统中每一个设备的代表。每一个被系统检测到的网络设备都应该使用struct device类型的节点存在于dev_base中, dev_base是内核中存在的外部变量。如果采取的是内核检测网卡设备的方式, 那么在整个内核可能支持的网络设备的struct device节点。在网络驱动程序检测完之后, 将不存在的网络设备对应的节点删除, 剩下的就是检测到并且得到正确驱动的网络设备。

在这个数据结构里面, 主要定义的成员变量init函数指针, 这个函数指针初始化为设备驱动程序中提供的用来初始化device结构的过程, 这个过程实际上就是用来检测和驱动网络设备的。在检测进行之前, 每一个节点的init函数都指向对应的初始化函数;检测的时候对每一个节点都分别调用init函数指针, 如果成功返回的话, 将该节点保留。

同时, 在这个结构里面定义了对硬件设备的打开和关闭函数指针 (open, close) 硬件头的建立函数指针 (hard_header) ;硬件上数据的传输过程 (hard_sart_xmit) 。当网络设备打开的时候, 就可以通过这个网络设备开始传输数据了, 传输出来的数据存放在struct sk_buff结构里面。Hard_start_xmit函数指针是和某一种具体的硬件相关的, 通过d e v_q u e u e_x m i t这个外部函数调用hard_start_xmit函数指针完成网络数据的发送过程。

3 重要的驱动过程

驱动网络设备驱动程序方法有两种:通过模块驱动和通过内核启动时自动检测的方法。通过模块驱动的方法是Linux中使用模块设计的一种方案。我们知道, linux的内核是将所有的支持编译在一起的, 并不是微内核技术。如果对linux内核增加一项功能, 就把它的实现直接放在内核的代码中。不过为了让linux的内核体积不至于过于庞大, 采用了编译成模块的方式。在需要用到这个模块的时候, 用shell命令的insmod将该模块插入到内核运行空间;如果不需要了, 可以用rmmod命令将该模块卸载。I n s m o d触发的是cleanup_module () 函数。在init_module () 函数里面, 会调用到这种网络设备的init函数指针, 如果检测到了这种网络设备, 并且初始化成功, 那么就将这个网络设备对应的device结构插入到dev_base链表里面。

使用内核启动检测的方法有所不同。在系统启动的时候, 内核把所有编译在内核内部支持的网卡设备都初始化在一个s t r u c t device类型的dev_base链表里面, 然后对于每个节点都调用自己的init函数指针。如果该函数返回成功, 那么该节点对应的设备保留;否则, 该节点对应的设备不存在, 将该节点删除。这样, 在系统初始化的最后, 剩下来在dev_base里面的所有节点就全是系统检测到的网络设备了。

以ne.c为例, init_module () 函数首选初始化d ev设备的in it函数指针, 然后调用register_netdev () 函数在系统中登记该设备。如果登记成功, 那么模块插入成功, 否则就返回出错信息。在register_netdev () 里面, 首先检查该网络设备名是否已经确定, 如果没有, 就赋给一个缺省的名称。然后中, 调用网络设备驱动程序中的init_function, 也就是dev->init函数指针来检测网卡设备是否存在, 并且做dev的初始化工作。如果初始化成功, 将dev插入到dev_base链表的尾部。整个调用流程如图1所示。

内核启动的驱动方法, 内核启动的驱动方法和模块驱动的方法不同, 前者要对所有内核支持的网络调和设备进行检测和初始化, 而后者只需要检测和初始化被装载的网络设备。为了在启动的时候对所有可能存在的网络设备都检测一遍, 系统会在启动前将所有支持的网络设备对应的device结构都挂在dev_base链表上, 正如前文所述。然后使用net_dev_init () 函数依次对dev_base里面每一个节点都运行init函数指针, 如果返回成功, 那么该节点对应的设备存在;否则就将该节点删除。最后在链表中剩下的所有网络设备都是存在的, 并且已经完成了初始化。

当系统转入内核后, start_kernel会创建一个init进程, 这个init进程会通过系统调用sys_setup进行所有尚未初始化的设备的初始化 (内存, PCI等已经在些之前初始化过了) 。在sys_setup中调用device_setup, 进而调用net_dev_init检测和初始化所有的网络设备。Net_dev_init内部调用所有dev->init函数指针, 进行具体的物理设备的初始化工作。

摘要:Linux是一套免费使用和自由传播的类似Unix操作系统, 它主要用于基于Intel x86系列CPU的计算机上。这个系统是由世界各地的成千上万的程序员设计和实现的。其目的是建立不受任何商品化软件的版权制约的、全世界都能自由使用的Unix兼容产品。

关键词:网络,驱动,Linux

上一篇:胃癌患者全胃切除术后两种消化道重建方式的比较下一篇:学分制与网页设计技能