网络课程设计之网卡linux驱动程序_第1页
网络课程设计之网卡linux驱动程序_第2页
网络课程设计之网卡linux驱动程序_第3页
网络课程设计之网卡linux驱动程序_第4页
网络课程设计之网卡linux驱动程序_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、网络课程设计-linux 网卡驱动程序 pcnet32目录1. 开发环境与开发语言 . 22. Linux 操作系统概述 . 22.1.Linux 内核简介 . 22.2.Linux 设备驱动程序概述 . 32.3.编写网络驱动程序的一些基本概念 . 43. Linux 网络栈 . 43.1.网络栈的 Internet 模型 . 53.2.Linux 高级网络栈架构 . 63.2.1. 系统调用接口 . 63.2.2. 协议无关接口 . 63.2.3. 网络协议 . 73.2.4. 设备驱动程序 . 74. Pcnet32 网卡驱动分析 . 84.1.PCNET32 驱动程序的 pcnet32

2、_private 结构 . 84.2.网卡设备的初始化 . 114.3.网卡数据发送的基本流程 . 124.4.数据报在链路层的发送 . 124.5.网卡数据接收过程简述 . 155. Linux 中驱动程序应用 . 155.1.Pcnet32 驱动程序编译与生成 . 155.1.1. Makefile 文件代码 . 155.1.2. make 编译生成驱动程序 . 155.2.pcnet32 驱动安装与初始化 . 165.3.Pcnet32 驱动程序发送数据 . 176. 参考书目 . 177. 小组成员及分工 . 188. 卷尾语 . 181.开发环境与开发语言开发工具:GCC、CentO

3、s linux 操作系统(内核 2.6.18)、vi、Sourcelnsight3.0调试:在linux系统中通过 make命令(需要编写相应的makefile文件)进行编译、连接、生成;开发语言:C2.Linux操作系统概述Linux操作系统是UNIX操作系统的一种克隆版本,最早是由芬兰大学的学生LinusTorvalds于1991年开始开发的,并于 1991年的10月5日第一次正式向外公布,以后借助 于互联网,经过一群遍布于全世界的In ternet上的自愿参加的程序员的不懈努力,加上计算机公司的支持,Linux的影响和应用日益广泛,发展成为目前世界上用户最多的一种类UNIX操作系统。Li

4、nux目前是计算机技术的一大热点之一,最近几年在我国得到迅猛发展,被广泛应 用在嵌入式系统、安全产品、服务器和桌面应用等领域。2.1.Linux内核简介在最开始的时候,Linux系统并没有现在所看到的Linux系统的体积这么庞大,各种免费开放的驱动代码也还没有来得及加入到系统中,所以,之初的Linux实际意义上就是Linux 内核。首先来分析一下Linux操作系统的体系结构,可以从两个层次上来考虑操作系统,如下图1所示:GNU/Li nux操作系统的基本体系结构图最上面是用户(或应用程序)空间。这是用户应用程序执行的地方。用户空间之下是 内核空间,Linux内核正是位于这里。GNU C Lib

5、rary( glibc )也在这里,它提供了连接内核的系统调用接口,还提供了在用户空间应用程序和内核之间进行转换的机制。这点非常重要,因为内核和用户空间的应用GNtJ/ VLrrnjKUserKerndtSpace而内fl *i i | 一如 VFStty Kt ftVS JLinux系统模块及功能图Linux是个人计算机和工作站上的Unix类操作系统,但是,它绝不仅仅是简化的Unix系统。相反,Linux是具有创新意义的Linux内核程序使用的是不同的保护地址空间,每个用户空间的进程都使用自己的虚拟地址空间,核则占用单独的地址空间。实际上,体系结构可能并不像图1所示的一样清晰。例如,处理系统

6、调用(从用户空间切换到内核空间)的机制可能在各个体系结构上都不相同。Linux系统支持多个进程的并发运行,每个进程都请求系统资源,比如运算、内存、网络连接或其他一些资源等。内核负责处理所有这些请求,根据内核完成任务的不同,我们可以将内核划分成如下图 2的功能模块:JJf f V;珂 I Q1IE drlv erarn 典模火力犬空现的力他Unix类操作系统。它不仅继承了Unix的特征,而且在许多方面超过了 Unix。作为Unix类操作系统,Linux内核具有下列基本特征:的组织形式为整体式结构、Linux的进程调度方式简单而有效、Linux支持内核线程(或称守护进程卜Linux支持多种平台的虚

7、拟内存管理、虚拟文件系统(VFS)、Linux的模块机制使得内核保持独立而又易于扩充,网络部分采用了面向对象的设计思想,使得Linux内核支持多种协议、多种网卡驱动程序变得更加的容易,为驱动的开发提供了便捷性,减少了工作量, 提高了工作效率。2.2.Linux设备驱动程序概述Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序的增加。Linux系统的设备分为字符设备(char device),块设备(block device)和网络设备(net work device)三种。其中网络接口:任何网络事件都是通过一个网络接口形成 的,一个网络接口就是

8、一个能够和其他主机交换数据的设备。通常,接口都是硬件设备,但也可能是纯软件设备,比如回环(loopback)接口。网络接口由内核中的网络子系统驱动,负责数据包的接收和发送,但它不需要了解每项事务是如何映射到实际传送的数据包的。许多网络连接是面向流的, 但网络设备却围绕数据包的传输和接收而设计。网络驱动程序不需要/dev/tty1 )比ethO),但这个完全不同于内核read、知道各个连接的相关信息,它只要处理数据包即可。由于不是面向流的设备,因此将网络接口映射到文件系统中的节点(比如 较困难。 Unix 访问网络接口的方法仍然是给他们分配一个唯一的名字(比如 名字在文件系统中不存在对应的节点。

9、 内核和网络设备驱动程序间的通信, 和字符设备以及块驱动程序之间的通信,内核调用一套和数据传输相关的函数而不是write 等。2.3.编写网络驱动程序的一些基本概念无论是什么操作系统的驱动程序,都有一些通用的概念。操作系统提供给驱动程序的 支持也大致相同。下面简单介绍一下网络设备驱动程序的一些基本的也是最重要的概念。发送和接收:这是一个网络设备最基本的功能。一块网卡所做的工作无非就是数据的 发送和接收, 所以在驱动程序中必须要告诉系统数据的发送函数在哪里, 系统在有数据要发 送时就会调用发送程序。 驱动程序由于是直接操纵硬件的, 所以网络硬件有数据收到时, 最 先能得到这个数据的就是驱动程序,

10、 它负责把这些原始数据进行必要的处理, 然后送给系统。 这里, 操作系统必须要提供两个机制, 一个是找到驱动程序的发送函数, 一个是驱动程序把 收到的数据送给系统。中断:中断在现代计算机结构中占有重要的地位。操作系统必须提供驱动程序响应中 断的能力。 一般是把一个中断处理程序注册到系统中去,操作系统在硬件中断发生后调用 驱动程序的处理程序。 Linux 支持中断的共享,即多个设备共享一个中断。时钟 :在实现驱动程序时,很多地方会用到时钟。如某些协议里的超时处理,没有中 断机制的硬件的轮询等, 操作系统应为驱动程序提供定时机制, 一般是在预定的时间过了以 后,系统自动回调注册的时钟函数。 在网络

11、驱动程序中, 如果硬件没有中断功能, 定时器可 以提供轮询 (poll) 方式对硬件进行存取,或者是实现某些协议时需要的超时重传等。3.Linux 网络栈Linux操作系统的最大特性之一就是它的网络栈。它最初源于 BSD (伯利克软件套件)的网络栈, 具有一套非常干净的接口,组织得非常好。其接口范围从协议无关层(例如通用 socket 层接口或设备层)到各种网络协议的具体层。3.1. 网络栈的In ternet模型如下:Exampl置。再上一层称为传输层,负责端到端的通信(例如,在一台主机内部)。尽管网络层负责管理主机之间的通信,但是传输层需要负责管理主机内部各端之间的通信。最后一层是应用层,

12、它通常是一个语义层,能够理解要传输的数据。例如,超文本传输协议(HTTP )就负责传输服务器和客户机之间对Web内容的请求与响应。实际来说,网络栈的各个层次有一些更为人所熟知的名字。在链路层上,可以找到以 太网,这是最常用的一种高速介质。更早的链路层协议包括一些串口协议,例如SLIP( SerialLine In ternet Protocol )、CSLIP ( Compressed SLIP)和 PPP( Poi nt-to-Poi nt Protocol )。最常见 的网络层协议是IP (Internet Protocol),但是网络层中还存在一些满足其他需求的协议,例 如 ICMP (

13、Internet Control Message Protocol )和 ARP ( Address Resolution Protocol)。在传 输层上是 TCP ( Transmission Control Protocol )和 UDP ( User Datagram Protocol)。最后, 应用层中包含很多大家都非常熟悉的协议,包括标准的Web协议HTTP和电子邮件协议SMTP。Ke-nslAppliestior layerfSystem call interfaceProtocol agnostic interfa亡总Network protocolsDevice agnost

14、ic interfaceDevice drive帼3.2.Linux高级网络栈架构如下:顶部是系统调用接口。它简单地为用户空间的应用程序提供了一种访问内核网络子系 统的方法。位于其下面的是一个协议无关层,它提供了一种通用方法来使用底层传输层协议。然后是实际协议,在Linux中包括内嵌的协议TCP、UDP,当然还有IP。然后是另外一个协议无关层,提供了与各个设备驱动程序通信的通用接口,最下面是设备驱动程序本身。3.2.1. 系统调用接口系统调用接口可以从两个角度进行描述。用户发起网络调用时,通过系统调用接口进入内核的过程应该是多路的。最后调用./net/socket.c中的sys_socketc

15、all结束该过程,然后进一步将调用分路发送到指定目标。系统调用接口的另一种描述是使用普通文件操作作为网络I/O。例如,典型的读写操作可以在网络socket上执行(socket使用一个文件描述符表示,与一个普通文件一样)。因此,尽管有很多操作是网络专用的(使用 socket调用创建 一个socket,使用connect调用连接一个收信方,等等),但是也有一些标准的文件操作可 以应用于网络对象,就像操作普通文件一样。最后,系统调用接口提供了在用户空间应用程 序和内核之间转移控制的方法。3.2.2. 协议无关接口socket层是一个协议无关接口,它提供了一组通用函数来支持各种不同协议。socket层

16、不但可以支持典型的TCP和UDP协议,而且还可以支持IP、裸以太网和其他传输协议,例如 SCTP( Stream Control Transmission Protocol )。通过网络栈进行的通信都需要对socket进行操作。Linux中的socket结构是 struct sock,这个结构是在 linux/include/net/sock.h 中 定义的。这个巨大的结构中包含了特定socket所需要的所有状态信息,其中包括socket所使用的特定协议和在socket上可以执行的一些操作。网络子系统可以通过一个定义了自己功能的特殊结构来了解可用协议。每个协议都维护了一个名为 proto的结构

17、(可以在 linux/include/net/sock.h 中找到)。这个结构定义了可 以在从socket层到传输层中执行特定的socket操作(例如,如何创建一个socket,如何User spacePhysical dfrvice hardware使用 socket 建立一个连接,如何关闭一个 socket 等等)。3.2.3. 网络协议网络协议这一节对一些可用的特定网络协议作出了定义(例如TCP 、UDP 等)。它们都是在 linux/net/ipv4/af_inet.c 文件中一个名为 inet_init 的函数中进行初始化的 (因为 TCP 和 UDP 都是 inet 簇协议的一部分

18、) 。 inet_init 函数使用 proto_register 函数来注册每个内 嵌协议。这个函数是在 linux/net/core/sock.c 中定义的,除了可以将这个协议添加到活动协 议列表中之外,如果需要,该函数还可以选择分配一到多个 slab 缓存。通过 linux/net/ipv4/ 目录中 udp.c 和 raw.c 文件中的 proto 接口,您可以了解各个协 议是如何标识自己的。这些协议接口每个都按照类型和协议映射到inetsw_array ,该数组将内嵌协议与操作映射到一起。 inetsw_array 结构及其关系如图 3 所示。最初,会调用 inet_init 中的

19、inet_register_protosw 将这个数组中的每个协议都初始化为inetsw 。函数 inet_init 也会对各个 inet 模块进行初始化, 例如 ARP、ICMP 和 IP 模块, 以及 TCP 和 UDP 模块。 设备无关接口协议层下面是另外一个无关接口层,它将协议与具有很多各种不同功能的硬件设备连 接在一起。 这一层提供了一组通用函数供底层网络设备驱动程序使用, 让它们可以对高层协 议栈进行操作。首先,设备驱动程序可能会通过调用 register_netdevice 或 unregister_netdevice 在内核 中进行注册或注销。调用者首先填写 net_devic

20、e 结构,然后传递这个结构进行注册。内核 调用它的 init 函数(如果定义了这种函数) ,然后执行一组健全性检查, 并创建一个 sysfs 条 目,然后将新设备添加到设备列表中(内核中的活动设备链表) 。在 linux/include/linux/netdevice.h 中可以找到这个 net_device 结构。这些函数都是在 linux/net/core/dev.c 中实现的。要从协议层向设备中发送 sk_buff ,就需要使用 dev_queue_xmit 函数。这个函数可以 对 sk_buff 进行排队,从而由底层设备驱动程序进行最终传输(使用 sk_buff 中引用的 net_de

21、vice 或 sk_buff-dev 所定义的网络设备) 。 dev 结构中包含了一个名为 hard_start_xmit 的方法,其中保存有发起 sk_buff 传输所使用的驱动程序函数。报文的接收通常是使用 netif_rx 执行的。当底层设备驱动程序接收一个报文(包含在 所分配的 sk_buff 中)时,就会通过调用 netif_rx 将 sk_buff 上传至网络层。然后,这个 函数通过 netif_rx_schedule 将 sk_buff 在上层协议队列中进行排队,供以后进行处理。可 以在 linux/net/core/dev.c 中找到 dev_queue_xmit 和 neti

22、f_rx 函数。最近,内核中引入了一种新的应用程序编程接口( NAPI ),该接口允许驱动程序与设 备无关层(dev)进行交互。有些驱动程序使用的是NAPI,但是大多数驱动程序仍然在使用老式的帧接收接口 (比例大约是 6比1)o NAPI在高负载的情况下可以产生更好的性能, 它避免了为每个传入的帧都产生中断。3.2.4. 设备驱动程序网络栈底部是负责管理物理网络设备的设备驱动程序。例如,包串口使用的SLIP 驱动程序以及以太网设备使用的以太网驱动程序都是这一层的设备。在进行初始化时,设备驱动程序会分配一个 net_device 结构,然后使用必须的程序对unsigned intstruct n

23、et_device struct mii_if_info struct timer_list struct timer_listdxsuflo:1,mii:1;*next;mii_if; watchdog_timer; blink_timer;其进行初始化。这些程序中有一个是dev-hard_start_xmit ,它定义了上层应该如何对sk_buff 排队进行传输。这个程序的参数为sk_buff 。这个函数的操作取决于底层硬件,但是通常 sk_buff 所描述的报文都会被移动到硬件环或队列中。 就像是设备无关层中所描述的 一样,对于 NAPI 兼容的网络驱动程序来说, 帧的接收使用了 net

24、if_rx 和 netif_receive_skb 接口。 NAPI 驱动程序会对底层硬件的能力进行一些限制。设备驱动程序在 dev 结构中配置好自己的接口之后,调用 register_netdevice 便可以 使用该配置。在 linux/drivers/net 中可以找出网络设备专用的驱动程序。4.Pcnet32 网卡驱动分析4.1.PCNET32 驱动程序的 pcnet32_private 结构 一个网络设备,在其驱动程序中用一个net_device 结构描述,该结构有一个重要的成员 priv ,它是一个指针,指向驱动程序自己定义的私有数据。不同的网络设备驱动程序,其 私有数据各不相同。

25、下面是 PCNET32 驱动程序的私有数据:struct pcnet32_private struct pcnet32_rx_headrx_ringRX_RING_SIZE;struct pcnet32_tx_headtx_ringTX_RING_SIZE;struct pcnet32_init_block init_block;dma_addr_t dma_addr;struct pci_dev *pci_dev;const char *name;struct sk_buff struct sk_buff dma_addr_t dma_addr_t*tx_skbuffTX_RING_SIZE

26、;*rx_skbuffRX_RING_SIZE; tx_dma_addrTX_RING_SIZE; rx_dma_addrRX_RING_SIZE;struct pcnet32_access a;spinlock_t lock; unsigned intcur_rx, cur_tx;unsigned intdirty_rx, dirty_tx;struct net_device_stats stats; chartx_full;intoptions; 下面逐个分析该结构的重要成员。 tx_skbuff 和 rx_skbuff 。这是两个 sk_buff 的数组,其长度分别为 TX_RING_S

27、IZE(16) 和RX_RING_SIZE (32)。sk_buff是一个套接字缓冲区,在Linux内核中处于网络子系统的核心 地位,由它保存发送和接收的数据,关于该结构的详细描述参阅 Linux 设备驱动程序第 三版 512 页。其中 tx_skbuff 是发送缓冲区数组,总共有 16 个,而 rx_skbuff 是接收缓冲区 数组,总共有 32 项。tx_dma_addr 和 rx_dma_addr 。对于分配好的 sk_buff 缓冲区,我们都要把它们映射成为 DMA 缓冲区,这两个数组用于记录相应的 DMA 缓冲区的地址。rx_ring 和 tx_ring 。这是 pcnet32_rx

28、_head 和 pcnet32_tx_head 的结构体, 它们用于记录对 应的 sk_buff 的当前状态,可以称为描述符。这两个结构体的定义如下:struct pcnet32_rx_head u32 base;s16 buf_length;s16 status;u32 msg_length;u32 reserved;struct pcnet32_tx_head u32 base;s16 length;s16 status;u32 misc;u32 reserved;对于 tx_skbuff 和 rx_skbuff 数组,在使用逻辑上是呈环形的,即用完最后 1 个后,重新使用 第 1 个,所

29、以,在源代码中会看到很多的 ring 命名。下面是对 rx ring 和 tx ring 的初始化函 数:static int pcnet32_init_ring(struct net_device *dev)struct pcnet32_private *lp = dev-priv;int i;lp-tx_full = 0;lp-cur_rx = lp-cur_tx = 0;lp-dirty_rx = lp-dirty_tx = 0;for (i = 0; i rx_skbuffi;if (rx_skbuff = NULL) if (!(rx_skbuff = lp-rx_skbuffi =

30、 dev_alloc_skb (PKT_BUF_SZ) printk(KERN_ERR %s: pcnet32_init_ring dev_alloc_skb failed.n, dev-name);return -1;skb_reserve (rx_skbuff, 2);rmb();if (lp-rx_dma_addri = 0) lp-rx_dma_addri = pci_map_single(lp-pci_dev, rx_skbuff-data,PKT_BUF_SZ-2, PCI_DMA_FROMDEVICE); lp-rx_ringi.base = (u32)le32_to_cpu(l

31、p-rx_dma_addri); lp-rx_ringi.buf_length = le16_to_cpu(2-PKT_BUF_SZ);wmb(); /* Make sure owner changes after all others are visible */ lp-rx_ringi.status = le16_to_cpu(0 x8000);for (i = 0; i tx_ringi.status = 0;/* CPU owns buffer */wmb(); /* Make sure adapter sees owner change */ lp-tx_ringi.base = 0

32、;lp-tx_dma_addri = 0;lp-init_block.tlen_rlen = le16_to_cpu(TX_RING_LEN_BITS RX_RING_LEN_BITS);for (i = 0; i init_block.phys_addri = dev-dev_addri; lp-init_block.rx_ring = (u32)le32_to_cpu(lp-dma_addr + offsetof(struct pcnet32_private, rx_ring);lp-init_block.tx_ring = (u32)le32_to_cpu(lp-dma_addr + o

33、ffsetof(struct pcnet32_private, tx_ring);wmb(); /* Make sure all changes are visible */ return 0;该初始化函数中,对接收缓冲区数组,都是预先分配好,并置好了描述符的相应状态。 而对于发送缓冲区,并没有分配,只是初始化了描述符,等到使用时再进行分配。该初始化函数中, 还有一些 pcnet32_private 中的成员, 我们还未描述到, 下面一一描述。 cur_rx , cur_tx , dirty_rx 和 dirty_tx 。这是四个关于套接字缓冲区的统计变量,记录当 前缓冲区的使用情况。init

34、_block 。这是一个 pcnet32_init_block 的结构,该结构定义如下: struct pcnet32_init_block u16 mode;u16 tlen_rlen;u8 phys_addr6;u16 reserved;u32 filter2;/* Receive and transmit ring base, along with extra bits. */u32 rx_ring;u32 tx_ring; phys_addr 记录 mac 地址, rx_ring 和 tx_ring 记录缓冲区数组的首地址。 其它几个成员,在用到时再具体描述。4.2.网卡设备的初始化

35、网络接口是字符设备,块设备之后的第三类标准 Linux 设备。 网络驱动程序和其它内核模块一样, 当被装载到正在运行的内核中时, 它要请求资源并 提供一些功能设施。 网络驱动程序对每个新检测到的接口, 会向全局的网络设备链表中插入 一个数据结构。每个接口由一个 net_device 结构描述,这是一个很庞大的结构,在下面的描 述中,我们会看到一些这个结构的成员。在网卡的驱动程序中,我们看到 alloc_etherdev 动态分配了该结构,这是为以太网接口 封装的一个分配函数 :struct net_device *alloc_etherdev(int sizeof_priv)return al

36、loc_netdev(sizeof_priv, eth%d, ether_setup);EXPORT_SYMBOL(alloc_etherdev); 真正用来实现网络设备接口分配的函数是 alloc_netdev alloc_ehterdev 的封装为以太网设备接口分配了形如eth%d ”的名字,同时,指定了一个ether_setup的初始化函数:void ether_setup(struct net_device *dev)dev-change_mtu = eth_change_mtu; dev-hard_header = eth_header; dev-rebuild_header = e

37、th_rebuild_header; dev-set_mac_address = eth_mac_addr; dev-hard_header_cache = eth_header_cache; dev-header_cache_update= eth_header_cache_update; dev-hard_header_parse = eth_header_parse;dev-type = ARPHRD_ETHER; dev-hard_header_len = ETH_HLEN;dev-mtu = 1500; /* eth_mtu */ dev-addr_len = ETH_ALEN;de

38、v-tx_queue_len = 1000; /* Ethernet wants good queues */ dev-flags = IFF_BROADCAST|IFF_MULTICAST;memset(dev-broadcast,0 xFF, ETH_ALEN);EXPORT_SYMBOL(ether_setup); 它为表示以太网设备接口的 net_device 结构进行了部分初始化,因为对以太网设备来 讲,很多操作与属性是固定的,内核可以帮助完成。alloc_netdev 分配 sizeof(dev) + sizeof_priv 的内核页,并调用初始化函数。 这些都是在 PCI 的探测

39、函数中做的事情,在完成了网络设备接口的分配后,我们要访问 PCI 设备的一些资源。在能够访问 PCI 设备的任何资源之前,我们必须激活 PCI 设备: /* enable device (incl. PCI PM wakeup and hotplug setup) */rc = pci_enable_device (pdev);if (rc) goto err_out;激活 PCI 设备后, 我们可以从 PCI 设备的配置空间读取 6 个 I/O 地址区域, 在我们的例 子程序中,是从 pcnet32网卡的第 1 个 I/O 地址区域读取 I/O 内存空间,然后调用 pci_request_r

40、egions(pdev, pcnet32); 进行 I/O 内存分配。该函数实际调用 request_mem_region 函数。接下来,进行一系例的板卡相关的初始化操作。最后,接着 ether_setup 的操作,我们要对 net_device 作一些自己的初始化操作: dev-open =pcnet32_open;dev-hard_start_xmit =pcnet32_start_xmit;dev-get_stats = pcnet32_get_stats;dev-stop =pcnet32_close;open 函数会在网络设备接口被注册的时候被调用, hard_start_xmit

41、在有数据需要传输的 时候被调用, get_stats 返回网络接口的统计信息。以上三个成员如果没有被初始化,则在注 册网络接口的时候会造成内核崩溃。最后一个 stop 会在网络接口被停掉的时候被调用。完 成了这些初始化操作之后, 我们就可以注册网络设备接口了, 一旦注册完毕, 网络设备接口 就可以被使用了:i = register_netdev(dev);if(i) return i;dev首先为网络设备接口分配一个名称(比如把eth%d替换为ethO)。然后将dev插入到一个叫做 dev_base的网络设备全局链表中。4.3.网卡数据发送的基本流程 发送函数把数据准备好(放在私有数据结构中的

42、特定变量中) ,并触发硬件发送。1、 从发送缓冲区环中选择一个空的缓冲区,包括tx_ring, tx_skbuff, tx_dma_addr 。2、 设置 tx_ring 的成员, length 设为待发送数据长度取反, misc 设零, base 设为 tx_dma_addr, status设为缺省的 0 x8300。3、 tx_skbuff 指向待发送数据套接字缓冲区skb。4、 将 skb 映射成的 dma 地址存放在 tx_dma_addr 中。5、 cur_tx +;6、 写网卡硬件,触发一个立即发送的信号。7、 检查环是否已用完,如果是,暂停后续发送。中断处理函数中,其主要工作是把

43、已发送过的缓冲区回收,使其重新可用。1、 如果 dirty_tx cur_tx ,则说明有已送完的缓冲区还没回收掉,循环处理。2、 检查tx_ring的成员status,如果dst-hh 是否已被创建 来决定如何调用链路层的输出函数,hh实际是neighbour的hh成员,它在ARP解析完成,邻居节点被更新时进行创建, 对于不需要 ARP 解析的设备接口 (loopback 等),它在第一次发送数据报时被创建。 所以,不管网络层如何调用链路层的输出函数,链路层的第一个输出函数始终是 dev_queue_xmit。该函数首先检查 skb_sh in fo(skb)-frag_list是否有值,如

44、果有,但是网络设备接口不支持skb的碎片列表(NETIF_F_FRAGLIST),则需要把这些碎片重组到一个完整的skb中(通过函数 _skb_linearize)。第二步检查 skb_shinfo(skb)-nr_frags,如果不为 0,表示这个 skb 使用 了分散陳焦10,如果网络设备接口不支持(NETIF_F_SG),同样需要重新线性化(通过函数_skb_linearize)。第三步检查是关于校验和的,需要注意的是这个校验和不是IP首部的首部校验和,IP首部校验和在每个IP数据报中是必需的,由软件来完成,对IP首部以16bit为段进行反码求和得到,只覆盖到IP首部,而未覆盖到IP数据

45、。而这里的校验和是其上层协议(比如UDP) 的校验和,它覆盖到上层协议的首部和数据。struc sk_buff有一个成员ip_summed,表示校验和的执行策略,其可能的取值有三种, CHECKSUM_HW 表示由硬件来执行校验和,CHECKSUM_NONE 表示完全由软件来执行校验和,CHECKSUM_UNNECESSARY 表示没有必要执行校验和。对于新分配的一个skb,总是默认由软件来执行校验和,如果网络设备接口拥有以下三个标志之一,并满足其它一些相关条件,就由硬件执行校验和:NETIF_F_IP_CSUM(硬件只能执行IPv4上的TCP/UDP协议的校验和),NETIF_F_NO_CS

46、UM(硬件不需要执行校验和,比如环回设备), NETIF_F_HW_CSUM(硬件能执行所有数据报的校验和)。如果校验和由软件执行,则在ip_generic_getfrag拷贝应用数据的时候执行,计算得到的校验和存放在skb-csum,由上层协议填写自己的协议首部时填入。否则,如果校验和由硬件执行,则上层协议在填写自己的协议首部时,为skb-csum填上自己首部中校验和所处的位置,以备硬件生成校验和时可以找到这个位置填入。dev_queue_xmit检查校验和,只是为了作一个补救措施,即:如果 skb-ip_summed=CHECKSUM_HW(由硬件执行校验和,即当前还未生成校验和),但是网

47、络设备接口 的成员features 上没有标志NETIF_F_HW_CSUM ,NETIF_F_NO_CSUM 或NETIF_F_IP_CSUM,即网络设备接口既没有表示不需要执行校验和,也说明自己没有执行 校验和的能力,或者,如果features上有NETIF_F_IP_CSUM,但是数据报又不是IP协议的。 这时候,还需要执行软件校验和,dev_queue_xmit就调用skb_checksum_help补上这个校验和,并把 skb-ip_summed 设为 CHECKSUM_NONE。struct net_device的成员qdisc是一个发送队列,缓冲等待网络设备进行发送的skb,如果

48、网络设备设置了这个队列,则把skb加到这个队列中,并启动队列的发送。否则,如果网络设备处于启用状态,则直接调用网络设备的输出函数进行发送,但在发送前,还需要做一件事情,就是,如果有ETH_P_ALL数据报类型被添加到ptype_all中来,则需要把数据报复制一份给这个数据报类型的接收函数,因为该类型需要接收到所有的数据报,包括输出的数据报。5、dirty_tx +;6、如果前面已经因为环用完,而发送被暂停,而现在已有空余缓冲区,则继续发送。 发送部分代码:struct ethhdr *p_ethhdr;struct iphdr *p_iphdr;struct icmphdr *p_icmphd

49、r;p_icmphdr = (struct icmphdr*)( skb-h.icmph );if( p_icmphdr != NULL )prin tk(KERNNFO n 设备无关接口层传来 skb);#ifdef DEBUG_SKBprint_skb(skb);#endifp_ethhdr = (struct ethhdr *)(skb-data);printk( KERN_INFO ethernet header info:n );printk( KERN_INFO tsource: %02x:%02x:%02x:%02x:%02x:%02xn, p_ethhdr-h_source0,

50、 p_ethhdr-h_source1, p_ethhdr-h_source2, p_ethhdr-h_source3, p_ethhdr-h_source4, p_ethhdr-h_source5 );printk( KERN_INFO tdest: %02x:%02x:%02x:%02x:%02x:%02xn,p_ethhdr-h_dest0, p_ethhdr-h_dest1,p_ethhdr-h_dest2, p_ethhdr-h_dest3, p_ethhdr-h_dest4, p_ethhdr-h_dest5 );printk( KERN_INFO tprotol: %04xn,

51、ntohs(p_ethhdr-h_proto) );p_iphdr = (struct iphdr*)( skb-nh.iph );printk( KERN_INFO ip header info: n );printk( KERN_INFO tip 4 位版本 : %u %01xn, p_iphdr-version, p_iphdr-version);printk( KERN_INFO tip 4 位首部长度 : %u %01xn, p_iphdr-ihl, p_iphdr-ihl );printk( KERN_INFO tip 8 位服务类型 : %u %02xn, p_iphdr-tos

52、, p_iphdr-tos );printk( KERN_INFO t16 位 总 长 度 ( 字 节 数 ) :%04xn, ntohs(*(unsigned short *)p_iphdr+1);printk( KERN_INFO t16 位标识 : %u %04xn, p_iphdr-id,p_iphdr-id);printk( KERN_INFO t3 位 标 志 , 13 位 片 位 移 : %u %04xn, p_iphdr-frag_off,p_iphdr-frag_off);printk( KERN_INFO t8 位生存时间 : %u %02xn, p_iphdr-ttl,

53、p_iphdr-ttl);printk( KERN_INFO t8 位协议 : %u %02xn, p_iphdr-protocol, p_iphdr-protocol);printk( KERN_INFO t16 位检验和 : %u %04xn, p_iphdr-check, p_iphdr-check);printk( KERN_INFO tsource ip: %u:%u:%u:%un,(ntohl(p_iphdr-saddr) 24) & 0 xff,(ntohl(p_iphdr-saddr) 16) & 0 xff,(ntohl(p_iphdr-saddr) 8 ) & 0 xff,

54、 ntohl(p_iphdr-saddr) & 0 xff );printk( KERN_INFO tdest ip: %u:%u:%u:%un,(ntohl(p_iphdr-daddr) 24) & 0 xff,(ntohl(p_iphdr-daddr) 16) & 0 xff,(ntohl(p_iphdr-daddr) 8 ) & 0 xff, ntohl(p_iphdr-daddr) & 0 xff );printk( KERN_INFO icmp header info:n );/ if( p_icmphdr = NULL )/printk( KERN_INFO tnot a icmp

55、 packet!n);/ elseprintk( KERN_INFO ticmp 8 位类型 : %02xn, p_icmphdr-type );printk( KERN_INFO ticmp 8 位代码 : %02xn, p_icmphdr-code );printk( KERN_INFO ticmp 16 位检验和 : %04xn, p_icmphdr-checksum );printk( KERN_INFO ticmp 16 位标识符 : %04xn, p_icmphdr-un.echo.id);printk( KERN_INFO ticmp 16 位序号 : %04xn, ntohs(

56、p_icmphdr-un.echo.sequence) );/ 4.5.网卡数据接收过程简述当网卡设备上有新的数据到达时,硬件会产生一个中断,驱动程序处理这个中断,就 可以接收数据。其接收过程大致可以分为以下几步:1、检查 rx_ringentry.status ,大于等于零,表示有数据到达,需要处理。2、取status的高8位,如果高8位不等于0 x03,表示有错误发生。3、否则从 rx_ringentry.msg_length 中可以取到数据包的长度,并判断长度是否在合理 的范围内。4、 如果长度值超过一个阀值(可以通过参数设定),则取下 rx_ring 的接收缓冲区作为 传给上层的缓冲区

57、,同时,还给 rx_ring 一个空的缓冲区,如果没有超过阀值,则把 rx_ring 的接收缓冲区数据拷贝出来。5、 更新统计信息,把接收缓冲区传给接收层。5.Linux 中驱动程序应用5.1.Pcnet32 驱动程序编译与生成5.1.1. Makefile 文件代码:PWD = $(shell pwd)KERNEL_SRC =/usr/src/kernels/2.6.18-194.el5-i686obj-m := pcnet32.omodule-objs := pcnet32.oall:$(MAKE) -C $(KERNEL_SRC) M=$(PWD) modulesclean:rm -f *.korm -f *.orm -f *.mod.crm -f *5.1.2. make 编译生成驱动程序根据 Makefile 文件执行 make 命令编译生成 pcnet

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论