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

下载本文档

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

文档简介

网络课程设计- AMD 79c970 PCnet32 LANCElinux网卡驱动程序作者:于小安、孙艳艳、江荣萍、张靖海、谢雨、齐敬强、刘伟、杨振西、马国伟学院:理工学院指导老师:王晓燕目录1.Linux操作系统概述21.1.Linux内核简介21.2.Linux 设备驱动程序概述31.3.编写网络驱动程序的一些基本概念42.开发环境与开发语言43.Linux 网络栈53.1.网络栈的 Internet 模型63.2.Linux 高级网络栈架构73.2.1.系统调用接口73.2.2.协议无关接口73.2.3.网络协议83.2.4.设备驱动程序84.Pcnet32网卡驱动分析94.1.PCNET32驱动程序的pcnet32_private结构94.2.网卡设备的初始化124.3.网卡数据发送的基本流程134.4.数据报在链路层的发送134.5.数据报在链路层的发送实现代码144.6.网卡数据接收过程简述165.Linux内核模块编写165.1.Pcnet32驱动程序编译与生成165.2.make编译生成驱动程序166.Linux中数据链路层协议解析176.1.pcnet32驱动安装与初始化176.2.以icmp询问报文为例的协议栈解析176.3.Pcnet32驱动程序发送数据207.参考书目218.小组成员及分工219.卷尾语221. Linux操作系统概述Linux操作系统是UNIX操作系统的一种克隆版本,最早是由芬兰大学的学生Linus Torvalds于1991年开始开发的,并于1991年的10月5日第一次正式向外公布,以后借助于互联网,经过一群遍布于全世界的Internet上的自愿参加的程序员的不懈努力,加上计算机公司的支持,Linux的影响和应用日益广泛,发展成为目前世界上用户最多的一种类UNIX操作系统。Linux 目前是计算机技术的一大热点之一,最近几年在我国得到迅猛发展,被广泛应用在嵌入式系统、安全产品、服务器和桌面应用等领域。1.1. Linux内核简介在最开始的时候,Linux系统并没有现在所看到的Linux系统的体积这么庞大,各种免费开放的驱动代码也还没有来得及加入到系统中,所以,之初的Linux实际意义上就是Linux内核。首先来分析一下Linux操作系统的体系结构,可以从两个层次上来考虑操作系统,如下图 1所示:GNU/Linux 操作系统的基本体系结构图最上面是用户(或应用程序)空间。这是用户应用程序执行的地方。用户空间之下是内核空间,Linux 内核正是位于这里。GNU“GNUs Not Unix”的递归缩写 C Library (glibc)也在这里,它提供了连接内核的系统调用接口,还提供了在用户空间应用程序和内核之间进行转换的机制。这点非常重要,因为内核和用户空间的应用程序使用的是不同的保护地址空间,每个用户空间的进程都使用自己的虚拟地址空间,而内核则占用单独的地址空间。实际上,体系结构可能并不像图1所示的一样清晰。例如,处理系统调用(从用户空间切换到内核空间)的机制可能在各个体系结构上都不相同。Linux系统支持多个进程的并发运行,每个进程都请求系统资源,比如运算、内存、网络连接或其他一些资源等。内核负责处理所有这些请求,根据内核完成任务的不同,我们可以将内核划分成如下图2的功能模块:Linux 系统模块及功能图Linux是个人计算机和工作站上的Unix类操作系统,但是,它绝不仅仅是简化的Unix系统。相反,Linux是具有创新意义的Unix类操作系统。它不仅继承了Unix的特征,而且在许多方面超过了Unix。作为Unix类操作系统,Linux内核具有下列基本特征:Linux内核的组织形式为整体式结构、Linux的进程调度方式简单而有效、Linux支持内核线程(或称守护进程)、Linux支持多种平台的虚拟内存管理、虚拟文件系统(VFS)、Linux的模块机制使得内核保持独立而又易于扩充,网络部分采用了面向对象的设计思想,使得Linux内核支持多种协议、多种网卡驱动程序变得更加的容易,为驱动的开发提供了便捷性,减少了工作量,提高了工作效率。1.2. Linux 设备驱动程序概述Linux设备驱动程序在Linux的内核源代码中占有很大的比例,源代码的长度日益增加,主要是驱动程序的增加。Linux系统的设备分为字符设备(chardevice),块设备(blockdevice)和网络设备(networkdevice)三种。其中网络接口:任何网络事件都是通过一个网络接口形成的,一个网络接口就是一个能够和其他主机交换数据的设备。通常,接口都是硬件设备,但也可能是纯软件设备,比如回环(loopback)接口。网络接口由内核中的网络子系统驱动,负责数据包的接收和发送,但它不需要了解每项事务是如何映射到实际传送的数据包的。许多网络连接是面向流的,但网络设备却围绕数据包的传输和接收而设计。网络驱动程序不需要知道各个连接的相关信息,它只要处理数据包即可。由于不是面向流的设备,因此将网络接口映射到文件系统中的节点(比如/dev/tty1)比较困难。Unix访问网络接口的方法仍然是给他们分配一个唯一的名字(比如eth0),但这个名字在文件系统中不存在对应的节点。内核和网络设备驱动程序间的通信,完全不同于内核和字符设备以及块驱动程序之间的通信,内核调用一套和数据传输相关的函数而不是read、write等。1.3. 编写网络驱动程序的一些基本概念无论是什么操作系统的驱动程序,都有一些通用的概念。操作系统提供给驱动程序的支持也大致相同。下面简单介绍一下网络设备驱动程序的一些基本的也是最重要的概念。发送和接收:这是一个网络设备最基本的功能。一块网卡所做的工作无非就是数据的发送和接收,所以在驱动程序中必须要告诉系统数据的发送函数在哪里,系统在有数据要发送时就会调用发送程序。驱动程序由于是直接操纵硬件的,所以网络硬件有数据收到时,最先能得到这个数据的就是驱动程序,它负责把这些原始数据进行必要的处理,然后送给系统。这里,操作系统必须要提供两个机制,一个是找到驱动程序的发送函数,一个是驱动程序把收到的数据送给系统。中断:中断在现代计算机结构中占有重要的地位。操作系统必须提供驱动程序响应中断的能力。一般是把一个中断处理程序注册到系统中去,操作系统在硬件中断发生后调用驱动程序的处理程序。Linux支持中断的共享,即多个设备共享一个中断。时钟:在实现驱动程序时,很多地方会用到时钟。如某些协议里的超时处理,没有中断机制的硬件的轮询等,操作系统应为驱动程序提供定时机制,一般是在预定的时间过了以后,系统自动回调注册的时钟函数。在网络驱动程序中,如果硬件没有中断功能,定时器可以提供轮询(poll)方式对硬件进行存取,或者是实现某些协议时需要的超时重传等。2. 开发环境与开发语言开发工具:GCC、CentOs linux操作系统(内核2.6.18)、vi、SourceInsight3.0开发语言:C调试:在linux系统中通过make命令(需要编写相应的makefile文件)进行编译、连接、生成;其中需要注意的有如下几点。(1)Kernel debugging 选项(对应于 CONFIG_DEBUG_KERNEL)应设为“*”,只有把该选项选为“*”,才能够使其他内核调试选项可见。(2)使用 gdb 调试内核需要在编译内核的时候应选中 Compile the kernel with debug info选项(CONFIG_DEBUG_INFO)(2)Spinlock debugging 选项(对应于 CONFIG_DEBUG_SPINLOCK)应设为“*”,这样系统将检查到例如对没有初始化的 spinlock 的操作、对同一个锁解锁多次等类似错误。(3)Sleep-inside-spinlock checking 选项(对应于 CONFIG_DEBUG_SPINLOC_SLEEP)通常可不选。选中该选项将使系统检查到占有 spinlock 的进程进入 sleep 状态的错误,但是系统在进程只是有可能 sleep 的情况下也会发出警告,从而可能在正常情况下产生大量无用信息。在使用 VMware 虚拟机的时候这种现象尤为明显。(4)Kprobes 选项(对应于 CONFIG_KPROBES)通常可不选。该选项的作用是使人们能够通过 register_kprobe()函数在内核中适当的位置设置自定义的回调函数。但是选中该选项将会定义一个和 kdb 中同名的函数(用于产生 int3 中断),造成冲突。如果希望使用 kdb,则不应选中 Kprobes 选项。(5)如果打了 kdb 的 patch,那么在最后将会有 Built-in Kernel Debugger support 选项,我们将在 1.8 节“使用 kdb”中进行介绍。(6)在顶层选项的 Loadable module support 中只有选中 Module versioning support 选项,才能够为模块添加版本支持机制。(通常可以不选)。设置完成后保存并退出编译选项配置环境,通过 make install 命令编译内核。(或者使用make bzImage,make modules,make modules_install,make install)。编译完成后即可对内核进行调试了。3. Linux 网络栈Linux 操作系统的最大特性之一就是它的网络栈。它最初源于 BSD(伯利克软件套件) 的网络栈,具有一套非常干净的接口,组织得非常好。其接口范围从协议无关层(例如通用 socket 层接口或设备层)到各种网络协议的具体层。3.1. 网络栈的 Internet 模型如下:这个栈的最底部是链路层。链路层是指提供对物理层访问的设备驱动程序,这可以是各种介质,例如串口链路或以太网设备。链路层上面是网络层,它负责将报文定向到目标位置。再上一层称为传输层,负责端到端的通信(例如,在一台主机内部)。尽管网络层负责管理主机之间的通信,但是传输层需要负责管理主机内部各端之间的通信。最后一层是应用层,它通常是一个语义层,能够理解要传输的数据。例如,超文本传输协议(HTTP)就负责传输服务器和客户机之间对 Web 内容的请求与响应。实际来说,网络栈的各个层次有一些更为人所熟知的名字。在链路层上,可以找到以太网,这是最常用的一种高速介质。更早的链路层协议包括一些串口协议,例如 SLIP(Serial Line Internet Protocol)、CSLIP(Compressed SLIP)和PPP(Point-to-Point Protocol)。最常见的网络层协议是 IP(Internet Protocol),但是网络层中还存在一些满足其他需求的协议,例如 ICMP(Internet Control Message Protocol)和ARP( Address Resolution Protocol)。在传输层上是 TCP(Transmission Control Protocol)和 UDP(User Datagram Protocol)。最后,应用层中包含很多大家都非常熟悉的协议,包括标准的 Web 协议 HTTP 和电子邮件协议 SMTP。3.2. Linux 高级网络栈架构如下:顶部是系统调用接口。它简单地为用户空间的应用程序提供了一种访问内核网络子系统的方法。位于其下面的是一个协议无关层,它提供了一种通用方法来使用底层传输层协议。然后是实际协议,在 Linux 中包括内嵌的协议 TCP、UDP,当然还有 IP。然后是另外一个协议无关层,提供了与各个设备驱动程序通信的通用接口,最下面是设备驱动程序本身。3.2.1. 系统调用接口系统调用接口可以从两个角度进行描述。用户发起网络调用时,通过系统调用接口进入内核的过程应该是多路的。最后调用 ./net/socket.c 中的 sys_socketcall 结束该过程,然后进一步将调用分路发送到指定目标。系统调用接口的另一种描述是使用普通文件操作作为网络 I/O。例如,典型的读写操作可以在网络 socket 上执行(socket 使用一个文件描述符表示,与一个普通文件一样)。因此,尽管有很多操作是网络专用的(使用 socket 调用创建一个 socket,使用 connect 调用连接一个收信方,等等),但是也有一些标准的文件操作可以应用于网络对象,就像操作普通文件一样。最后,系统调用接口提供了在用户空间应用程序和内核之间转移控制的方法。3.2.2. 协议无关接口socket 层是一个协议无关接口,它提供了一组通用函数来支持各种不同协议。socket 层不但可以支持典型的 TCP 和 UDP 协议,而且还可以支持 IP、裸以太网和其他传输协议,例如 SCTP(Stream Control Transmission Protocol)。通过网络栈进行的通信都需要对 socket 进行操作。Linux 中的 socket 结构是 struct sock,这个结构是在 linux/include/net/sock.h 中定义的。这个巨大的结构中包含了特定 socket 所需要的所有状态信息,其中包括 socket 所使用的特定协议和在 socket 上可以执行的一些操作。网络子系统可以通过一个定义了自己功能的特殊结构来了解可用协议。每个协议都维护了一个名为 proto 的结构(可以在 linux/include/net/sock.h 中找到)。这个结构定义了可以在从 socket 层到传输层中执行特定的 socket 操作(例如,如何创建一个 socket,如何使用 socket 建立一个连接,如何关闭一个 socket 等等)。3.2.3. 网络协议网络协议这一节对一些可用的特定网络协议作出了定义(例如 TCP、UDP 等)。它们都是在 linux/net/ipv4/af_inet.c 文件中一个名为 inet_init 的函数中进行初始化的(因为 TCP 和 UDP 都是 inet 簇协议的一部分)。 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 中的 inet_register_protosw 将这个数组中的每个协议都初始化为 inetsw。函数 inet_init 也会对各个 inet 模块进行初始化,例如 ARP、ICMP 和 IP 模块,以及 TCP 和 UDP 模块。设备无关接口协议层下面是另外一个无关接口层,它将协议与具有很多各种不同功能的硬件设备连接在一起。这一层提供了一组通用函数供底层网络设备驱动程序使用,让它们可以对高层协议栈进行操作。首先,设备驱动程序可能会通过调用 register_netdevice 或 unregister_netdevice 在内核中进行注册或注销。调用者首先填写 net_device 结构,然后传递这个结构进行注册。内核调用它的 init 函数(如果定义了这种函数),然后执行一组健全性检查,并创建一个 sysfs 条目,然后将新设备添加到设备列表中(内核中的活动设备链表)。在 linux/include/linux/netdevice.h 中可以找到这个 net_device 结构。这些函数都是在 linux/net/core/dev.c 中实现的。要从协议层向设备中发送 sk_buff,就需要使用 dev_queue_xmit 函数。这个函数可以对 sk_buff 进行排队,从而由底层设备驱动程序进行最终传输(使用 sk_buff 中引用的 net_device 或 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 和 netif_rx 函数。最近,内核中引入了一种新的应用程序编程接口(NAPI),该接口允许驱动程序与设备无关层(dev)进行交互。有些驱动程序使用的是 NAPI,但是大多数驱动程序仍然在使用老式的帧接收接口(比例大约是 6 比 1)。NAPI 在高负载的情况下可以产生更好的性能,它避免了为每个传入的帧都产生中断。3.2.4. 设备驱动程序网络栈底部是负责管理物理网络设备的设备驱动程序。例如,包串口使用的 SLIP 驱动程序以及以太网设备使用的以太网驱动程序都是这一层的设备。在进行初始化时,设备驱动程序会分配一个 net_device 结构,然后使用必须的程序对其进行初始化。这些程序中有一个是 dev-hard_start_xmit,它定义了上层应该如何对 sk_buff 排队进行传输。这个程序的参数为 sk_buff。这个函数的操作取决于底层硬件,但是通常 sk_buff 所描述的报文都会被移动到硬件环或队列中。就像是设备无关层中所描述的一样,对于 NAPI 兼容的网络驱动程序来说,帧的接收使用了 netif_rx 和 netif_receive_skb 接口。NAPI 驱动程序会对底层硬件的能力进行一些限制。设备驱动程序在 dev 结构中配置好自己的接口之后,调用 register_netdevice 便可以使用该配置。在 linux/drivers/net 中可以找出网络设备专用的驱动程序。4. Pcnet32网卡驱动分析4.1. PCNET32驱动程序的pcnet32_private结构一个网络设备,在其驱动程序中用一个net_device结构描述,该结构有一个重要的成员priv,它是一个指针,指向驱动程序自己定义的私有数据。不同的网络设备驱动程序,其私有数据各不相同。下面是PCNET32驱动程序的私有数据: struct pcnet32_private struct pcnet32_rx_head rx_ringRX_RING_SIZE; struct pcnet32_tx_head tx_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 *tx_skbuffTX_RING_SIZE; struct sk_buff *rx_skbuffRX_RING_SIZE; dma_addr_t tx_dma_addrTX_RING_SIZE; dma_addr_t rx_dma_addrRX_RING_SIZE; struct pcnet32_access a; spinlock_t lock; unsigned int cur_rx, cur_tx; unsigned int dirty_rx, dirty_tx; struct net_device_stats stats; char tx_full; int options; unsigned int dxsuflo:1, mii:1; struct net_device *next; struct mii_if_info mii_if; struct timer_list watchdog_timer; struct timer_list blink_timer; ; 下面逐个分析该结构的重要成员。 tx_skbuff和rx_skbuff。这是两个sk_buff的数组,其长度分别为TX_RING_SIZE(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_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个,所以,在源代码中会看到很多的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 = 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(lp-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(0x8000); for (i = 0; i tx_ringi.status = 0; /* CPU owns buffer */ wmb(); /* Make sure adapter sees owner change */ lp-tx_ringi.base = 0; 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 + offsetof(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_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. 网卡设备的初始化网络接口是字符设备,块设备之后的第三类标准Linux设备。网络驱动程序和其它内核模块一样,当被装载到正在运行的内核中时,它要请求资源并提供一些功能设施。网络驱动程序对每个新检测到的接口,会向全局的网络设备链表中插入一个数据结构。每个接口由一个net_device结构描述,这是一个很庞大的结构,在下面的描述中,我们会看到一些这个结构的成员。在网卡的驱动程序中,我们看到alloc_etherdev动态分配了该结构,这是为以太网接口封装的一个分配函数:struct net_device *alloc_etherdev(int sizeof_priv)return alloc_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 = eth_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;dev-tx_queue_len = 1000; /* Ethernet wants good queues */dev-flags = IFF_BROADCAST|IFF_MULTICAST;memset(dev-broadcast,0xFF, ETH_ALEN);EXPORT_SYMBOL(ether_setup);它为表示以太网设备接口的net_device结构进行了部分初始化,因为对以太网设备来讲,很多操作与属性是固定的,内核可以帮助完成。alloc_netdev分配sizeof(dev) + sizeof_priv的内核页,并调用初始化函数。这些都是在PCI的探测函数中做的事情,在完成了网络设备接口的分配后,我们要访问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_regions(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在有数据需要传输的时候被调用,get_stats返回网络接口的统计信息。以上三个成员如果没有被初始化,则在注册网络接口的时候会造成内核崩溃。最后一个stop会在网络接口被停掉的时候被调用。完成了这些初始化操作之后,我们就可以注册网络设备接口了,一旦注册完毕,网络设备接口就可以被使用了:i = register_netdev(dev);if(i) return i;register_netdev首先为网络设备接口分配一个名称(比如把eth%d替换为eth0)。然后将dev插入到一个叫做dev_base的网络设备全局链表中。4.3. 网卡数据发送的基本流程发送函数把数据准备好(放在私有数据结构中的特定变量中),并触发硬件发送。 1、从发送缓冲区环中选择一个空的缓冲区,包括tx_ring, tx_skbuff, tx_dma_addr。 2、设置tx_ring的成员,length设为待发送数据长度取反,misc设零,base设为tx_dma_addr,status设为缺省的0x8300。 3、tx_skbuff指向待发送数据套接字缓冲区skb。 4、将skb映射成的dma地址存放在tx_dma_addr中。 5、cur_tx +; 6、写网卡硬件,触发一个立即发送的信号。 7、检查环是否已用完,如果是,暂停后续发送。 中断处理函数中,其主要工作是把已发送过的缓冲区回收,使其重新可用。 1、如果dirty_tx cur_tx,则说明有已送完的缓冲区还没回收掉,循环处理。 2、检查tx_ring的成员status,如果dst-hh是否已被创建来决定如何调用链路层的输出函数,hh实际是neighbour的hh成员,它在ARP解析完成,邻居节点被更新时进行创建,对于不需要ARP解析的设备接口(loopback等),它在第一次发送数据报时被创建。所以,不管网络层如何调用链路层的输出函数,链路层的第一个输出函数始终是dev_queue_xmit。 该函数首先检查skb_shinfo(skb)-frag_list是否有值,如果有,但是网络设备接口不支持skb的碎片列表(NETIF_F_FRAGLIST),则需要把这些碎片重组到一个完整的skb中(通过函数_skb_linearize)。第二步检查skb_shinfo(skb)-nr_frags,如果不为0,表示这个skb使用了分散/聚焦IO,如果网络设备接口不支持(NETIF_F_SG),同样需要重新线性化(通过函数_skb_linearize)。 第三步检查是关于校验和的,需要注意的是这个校验和不是IP首部的首部校验和,IP首部校验和在每个IP数据报中是必需的,由软件来完成,对IP首部以16bit为段进行反码求和得到,只覆盖到IP首部,而未覆盖到IP数据。而这里的校验和是其上层协议(比如UDP)的校验和,它覆盖到上层协议的首部和数据。 struc sk_buff有一个成员ip_summed,表示校验和的执行策略,其可能的取值有三种,CHECKSUM_HW表示由硬件来执行校验和,CHECKSUM_NONE表示完全由软件来执行校验和,CHECKSUM_UNNECESSARY表示没有必要执行校验和。对于新分配的一个skb,总是默认由软件来执行校验和,如果网络设备接口拥有以下三个标志之一,并满足其它一些相关条件,就由硬件执行校验和: NETIF_F_IP_CSUM(硬件只能执行IPv4上的TCP/UDP协议的校验和),NETIF_F_NO_CSUM(硬件不需要执行校验和,比如环回设备),NETIF_F_HW_CSUM(硬件能执行所有数据报的校验和)。如果校验和由软件执行,则在ip_generic_getfrag拷贝应用数据的时候执行,计算得到的校验和存放在skb-csum,由上层协议填写自己的协议首部时填入。否则,如果校验和由硬件执行,则上层协议在填写自己的协议首部时,为skb-csum填上自己首部中校验和所处的位置,以备硬件生成校验和时可以找到这个位置填入。 dev_queue_xmit检查校验和,只是为了作一个补救措施,即:如果skb-ip_summed=CHECKSUM_HW(由硬件执行校验和,即当前还未生成校验和),但是网络设备接口的成员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,如果网络设备设置了这个队列,则把skb加到这个队列中,并启动队列的发送。否则,如果网络设备处于启用状态,则直接调用网络设备的输出函数进行发送,但在发送前,还需要做一件事情,就是,如果有ETH_P_ALL数据报类型被添加到ptype_all中来,则需要把数据报复制一份给这个数据报类型的接收函数,因为该类型需要接收到所有的数据报,包括输出的数据报。 5、dirty_tx +; 6、如果前面已经因为环用完,而发送被暂停,而现在已有空余缓冲区,则继续发送。4.5. 数据报在链路层的发送实现代码:static int pcnet32_start_xmit(struct sk_buff *skb, struct net_device *dev)struct pcnet32_private *lp = dev-priv;unsigned long ioaddr = dev-base_addr;u16 status;int entry;unsigned long flags;spin_lock_irqsave(&lp-lock, flags);if (netif_msg_tx_queued(lp) printk(KERN_DEBUG %s: pcnet32_start_xmit() called, csr0 %4.4x.n, dev-name, lp-a.read_csr(ioaddr, 0);/* Default status - will not enable Successful-TxDone * interrupt when that option is available to us. */status = 0x8300;/* Fill in a Tx ring entry */* Mask to ring buffer boundary. */entry = lp-cur_tx & lp-tx_mod_mask;/* Caution: the write order is important here, set the status * with the ownership bits last. */lp-tx_ringentry.length = l

温馨提示

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

评论

0/150

提交评论