⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 (ldd) ch14-网络驱动程序(上)(转载).txt

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 3 页
字号:
(LDD) Ch14-网络驱动程序(上)(转载)
       
      第十四章    网络驱动程序
       
       
       
      我们已经讨论了字符设备和块设备驱动程序,接着要讨论的是迷人的网络世界。网络接
      口是Linux设备中的第三标准类,这一章就是讲述它们是如何与核心的其余部分交互的。
       
      网络接口并不象字符和块设备那样存在于文件系统。相反,它在核心层处理包的发送和
      接收,并不与进程中的某个打开的文件绑定在一起。
       
      网络接口在文件系统中的角色就象被安装的块设备。一个块设备在blk_dev数组和其它核
      心结构中注册它的特征,接着按照要求通过它的request_fn函数“发送”和“接收”块
      。类似地,一个网络接口必须在特定的数据结构中注册自己,从而在与外部世界交换包
      时可以被调用。

      时可以被调用。
       
      安装的磁盘与包发送接口有几个重要的不同。首先,磁盘以一个结点的形式存在于/dev
      目录,而网络接口并不在文件系统中出现。不过两者之间最大的不同在于:磁盘是被请
      求向核心发送一个缓冲区,而网络接口则是请求向核心推送进来的包。
       
      Linux的网络子系统被设计成完全协议无关的。这对网络协议(IP vs. IPX 或其它协议
      )和硬件协议(以太网vs.令牌环等)都是如此。网络驱动程序和核心之间的交互一次处
      理一个网络包;这允许协议可以干净地对驱动程序隐`藏起来,而物理传输则可以对协议
      隐藏起来。
       
      本章描述网络接口如何与核心的其它部分紧密合作,并给出一个基于内存的模块化的网
      络接口,称之为(你可能已经猜到了)snull。为简化讨论,这个接口使用以太网硬件协
      议并传送IP包。通过snull获得的知识可以很好地应用于IP以外的协议,从以太网移到其
      它硬件协议只要求你对使用的物理协议有所了解。
       
      snull的另一个限制是它不能在Linux1.2中编译。再说一遍,这样做只是为了保持代码简
      单,并避免在snull中加入一些另人厌倦的条件。不过,本章将会提到与网络驱动程序相
      关的可移植性问题。
       
      本章并不介绍IP的编号原则,网络协议,以及其它普通的网络概念。这个主题与驱动程
      序作者无关,而且以不到几百页的篇幅想对网络技术有一个令人满意的概述是不可能的
      。感兴趣的读者可以参考一些讲述网络问题的书。

      。感兴趣的读者可以参考一些讲述网络问题的书。
       
      在讨论网络设备之前,我想提醒你网络事务中的原子数据项被称做一个八元组(octet)
      ,由八个数据位组成。在本章中我都这样使用。网络文档从不使用术语“字节”。
       
       
       
      Snull如何设计
       
       
       
      本节讨论与snull网络接口有关的一些设计概念。尽管这些信息可能显得用处并不大,但
      如果不理解它则可能在研究示例代码时遇到一些困难。
       
      第一位的设计决定(也是最重要的)是示例接口不应绑定于任何实际硬件。实际接口不
      依赖于所传送的协议,snull的这个限制并不影响本章给出的示例代码,因为它是协议无
      关的。IP限制的唯一影响是地址分配----我们将位示例接口分配IP地址。
       
      分配IP号码
       
      snull模块生成两个接口。这样的接口与简单的环回(loopback)并不一样,这里你从一
      个接口传送的包总是环回到另一个接口,而不是它自己。它看起来好象是你有两个外部
      链接,但实际上你的计算机只是应答自己。

      链接,但实际上你的计算机只是应答自己。
       
      不幸的是,这个效果并不能仅仅通过IP号码分配来达到,因为核心不会从接口A发送一个
      指向它自己接口B的包。相反,这时它会使用环回通道,从而根本不通过snull。为了能
      建立一个通过snull接口的通信,源和目的地址必须在数据传送的时候修改一下。换句话
      说,从一个接口发出的包应能被另一个接口接收,但外出包的接收者不能被认为是本机
      。这也适用于收到包的源地址。
       
      为了收到这种“隐藏环回”的效果,snull接口反转一下源和目的地址的第三个八元组的
      最低位。其效果就是发向网络A(连在接口sn0上)的包在sn1接口上好象是属于网络B。
       
      为了避免和太多的号码打交道,我们给用到的IP号码分配一些符号名:
       
      l         snullnet0是一个连接在sn0接口上的一个C类网络。类似地,snullnet1是连
      在sn1上的网络。这两个网络的地址仅在第三个八元组的最低位不同。
       
      l         local0是分配给接口sn0的IP地址;它属于snullnet0。与sn1相关联的地址是
      local1。local0和local1的第三和第四个八元组必须都不相同。
       
      l         remote0是snullnet0中的一个主机,它的第四个八元组与local1相同。所有
      发向remote0的包在其C类地址被接口代码修改后将到达local1。主机remote1属于snulln
      et1,并且它的第四个八元组与local0相同。
       

       
      snull接口的操作见图14-1,图中与接口相关联的主机名印在接口名旁边。
       
      下面是几个可能的网络号码。一旦你把这几行写到/etc/networks,你就可以用名字来称
      呼这些网络。这些值是从保留私用的号码范围中选取的。
       
                            snullnet0      192.168.0.0
       
                            snullnet1      192.168.1.0
       
      下面是写入/etc/hosts的可能的主机号码:
       
      192.168.0.88    local0
       
      192.168.0.99            remote0
       
      192.168.1.99            local1
       
      192.168.1.88    remote1
       
       
       
      (图14-1 Page304)

      (图14-1 Page304)
       
      不过如果你的计算机已经连到了一个网络上,那么一定要注意。你选择的号码有可能是
      实际的Internet或intranet的号码,把它们分配给你的接口可能会妨碍与真正主机的通
      信。而且,尽管我给出的这些号码不是实际的Internet号,但也有可能被你的私用网所
      适用,如果它处于防火墙之后的话。
       
      不论你选择什么号码,你可以通过发出下面的命令来正确地设置接口:
       
      (代码304 #1)
       
      到此为止,接口的“远”端已经可以到达了。下面的屏幕快照显示了我的主机是如何通
      过snull到达remote0和remote1的。
       
      (代码304 #2)
       
      注意你不可能达到属于这两个网络的其它主机,因为在包的地址被改变并被接收到后,
      你的计算机会把它丢弃。
       
      包的物理传送
       
      至于数据传送,snull属于以太网一类。示例代码使用了核心的以太网支持。这使我们不
      必去实现网络设备一些令人厌倦的细节。

      必去实现网络设备一些令人厌倦的细节。
       
      我选择以太网是因为现存网络的主体----至少与工作站相连的这一部分-----都是基于以
      太网技术,不论是10base2,10baseT,还是100baseT。另外,核心还提供了对以太网设
      备的一般化的支持,因此没有理由拒绝使用。以太网设备的优势如此明显,连plip接口
      (一类使用打印机端口的接口)都自称是以太网设备。
       
      在snull中使用以太网设置的最后一个优势是你可以在接口上运行tcpdump。不过,如果
      你想这样做,你需要把接口称做ethx,而不是snx。snull模块已经准备好将自己声明为e
      thx。如果在insmode命令行中指定eth=1,你就选择了这种行为。如果你忘了为snull请
      求eth命名,tcpdump会拒绝倾倒这个接口,而是返回一个“未知的物理层类型”错。
       
      snull接口的另一个设计决定是只处理IP协议,本章的讨论也仅限于IP。不过要注意,接
      口驱动程序本身并不依赖于它所处理的底层协议;网络驱动程序根本不查看它所传送的
      包。关于多协议传送将在后面的“非以太网包头”中详细介绍。
       
      不过说实话,snull还是查看包内容的,甚至还要修改它们,因为这是为保证代码工作要
      求的。代码修改每个IP包头的源,目的,以及校验和,但不检查它是否真地携带了IP信
      息。这种快而脏的数据修改会破坏非IP包。如果你想让snull处理其它协议,你必须修改
      这个模块的源码。不过,这种需求不太可能增长,因为每个拥有Linux盒的人都运行IP,
      而其它协议则是可选的。
       
       

       
       
      与核心相连
       
      我们将通过拆解snull源码来看看网络驱动程序的结构。保证有几个驱动程序的源码在手
      边会很有助于你理解我们的讨论。我个人推荐loopback.c,plip.c,以及3c509.c,以逐
      渐增加的复杂性排序。有skeleton.c在手边也很有帮助,尽管这个示例驱动程序并不能
      真正运行。所有这些文件都居于核心源码树的drivers/net下。
       
      模块加载
       
      当一个模块被加载到运行的核心时,它要请求一些资源,并提供一些方便的功能;这已
      不再新鲜。另外请求资源的方式也不新鲜。驱动程序要探测它的设备及硬件位置(I/O端
      口和IRQ线)----但并不注册它们----就象在第九章中断处理中“安装一个中断处理程序
      ”一节中介绍的一样。网络驱动程序通过它的函数init_module进行注册的方法与字符或
      块设备驱动程序不一样。与请求一个主设备号不同,驱动程序为每个新检测到的接口在
      一个网络设备的全局列表中插入一个数据结构。
       
      每个接口用一个device结构描述。sn0和sn1这两个snull接口的结构如下所示:
       
      (代码 306)
       
      注意第一个域,既名字域指向一个静态缓冲区,它在加载时将被填充。通过这个方法,

      注意第一个域,既名字域指向一个静态缓冲区,它在加载时将被填充。通过这个方法,
      可以晚点儿选择接口名,下面会给出解释。如果你想在这个结构中使用一个显式缓冲区
      ,如“01234567”,我要警告你那样可能导致代码不能可靠地工作。这是因为编译器会
      将两个重复的串折叠;因此你得到的会是一个缓冲区和两个指向它的缓冲区。而且,编
      译器有可能将常量串存在只读内存中,这显然不是你想要的。
       
      在下节之前我不想完整描述结构device,因为它是一个庞大的结构,太早地肢解它没有
      什么好处。我想在驱动程序中使用这个结构,并在每个域被使用时再解释它。
       
      前面的代码显式地使用了device结构中的name和init域。name是第一个域,含有接口名
      (识别接口的字符串)。驱动程序可以将接口名硬写在程序中,也允许动态赋值,其工
      作方式如下:如果名字的第一个字符是个空或者空格,那么设备注册项就使用第一个可
      用的ethn名。这样第一个以太网接口就被称做eth0,其它的按序号类推。snull接口则被
      缺省地称为sn0和sn1。不过,如果在加载时指定eth=1,那么init_module将使用动态赋
      值。缺省名由init_module给出:
       
      (代码307 #1)
       
      init域是个函数指针。任何时候当你注册一个设备时,核心要求驱动程序初始化自己。
      初始化就是指探测物理接口,用正确的数值填充device结构,下一节将给予描述。如果
      初始化失败,这个结构就不能被链入网络设备的全局列表。这种特别的设置的方法在系
      统引导时特别有效;每个驱动程序都试图注册它自己的设备,但只有确实存在的设备才
      被链入列表。这与字符和块设备驱动程序不同,它们被组织成一个两级树,由主设备号

      被链入列表。这与字符和块设备驱动程序不同,它们被组织成一个两级树,由主设备号
      和次设备号索引。
       
      由于真正的初始化在别的地方完成,init_module要做的工作非常少,只需一句如下:
       
      (代码307 #2)
       
      初始化每个设备
       
      设备的探测在接口的init函数里完成,它通常被称做“探测”函数。init收到的唯一的
      参数是一个指向正被初始化的设备的指针,其返回值是0或者一个负的错误代码----通常
      是-ENODEV。
       
      对snull接口并没有进行实际的探测,因为它未绑定到任何硬件上。当你为一个实际的接
      口写实际的驱动程序时,探测字符设备的原则仍然适用:在使用I/O端口之前先检测它们
      ,在检测期间不要向它写。另外,你还要避免在此时注册I/O端口和中断线。真正的注册
      应该推迟到设备打开时;这个非常重要,特别是当中断线被其它设备共享时。每次当别
      的设备触发中断线时,你的接口当然不希望被调用,而应简单地回答:不,它不是我的
       
      实际上,在加载时进行设备探测对ISA设备并不鼓励,因为这有可能很危险----ISA体系
      结构在容错方面名声不佳。由于这个原因,大多数网络驱动程序在以模块的方式加载时
      拒绝为其硬件探测,核心也只探测第一个网络接口,在一个网络设备检测出来后不再进

      拒绝为其硬件探测,核心也只探测第一个网络接口,在一个网络设备检测出来后不再进
      行任何硬件测试。通常dev->base_addr----当前设备的I/O基地址----决定了要做什么:

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -